draftgo-cli 3.0.1 → 3.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -124
  3. package/package.json +21 -8
  4. package/resources/skill/SKILL.md +62 -89
  5. package/resources/skill/init/SKILL.md +18 -67
  6. package/resources/skill/manifest.json +27 -0
  7. package/resources/skill/pull/SKILL.md +18 -44
  8. package/resources/skill/push/SKILL.md +30 -247
  9. package/resources/skill/references/aihub.md +86 -0
  10. package/resources/skill/references/api-endpoints.md +178 -0
  11. package/resources/skill/references/api.json +20248 -0
  12. package/resources/skill/{quickref → references}/app-api.md +44 -14
  13. package/resources/skill/{core → references}/architecture.md +6 -26
  14. package/resources/skill/references/chat-sdk.md +201 -0
  15. package/resources/skill/references/custom-services.md +308 -0
  16. package/resources/skill/references/data.md +298 -0
  17. package/resources/skill/references/db-relations.md +227 -0
  18. package/resources/skill/references/frontend.md +788 -0
  19. package/resources/skill/references/modules.md +66 -0
  20. package/resources/skill/references/parallel.md +48 -0
  21. package/resources/skill/{specs → references}/runtime.md +31 -1
  22. package/resources/skill/{specs → references}/security.md +3 -3
  23. package/resources/skill/references/ui-protocol.md +99 -0
  24. package/resources/skill/scripts/draftgo_delete.py +0 -2
  25. package/resources/skill/scripts/draftgo_init.py +15 -3
  26. package/resources/skill/scripts/draftgo_pull.py +154 -87
  27. package/resources/skill/scripts/draftgo_push.py +440 -183
  28. package/resources/skill/story/SKILL.md +13 -23
  29. package/src/cli.js +22 -7
  30. package/src/commandRegistry.js +34 -0
  31. package/src/commands/api.js +204 -0
  32. package/src/commands/autoPush.js +41 -0
  33. package/src/commands/check.js +27 -17
  34. package/src/commands/delete.js +6 -4
  35. package/src/commands/deploy.js +31 -0
  36. package/src/commands/help.js +41 -28
  37. package/src/commands/init.js +34 -20
  38. package/src/commands/local.js +9 -3
  39. package/src/commands/map.js +18 -7
  40. package/src/commands/sync.js +11 -4
  41. package/src/commands/update.js +39 -52
  42. package/src/commands/verifyUi.js +199 -0
  43. package/src/index.js +13 -46
  44. package/src/localdev/compose.js +48 -197
  45. package/src/localdev/index.js +116 -216
  46. package/src/localdev/mysqlClient.js +12 -9
  47. package/src/localdev/services.js +163 -0
  48. package/src/platforms.js +3 -3
  49. package/src/projectConfig.js +12 -2
  50. package/src/projectMap.js +240 -68
  51. package/src/skill.js +113 -29
  52. package/src/updateCheck.js +37 -15
  53. package/resources/skill/core/modules.md +0 -54
  54. package/resources/skill/practices/anti-patterns.md +0 -70
  55. package/resources/skill/practices/best-practices.md +0 -41
  56. package/resources/skill/practices/dev-declaration.md +0 -94
  57. package/resources/skill/quickref/api-endpoints.md +0 -130
  58. package/resources/skill/quickref/api.json +0 -17675
  59. package/resources/skill/quickref/dg-components.md +0 -198
  60. package/resources/skill/rules/dev-workflow.md +0 -652
  61. package/resources/skill/rules/frontend.md +0 -210
  62. package/resources/skill/rules/parallel.md +0 -263
  63. package/resources/skill/specs/data.md +0 -108
  64. package/resources/skill/specs/ui-protocol.md +0 -68
  65. package/src/commands/doctor.js +0 -54
  66. package/src/commands/new.js +0 -183
  67. package/src/commands/projectScript.js +0 -37
  68. /package/resources/skill/{rules → references}/debugging-syntax.md +0 -0
@@ -0,0 +1,298 @@
1
+ ---
2
+ read_when: 操作动态 DB 之前 · 设计数据结构时 · 使用 filters 检索时 · 定义关联关系时
3
+ ---
4
+
5
+ # 动态 DB & 数据层
6
+
7
+ ## DB Meta 结构
8
+
9
+ ```json
10
+ {
11
+ "type": "order",
12
+ "label": "订单",
13
+ "schema": {
14
+ "type": "object",
15
+ "properties": {
16
+ "name": { "type": "string", "title": "姓名", "required": true, "searchable": "fuzzy" },
17
+ "status": { "type": "string", "title": "状态", "required": false, "searchable": "exact" },
18
+ "amount": { "type": "number", "title": "金额", "required": false, "searchable": "range" },
19
+ "paid_at": { "type": "datetime", "title": "支付时间", "required": false, "searchable": "range" },
20
+ "tags": { "type": "array", "title": "标签", "required": false, "searchable": "contains" },
21
+ "note": { "type": "string", "title": "备注", "required": false, "searchable": false }
22
+ }
23
+ },
24
+ "permission": {
25
+ "public": { "read": "none", "create": "none", "update": "none", "delete": "none" },
26
+ "login": { "read": "all", "create": "all", "update": "owner", "delete": "owner" },
27
+ "admin": { "read": "all", "create": "all", "update": "all", "delete": "all" }
28
+ }
29
+ }
30
+ ```
31
+
32
+ **searchable 模式**:`false`(不可检索)/ `"exact"`(精确)/ `"fuzzy"`(模糊)/ `"range"`(数值/时间范围)/ `"contains"`(数组包含)
33
+
34
+ `date` 字段保存为 `YYYY-MM-DD`;`datetime` 字段保存为 ISO 8601 字符串,带时区的输入会规范化为 UTC。`searchable: true` 对 `number` / `date` / `datetime` 会自动推断为 `range`。
35
+
36
+ **系统字段**:以下系统字段**无需在 schema 中定义**,可直接用于检索和排序:
37
+
38
+ | 字段 | 类型 | 检索模式 | 说明 |
39
+ |---|---|---|---|
40
+ | `id` | number | range | 记录 ID,支持 `eq` / `gte` / `lte` / `gt` / `lt` / `in` |
41
+ | `userid` | number | range | 所属用户 ID,支持 `eq` / `gte` / `lte` / `gt` / `lt` / `in` |
42
+ | `created_at` | datetime | range | 创建时间,支持 `eq` / `gte` / `lte` / `gt` / `lt` / `in` |
43
+ | `updated_at` | datetime | range | 更新时间,支持 `eq` / `gte` / `lte` / `gt` / `lt` / `in` |
44
+
45
+ ⚠️ **注意**:虽然 DB 表有 `status` 系统列(1=正常 0=禁用 -1=删除),但由于业务常用此字段名,**需在 schema 中显式声明 `status` 的 searchable 才可检索**。
46
+
47
+ ---
48
+
49
+ ## 自定义服务内的 draftgo.DB.Query
50
+
51
+ Go 自定义服务使用 `draftgo.DB.Query(type, sdk.QueryOptions{...})` 操作动态 DB,返回 `sdk.QueryResult`:
52
+
53
+ ```go
54
+ result, err := draftgo.DB.Query("order", sdk.QueryOptions{
55
+ Filters: map[string]any{"status": "paid"},
56
+ Page: 1, PageSize: 20, OrderBy: "id", Order: "desc",
57
+ })
58
+ items := result.Items
59
+ ```
60
+
61
+ | 参数 | 说明 |
62
+ |---|---|
63
+ | `filters` | 字典形式结构化过滤;默认 `{field: value}` 是 `eq` 精确匹配 |
64
+ | `Page` / `PageSize` | 分页参数;零值交由平台使用默认列表行为 |
65
+ | `order_by` / `order` | 按 searchable 字段排序,`order` 为 `asc` / `desc` |
66
+
67
+ ⚠️ 返回结构为 `sdk.QueryResult{Items, Total, Page, PageSize}`。需要完整数据时必须按 `Total` 分页读取,不能用超大 `PageSize` 假装全量。
68
+
69
+ 普通用户调用时只读 `status=1` 数据;系统身份/管理员脚本可读全部状态数据,但仍受 db_meta permission 约束。不要把“拿不到禁用数据”和分页截断混在一起排查。
70
+
71
+ `filters` 操作符示例:
72
+
73
+ ```go
74
+ draftgo.DB.Query("order", sdk.QueryOptions{Filters: map[string]any{
75
+ "status": "paid",
76
+ "customer_name": map[string]any{"op": "like", "value": "张"},
77
+ "amount": map[string]any{"op": "gte", "value": 100},
78
+ "id": map[string]any{"op": "in", "value": []int{1, 2, 3}},
79
+ }})
80
+ ```
81
+
82
+ 字段必须在 db_meta schema 中标记 `searchable`,系统字段 `id/userid/created_at/updated_at` 可直接检索和排序。`status` 若作为业务字段检索,仍需在 schema 中显式声明。
83
+
84
+ ---
85
+
86
+ ## 关联关系(ref)
87
+
88
+ **完整示例**:`references/db-relations.md`(随 skill 分发,不依赖项目根目录文档)
89
+
90
+ ### 基本配置
91
+
92
+ ```json
93
+ {
94
+ "type": "order",
95
+ "schema": {
96
+ "properties": {
97
+ "user_id": {
98
+ "type": "number",
99
+ "label": "用户",
100
+ "ref": {
101
+ "type": "user", // 关联的数据类型(必填)
102
+ "relation": "many-to-one", // 关联类型(可选,默认 many-to-one)
103
+ "onDelete": "cascade" // 删除策略(可选,默认 no_action)
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ ### 关联类型(relation)
112
+
113
+ | 类型 | 字段类型 | 说明 | 示例 |
114
+ |-----|---------|------|------|
115
+ | `many-to-one` | number | 多个记录指向一个(默认) | 多个订单 → 一个用户 |
116
+ | `one-to-many` | 虚拟字段 | 反向关系,需指定 `inverse_field` | 一个用户 → 多个订单 |
117
+ | `many-to-many` | array | 字段存储 ID 数组 | 一篇文章 ↔ 多个标签 |
118
+
119
+ ### 删除策略(onDelete)
120
+
121
+ | 策略 | 行为 | 适用场景 |
122
+ |-----|------|----------|
123
+ | `cascade` | 级联删除所有引用记录 | 订单依赖用户 |
124
+ | `set_null` | 将引用字段置为 null | 删除分类后商品仍保留 |
125
+ | `set_default` | 设为 `ref.default` 指定的值 | 删除自定义分类后归入"默认分类" |
126
+ | `restrict` | 有引用时禁止删除,返回 400 | 防止误删有依赖的关键数据 |
127
+ | `detach` | 仅多对多,从数组中移除 ID | 删除标签时从文章中移除 |
128
+ | `no_action` | 不处理(默认) | 兼容旧数据 |
129
+
130
+ **删除链路规则:**
131
+ - `many-to-one` 和 `many-to-many` 是真实持有引用 ID 的字段,会参与 `onDelete` 处理。
132
+ - `one-to-many` 是查询用虚拟反向关系,只用于 `populate`,删除链路会跳过它。
133
+ - 需要删除 A 时自动处理引用 A 的 B,必须在 B 的真实引用字段上配置 `ref.onDelete`。
134
+
135
+ ### 关联查询(populate)
136
+
137
+ ```javascript
138
+ // 多对一:查询订单,自动填充用户信息
139
+ const res = await App.get(`db/order`, { populate: ['user_id'] });
140
+ // 返回:data.user_id_obj = {id, data: {name, email}}
141
+
142
+ // 多对多:查询文章,自动填充标签列表
143
+ const res = await App.get(`db/article`, { populate: ['tag_ids'] });
144
+ // 返回:data.tag_ids_objs = [{id, data}, {id, data}, ...]
145
+
146
+ // 一对多:查询用户,自动填充订单列表
147
+ const res = await App.get(`db/user/123`, { populate: ['orders'] });
148
+ // 返回:data.orders_objs = [{id, data}, {id, data}, ...]
149
+
150
+ // 多字段同时填充
151
+ const res = await App.get(`db/order`, { populate: ['user_id', 'product_id'] });
152
+ ```
153
+
154
+ ### 完整示例
155
+
156
+ ```json
157
+ {
158
+ "type": "order",
159
+ "label": "订单",
160
+ "schema": {
161
+ "properties": {
162
+ "user_id": {
163
+ "type": "number",
164
+ "label": "用户",
165
+ "required": true,
166
+ "ref": {
167
+ "type": "user",
168
+ "relation": "many-to-one",
169
+ "onDelete": "cascade"
170
+ }
171
+ },
172
+ "product_ids": {
173
+ "type": "array",
174
+ "label": "商品列表",
175
+ "ref": {
176
+ "type": "product",
177
+ "relation": "many-to-many",
178
+ "onDelete": "detach"
179
+ }
180
+ },
181
+ "category_id": {
182
+ "type": "number",
183
+ "label": "分类",
184
+ "ref": {
185
+ "type": "category",
186
+ "relation": "many-to-one",
187
+ "onDelete": "set_default",
188
+ "default": 0
189
+ }
190
+ }
191
+ }
192
+ }
193
+ }
194
+ ```
195
+
196
+ **行为演示:**
197
+ - 删除用户 → 自动删除其所有订单(cascade)
198
+ - 删除商品 → 从订单的 product_ids 数组中移除(detach)
199
+ - 删除分类 → 订单的 category_id 设为 0(set_default)
200
+
201
+ **⚠️ 注意事项:**
202
+ - 级联删除会递归处理,深层级联可能影响性能
203
+ - 级联操作继承当前用户权限,无权删除子记录会导致整个操作失败
204
+ - 所有级联操作在同一事务中执行,失败会自动回滚
205
+ - 不要在前端用多次 DELETE 手工模拟级联;优先用 DBMeta 的 `ref.onDelete` 让基座统一保证一致性
206
+
207
+ ---
208
+
209
+ ## CRUD 操作范式
210
+
211
+ ```javascript
212
+ // 查询(分页 + 结构化检索)
213
+ const res = await App.get(`db/order`, {
214
+ page: 1, page_size: 20,
215
+ filters: ['name:like:张', 'status:eq:paid', 'amount:gte:100'],
216
+ order_by: 'amount', order: 'desc',
217
+ });
218
+ const { items, total } = res.data;
219
+
220
+ // 非后台管理页面读取业务列表时,如果当前用户含 admin 角色,带 scope=mine
221
+ // 这只影响 GET /api/db/{type} 列表,让管理员业务视角只看自己的 owner 数据
222
+ const ownOrders = await App.get(`db/order`, {
223
+ page: 1,
224
+ page_size: 20,
225
+ scope: 'mine',
226
+ });
227
+
228
+ // 系统字段检索和排序(无需在 schema 中定义)
229
+ const res = await App.get(`db/order`, {
230
+ filters: ['created_at:gte:2024-01-01', 'userid:eq:123'],
231
+ order_by: 'created_at', order: 'desc',
232
+ });
233
+
234
+ // 创建(业务字段必须放在 data 包裹里)
235
+ await App.post(`db/order`, { data: { name: '张三', amount: 200 }, status: 1 });
236
+
237
+ // 更新
238
+ await App.put(`db/order/${id}`, { data: { amount: 250 } });
239
+
240
+ // 删除
241
+ await App.delete(`db/order/${id}`);
242
+
243
+ // 批量更新(原子事务,任一失败全批回滚)
244
+ await App.patch(`db/order/batch`, [
245
+ { id: 1, data: { status: 'paid' } },
246
+ { id: 2, data: { status: 'paid' } },
247
+ ]);
248
+ ```
249
+
250
+ ---
251
+
252
+ ## filters 操作符
253
+
254
+ | 操作符 | 含义 | 适用 searchable 模式 |
255
+ |---|---|---|
256
+ | `eq` | 精确等于 | exact / fuzzy / range |
257
+ | `like` | 模糊包含 | fuzzy |
258
+ | `gte` / `lte` / `gt` / `lt` | 数值/时间范围 | range |
259
+ | `in` | 枚举命中(值逗号分隔:`status:in:paid,pending`) | exact / fuzzy / range |
260
+ | `contains` | 数组字段包含某值 | contains |
261
+
262
+ - 省略操作符(`filters: ['name:张三']`)默认 `like`
263
+ - 多个 filters 为 AND
264
+ - 字段未标 searchable 或操作符不匹配 → 后端返回 400
265
+ - **系统字段**(`id` / `userid` / `created_at` / `updated_at`)无需在 schema 中声明,可直接使用
266
+ - `scope=mine` 只对拥有 admin 角色的用户在 `GET /api/db/{type}` 列表请求中生效;非后台管理页面若当前用户是管理员,读取动态 DB 业务列表时应带该参数;后台管理页不要带,详情和写操作也不要带
267
+
268
+ ---
269
+
270
+ ## db_meta 本地文件
271
+
272
+ 开发前先读 `.draftgo/db_meta/index.json` 了解可用 type 和字段:
273
+
274
+ ```json
275
+ [
276
+ {
277
+ "id": 1,
278
+ "type": "order",
279
+ "label": "订单",
280
+ "schema": { ... }
281
+ }
282
+ ]
283
+ ```
284
+
285
+ ⚠️ **GET `/api/db-meta/{type}` 用 type(如 `order`),不是 id。PUT/DELETE 才用 id。**
286
+
287
+ ---
288
+
289
+ ## 通用筛选参数(非动态 DB)
290
+
291
+ 用于 users / roles / pages / navigations / feedback 等标准资源:
292
+
293
+ | 参数 | 说明 |
294
+ |---|---|
295
+ | `page` / `page_size` | 分页(不传返回全量且无上限;任一传入则分页,缺失项按 `page=1` / `page_size=20` 兜底) |
296
+ | `search` | 全文搜索(动态 DB 不用此参数) |
297
+ | `status` | 状态过滤 |
298
+ | `type` / `tag` | 类型/标签过滤 |
@@ -0,0 +1,227 @@
1
+ ---
2
+ read_when: 需要 DB 关联关系完整示例时 · 设计 populate / onDelete 时
3
+ ---
4
+
5
+ # DB 关联关系与级联操作
6
+
7
+ DraftGo 动态 DB 通过 DBMeta.schema.properties.<field>.ref 定义关联关系,并支持删除一致性处理和 populate 关联查询。
8
+
9
+ ## 关联类型
10
+
11
+ ### many-to-one
12
+
13
+ 多个记录指向一个目标记录,字段真实保存目标记录 ID。
14
+
15
+ ```json
16
+ {
17
+ "type": "order",
18
+ "schema": {
19
+ "properties": {
20
+ "user_id": {
21
+ "type": "number",
22
+ "label": "用户ID",
23
+ "ref": {
24
+ "type": "user",
25
+ "relation": "many-to-one",
26
+ "onDelete": "cascade"
27
+ }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ 数据示例:
35
+
36
+ ```json
37
+ {
38
+ "data": {
39
+ "user_id": 123,
40
+ "amount": 99.99
41
+ }
42
+ }
43
+ ```
44
+
45
+ ### one-to-many
46
+
47
+ 一个目标记录反向查询多个引用它的记录。该字段是查询用虚拟字段,本身不保存 ID。
48
+
49
+ ```json
50
+ {
51
+ "type": "user",
52
+ "schema": {
53
+ "properties": {
54
+ "orders": {
55
+ "type": "array",
56
+ "label": "订单列表",
57
+ "ref": {
58
+ "type": "order",
59
+ "relation": "one-to-many",
60
+ "inverse_field": "user_id"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ 规则:
69
+
70
+ - `inverse_field` 是对方类型中指向当前记录 ID 的字段。
71
+ - one-to-many 只用于 populate,不参与删除链路。
72
+ - 删除一致性要配置在真实持有引用 ID 的 many-to-one 或 many-to-many 字段上。
73
+
74
+ ### many-to-many
75
+
76
+ 字段保存目标记录 ID 数组。
77
+
78
+ ```json
79
+ {
80
+ "type": "article",
81
+ "schema": {
82
+ "properties": {
83
+ "tag_ids": {
84
+ "type": "array",
85
+ "label": "标签列表",
86
+ "ref": {
87
+ "type": "tag",
88
+ "relation": "many-to-many",
89
+ "onDelete": "detach"
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ 数据示例:
98
+
99
+ ```json
100
+ {
101
+ "data": {
102
+ "title": "Hello World",
103
+ "tag_ids": [1, 2, 3]
104
+ }
105
+ }
106
+ ```
107
+
108
+ ## onDelete 策略
109
+
110
+ | 策略 | 行为 | 适用场景 |
111
+ |---|---|---|
112
+ | `cascade` | 删除所有引用记录 | 子记录依赖父记录 |
113
+ | `set_null` | 将引用字段置为 null | 删除分类后保留商品 |
114
+ | `set_default` | 设为 `ref.default` 指定值 | 删除分类后归入默认分类 |
115
+ | `restrict` | 有引用时禁止删除,返回 400 | 防止误删关键数据 |
116
+ | `detach` | 仅多对多,从数组中移除 ID | 删除标签后从文章标签列表移除 |
117
+ | `no_action` | 不处理,默认值 | 兼容旧数据 |
118
+
119
+ 删除链路规则:
120
+
121
+ - many-to-one 和 many-to-many 是真实持有引用 ID 的字段,会参与 onDelete。
122
+ - one-to-many 是虚拟反向关系,只用于 populate,删除链路跳过。
123
+ - 需要删除 A 时自动处理引用 A 的 B,必须在 B 的真实引用字段上配置 `ref.onDelete`。
124
+
125
+ ## populate 查询
126
+
127
+ ### many-to-one
128
+
129
+ ```bash
130
+ GET /api/db/order?populate=user_id
131
+ ```
132
+
133
+ 返回时保留原字段,并在 data 中追加 `<field>_obj`:
134
+
135
+ ```json
136
+ {
137
+ "id": 1,
138
+ "data": {
139
+ "user_id": 123,
140
+ "amount": 99.99,
141
+ "user_id_obj": {
142
+ "id": 123,
143
+ "data": {
144
+ "name": "张三",
145
+ "email": "zhang@example.com"
146
+ }
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ ### many-to-many
153
+
154
+ ```bash
155
+ GET /api/db/article?populate=tag_ids
156
+ ```
157
+
158
+ 返回时追加 `<field>_objs`:
159
+
160
+ ```json
161
+ {
162
+ "id": 1,
163
+ "data": {
164
+ "title": "Hello World",
165
+ "tag_ids": [1, 2, 3],
166
+ "tag_ids_objs": [
167
+ { "id": 1, "data": { "name": "技术" } },
168
+ { "id": 2, "data": { "name": "编程" } }
169
+ ]
170
+ }
171
+ }
172
+ ```
173
+
174
+ ### one-to-many
175
+
176
+ ```bash
177
+ GET /api/db/user/123?populate=orders
178
+ ```
179
+
180
+ 返回时追加 `<field>_objs`:
181
+
182
+ ```json
183
+ {
184
+ "id": 123,
185
+ "data": {
186
+ "name": "张三",
187
+ "orders_objs": [
188
+ { "id": 1, "data": { "amount": 99.99 } },
189
+ { "id": 2, "data": { "amount": 199.99 } }
190
+ ]
191
+ }
192
+ }
193
+ ```
194
+
195
+ ## 字段配置速查
196
+
197
+ ```json
198
+ {
199
+ "field_name": {
200
+ "type": "number",
201
+ "label": "显示名称",
202
+ "required": false,
203
+ "searchable": "range",
204
+ "ref": {
205
+ "type": "user",
206
+ "relation": "many-to-one",
207
+ "onDelete": "cascade",
208
+ "default": 0,
209
+ "inverse_field": "user_id"
210
+ }
211
+ }
212
+ }
213
+ ```
214
+
215
+ 配置规则:
216
+
217
+ - many-to-one:`type: "number"` + `relation: "many-to-one"`。
218
+ - many-to-many:`type: "array"` + `relation: "many-to-many"`。
219
+ - one-to-many:虚拟字段,必须配置 `inverse_field`。
220
+ - `ref.type` 指向动态 DB 类型,例如 `user`、`doctor`、`order`,必须与 DBMeta.type 一致。
221
+
222
+ ## 注意事项
223
+
224
+ - populate 只会填充 schema 中声明了 `ref` 的字段;普通的 `doctorid: 1` 不会自动展开。
225
+ - 字段名不要求必须是 `*_id`,但要与 schema 和 populate 参数完全一致,例如字段叫 `doctorid` 就传 `populate=doctorid`。
226
+ - 关联类型自身的 read 权限仍会生效;无权读取的关联记录不会作为可用数据返回。
227
+ - 级联删除在同一事务中执行,失败会回滚。