扩展 NocoBase 用户数据同步数据源:SyncSource 接口实现与注册全流程
【免费下载链接】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
NocoBase 的用户数据同步插件(@nocobase/plugin-user-data-sync)提供了从外部系统拉取用户与部门数据的扩展点。本文围绕官方文档「扩展同步数据源」展开,完整覆盖SyncSource抽象类的pull()接口、UserData返回结构、服务端registerType类型注册以及客户端AdminSettingsForm配置表单的注册方式,并结合插件源码验证了options配置的实际来源、任务生命周期与同步落库流程,读完即可在自研插件中接入一个完整的自定义同步数据源。
概述:数据源类型如何被管理和使用
用户数据同步插件的服务端入口在 PluginUserDataSyncServer。它在beforeLoad阶段注册SyncSourceModel并创建SyncSourceManager(数据源类型注册与管理)与UserDataResourceManager(同步数据落库),在load阶段创建UserDataSyncService并对外定义userData资源,挂载了四个核心 action:
| Action | 作用 |
|---|---|
userData:listSyncTypes | 列出已注册的同步数据源类型(客户端「新增」下拉列表的数据来源) |
userData:pull | 按数据源名称触发一次拉取同步 |
userData:push | 推送同步 |
userData:retry | 重试失败的同步任务 |
从源码结构看,扩展一个数据源需要「两条腿走路」:
- 服务端:实现
SyncSource子类并向sourceManager注册类型,负责真正去外部系统pull()数据; - 客户端:向客户端插件的
sourceTypes注册AdminSettingsForm组件,负责在管理界面渲染该类型的自定义配置表单。
数据源实例本身持久化在 userDataSyncSources 集合中,核心字段包括:name(唯一的数据源名称)、sourceType(对应注册的类型标识)、enabled(是否启用,默认false)、options(json 类型自定义配置,默认{}),以及指向userDataSyncTasks的tasks一对多关联。这正是下面服务端与客户端两半代码衔接起来的「中间层」。
服务端扩展
数据源接口:继承 SyncSource 抽象类
内置的用户数据同步插件提供了数据源类型的注册和管理。扩展数据源类型,需要继承插件提供的SyncSource抽象类,并实现抽象方法pull():
import { SyncSource, UserData } from '@nocobase/plugin-user-data-sync'; class CustomSyncSource extends SyncSource { async pull(): Promise<UserData[]> { return []; } }对照 SyncSource 抽象类源码,可以看到它通过构造参数SyncSourceConfig(包含sourceInstance、options、ctx)注入了三个实例属性:
| 属性 | 说明 |
|---|---|
instance | 当前数据源的SyncSourceModel实例(即userDataSyncSources集合中的一条记录),因此可以直接读取this.instance.name等字段 |
options | 数据源的自定义配置对象,即userDataSyncSources.options字段中的 json 内容 |
ctx | 请求上下文,可用于访问数据库、日志等应用上下文能力 |
除抽象方法pull()外,SyncSource还内置了同步任务生命周期辅助方法,扩展方无需自行管理任务状态:
| 方法 | 行为 | 源码位置 |
|---|---|---|
newTask() | 创建一个status: 'init'的任务,batch为时间戳+随机数生成 | sync-source.ts#L43-L46 |
beginTask(taskId) | 校验任务处于init后置为processing,否则抛错 | sync-source.ts#L48-L59 |
endTask({ taskId, success, cost, message }) | 校验任务处于processing后置为success/failed,并记录耗时与错误信息 | sync-source.ts#L61-L75 |
retryTask(taskId) | 仅当任务为failed时重置为processing | sync-source.ts#L77-L90 |
读取自定义配置 options
SyncSource提供了options属性,用于获取数据源的自定义配置(如第三方系统的appid、secret):
import { SyncSource, UserData } from '@nocobase/plugin-user-data-sync'; class CustomSyncSource extends SyncSource { async pull(): Promise<UserData[]> { //... const { appid, secret } = this.options; //... return []; } }options的流转链路可以从源码得到印证:SyncSourceManager.create() 在实例化数据源时,将集合记录的sourceInstance.options原样传入构造函数,即new syncSource({ sourceInstance, options: sourceInstance.options, ctx })。这些options的值由客户端配置表单提交后写入userDataSyncSources.options(json 字段,默认{}),因此服务端this.options上能取到的键名必须与客户端AdminSettingsForm收集并提交的字段保持一致。
UserData 字段说明
pull()的返回值是UserData[],其通用字段说明如下:
| 字段 | 说明 |
|---|---|
dataType | 数据类型,可选值为user和department |
uniqueKey | 唯一标识字段 |
records | 数据记录 |
sourceName | 数据源名称 |
若dataType为user,则records包含以下字段:
| 字段 | 说明 |
|---|---|
id | 用户 ID |
nickname | 用户昵称 |
avatar | 用户头像 |
email | 邮箱 |
phone | 手机号 |
departments | 所属部门 ID 数组 |
若dataType为department,则records包含以下字段:
| 字段 | 说明 |
|---|---|
id | 部门 ID |
name | 部门名称 |
parentId | 父级部门 ID |
源码核对提示:对照当前仓库中的类型定义 UserData,matchKey为可选字段,且落库前会校验每条记录必须包含uid字段——saveOriginRecords 中,若record.uid === undefined会直接抛出record must has uid错误。当前源码中的 FormatUser 与 FormatDepartment 分别以uid(唯一标识)、nickname、email、phone、departments以及uid、title、parentUid等字段承载数据,并额外支持isDeleted软删除标记。实际开发时建议以源码类型定义为准:保证每条记录携带uid,sourceName使用this.instance.name。
数据源接口实现示例
结合第三方 API 的完整实现示例(来自官方文档):
import { SyncSource, UserData } from '@nocobase/plugin-user-data-sync'; class CustomSyncSource extends SyncSource { async pull(): Promise<UserData[]> { // ... const ThirdClientApi = new ThirdClientApi( this.options.appid, this.options.secret, ); const departments = await this.clientapi.getDepartments(); const users = await this.clientapi.getUsers(); // ... return [ { dataType: 'department', uniqueKey: 'id', records: departments, sourceName: this.instance.name, }, { dataType: 'user', uniqueKey: 'id', records: users, sourceName: this.instance.name, }, ]; } }要点说明:
- 一次
pull()可以同时返回部门与用户两类UserData,插件会按dataType分发给不同的目标资源处理; sourceName使用this.instance.name,即该数据源实例在userDataSyncSources中注册的唯一名称,同步记录表(userDataSyncRecords)会按sourceName + 唯一键 + dataType三元组做增量比对与更新;- 外部 API 客户端(示例中的
ThirdClientApi)由扩展方自行实现,认证凭据从this.options读取。
数据源类型注册
扩展的数据源需要向数据管理模块注册:
import UserDataSyncPlugin from '@nocobase/plugin-user-data-sync'; class CustomSourcePlugin extends Plugin { async load() { const syncPlugin = this.app.pm.get( UserDataSyncPlugin, ) as UserDataSyncPlugin; if (syncPlugin) { syncPlugin.sourceManager.registerType('custom-source-type', { syncSource: CustomSyncSource, title: 'Custom Source', }); } } }注:官方文档原文示例中写的是
reigsterType,结合 SyncSourceManager 源码 核对,实际方法名为registerType,文档示例存在拼写笔误,以源码为准。
registerType(syncSourceType, { syncSource, title })内部将配置写入一个Registry,注册后有两个直接收益:
title会随 listTypes() 一起返回,经userData:listSyncTypesaction 提供给前端,成为「新增数据源」下拉菜单中的展示名;- 管理界面按
name/id查找数据源并触发同步时,SyncSourceManager.getByName/getById 会先按enabled: true过滤出启用状态的记录,再用注册表中的构造函数实例化SyncSource子类——若类型未注册会抛出SyncSourceType [...] is not found。
注册动作放在插件的load()生命周期中,通过this.app.pm.get(UserDataSyncPlugin)获取已启用的同步插件实例;由于依赖同步插件存在,源码示例中保留了if (syncPlugin)的空值保护,这一写法建议沿用。
客户端扩展
registerType 注册类型与 AdminSettingsForm
客户端用户界面通过用户数据同步插件客户端提供的registerType接口注册:
import SyncPlugin from '@nocobase/plugin-user-data-sync/client'; class CustomSourcePlugin extends Plugin { async load() { const sync = this.app.pm.get(SyncPlugin); sync.registerType('custom-source-type', { components: { AdminSettingsForm, // 后台管理表单 }, }); } }从 客户端插件源码 看,PluginUserDataSyncClient内部维护了一个sourceTypes注册表(Registry<SourceOptions>),SourceOptions的类型定义为{ components: Partial<{ AdminSettingsForm: ComponentType }> }。注册时的类型标识字符串必须与服务端registerType的第一个参数一致(示例中均为'custom-source-type'),这样客户端表单才能与服务端拉取逻辑对应到同一个数据源类型。
该组件的渲染时机可在 Options.tsx 中确认:useAdminSettingsForm(sourceType)按当前表单的sourceType(新增)或记录的sourceType(编辑)从注册表取出AdminSettingsForm组件,Options组件在其存在时渲染之,否则不渲染任何内容——因此未注册客户端组件的类型只能走通用配置,无法收集自定义options。
后台管理表单
管理界面中,数据源编辑表单的结构是「上通用 + 下自定义」:
- 上方为通用的数据源配置,对应
userDataSyncSources集合的基础字段:数据源名称name、类型sourceType、显示名displayName、是否启用enabled等,由插件内置的userDataSyncSourcesSchema表单 Schema 渲染(见 UserDataSyncSource.tsx 中挂载的 SchemaComponent); - 下方为可注册的自定义配置表单部分,即扩展方注册的
AdminSettingsForm组件,用于收集该类型的专属配置(如appid、secret),提交后写入options字段,并在服务端pull()时通过this.options取用。
此外,客户端插件在load()中还会通过app.pluginSettingsManager.add('users-permissions.sync', ...)把UserDataSyncSource页面挂到「用户与权限 → 同步」设置入口下(client/index.tsx#L32-L40),并绑定 ACL 片段pm.user-data-sync,与服务端 plugin.ts 中注册的userData:*、userDataSyncSources:*、userDataSyncTasks:*权限片段配套。
同步执行流程:pull 之后的落库与任务管理
理解扩展接口时,有必要知道pull()返回的数据在插件内部如何流转(可结合 UserDataResourceManager 与测试用例 resource-manager.test.ts、api.test.ts 验证):
- 落原始记录:
saveOriginRecords(data)将每条记录按sourceName + uid + dataType查重,存在则把旧数据存入lastMetaData再覆盖metaData,不存在则新建,形成同步记录表(userDataSyncRecords)的增量基线; - 分发给目标资源:
updateOrCreate(data)遍历注册的UserDataResource节点(按拓扑排序,天然支持「先部门后用户」这类依赖顺序),对accepts匹配dataType的资源逐条执行update()或create()——已有本地映射走更新,无映射走创建; - 维护映射关系:目标资源返回的资源主键会写回/移除
userDataSyncRecordsResources映射表,供下次同步判断「更新还是创建」; - 任务与重试:任务状态机为
init → processing → success/failed,失败后可在管理界面「Tasks」面板对failed状态任务点击重试(客户端对应userData:retryaction),任务耗时与错误信息记录在任务表的cost、message字段中。
这也解释了为什么扩展SyncSource时只需要关心pull():任务状态、记录比对、资源分发、失败重试均由插件框架统一接管。
关键文件索引
| 文件 | 内容 |
|---|---|
| src/server/sync-source.ts | SyncSource抽象类、任务生命周期方法 |
| src/server/sync-source-manager.ts | 服务端类型注册表registerType/listTypes/实例化逻辑 |
| src/server/plugin.ts | 服务端插件入口,userData资源与 ACL 片段注册 |
| src/server/user-data-resource-manager.ts | UserData/FormatUser/FormatDepartment类型与落库分发逻辑 |
| src/server/collections/user-data-sync-sources.ts | 数据源集合字段定义(options、enabled等) |
| src/client/index.tsx | 客户端插件入口与registerType |
| src/client/Options.tsx | AdminSettingsForm组件的查找与渲染 |
| src/client/UserDataSyncSource.tsx | 管理界面:新增/同步/任务/重试交互 |
| docs/docs/cn/users-permissions/sync/dev/source.md | 官方原始文档 |
小结
扩展一个 NocoBase 用户数据同步数据源的最小闭环是:实现SyncSource子类并在load()中调用服务端sourceManager.registerType;实现AdminSettingsForm并调用客户端插件registerType;两端类型标识一致后,管理界面即可新增该类型数据源、填写自定义options,点击同步后由userData:pull触发pull(),框架自动完成任务管理、记录比对与向用户/部门等目标资源的分发。实现时注意以当前源码类型定义核对记录字段(尤其uid必填约束),并保留对同步插件实例的空值保护。
【免费下载链接】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),仅供参考