본문으로 건너뛰기

NocoBase 数据源元数据配置指南

概述

NocoBase 使用 PostgreSQL (rgb schema) 存储 collection 和 field 的元数据。当通过 db2cm(Database to Collection Map)方式从已有数据库同步表结构时,生成的元数据往往不完整且组件名错误,需要手动修正。

本文档记录了正确的元数据格式、常见问题及修复方法。


1. 环境信息

项目
NocoBase 版本2.0.16
PostgreSQLSupabase (supabase-db 容器)
PG Schemargb
元数据表rgb.collectionsrgb.fieldsrgb."uiSchemas"
连接方式docker exec supabase-db psql -U supabase_admin -d postgres

2. Collection 配置 (rgb.collections)

2.1 正确格式(以 player_deposit 为参考)

{
"schema": "rgb",
"tableName": "player_deposit",
"timestamps": false,
"autoGenId": false,
"filterTargetKey": "guid",
"underscored": false
}

2.2 db2cm 同步的错误格式(修复前)

{
"from": "db2cm",
"autoGenId": false,
"createdAt": false,
"createdBy": false,
"updatedAt": false,
"updatedBy": false,
"timestamps": false
}

2.3 关键字段说明

字段必须说明
schema数据库 schema 名称,固定 "rgb"
tableName数据库实际表名,通常与 collection name 一致
autoGenId业务表必须为 false(使用自定义主键 guid
filterTargetKey记录标识字段,业务表统一为 "guid"。缺失会导致关联查询失败
timestamps是否自动管理时间戳,业务表为 false
underscored字段名是否使用下划线风格,设为 false。RGB 的 MySQL 表/列本身已是 snake_case,设 false 告诉 NocoBase 不要再做二次命名转换
from-"db2cm" 表示通过数据库同步创建,可保留

2.4 批量修复 SQL

-- 1. 为所有 db2cm 表补充 schema/tableName/underscored
UPDATE rgb.collections
SET options = options::jsonb
|| jsonb_build_object('schema', 'rgb')
|| jsonb_build_object('tableName', name)
|| jsonb_build_object('underscored', false)
WHERE options->>'from' = 'db2cm'
AND options->>'schema' IS NULL;

-- 2. 为所有 db2cm 同步的业务表补充 filterTargetKey
UPDATE rgb.collections
SET options = jsonb_set(options::jsonb, '{filterTargetKey}', '"guid"')
WHERE (options->>'autoGenId')::text = 'false'
AND options->>'filterTargetKey' IS NULL
AND options->>'from' = 'db2cm';

3. Field 配置 (rgb.fields)

3.1 正确格式对照表

以下是 NocoBase 各数据类型的正确 interfacex-component 配置:

DB 类型 (type)interfacex-componentx-component-props
stringinputInput{"style":{"width":"100%"}}
integerintegerInputNumber{"stringMode":true,"step":"1"}
bigIntintegerInputNumber{"stringMode":true,"step":"1"}
decimalnumberInputNumber{"stringMode":true,"step":"1"}
floatnumberInputNumber{"stringMode":true,"step":"1"}
booleancheckboxCheckbox-
datetimeNoTzdatetimeNoTzDatePicker{"showTime":false,"utc":false}
datetimedatetimeDatePicker{"showTime":true}
jsonjsonInput.JSON-
texttextareaInput.TextArea-
belongsTom2oAssociationField{"fieldNames":{"value":"guid","label":"..."}}
passwordpasswordPassword-
datedateOnlyDatePicker{"showTime":false,"utc":false}
urlurlInput.URL{"style":{"width":"100%"}}
emailemailInput{"style":{"width":"100%"}}
phonephoneInput{"style":{"width":"100%"}}
percentpercentInputNumber{"stringMode":true,"step":"1"}
unixTimestampunixTimestampDatePicker{"showTime":true,"utc":false}
multipleSelectmultipleSelectSelect{"mode":"multiple"}

注:dateOnlyurlemailphonepercentunixTimestampmultipleSelectsync_meta.pyUI_KIND_OVERRIDE 机制生成,利用 NocoBase 原生 interface 获得内置渲染与验证支持。

3.2 db2cm 常见错误

db2cm 同步时,interfacex-component 经常被设为与 type 相同的值

错误 interface正确 interface错误 x-component正确 x-component
stringinputInput
bigIntintegerInputNumber
decimalnumberNumberInputNumber
floatnumberInputNumber
IntegerInputNumber

⚠️ IntegerNumber 不是 NocoBase 注册的组件,使用会导致 React error #130(Element type is invalid: expected a string or class/function but got: undefined)

3.3 完整 field options 结构

正确示例(player_deposit.amount):

{
"allowNull": false,
"primaryKey": false,
"unique": false,
"possibleTypes": ["bigInt", "snowflakeId", "unixTimestamp", "sort"],
"rawType": "BIGINT",
"field": "amount",
"uiSchema": {
"x-component": "InputNumber",
"x-component-props": { "style": { "width": "100%" } },
"title": "金额"
}
}

错误示例(db2cm 同步的 player.balance):

{
"allowNull": false,
"uiSchema": {
"title": "余额",
"x-component": "Number" // ← 不存在的组件!
}
// 缺少: field, rawType, possibleTypes, primaryKey, unique
}

3.4 批量修复 SQL

-- 1. 修复 interface 值
UPDATE rgb.fields SET "interface" = 'input' WHERE "interface" = 'string';
UPDATE rgb.fields SET "interface" = 'integer' WHERE "interface" = 'bigInt';
UPDATE rgb.fields SET "interface" = 'number' WHERE "interface" = 'decimal';
UPDATE rgb.fields SET "interface" = 'number' WHERE "interface" = 'float';

-- 2. 修复 x-component: Integer → InputNumber
UPDATE rgb.fields
SET options = jsonb_set(
jsonb_set(options::jsonb, '{uiSchema,x-component}', '"InputNumber"'),
'{uiSchema,x-component-props}', '{"stringMode":true,"step":"1"}'
)
WHERE options->'uiSchema'->>'x-component' = 'Integer';

-- 3. 修复 x-component: Number → InputNumber
UPDATE rgb.fields
SET options = jsonb_set(
jsonb_set(options::jsonb, '{uiSchema,x-component}', '"InputNumber"'),
'{uiSchema,x-component-props}', '{"stringMode":true,"step":"1"}'
)
WHERE options->'uiSchema'->>'x-component' = 'Number';

-- 4. 补充缺失的 field 属性(DB 列名映射)
UPDATE rgb.fields
SET options = options::jsonb || jsonb_build_object('field', name)
WHERE options->>'field' IS NULL
AND type NOT IN ('belongsTo', 'hasMany', 'belongsToMany', 'hasOne');

3.5 RGB 语义提示(options.rgbMeta

为了让 NocoBase 尽量还原旧 bo/admin 的展示层语义,当前同步脚本会在 rgb.fields.options 下额外写入一层 rgbMeta。这一层不直接替代 NocoBase 官方字段配置,而是作为“显示语义补充”,供后续页面生成器、自定义字段或插件读取。

示例(banner.img_url):

{
"field": "img_url",
"uiSchema": {
"title": "图片",
"x-component": "Input"
},
"rgbMeta": {
"uiKind": "attachment_url",
"preview": "image",
"uploadStrategy": "base64_image"
}
}

当前约定的重点语义:

rgbMeta.uiKind典型字段说明
urldownload.url banner.jump_url外链,适合链接跳转/下载
attachment_urlbanner.img_url game.icon_url外部资源 URL,适合图片预览
internal_pathbanner.jump_path player_notice.insite_url站内路径,不应做 URL 校验
domainplayer.register_domain player_level.domain裸域名,走域名校验
emaildaebak_email.email邮箱字段
phonephone_num电话输入与脱敏语义
richtext_htmlpromotion.content agent_notice.content富文本 HTML,不是 Markdown
unix_timestamp*_tsUnix 时间戳,展示层应转时间控件/时间列
moneybank_stat_day.deposit_amount金额显示
percentprovider.percent百分比显示
oddsdeposit_event.odds_need赔率显示
m2m_arrayplayer_message.tags多对多数组,不建议裸 JSON 编辑
multi_selectdeposit_event.weeks有限选项数组

辅助字段说明:

字段说明
rgbMeta.previewimage / link / download
rgbMeta.copyable是否建议展示“复制”按钮
rgbMeta.uploadStrategybase64_image / attachment / external_url
rgbMeta.validatorurl / domain / internal_path
rgbMeta.timezone时间字段建议时区
rgbMeta.formatHint轻量格式提示,预留给复杂数字格式

注意:rgbMeta 是 RGB 侧扩展,不是 NocoBase 官方内置协议。它的目标是先把“唯一真相”里的显示语义保留下来,再由 NocoBase 页面层逐步消费这些 hint。


4. uiSchema 配置 (rgb."uiSchemas")

4.1 TableV2 的 rowKey 问题

NocoBase 客户端创建新的 Table Block 时,会硬编码 rowKey: "id"。对于使用 guid 作为主键的业务表,这会导致渲染异常。

修复方式:

插件服务端 (plugin-rgb-preset-actions) 已添加中间件,在 uiSchemas:insertAdjacent 请求中自动将 rowKey: "id" 替换为 collection 的 filterTargetKey(通常为 "guid")。

如需手动修复已有的 uiSchema:

-- 查找所有 rowKey 为 id 的 TableV2 schema
SELECT "x-uid", schema::text
FROM rgb."uiSchemas"
WHERE schema::text LIKE '%"rowKey":"id"%'
AND schema::text LIKE '%TableV2%';

-- 替换为 guid(谨慎操作,建议逐条确认)
UPDATE rgb."uiSchemas"
SET schema = replace(schema::text, '"rowKey":"id"', '"rowKey":"guid"')::jsonb
WHERE schema::text LIKE '%"rowKey":"id"%'
AND schema::text LIKE '%TableV2%';

4.2 RGBPrettyField 约定

对于表格列、详情区块这类只读展示场景,当前 RGB 侧页面生成器不再一律写死为 CollectionField。如果字段带有 rgbMeta 语义提示,会改用自定义组件 RGBPrettyField

{
"x-collection-field": "banner.img_url",
"x-component": "RGBPrettyField",
"x-component-props": {
"fieldName": "img_url",
"uiKind": "attachment_url",
"preview": "image",
"copyable": false,
"validator": "none",
"ellipsis": false
},
"x-read-pretty": true
}

RGBPrettyField 当前主要负责:

场景行为
attachment_url + image直接图片预览
url / email / phone / domain直接渲染成可点击链接
unix_timestamp格式化为可读时间
richtext_html以纯文本预览 HTML 内容
m2m_array / multi_select数组转逗号分隔文本
copyable=true增加复制能力

依赖关系说明:

组件/工具作用依赖
plugin-rgb-preset-actions注册客户端组件 RGBPrettyField运行时必须存在
plugin-rgb-ai-builder生成带 RGBPrettyField 的 uiSchema依赖上面的客户端插件
rgb-builder.js / gen_pages.py直接写 PG 生成页面依赖上面的客户端插件

默认部署约定是这些插件/工具同时存在。如果运行时移除了 plugin-rgb-preset-actions,已经生成的 RGBPrettyField 节点会失去组件注册来源。

时间显示约定:

  • RGBPrettyFieldunix_timestamp 展示统一强制按 韩国时区(UTC+9 / Asia/Seoul) 格式化。
  • PATCH 抽屉里的时间控件也按同一套韩国时区换算显示与回写。
  • 即使字段元数据里带了 rgbMeta.timezone,当前 NocoBase 显示层也不会覆盖这条约定,展示仍以韩国时区为准。

这样可以在不强依赖 NocoBase 官方字段接口名的前提下,先把 RGB 自己的显示语义恢复出来。


5. 修改后的注意事项

5.1 必须触发数据源同步

直接修改 PG 中的元数据后,NocoBase 的内存缓存不会自动刷新。必须执行以下操作之一:

方式一:API 调用(推荐)

# 获取 token
TOKEN=$(curl -s http://localhost:8880/api/auth:signIn -X POST \
-H 'Content-Type: application/json' \
-d '{"account":"nocobase","password":"admin123"}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['data']['token'])")

# 刷新数据源
curl -s -H "Authorization: Bearer $TOKEN" \
"http://localhost:8880/api/dataSources:refresh?filterByTk=main" -X POST

方式二:UI 操作

进入 NocoBase → 设置 → 数据源 → 主数据源 → 点击「刷新」按钮

方式三:重启容器

docker restart nocobase

⚠️ 单纯重启可能不够,建议重启后再通过 API 调用 dataSources:refresh

辅助方式:rgb-builder.js clearCache

# 清除 NocoBase 应用层缓存(不等同于 dataSources:refresh,仅清理 UI 层缓存)
node tools/nocobase/rgb-builder.js clearCache

注意:clearCache 只清理 NocoBase 应用缓存并提示重启,不会触发数据源元数据重新加载。正式同步后仍需使用上面的 API 调用或 UI 刷新。

5.2 验证检查清单

修改完成后,按以下步骤验证:

-- 1. 检查是否有非法 interface
SELECT DISTINCT "interface", count(*)
FROM rgb.fields
WHERE "interface" IN ('string', 'bigInt', 'decimal', 'float')
GROUP BY "interface";
-- 期望: 0 rows

-- 2. 检查是否有非法 x-component
SELECT DISTINCT options->'uiSchema'->>'x-component' as comp, count(*)
FROM rgb.fields
WHERE options->'uiSchema'->>'x-component' IN ('Integer', 'Number')
GROUP BY comp;
-- 期望: 0 rows

-- 3. 检查 autoGenId=false 表是否都有 filterTargetKey
SELECT name
FROM rgb.collections
WHERE (options->>'autoGenId')::text = 'false'
AND options->>'filterTargetKey' IS NULL
AND options->>'from' = 'db2cm';
-- 期望: 0 rows (或仅有 NocoBase 内部表)

-- 4. 检查 collection 是否有 schema/tableName
SELECT name
FROM rgb.collections
WHERE options->>'from' = 'db2cm'
AND options->>'schema' IS NULL;
-- 期望: 0 rows

-- 5. 通过 API 验证字段元数据
curl -s -H "Authorization: Bearer $TOKEN" \
"http://localhost:8880/api/fields:list?filter[collectionName]=player&paginate=false" \
| python3 -c "
import sys,json
for f in json.load(sys.stdin)['data']:
comp = (f.get('uiSchema') or {}).get('x-component','?')
print(f'{f[\"name\"]:20s} interface={f.get(\"interface\",\"?\"):15s} x-component={comp}')
"

5.3 新增业务表时的操作流程

  1. 在 MySQL 中创建新表(确保有 guid 主键)
  2. 在 NocoBase 数据源管理中点击「刷新」同步表结构
  3. 使用 NocoBase UI「配置字段 → 从数据库同步」来创建字段
  4. 不要使用 db2cm 批量同步,会导致元数据不完整
  5. 如果已经通过 db2cm 同步,需要按本文档的 SQL 进行修复

6. 排错指南

React error #130

现象: 前端报 Minified React error #130; Element type is invalid: expected a string or class/function but got: undefined

排查步骤:

  1. 下载 NocoBase 日志(设置 → 系统日志 → 下载)
  2. system_error 日志中搜索 Diagnostic information
  3. 查看 diagnostic schema 中的 x-decorator-props.collection 确定是哪个表
  4. 分层检查 x-component 是否合法

字段层rgb.fields)— 检查字段元数据中的组件是否为 NocoBase 注册组件:

SELECT name, options->'uiSchema'->>'x-component' as comp
FROM rgb.fields
WHERE "collectionName" = '<table_name>'
AND options->'uiSchema'->>'x-component' NOT IN (
-- NocoBase 原生字段组件
'Input', 'Input.TextArea', 'Input.URL', 'Input.JSON',
'InputNumber', 'DatePicker', 'Select',
'Checkbox', 'Switch', 'Radio.Group', 'Checkbox.Group',
'AssociationField', 'Markdown', 'Password', 'ColorPicker',
'Upload.Attachment',
-- sync_meta UI_KIND_OVERRIDE 生成的原生组件(interface 不同但组件复用)
-- url→Input.URL, email/phone→Input, percent→InputNumber,
-- unixTimestamp→DatePicker, multipleSelect→Select
-- 组件本身已在上方列出,此注释仅说明来源
);

💡 UI_KIND_OVERRIDE 生成的字段使用的组件(Input.URLInputInputNumberDatePickerSelect)均为 NocoBase 原生注册组件,不会触发 React #130。新增的 interface 值为:dateOnlyurlemailphonepercentunixTimestampmultipleSelect

页面层rgb."uiSchemas")— 检查页面 schema 中的组件是否已注册(含 RGB 自定义组件):

SELECT "x-uid", schema->>'x-component' as comp
FROM rgb."uiSchemas"
WHERE schema::text LIKE '%<table_name>%'
AND schema->>'x-component' NOT IN (
-- NocoBase 原生字段组件(同上)
'Input', 'Input.TextArea', 'Input.URL', 'Input.JSON',
'InputNumber', 'DatePicker', 'Select',
'Checkbox', 'Switch', 'Radio.Group', 'Checkbox.Group',
'AssociationField', 'Markdown', 'Password', 'ColorPicker',
'Upload.Attachment',
-- NocoBase 原生布局/区块组件
'CollectionField', 'TableV2', 'Grid', 'Grid.Row', 'Grid.Col',
'CardItem', 'FormItem', 'ActionBar', 'Action', 'Space',
-- RGB 自定义组件(需 plugin-rgb-preset-actions)
'RGBPrettyField', 'RGBPostAction', 'RGBPatchAction'
)
AND schema->>'x-component' IS NOT NULL;

Association not found

现象: association xxx_ref in yyy not found

排查步骤:

  1. 检查 belongsTo 字段的 target collection 是否存在
  2. 检查 targetKey 字段是否在目标表中存在
  3. 检查 foreignKey 字段是否在当前表中存在且有 isForeignKey: true
SELECT f.name, f.options->>'target' as target,
f.options->>'targetKey' as tk, f.options->>'foreignKey' as fk
FROM rgb.fields f
WHERE f."collectionName" = '<table_name>' AND f.type = 'belongsTo';

7. 参考