NocoBase 前端 SDK APIClient 完全指南:HTTP 请求、资源操作与认证存储
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
APIClient是 NocoBase 前端 SDK(packages/core/sdk)的核心客户端类,它基于 axios 封装,为插件和业务代码提供了统一的资源操作(Resource Action)调用、鉴权信息管理与本地存储能力。通过本文,你将掌握APIClient的构造与配置、request()/resource()两种请求范式、Auth登录认证流程以及Storage存储抽象,并能在自己的插件中直接落地使用。
概览:APIClient 是什么
APIClient基于 axios 封装,用于在客户端(浏览器)通过 HTTP 请求 NocoBase 的资源操作接口。它承担了三类职责:
- 发起 HTTP 请求:既支持原生 axios 请求配置,也支持面向 NocoBase 资源操作(
resource/action)的专用配置; - 管理鉴权状态:通过内部持有的
Auth实例维护 token、角色、语言、认证器,并在每次请求中自动注入对应请求头; - 统一客户端存储:通过内部持有的
Storage实例读写 token 等状态,默认使用localStorage。
在插件中,最典型的用法是直接在插件的load()生命周期里通过this.app.apiClient发起请求:
class PluginSampleAPIClient extends Plugin { async load() { const res = await this.app.apiClient.request({ // ... }); } }这里的this.app.apiClient即当前应用(@nocobase/client的Application)在启动时创建的APIClient实例。
实例属性
APIClient暴露三个核心实例属性(定义见 APIClient.ts):
| 属性 | 类型 | 说明 |
|---|---|---|
axios | AxiosInstance | 内部持有的 axios 实例,可以直接访问 axios API,例如apiClient.axios.interceptors注册自定义拦截器 |
auth | Auth | 客户端鉴权类,负责 token、角色、语言的存取与请求头注入,详见 Auth |
storage | BaseStorage | 客户端存储类,默认封装localStorage,详见 Storage |
例如,需要为所有请求追加自定义请求头时,可直接操作底层 axios 实例:
apiClient.axios.interceptors.request.use((config) => { config.headers['X-Custom'] = 'custom-value'; return config; });构造函数与配置选项
签名
constructor(instance?: APIClientOptions)
类型
interface ExtendedOptions { authClass?: any; storageType?: 'localStorage' | 'sessionStorage' | 'memory'; storageClass?: any; storagePrefix?: string; appName?: string; // 共享 token shareToken?: boolean; } export type APIClientOptions = AxiosInstance | (AxiosRequestConfig & ExtendedOptions);从源码看,APIClientOptions是一个联合类型(APIClient.ts):
- 直接传入一个axios 实例(
AxiosInstance):此时APIClient直接复用该实例,不再新建; - 传入axios 请求配置 + 扩展选项(
AxiosRequestConfig & ExtendedOptions):内部通过axios.create(others)创建新实例,并初始化存储与鉴权。
扩展选项说明如下:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
authClass | any | Auth | 自定义鉴权类,必须继承Auth,用于替换默认登录/注册逻辑 |
storageType | 'localStorage' \| 'sessionStorage' \| 'memory' | 'localStorage' | 存储后端类型,memory表示纯内存存储(不持久化) |
storageClass | any | 无 | 自定义存储类,传入后将优先于storageType使用 |
storagePrefix | string | 'NOCOBASE_' | 存储 key 前缀,默认值在源码中通过storagePrefix = 'NOCOBASE_'声明 |
appName | string | 无 | 应用名称;设置后存储前缀变为storagePrefix + appName.toUpperCase() + '_' |
shareToken | boolean | false | 是否跨应用共享 token;为true时 token 使用基础前缀存储(见 Storage 一节) |
storagePrefix 的生成规则
源码(APIClient.ts)展示了前缀的具体拼接逻辑:
this.baseStoragePrefix = storagePrefix; this.storagePrefix = appName ? `${storagePrefix}${appName.toUpperCase()}_` : storagePrefix;即:设置appName: 'myApp'、storagePrefix: 'NOCOBASE_'时,实际存储前缀为NOCOBASE_MYAPP_,对应测试 api-client.test.ts 中断言 token 被写入N2_MYAPP_TOKEN(前缀N2_+MYAPP)。这种按应用命名空间隔离存储的设计,是为了支持多应用(Multi-App)场景下各自维护登录态。
创建实例示例
import { APIClient } from '@nocobase/sdk'; const api = new APIClient({ baseURL: 'https://localhost:8000/api', storagePrefix: 'NOCOBASE_', appName: 'myApp', }); await api.auth.signIn({ email: 'admin@nocobase.com', password: 'admin123' }, 'basic');request():发起 HTTP 请求
签名
request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D> | ResourceActionOptions): Promise<R>
类型
type ResourceActionOptions<P = any> = { resource?: string; resourceOf?: any; action?: string; params?: P; };request()接受两种配置:
1. AxiosRequestConfig:通用 axios 请求参数
直接透传给 axios 的请求配置,参考 axios 的 Request Config:
const res = await apiClient.request({ url: '' });例如发起一个带查询参数的 GET 请求:
const res = await apiClient.request({ url: 'users:list', method: 'get', params: { pageSize: 10, page: 1 }, });2. ResourceActionOptions:NocoBase 资源操作请求参数
const res = await apiClient.request({ resource: 'users', action: 'list', params: { pageSize: 10, }, });参数说明:
| 属性 | 类型 | 描述 |
|---|---|---|
resource | string | 1. 资源名称,比如a2. 资源的关联对象名称,比如 a.b |
resourceOf | any | 当resource为资源的关联对象名称时,资源的主键值。比如a.b时,代表a的主键值 |
action | string | 操作名称 |
params | any | 请求参数对象,主要是 URL 参数,请求体放到params.values中 |
params.values | any | 请求体对象 |
底层分发逻辑
从源码(APIClient.ts)可以看到request()的分发实现:
request(config): Promise<R> { const { resource, resourceOf, action, params, headers } = config as any; if (resource) { return this.resource(resource, resourceOf, headers)action; } return this.axios.request(config); }也就是说:一旦配置中带有resource字段,请求会转交给resource()方法生成的操作对象执行;否则走原生axios.request()。
resource():获取资源操作方法对象
resource()返回一个资源操作对象,可以链式调用任意操作名(create、list、update、destroy以及自定义操作等):
const resource = apiClient.resource('users'); await resource.create({ values: { username: 'admin', }, }); const res = await resource.list({ page: 2, pageSize: 20, });签名
resource(name: string, of?: any, headers?: AxiosRequestHeaders): IResource
类型
export interface ActionParams { filterByTk?: any; [key: string]: any; } type ResourceAction = (params?: ActionParams) => Promise<any>; export type IResource = { [key: string]: ResourceAction; };IResource是一个索引签名类型,任何操作名都会被解析为(params) => Promise<any>的函数。
参数说明
| 参数名 | 类型 | 描述 |
|---|---|---|
name | string | 1. 资源名称,比如a2. 资源的关联对象名称,比如 a.b |
of | any | 当resource为资源的关联对象名称时,资源的主键值。比如a.b时,代表a的主键值 |
headers | AxiosRequestHeaders | 后续要发起资源操作请求时,携带的 HTTP 请求头 |
关联对象(关联资源)请求
当资源是关联对象时,通过of指定主资源的主键值。例如「获取 id 为 1 的用户的角色列表」:
const res = await apiClient.resource('users.roles', 1).list({ pageSize: 20, });底层会构造出形如users/1/roles:list的请求 URL。
Proxy 实现原理
resource()使用 JavaScriptProxy实现(APIClient.ts),其关键行为如下:
- URL 构造:
name.split('.').join('/{of}/')将a.b展开为a/{of}/b,再追加:actionName,得到users/1/roles:list这样的资源操作 URL; - HTTP 方法映射:操作名为
get、list时使用 GET 方法,其余操作一律使用 POST:if (['get', 'list'].includes(actionName)) { config['method'] = 'get'; } else { config['method'] = 'post'; } - 参数拆分:调用操作函数时,
params会被拆分为三部分:values→ 请求体(仅非 GET 方法时写入config.data);filter→ 转为 JSON 字符串放入 URL 参数filter(若已传字符串则原样透传,且会剔除通配键*);- 其余字段 → 直接作为 URL 查询参数。
例如:
await resource.list({ filter: { status: 'published' }, page: 1, pageSize: 10, sort: '-createdAt', });最终请求为GET users:list?filter={"status":"published"}&page=1&pageSize=10&sort=-createdAt。
内置拦截器与参数序列化
APIClient在构造函数末尾调用this.interceptors()(APIClient.ts),为 axios 实例注册了一个请求拦截器,统一配置 URL 参数序列化方式:
config.paramsSerializer = (params) => { return qs.stringify(params, { strictNullHandling: true, arrayFormat: 'brackets', }); };这意味着:
null值参数会被保留(strictNullHandling: true);- 数组参数使用
brackets格式序列化,例如ids[]=1&ids[]=2; - 上文中
filter的 JSON 字符串也是通过该序列化器编码到 URL 中的。
此外,request()还支持两个额外开关(定义于 APIClient.ts):
| 选项 | 类型 | 说明 |
|---|---|---|
skipNotify | boolean \| ((error) => boolean) | 跳过全局错误提示(如auth:syncCookies内部使用) |
skipAuth | boolean | 跳过鉴权相关逻辑 |
Auth:客户端鉴权
Auth类(Auth.ts)负责在客户端存取用户信息、请求用户认证相关接口。它是APIClient的auth属性,相关完整文档见 Auth。
实例属性
| 属性 | 说明 |
|---|---|
locale | 当前用户使用的语言 |
role | 当前用户使用的角色 |
token | API 接口token |
authenticator | 当前用户认证时所用的认证器,参考 用户认证 |
从源码看,这四个属性都是基于Storage的读写 getter/setter(Auth.ts):读取时通过api.storage.getItem(key)获取,写入时通过api.storage.setItem(key, value)持久化。其中authenticator实际存储的 key 是auth(见getAuthenticator()中的getOption('auth'))。
请求头自动注入(middleware)
Auth构造函数为 axios 实例注册了请求拦截器middleware(Auth.ts),每次请求自动注入以下请求头:
| 请求头 | 触发条件 | 值 |
|---|---|---|
X-Locale | 设置了locale | 语言标识 |
X-Role | 设置了role | 角色标识 |
X-Authenticator | 设置了authenticator且未手动传该请求头 | 认证器标识 |
Authorization | 设置了token且未手动传该请求头 | Bearer ${token} |
X-CSRF-Token | 非安全方法(非get/head/options)且有 CSRF token | 从 cookie 读取 |
CSRF 防护逻辑值得注意:SAFE_METHODS = new Set(['get', 'head', 'options']),只有写操作(POST 等)才会携带X-CSRF-Token,CSRF token 从 cookie 中读取(getAuthCookieName('csrfToken', appName))。
登录 / 注册 / 注销
signIn():用户登录
- 签名:
async signIn(values: any, authenticator?: string): Promise<AxiosResponse<any>>
| 参数名 | 类型 | 描述 |
|---|---|---|
values | any | 登录接口请求参数 |
authenticator | string | 登录使用的认证器标识 |
登录成功后会通过setAuthenticator()/setToken()将认证器与 token 写入存储(Auth.ts):
async signIn(values: any, authenticator?: string) { const response = await this.api.request({ method: 'post', url: 'auth:signIn', data: values, headers: { 'X-Authenticator': authenticator }, }); const data = response?.data?.data; this.setAuthenticator(authenticator); this.setToken(data?.token); return response; }await apiClient.auth.signIn( { email: 'admin@nocobase.com', password: 'admin123' }, 'basic', );signUp():用户注册
- 签名:
async signUp(values: any, authenticator?: string): Promise<AxiosResponse<any>>
| 参数名 | 类型 | 描述 |
|---|---|---|
values | any | 注册接口请求参数 |
authenticator | string | 注册使用的认证器标识 |
await apiClient.auth.signUp( { email: 'user@example.com', password: 'pass123' }, 'basic', );signOut():注销登录
- 签名:
async signOut(values: any, authenticator?: string): Promise<AxiosResponse<any>>
| 参数名 | 类型 | 描述 |
|---|---|---|
values | any | 注销接口请求参数 |
authenticator | string | 注销使用的认证器标识 |
注销时会调用auth:signOut接口,并清除 token、角色与认证器(Auth.ts):
async signOut() { const response = await this.api.request({ method: 'post', url: 'auth:signOut', }); this.setToken(null); this.setRole(null); this.setAuthenticator(null); return response; }其他认证方法
Auth还封装了若干补充方法(Auth.ts):
| 方法 | 请求地址 | 说明 |
|---|---|---|
syncCookies() | auth:syncCookies | 已有 token 时同步 Cookie(带skipNotify) |
lostPassword() | auth:lostPassword | 忘记密码,自动携带当前页面 URL 信息 |
resetPassword() | auth:resetPassword | 重置密码 |
checkResetToken() | auth:checkResetToken | 校验重置密码 token |
另外,setToken()在应用上下文存在时还会派发auth:tokenChanged事件(Auth.ts),供其他模块监听 token 变化。
自定义 Auth 类
通过authClass选项可以替换默认的认证逻辑,自定义类需继承Auth并覆写signIn等方法。测试 api-client.test.ts 演示了这一用法:
class TestAuth extends Auth { async signIn(values: any) { const response = await this.api.request({ method: 'post', url: 'auth:test', data: values, }); const data = response?.data?.data; this.setAuthenticator('test'); this.setToken(data?.token); return response; } } const api = new APIClient({ baseURL: 'https://localhost:8000/api', authClass: TestAuth, }); expect(api.auth).toBeInstanceOf(TestAuth);Storage:客户端存储抽象
Storage类(Storage.ts)用于客户端信息存储,默认使用localStorage,完整文档见 Storage。
抽象基类
export abstract class Storage { abstract clear(): void; abstract getItem(key: string): string | null; abstract removeItem(key: string): void; abstract setItem(key: string, value: string): void; } export class CustomStorage extends Storage { // ... }从源码看,实际抽象基类名为BaseStorage(Storage.ts),它额外提供toUpperCase(prefix, ...arr)工具方法,用于将前缀与 key 统一转为大写并用下划线连接,例如toUpperCase('app_', 'token')得到APP_TOKEN。
四种存储实现
| 实现类 | 存储后端 | 说明 |
|---|---|---|
MemoryStorage | Map | 纯内存存储,不持久化;刷新页面即丢失 |
LocalStorage | window.localStorage | 默认实现,key 统一按PREFIX_KEY大写格式写入 |
SessionStorage | window.sessionStorage | 继承LocalStorage,仅替换存储后端为 sessionStorage |
| 自定义类 | 任意 | 继承BaseStorage实现四个抽象方法,通过storageClass注入 |
APIClient.createStorage()(APIClient.ts)根据storageType决定实例化哪种实现,且当localStorage/sessionStorage不可用(如 SSR 环境)时自动回退到MemoryStorage。
类方法
| 方法 | 签名 | 说明 |
|---|---|---|
setItem() | setItem(key: string, value: string): void | 存储内容 |
getItem() | getItem(key: string): string \| null | 获取内容 |
removeItem() | removeItem(key: string): void | 删除内容 |
clear() | clear(): void | 清除所有内容 |
shareToken:跨应用共享 token
当shareToken: true时,LocalStorage对token这个 key 使用baseStoragePrefix(基础前缀)而非应用专属前缀进行读写(Storage.ts),从而让多个应用共享同一登录态。测试 api-client.test.ts 验证了该行为:
const api1 = new APIClient({ baseURL, storagePrefix: 'N2_', shareToken: true }); api1.auth.setToken('123'); const api = new APIClient({ baseURL, appName: 'myApp', storagePrefix: 'N2_', shareToken: true }); expect(api.auth.getToken()).toBe('123'); // 跨应用读到共享 token测试验证:SDK 行为如何被保障
SDK 配套了两个测试文件,可以作为行为契约参考:
- api-client.test.ts 覆盖:实例创建与
baseURL、显式withCredentials、signIn后 token 写入存储、syncCookies携带Authorization: Bearer头、appName命名空间存储(N2_MYAPP_TOKEN)、shareToken共享、resource().test()自定义操作、自定义authClass等; - Storage.test.ts 覆盖:
MemoryStorage的增删改查、LocalStorage/SessionStorage的前缀大写化(TESTPREFIX_KEY1)、多前缀隔离、clear()行为等。
这些测试同时演示了如何在 Node 环境中 mocklocalStorage/window/document.cookie,并用axios-mock-adapter拦截请求来验证APIClient的行为,是编写 SDK 相关单测的很好范例。
在 React 客户端中获取 APIClient
在 NocoBase 客户端插件中,除了this.app.apiClient,还可以通过useAPIClient()钩子在组件内获取当前应用的APIClient实例(useAPIClient.ts):
import { useAPIClient } from '@nocobase/client'; const MyComponent = () => { const apiClient = useAPIClient(); const handleClick = async () => { const res = await apiClient.resource('users').list({ pageSize: 10 }); console.log(res.data); }; return <button onClick={handleClick}>加载用户</button>; };该钩子优先从APIClientContext上下文取值,取不到时回退到app.apiClient,保证在应用初始化完成后的任意组件中都能拿到同一个实例。
总结
APIClient是 NocoBase 前端调用后端资源的统一入口,其设计可以概括为三层:
- 请求层:
request()同时支持原生 axios 配置与ResourceActionOptions资源操作配置,resource()基于 Proxy 提供users:list、users/1/roles:list等资源操作调用,并自动完成 HTTP 方法映射、values/filter/ 查询参数拆分与序列化; - 鉴权层:
Auth维护 token、角色、语言与认证器,通过请求拦截器自动注入Authorization、X-Locale、X-Role、X-Authenticator、X-CSRF-Token等请求头,并提供signIn/signUp/signOut/syncCookies等完整认证方法; - 存储层:
Storage抽象出localStorage/sessionStorage/memory三种后端,支持按应用命名空间隔离与shareToken跨应用共享,也允许通过storageClass注入自定义实现。
对于需要深度定制请求行为的开发者,还可以基于authClass替换认证逻辑、基于storageClass替换存储实现,或直接操作apiClient.axios.interceptors扩展全局拦截器。理解这三层结构,是编写健壮的 NocoBase 客户端插件与二次开发的基础。
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考