cloudcc-cli 2.3.9 → 2.4.1
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.
- package/README.md +48 -0
- package/bin/cc.js +2 -1
- package/bin/index.js +1 -0
- package/cloudcc-dev-skill/SKILL.md +31 -8
- package/cloudcc-dev-skill/cloudcc-dev-html.md +42 -0
- package/cloudcc-dev-skill/config.json +2 -2
- package/mcp/index.js +10 -4
- package/mcp/tools/JSP Migrator/handler.js +51 -866
- package/mcp/tools/Object Creator/handler.js +14 -4
- package/mcp/tools/Trigger List Retriever/handler.js +24 -8
- package/package.json +1 -1
- package/src/classes/docs/devguide.md +863 -356
- package/src/classes/docs/introduction.md +279 -143
- package/src/jsp/analyze.js +17 -0
- package/src/jsp/doc.js +18 -0
- package/src/jsp/docs/devguide.md +111 -0
- package/src/jsp/docs/introduction.md +50 -0
- package/src/jsp/docs.js +21 -0
- package/src/jsp/index.js +14 -0
- package/src/jsp/migration.js +871 -0
- package/src/jsp/split.js +17 -0
- package/src/object/create.js +36 -10
- package/src/object/docs/devguide.md +6 -3
- package/src/project/docs/devguide.md +1 -1
- package/src/script/docs/devguide.md +22 -2
- package/src/timer/docs/devguide.md +1038 -400
- package/src/timer/docs/introduction.md +343 -231
- package/src/triggers/docs/devguide.md +1027 -329
- package/src/triggers/docs/introduction.md +640 -369
- package/src/triggers/get.js +8 -0
- package/src/version/listModuleCommands.js +6 -0
- package/target/classes/com/cloudcc/core/BaseException.class +0 -0
- package/target/classes/com/cloudcc/core/BusiException.class +0 -0
- package/target/classes/com/cloudcc/core/CCObject.class +0 -0
- package/target/classes/com/cloudcc/core/CCSchedule.class +0 -0
- package/target/classes/com/cloudcc/core/CCService.class +0 -0
- package/target/classes/com/cloudcc/core/CCTrigger.class +0 -0
- package/target/classes/com/cloudcc/core/CCTriggerHandler.class +0 -0
- package/target/classes/com/cloudcc/core/DevLogger.class +0 -0
- package/target/classes/com/cloudcc/core/OperatationEnum.class +0 -0
- package/target/classes/com/cloudcc/core/PeakInterf.class +0 -0
- package/target/classes/com/cloudcc/core/SendEmail.class +0 -0
- package/target/classes/com/cloudcc/core/ServiceResult.class +0 -0
- package/target/classes/com/cloudcc/core/StringUtils.class +0 -0
- package/target/classes/com/cloudcc/core/TimeUtil.class +0 -0
- package/target/classes/com/cloudcc/core/Tool$1.class +0 -0
- package/target/classes/com/cloudcc/core/Tool.class +0 -0
- package/target/classes/com/cloudcc/core/TriggerInvoker.class +0 -0
- package/target/classes/com/cloudcc/core/TriggerMethod.class +0 -0
- package/target/classes/com/cloudcc/core/TriggerTimeEnum.class +0 -0
- package/target/classes/com/cloudcc/core/UserInfo.class +0 -0
- package/test/jsp.cli.test.js +70 -0
- package/test/object.cli.test.js +9 -1
|
@@ -1,442 +1,1140 @@
|
|
|
1
|
-
# CloudCC
|
|
1
|
+
# CloudCC 触发器 AI 开发规范
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> 文档版本:v1.0
|
|
5
|
-
> 更新时间:2026-03-24
|
|
3
|
+
## 1. 文档目的
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
本文用于指导 AI 在当前 CloudCC 项目中设计、编写、修改和发布触发器。
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
目标不是让 AI 只写出“能跑”的触发器,而是让 AI 写出的实现同时满足:
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
- 选型正确
|
|
10
|
+
- 触发时机正确
|
|
11
|
+
- SDK 使用正确
|
|
12
|
+
- 结构清晰
|
|
13
|
+
- 可维护
|
|
14
|
+
- 可发布
|
|
15
|
+
- 可排错
|
|
16
|
+
- 尽量避免递归、脏数据和性能问题
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
本文面向 AI,因此重点回答以下问题:
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
- 什么场景应该优先使用触发器
|
|
21
|
+
- 什么场景不应该使用触发器
|
|
22
|
+
- 触发器应该怎样组织代码
|
|
23
|
+
- 触发器中应该如何使用 CloudCC 后端 SDK
|
|
24
|
+
- 触发器开发时有哪些硬规则和常见坑
|
|
16
25
|
|
|
17
|
-
|
|
26
|
+
## 2. 编写依据
|
|
27
|
+
|
|
28
|
+
- 官方 SDK 文档负责定义
|
|
29
|
+
`CCObject`、`UserInfo`、`CCService`、`SendEmail`、`DevLogger`、`TimeUtil`、自定义设置、共享对象相关能力
|
|
30
|
+
- `triggers devguide` 负责定义
|
|
31
|
+
CLI、目录结构、创建发布拉取删除方式和触发器模块约束
|
|
32
|
+
- 当前项目触发器样例负责补充“本项目常见写法”和“本项目容易踩坑的地方”
|
|
33
|
+
|
|
34
|
+
## 3. 与 introduction 的分工(避免重复)
|
|
35
|
+
|
|
36
|
+
为避免与 `triggers introduction` 内容重复,本 `devguide` 只保留“开发落地规范”:
|
|
37
|
+
|
|
38
|
+
- 命令与入参(可直接执行)
|
|
39
|
+
- 目录与文件约束(可直接检查)
|
|
40
|
+
- SDK 用法与边界(可直接编码)
|
|
41
|
+
- 代码结构、性能/递归/幂等与交付要求(可直接评审)
|
|
42
|
+
|
|
43
|
+
“触发器是什么、适用/不适用场景、能力边界、与其他实现方式差异”等概念说明,统一以
|
|
44
|
+
`cloudcc doc triggers introduction` 为准。
|
|
45
|
+
|
|
46
|
+
## 4. triggers 模块支持的 CLI 命令总览(重点:入参)
|
|
47
|
+
|
|
48
|
+
说明:
|
|
49
|
+
|
|
50
|
+
- 下文命令中的资源名使用 `triggers`。
|
|
51
|
+
- `projectPath` 未传时,默认使用当前工作目录。
|
|
52
|
+
|
|
53
|
+
#### 1) 创建触发器
|
|
54
|
+
|
|
55
|
+
命令:
|
|
18
56
|
|
|
19
57
|
```bash
|
|
20
58
|
cloudcc create triggers <encodedCreateJson>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`encodedCreateJson`(先 JSON.stringify,再 encodeURI)推荐字段:
|
|
62
|
+
|
|
63
|
+
| 字段 | 必填 | 类型 | 说明 |
|
|
64
|
+
| ----------------- | ---- | -------- | ------------------------------------------------------ |
|
|
65
|
+
| `schemetableName` | 是 | `string` | 目标对象 API 名(目录中会转小写) |
|
|
66
|
+
| `targetObjectId` | 是 | `string` | 目标对象 ID |
|
|
67
|
+
| `triggerTime` | 是 | `string` | 触发时机,如 `beforeInsert`、`afterUpdate`、`approval` |
|
|
68
|
+
| `name` | 是 | `string` | 触发器名称 |
|
|
69
|
+
| `apiname` | 建议 | `string` | 触发器 API 名 |
|
|
70
|
+
|
|
71
|
+
#### 2) 发布触发器
|
|
72
|
+
|
|
73
|
+
命令:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
21
76
|
cloudcc publish triggers <namePath>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
参数:
|
|
80
|
+
|
|
81
|
+
| 参数 | 必填 | 类型 | 说明 |
|
|
82
|
+
| ---------- | ---- | -------- | --------------------------------------- |
|
|
83
|
+
| `namePath` | 是 | `string` | 触发器路径,格式:`对象小写名/触发器名` |
|
|
84
|
+
|
|
85
|
+
#### 3) 拉取触发器(按本地路径)
|
|
86
|
+
|
|
87
|
+
命令:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
22
90
|
cloudcc pull triggers <namePath>
|
|
23
|
-
cloudcc get triggers <listQueryJson> [projectPath]
|
|
24
|
-
cloudcc detail triggers <namePath>
|
|
25
|
-
cloudcc detail triggers "" <id>
|
|
26
|
-
cloudcc pullList triggers <id> <projectPath>
|
|
27
|
-
cloudcc delete triggers <namePathOrId> [projectPath]
|
|
28
|
-
cloudcc doc triggers <introduction|devguide>
|
|
29
91
|
```
|
|
30
92
|
|
|
31
|
-
|
|
93
|
+
参数:
|
|
32
94
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- `triggerTime`:触发时机(如 `beforeInsert`、`beforeUpsert` 等,以平台为准)
|
|
37
|
-
- `name`:触发器类名(目录名)
|
|
38
|
-
- `apiname`:触发器 API 名(全局唯一)
|
|
39
|
-
- `namePath`:**对象目录/触发器名**,与本地路径一致,例如 `account/MyTrigger`(`schemetableName` 小写 + `/` + `name`)。`publish`、`pull`、`delete`(按路径删除时)均使用该形式。
|
|
40
|
-
- `listQueryJson`:列表查询 JSON,经 `encodeURI(JSON.stringify(...))` 编码后传入(**必填**,实现上无默认空查询)。
|
|
41
|
-
- `projectPath`:项目根路径,可选。
|
|
42
|
-
- `id`:线上触发器 ID。`detail` 仅查服务器:`cloudcc detail triggers "" <id>`。
|
|
43
|
-
- `namePathOrId`:可为 `对象小写/触发器名` 形式的路径;若本地 `config.json` 含 `id`,删除时优先用该 ID。
|
|
95
|
+
| 参数 | 必填 | 类型 | 说明 |
|
|
96
|
+
| ---------- | ---- | -------- | -------------------------------------------------------------------------------- |
|
|
97
|
+
| `namePath` | 是 | `string` | 触发器路径,格式:`对象小写名/触发器名`;会读取该目录 `config.json` 的 `id` 拉取 |
|
|
44
98
|
|
|
45
|
-
|
|
99
|
+
#### 4) 查询触发器列表(支持条件查询)
|
|
46
100
|
|
|
47
|
-
|
|
48
|
-
|------|------|
|
|
49
|
-
| `create` | 在 `triggers/<对象小写>/<name>/` 生成继承 `CCTrigger` 的 Java 与 `config.json` |
|
|
50
|
-
| `publish` | 提交 SOURCE 区域等到服务器;首次成功后可写回 `config.json` 的 `id`、`apiname` 等 |
|
|
51
|
-
| `pull` | 按本地已发布 `id` 拉取远端源码,覆盖 SOURCE 区域 |
|
|
52
|
-
| `get` | 按条件查询线上触发器列表(JSON 输出) |
|
|
53
|
-
| `detail` | 按 `namePath` 优先读本地;或仅按 `id` 读服务器 |
|
|
54
|
-
| `pullList` | 按触发器 `id` 还原到指定项目的 `triggers/...` |
|
|
55
|
-
| `delete` | 删除服务器上的触发器 |
|
|
56
|
-
| `doc` | 输出模块文档;`devguide` 含附录后端 Java SDK 速查 |
|
|
101
|
+
命令:
|
|
57
102
|
|
|
58
|
-
|
|
103
|
+
```bash
|
|
104
|
+
cloudcc get triggers <listQueryJson> [projectPath]
|
|
105
|
+
```
|
|
59
106
|
|
|
60
|
-
|
|
107
|
+
`listQueryJson` 推荐结构:
|
|
61
108
|
|
|
62
109
|
```json
|
|
63
110
|
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
111
|
+
"shownum": 2000,
|
|
112
|
+
"showpage": 1,
|
|
113
|
+
"fid": "",
|
|
114
|
+
"sname": "",
|
|
115
|
+
"rptcond": "",
|
|
116
|
+
"rptorder": "",
|
|
117
|
+
"objId": ""
|
|
69
118
|
}
|
|
70
119
|
```
|
|
71
120
|
|
|
72
|
-
|
|
121
|
+
字段语义:
|
|
122
|
+
|
|
123
|
+
| 字段 | 含义 | 类型 | 是否推荐 | 说明 |
|
|
124
|
+
| ---------- | ----------------- | -------- | -------- | ------------------------ |
|
|
125
|
+
| `shownum` | 每页条数 | `number | string` | 推荐 |
|
|
126
|
+
| `showpage` | 页码 | `number | string` | 推荐 |
|
|
127
|
+
| `sname` | 触发器名字 | `string` | 可选 | 按名称模糊筛选,模糊查询 |
|
|
128
|
+
| `objId` | 触发器作用对象 ID | `string` | 可选 | 对象id |
|
|
129
|
+
|
|
130
|
+
#### 5) 查看触发器详情
|
|
131
|
+
|
|
132
|
+
命令:
|
|
73
133
|
|
|
74
134
|
```bash
|
|
75
|
-
|
|
135
|
+
cloudcc detail triggers <namePath> <id>
|
|
76
136
|
```
|
|
77
137
|
|
|
78
|
-
|
|
138
|
+
参数规则(实现口径):
|
|
139
|
+
|
|
140
|
+
| 参数 | 必填 | 类型 | 说明 |
|
|
141
|
+
| ---------- | -------- | -------- | ------------------------------------------------ |
|
|
142
|
+
| `namePath` | 条件必填 | `string` | 传 `namePath` 时优先查本地;本地不完整时再走线上 |
|
|
143
|
+
| `id` | 条件必填 | `string` | 当 `namePath` 为空时,必须传 `id` 走线上查询 |
|
|
79
144
|
|
|
80
|
-
|
|
145
|
+
等价理解:`namePath` 与 `id` 至少传一个,优先使用 `namePath` 路径。
|
|
146
|
+
|
|
147
|
+
#### 6) 按 ID 拉取并落地到本地目录
|
|
148
|
+
|
|
149
|
+
命令:
|
|
81
150
|
|
|
82
151
|
```bash
|
|
83
|
-
|
|
84
|
-
|
|
152
|
+
cloudcc pullList triggers <id> <projectPath>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
参数:
|
|
156
|
+
|
|
157
|
+
| 参数 | 必填 | 类型 | 说明 |
|
|
158
|
+
| ------------- | ---- | -------- | ----------------------------------------------------------- |
|
|
159
|
+
| `id` | 是 | `string` | 线上触发器 ID |
|
|
160
|
+
| `projectPath` | 是 | `string` | 项目根目录;会写入到 `<projectPath>/triggers/<obj>/<name>/` |
|
|
161
|
+
|
|
162
|
+
#### 7) 删除触发器
|
|
163
|
+
|
|
164
|
+
命令:
|
|
85
165
|
|
|
86
|
-
|
|
87
|
-
cloudcc
|
|
166
|
+
```bash
|
|
167
|
+
cloudcc delete triggers <namePathOrId> [projectPath]
|
|
168
|
+
```
|
|
88
169
|
|
|
89
|
-
|
|
90
|
-
cloudcc publish triggers account/MyTrigger
|
|
170
|
+
参数规则:
|
|
91
171
|
|
|
92
|
-
|
|
93
|
-
|
|
172
|
+
| 参数 | 必填 | 类型 | 说明 |
|
|
173
|
+
| -------------- | ---- | -------- | ------------------------------------------------------------------------------------- |
|
|
174
|
+
| `namePathOrId` | 是 | `string` | 可传触发器路径或线上 ID;若本地路径存在且 `config.json` 含 `id`,优先使用该 `id` 删除 |
|
|
175
|
+
| `projectPath` | 否 | `string` | 项目根目录,默认当前目录 |
|
|
94
176
|
|
|
95
|
-
|
|
96
|
-
cloudcc get triggers '<listQueryJson>' .
|
|
177
|
+
#### 8) 文档命令
|
|
97
178
|
|
|
98
|
-
|
|
99
|
-
cloudcc pullList triggers <线上触发器ID> <projectPath>
|
|
179
|
+
命令:
|
|
100
180
|
|
|
101
|
-
|
|
102
|
-
cloudcc
|
|
181
|
+
```bash
|
|
182
|
+
cloudcc doc triggers <introduction|devguide>
|
|
103
183
|
```
|
|
104
184
|
|
|
105
|
-
|
|
185
|
+
参数:
|
|
186
|
+
|
|
187
|
+
| 参数 | 必填 | 类型 | 说明 |
|
|
188
|
+
| ------------- | --------- | ---- | -------- |
|
|
189
|
+
| `introduction | devguide` | 是 | `string` |
|
|
190
|
+
|
|
191
|
+
## 5. 当前项目中触发器的真实约束
|
|
192
|
+
|
|
193
|
+
这是 AI 最容易忽视、但必须先接受的现实约束。
|
|
194
|
+
|
|
195
|
+
### 5.1 触发器不是普通 Java 类容器
|
|
196
|
+
|
|
197
|
+
根据当前项目的实际文件结构,触发器目录位于:
|
|
198
|
+
|
|
199
|
+
- `triggers/<对象 API 名小写>/<触发器名>/`
|
|
200
|
+
|
|
201
|
+
主类通常形如:
|
|
202
|
+
|
|
203
|
+
- `package triggers.<对象小写>.<触发器名>;`
|
|
204
|
+
- `public class Xxx extends CCTrigger`
|
|
205
|
+
- SOURCE 区域位于构造函数中
|
|
206
|
+
|
|
207
|
+
这意味着:
|
|
208
|
+
|
|
209
|
+
- 触发器天然是“薄入口”
|
|
210
|
+
- 不适合承载超长业务逻辑
|
|
211
|
+
- 更不适合在触发器里构建一整套复杂服务
|
|
212
|
+
|
|
213
|
+
### 5.2 复杂逻辑必须主动下沉
|
|
214
|
+
|
|
215
|
+
当逻辑具备以下任一特征时,AI 不应继续堆在触发器里:
|
|
216
|
+
|
|
217
|
+
- 超过一个对象联动
|
|
218
|
+
- 超过一个分支状态机
|
|
219
|
+
- 需要复用
|
|
220
|
+
- 需要外部集成
|
|
221
|
+
- 需要复杂查询和映射
|
|
222
|
+
- 需要多次通知或多步编排
|
|
223
|
+
|
|
224
|
+
推荐做法:
|
|
225
|
+
|
|
226
|
+
- 触发器只做入口判断和参数收集
|
|
227
|
+
- 核心逻辑下沉到自定义类
|
|
228
|
+
- 触发器通过 `new XxxService(userInfo)` 之类方式调用服务层
|
|
229
|
+
|
|
230
|
+
这是当前项目中最重要的 AI 设计原则之一。
|
|
231
|
+
|
|
232
|
+
## 6. AI 必须遵守的硬规则
|
|
233
|
+
|
|
234
|
+
### 6.1 只能通过 cloudcc-cli 管理触发器目录
|
|
235
|
+
|
|
236
|
+
不得手工创建或复制 `triggers/...` 目录,不得私自构造 `config.json`。
|
|
237
|
+
|
|
238
|
+
必须使用:
|
|
106
239
|
|
|
107
240
|
```bash
|
|
108
|
-
cloudcc
|
|
109
|
-
cloudcc
|
|
241
|
+
cloudcc create triggers <encodedCreateJson>
|
|
242
|
+
cloudcc publish triggers <namePath>
|
|
243
|
+
cloudcc pull triggers <namePath>
|
|
244
|
+
cloudcc get triggers <listQueryJson> [projectPath]
|
|
245
|
+
cloudcc detail triggers <namePath>
|
|
246
|
+
cloudcc detail triggers "" <id>
|
|
247
|
+
cloudcc pullList triggers <id> <projectPath>
|
|
248
|
+
cloudcc delete triggers <namePathOrId> [projectPath]
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### 6.2 只能在 SOURCE 区域内写业务逻辑
|
|
252
|
+
|
|
253
|
+
AI 修改已有触发器时,只能修改:
|
|
254
|
+
|
|
255
|
+
```java
|
|
256
|
+
// @SOURCE_CONTENT_START
|
|
257
|
+
// @SOURCE_CONTENT_END
|
|
110
258
|
```
|
|
111
259
|
|
|
112
|
-
|
|
260
|
+
之间的内容。
|
|
261
|
+
|
|
262
|
+
不得破坏:
|
|
263
|
+
|
|
264
|
+
- 包路径
|
|
265
|
+
- 类名
|
|
266
|
+
- 目录结构
|
|
267
|
+
- `config.json`
|
|
268
|
+
- SOURCE 标记外的框架代码
|
|
269
|
+
|
|
270
|
+
### 6.3 不得私改 `config.json` 的身份字段
|
|
271
|
+
|
|
272
|
+
尤其不要私自改动:
|
|
273
|
+
|
|
274
|
+
- `id`
|
|
275
|
+
- `apiname`
|
|
276
|
+
- `targetObjectId`
|
|
277
|
+
- `schemetableName`
|
|
278
|
+
- `triggerTime`
|
|
279
|
+
|
|
280
|
+
这些字段应通过创建、发布、拉取、云端同步保持一致。
|
|
281
|
+
|
|
282
|
+
### 6.4 必须保留 `userInfo` 上下文
|
|
113
283
|
|
|
114
|
-
|
|
284
|
+
所有 SDK 能力都应围绕当前上下文用户执行。
|
|
115
285
|
|
|
116
|
-
|
|
286
|
+
不得:
|
|
117
287
|
|
|
118
|
-
|
|
288
|
+
- 硬编码用户
|
|
289
|
+
- 绕过 `userInfo` 伪造“当前用户”
|
|
290
|
+
- 用固定时区或固定组织代替上下文
|
|
119
291
|
|
|
120
|
-
|
|
121
|
-
- 一段 Java 代码,运行在 CloudCC 平台沙箱环境中
|
|
122
|
-
- 使用 CCService、CCObject 等平台提供的 API 类
|
|
123
|
-
- 在数据变更的特定时刻(前/后)自动触发执行
|
|
292
|
+
### 6.5 触发器里必须显式考虑递归风险
|
|
124
293
|
|
|
125
|
-
|
|
294
|
+
凡是触发器里再去更新当前对象、父对象、子对象、共享对象,都要先判断是否会再次触发相关逻辑。
|
|
126
295
|
|
|
127
|
-
|
|
296
|
+
AI 不能默认“更新一下没事”。
|
|
128
297
|
|
|
129
|
-
|
|
130
|
-
|------|------|------|
|
|
131
|
-
| 自动赋值 | 在记录保存前自动计算并填充字段 | 个人业绩/部门业绩自动赋值 |
|
|
132
|
-
| 数据校验 | 阻止不符合业务规则的数据保存 | 预算金额校验、审批金额验证 |
|
|
133
|
-
| 关联更新 | 修改一条记录时自动更新关联记录 | 回款明细汇总到收款计划 |
|
|
134
|
-
| 自动生成 | 根据业务规则自动创建新记录 | 审批通过后自动生成回款记录 |
|
|
135
|
-
| 权限控制 | 自动设置数据共享权限 | 按部门/人员自动共享 |
|
|
136
|
-
| 状态同步 | 审批状态变更后同步更新相关字段 | 预算审批状态回写 |
|
|
137
|
-
| 汇总计算 | 实时汇总明细数据到主记录 | 部门业绩字段汇总 |
|
|
298
|
+
### 6.6 触发器里必须显式考虑幂等性
|
|
138
299
|
|
|
139
|
-
|
|
300
|
+
尤其是以下动作:
|
|
301
|
+
|
|
302
|
+
- 自动生成记录
|
|
303
|
+
- 自动发待办
|
|
304
|
+
- 自动发邮件
|
|
305
|
+
- 自动推送外部系统
|
|
306
|
+
- 自动创建共享
|
|
307
|
+
|
|
308
|
+
必须先判断是否已执行过,否则很容易重复生成、重复发送、重复推送。
|
|
309
|
+
|
|
310
|
+
### 6.7 涉及时间必须优先使用 `TimeUtil`
|
|
311
|
+
|
|
312
|
+
只要出现以下需求,AI 默认当成“有时区风险”处理:
|
|
313
|
+
|
|
314
|
+
- 当前时间
|
|
315
|
+
- 到期日比较
|
|
316
|
+
- 提醒时间
|
|
317
|
+
- 审批时间
|
|
318
|
+
- 统计周期
|
|
319
|
+
- 格式化时间字符串
|
|
320
|
+
|
|
321
|
+
## 7. 触发器中的运行时思维模型
|
|
322
|
+
|
|
323
|
+
基于当前项目样例,AI 在触发器里应默认建立以下思维模型:
|
|
324
|
+
|
|
325
|
+
- 当前记录通常有“新值”和“旧值”两个视角
|
|
326
|
+
- 当前触发器实例有上下文用户 `userInfo`
|
|
327
|
+
- 当前触发器通常可以直接使用平台或基类提供的数据访问能力
|
|
328
|
+
- 当前触发器要么做“拦截”,要么做“联动”,不要两类职责混在一起失控扩张
|
|
329
|
+
|
|
330
|
+
在当前项目中,触发器里高频出现的能力包括:
|
|
331
|
+
|
|
332
|
+
- `record_new`
|
|
333
|
+
- `record_old`
|
|
334
|
+
- `trigger`
|
|
335
|
+
- `userInfo`
|
|
336
|
+
- `CCService`
|
|
337
|
+
- `cquery` / `cqlQuery`
|
|
338
|
+
- `updateLt`
|
|
339
|
+
- `SendEmail`
|
|
340
|
+
- `DevLogger`
|
|
341
|
+
|
|
342
|
+
这说明 AI 编写触发器时,应优先遵循:
|
|
343
|
+
|
|
344
|
+
- 先拿清楚当前记录上下文
|
|
345
|
+
- 再做必要查询
|
|
346
|
+
- 再做校验或联动
|
|
347
|
+
- 最后记录关键日志或错误
|
|
348
|
+
|
|
349
|
+
## 8. SDK 使用规范
|
|
350
|
+
|
|
351
|
+
本节是本文的核心。以下 API 说明基于官方 SDK 页面整理,并专门补成“AI
|
|
352
|
+
可直接使用”的格式。
|
|
353
|
+
|
|
354
|
+
写法原则:
|
|
355
|
+
|
|
356
|
+
- 先看方法签名
|
|
357
|
+
- 再看参数表
|
|
358
|
+
- 再看返回值
|
|
359
|
+
- 再看 AI 使用规则
|
|
360
|
+
|
|
361
|
+
### 8.1 `CCObject` 详细说明
|
|
362
|
+
|
|
363
|
+
类说明:
|
|
364
|
+
|
|
365
|
+
- 官方定义:CloudCC 对象类
|
|
366
|
+
- 作用:作为对象数据的统一载体,用于新增、修改、删除、共享对象操作
|
|
367
|
+
|
|
368
|
+
#### 8.1.1 构造方法
|
|
369
|
+
|
|
370
|
+
方法签名:
|
|
140
371
|
|
|
141
372
|
```java
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
373
|
+
public CCObject()
|
|
374
|
+
public CCObject(String ccobj)
|
|
375
|
+
public CCObject(String ccobj, String isShared)
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
参数说明:
|
|
146
379
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
);
|
|
380
|
+
| 方法 | 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
381
|
+
| ----------------------------------------- | ---------- | -------- | -------- | --------------------------------------- | ------------------------------------------ |
|
|
382
|
+
| `CCObject(String ccobj)` | `ccobj` | `String` | 是 | 对象 API | 传对象 API 名,如 `Account`、`Opportunity` |
|
|
383
|
+
| `CCObject(String ccobj, String isShared)` | `ccobj` | `String` | 是 | 对象 API | 用于共享对象 |
|
|
384
|
+
| `CCObject(String ccobj, String isShared)` | `isShared` | `String` | 是 | 共享标识,建议使用 `CCObject.IS_SHARED` | 不要手写魔法值,优先常量 |
|
|
153
385
|
|
|
154
|
-
|
|
155
|
-
List<CCObject> opps2 = cs.cquery(
|
|
156
|
-
"Opportunity",
|
|
157
|
-
"khmc__c='" + record_new.get("id") + "'"
|
|
158
|
-
);
|
|
386
|
+
常量字段:
|
|
159
387
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
);
|
|
388
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
389
|
+
| ------------ | -------- | ------------- | ------------- |
|
|
390
|
+
| `OBJECT_API` | `String` | `CCObjectAPI` | 对象 API 常量 |
|
|
391
|
+
| `IS_SHARED` | `String` | `isShared` | 共享对象常量 |
|
|
165
392
|
|
|
166
|
-
|
|
167
|
-
cs.update(opp);
|
|
168
|
-
cs.delete(opp);
|
|
393
|
+
#### 8.1.2 常用方法
|
|
169
394
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
395
|
+
方法签名:
|
|
396
|
+
|
|
397
|
+
```java
|
|
398
|
+
public String getObjectApiName()
|
|
399
|
+
public String put(String apiName, String value)
|
|
173
400
|
```
|
|
174
401
|
|
|
175
|
-
|
|
402
|
+
参数说明:
|
|
403
|
+
|
|
404
|
+
| 方法 | 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
405
|
+
| ----- | --------- | -------- | -------- | ------------ | ---------------------------------------------------------------- |
|
|
406
|
+
| `put` | `apiName` | `String` | 是 | 字段 APIName | 必须传字段 API 名,不要传字段显示名 |
|
|
407
|
+
| `put` | `value` | `String` | 是 | 字段值 | 官方示例按字符串传;AI 应按当前项目已有写法与 SDK 能力处理值类型 |
|
|
408
|
+
|
|
409
|
+
返回值说明:
|
|
410
|
+
|
|
411
|
+
| 方法 | 返回类型 | 含义 |
|
|
412
|
+
| ------------------ | -------- | -------------------------------------------------- |
|
|
413
|
+
| `getObjectApiName` | `String` | 当前对象 API 名 |
|
|
414
|
+
| `put` | `String` | `HashMap` 风格返回值;在触发器里通常不依赖其返回值 |
|
|
415
|
+
|
|
416
|
+
AI 使用规则:
|
|
176
417
|
|
|
177
|
-
|
|
418
|
+
- 新增记录:`new CCObject("ObjectApiName")`
|
|
419
|
+
- 更新记录:必须先 `put("id", "...")`
|
|
420
|
+
- 共享对象:优先 `new CCObject("ObjectApiName", CCObject.IS_SHARED)`
|
|
421
|
+
- 不要把字段显示名直接写进 `put`
|
|
178
422
|
|
|
179
|
-
###
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
423
|
+
### 8.2 `UserInfo` 详细说明
|
|
424
|
+
|
|
425
|
+
类说明:
|
|
426
|
+
|
|
427
|
+
- 官方定义:用户信息对象,封装登录用户的基本信息
|
|
428
|
+
- 作用:提供当前用户、组织、角色、时区等上下文,是所有服务端能力的根上下文
|
|
429
|
+
|
|
430
|
+
构造方法:
|
|
183
431
|
|
|
184
432
|
```java
|
|
185
|
-
|
|
186
|
-
CCObject record = (CCObject) data.get("record");
|
|
187
|
-
BigDecimal income = record.getBigDecimal("ysr");
|
|
188
|
-
BigDecimal cost = record.getBigDecimal("cycb");
|
|
189
|
-
BigDecimal net = income.subtract(cost);
|
|
190
|
-
record.put("jyj", net);
|
|
191
|
-
if (net.compareTo(BigDecimal.ZERO) < 0) {
|
|
192
|
-
throw new Exception("毛利不能为负数!");
|
|
193
|
-
}
|
|
433
|
+
public UserInfo()
|
|
194
434
|
```
|
|
195
435
|
|
|
196
|
-
|
|
197
|
-
- 执行时机:记录创建成功后(可获取 ID)
|
|
198
|
-
- 典型用途:基于新记录 ID 创建关联记录、发送创建通知、初始化关联数据
|
|
199
|
-
- 生产实例:根据净业绩拆分生成净业绩拆分回款
|
|
436
|
+
常用方法:
|
|
200
437
|
|
|
201
438
|
```java
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
String
|
|
205
|
-
CCObject hkRecord = new CCObject("Huikuan");
|
|
206
|
-
hkRecord.put("mingxi__c", recordId);
|
|
207
|
-
hkRecord.put("je", newRecord.get("je"));
|
|
208
|
-
cs.insert(hkRecord);
|
|
439
|
+
public String getUserId()
|
|
440
|
+
public String getOrgId()
|
|
441
|
+
public String getRoleId()
|
|
209
442
|
```
|
|
210
443
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
444
|
+
返回值说明:
|
|
445
|
+
|
|
446
|
+
| 方法 | 返回类型 | 官方说明 | AI 使用说明 |
|
|
447
|
+
| ----------- | -------- | ------------------------------ | -------------------- |
|
|
448
|
+
| `getUserId` | `String` | 当前用户 ID,未设置返回 `null` | 适合做当前操作人识别 |
|
|
449
|
+
| `getOrgId` | `String` | 当前组织 ID,未设置返回 `null` | 适合做组织级逻辑判断 |
|
|
450
|
+
| `getRoleId` | `String` | 当前角色 ID,未设置返回 `null` | 适合做角色权限判断 |
|
|
451
|
+
|
|
452
|
+
AI 使用规则:
|
|
453
|
+
|
|
454
|
+
- `CCService`、`SendEmail`、`DevLogger`、`TimeUtil` 都应围绕 `userInfo`
|
|
455
|
+
构造或调用
|
|
456
|
+
- 不要硬编码用户、组织、角色信息替代 `userInfo`
|
|
457
|
+
|
|
458
|
+
### 8.3 `CCService` 详细说明
|
|
459
|
+
|
|
460
|
+
类说明:
|
|
461
|
+
|
|
462
|
+
- 官方定义:CloudCC 核心工具类,提供对象增删改查等方法
|
|
463
|
+
- 作用:触发器中最核心的数据访问入口
|
|
464
|
+
|
|
465
|
+
#### 8.3.1 构造函数
|
|
466
|
+
|
|
467
|
+
方法签名:
|
|
215
468
|
|
|
216
469
|
```java
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
470
|
+
public CCService(UserInfo userInfo)
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
参数说明:
|
|
474
|
+
|
|
475
|
+
| 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
476
|
+
| ---------- | ---------- | -------- | -------- | -------------------- |
|
|
477
|
+
| `userInfo` | `UserInfo` | 是 | 用户对象 | 必须传当前上下文用户 |
|
|
478
|
+
|
|
479
|
+
#### 8.3.2 `insert`
|
|
480
|
+
|
|
481
|
+
方法签名:
|
|
482
|
+
|
|
483
|
+
```java
|
|
484
|
+
public ServiceResult insert(CCObject ccobj) throws BusiException
|
|
232
485
|
```
|
|
233
486
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
487
|
+
参数说明:
|
|
488
|
+
|
|
489
|
+
| 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
490
|
+
| ------- | ---------- | -------- | -------- | ---------------- |
|
|
491
|
+
| `ccobj` | `CCObject` | 是 | CC 对象 | 用于新增单条记录 |
|
|
492
|
+
|
|
493
|
+
返回值:
|
|
238
494
|
|
|
239
|
-
|
|
240
|
-
- 执行时机:记录提交审批或审批通过时
|
|
241
|
-
- 典型用途:审批前校验、审批通过后执行业务操作
|
|
242
|
-
- 生产实例:更新拆分审批状态、订单审批判断、审批通过计算剩余欠款
|
|
495
|
+
- `ServiceResult`
|
|
243
496
|
|
|
244
|
-
|
|
497
|
+
适用场景:
|
|
245
498
|
|
|
246
|
-
|
|
499
|
+
- 新建关联记录
|
|
500
|
+
- 审批通过后生成下游单据
|
|
501
|
+
- 初始化台账、共享记录、中间记录
|
|
247
502
|
|
|
248
|
-
|
|
503
|
+
AI 使用规则:
|
|
504
|
+
|
|
505
|
+
- 新增前先补齐必要字段
|
|
506
|
+
- 派生记录创建前必须先做幂等判断
|
|
507
|
+
- 不要在可能重复触发的场景里无条件 `insert`
|
|
508
|
+
|
|
509
|
+
#### 8.3.3 `cquery`
|
|
510
|
+
|
|
511
|
+
方法签名:
|
|
512
|
+
|
|
513
|
+
```java
|
|
514
|
+
public List<CCObject> cquery(apiName, condtion, order) throws Exception
|
|
515
|
+
public List<CCObject> cquery(String apiName, String condtion, boolean isDataObject) throws Exception
|
|
516
|
+
```
|
|
249
517
|
|
|
250
|
-
|
|
251
|
-
|------|----------|------------|
|
|
252
|
-
| 数据一致性 | 依赖用户手动操作,容易遗漏 | 自动执行,保证数据准确 |
|
|
253
|
-
| 业务规则执行 | 前端校验可绕过 | 统一在平台层执行,无法绕过 |
|
|
254
|
-
| 跨对象联动 | 需要多次手动操作 | 自动关联更新 |
|
|
255
|
-
| 审批复杂逻辑 | 审批流配置难以承载 | Java 代码可实现复杂逻辑 |
|
|
256
|
-
| 实时性要求 | 定时任务有延迟 | 实时触发,立即执行 |
|
|
518
|
+
参数说明:
|
|
257
519
|
|
|
258
|
-
|
|
520
|
+
| 方法 | 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
521
|
+
| ----------------------------------------- | -------------- | --------- | -------- | --------------------------------------------------------------- | ------------------------------ |
|
|
522
|
+
| `cquery(apiName, condtion, order)` | `apiName` | `String` | 是 | 对象 APIName | 必传对象 API |
|
|
523
|
+
| `cquery(apiName, condtion, order)` | `condition` | `String` | 否 | 使用字段 APIName 查询,自定义字段一定要加 `__c`;无条件则查全部 | 必须收窄条件,避免全表查 |
|
|
524
|
+
| `cquery(apiName, condtion, order)` | `order` | `String` | 否 | 使用字段 APIName 排序,自定义字段一定要加 `__c` | 只在确实需要排序时传 |
|
|
525
|
+
| `cquery(apiName, condtion, isDataObject)` | `apiName` | `String` | 是 | 对象 APIName | 必传对象 API |
|
|
526
|
+
| `cquery(apiName, condtion, isDataObject)` | `condtion` | `String` | 否 | 同上 | 自定义字段记得 `__c` |
|
|
527
|
+
| `cquery(apiName, condtion, isDataObject)` | `isDataObject` | `Boolean` | 否 | `true` 查普通数据,`false` 查共享数据 | 不要混淆普通对象和共享对象口径 |
|
|
259
528
|
|
|
260
|
-
|
|
261
|
-
|------|------|------|----------|
|
|
262
|
-
| 触发器 | 实时、自动、无法绕过 | 需要 Java 开发能力 | 核心业务逻辑 |
|
|
263
|
-
| 审批流 | 配置简单、可视化 | 逻辑能力有限 | 简单审批流程 |
|
|
264
|
-
| 工作流 | 可视化编排 | 复杂逻辑实现困难 | 跨系统流程编排 |
|
|
265
|
-
| 定时任务 | 适合批量处理 | 有延迟 | 数据同步、报表生成 |
|
|
529
|
+
返回值:
|
|
266
530
|
|
|
267
|
-
|
|
531
|
+
- `List<CCObject>`
|
|
268
532
|
|
|
269
|
-
|
|
533
|
+
适用场景:
|
|
270
534
|
|
|
271
|
-
|
|
535
|
+
- 简单单表查询
|
|
536
|
+
- 普通对象查询
|
|
537
|
+
- 共享对象查询
|
|
272
538
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
回款明细 (cloudccproceeddetail)
|
|
286
|
-
├─ afterInsert:生成净业绩拆分回款
|
|
287
|
-
├─ afterUpsert:金额汇总到收款计划
|
|
288
|
-
├─ afterUpsert:更新回款明细
|
|
289
|
-
└─ afterDelete:回款明细删除
|
|
539
|
+
AI 使用规则:
|
|
540
|
+
|
|
541
|
+
- 简单查询优先 `cquery`
|
|
542
|
+
- 自定义字段条件和排序都要加 `__c`
|
|
543
|
+
- 条件为空会查全部数据,AI 应尽量避免
|
|
544
|
+
|
|
545
|
+
#### 8.3.4 `cqlQuery`
|
|
546
|
+
|
|
547
|
+
方法签名:
|
|
548
|
+
|
|
549
|
+
```java
|
|
550
|
+
public List<CCObject> cqlQuery(String apiName, String cql) throws Exception
|
|
290
551
|
```
|
|
291
552
|
|
|
292
|
-
|
|
553
|
+
参数说明:
|
|
554
|
+
|
|
555
|
+
| 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
556
|
+
| --------- | -------- | -------- | ------------------------------- | ------------------------------------------- |
|
|
557
|
+
| `apiName` | `String` | 是 | 对象 APIName | 必传主对象 API |
|
|
558
|
+
| `cql` | `String` | 否 | SQL 语句,支持常用 `where` 条件 | 适用于聚合、复杂过滤;避免无边界 `select *` |
|
|
559
|
+
|
|
560
|
+
返回值:
|
|
561
|
+
|
|
562
|
+
- `List<CCObject>`
|
|
563
|
+
|
|
564
|
+
适用场景:
|
|
565
|
+
|
|
566
|
+
- 聚合统计
|
|
567
|
+
- 求和、计数、汇总
|
|
568
|
+
- `cquery` 不足以表达的复杂查询
|
|
569
|
+
|
|
570
|
+
AI 使用规则:
|
|
293
571
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
572
|
+
- 能用 `cquery` 时优先不用 `cqlQuery`
|
|
573
|
+
- 使用 `cqlQuery` 时必须写清过滤条件
|
|
574
|
+
- 结果为空时必须做空值保护
|
|
575
|
+
|
|
576
|
+
#### 8.3.5 `update`
|
|
577
|
+
|
|
578
|
+
方法签名:
|
|
579
|
+
|
|
580
|
+
```java
|
|
581
|
+
public ServiceResult update(CCObject ccobj) throws Exception
|
|
299
582
|
```
|
|
300
583
|
|
|
301
|
-
|
|
584
|
+
参数说明:
|
|
585
|
+
|
|
586
|
+
| 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
587
|
+
| ------- | ---------- | -------- | -------- | --------------------- |
|
|
588
|
+
| `ccobj` | `CCObject` | 是 | CC 对象 | 修改记录时必须带 `id` |
|
|
589
|
+
|
|
590
|
+
返回值:
|
|
302
591
|
|
|
303
|
-
|
|
304
|
-
|--------|------|------|------|
|
|
305
|
-
| 借款单触发器 | 借款单 | beforeUpsert | 借款前校验、自动计算 |
|
|
306
|
-
| 还款记录审批通过更新借款单 | 还款记录 | approval | 审批后回写借款单 |
|
|
592
|
+
- `ServiceResult`
|
|
307
593
|
|
|
308
|
-
|
|
594
|
+
适用场景:
|
|
309
595
|
|
|
310
|
-
|
|
596
|
+
- 回写状态
|
|
597
|
+
- 同步字段
|
|
598
|
+
- 更新汇总结果
|
|
311
599
|
|
|
312
|
-
|
|
600
|
+
AI 使用规则:
|
|
601
|
+
|
|
602
|
+
- 必须先 `put("id", ...)`
|
|
603
|
+
- 只更新必要字段
|
|
604
|
+
- 优先评估递归触发风险
|
|
605
|
+
|
|
606
|
+
#### 8.3.6 `delete`
|
|
607
|
+
|
|
608
|
+
方法签名:
|
|
313
609
|
|
|
314
610
|
```java
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
611
|
+
public ServiceResult delete(CCObject ccobj) throws Exception
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
参数说明:
|
|
615
|
+
|
|
616
|
+
| 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
617
|
+
| ------- | ---------- | -------- | -------- | ------------------------- |
|
|
618
|
+
| `ccobj` | `CCObject` | 是 | CC 对象 | 删除记录时通常必须带 `id` |
|
|
619
|
+
|
|
620
|
+
返回值:
|
|
621
|
+
|
|
622
|
+
- `ServiceResult`
|
|
623
|
+
|
|
624
|
+
适用场景:
|
|
625
|
+
|
|
626
|
+
- 删除派生记录
|
|
627
|
+
- 删除中间记录
|
|
628
|
+
- 服务端清理明确对象
|
|
629
|
+
|
|
630
|
+
AI 使用规则:
|
|
631
|
+
|
|
632
|
+
- 删除条件必须精准
|
|
633
|
+
- 删除前后建议有日志
|
|
634
|
+
- 涉及共享关系时优先看共享专用接口
|
|
635
|
+
|
|
636
|
+
#### 8.3.7 `deleteShareObjectBySql`
|
|
637
|
+
|
|
638
|
+
方法签名:
|
|
639
|
+
|
|
640
|
+
```java
|
|
641
|
+
public void deleteShareObjectBySql(String objectApiName, String expression) throws Exception
|
|
322
642
|
```
|
|
323
643
|
|
|
324
|
-
|
|
644
|
+
参数说明:
|
|
645
|
+
|
|
646
|
+
| 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
647
|
+
| --------------- | -------- | -------- | ------------------------------ | ------------------------ |
|
|
648
|
+
| `objectApiName` | `String` | 是 | 对象 APIName | 共享对象 API |
|
|
649
|
+
| `expression` | `String` | 是 | SQL 表达式,字段不需要加 `__c` | 这是共享删除的关键差异点 |
|
|
650
|
+
|
|
651
|
+
返回值:
|
|
652
|
+
|
|
653
|
+
- `void`
|
|
654
|
+
|
|
655
|
+
适用场景:
|
|
656
|
+
|
|
657
|
+
- 删除共享表记录
|
|
658
|
+
- 删除旧共享关系
|
|
659
|
+
- 负责人变化后回收历史共享
|
|
660
|
+
|
|
661
|
+
AI 使用规则:
|
|
662
|
+
|
|
663
|
+
- 普通对象删除和共享对象删除不要混用
|
|
664
|
+
- 这里的字段表达式不需要加 `__c`
|
|
665
|
+
|
|
666
|
+
#### 8.3.8 自定义设置 API
|
|
667
|
+
|
|
668
|
+
方法签名:
|
|
325
669
|
|
|
326
670
|
```java
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
671
|
+
public Map getListCustomSetting(String objectApiName)
|
|
672
|
+
Map map = cs.getListCustomSetting(String apiName, String Name)
|
|
673
|
+
public Map getCustomSetting(String objectApiName, String id)
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
参数说明:
|
|
677
|
+
|
|
678
|
+
| 方法 | 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
679
|
+
| --------------------------------------------------- | --------------- | -------- | -------- | ------------------ | ---------------------------------- |
|
|
680
|
+
| `getListCustomSetting(String objectApiName)` | `objectApiName` | `String` | 是 | 对象 APIName | 返回某个列表型自定义设置的全部数据 |
|
|
681
|
+
| `getListCustomSetting(String apiName, String name)` | `apiName` | `String` | 是 | 对象 APIName | 取列表型自定义设置中的单条 |
|
|
682
|
+
| `getListCustomSetting(String apiName, String name)` | `name` | `String` | 是 | 自定义设置属性名称 | 通过名称定位单条配置 |
|
|
683
|
+
| `getCustomSetting(String objectApiName, String id)` | `objectApiName` | `String` | 是 | 对象 APIName | 用于层次结构型自定义设置 |
|
|
684
|
+
| `getCustomSetting(String objectApiName, String id)` | `id` | `String` | 是 | 简档 id 或用户 id | 按权限上下文取配置 |
|
|
685
|
+
|
|
686
|
+
返回值:
|
|
687
|
+
|
|
688
|
+
- `Map`
|
|
689
|
+
|
|
690
|
+
适用场景:
|
|
691
|
+
|
|
692
|
+
- 开关参数
|
|
693
|
+
- 业务阈值
|
|
694
|
+
- 模板映射
|
|
695
|
+
- 接口地址
|
|
696
|
+
- 角色映射
|
|
697
|
+
- 分环境配置
|
|
698
|
+
|
|
699
|
+
AI 使用规则:
|
|
700
|
+
|
|
701
|
+
- 可变参数优先配置化
|
|
702
|
+
- 不要把阈值、模板 ID、地址、角色映射硬编码在触发器里
|
|
703
|
+
|
|
704
|
+
### 8.4 `SendEmail` 详细说明
|
|
705
|
+
|
|
706
|
+
类说明:
|
|
707
|
+
|
|
708
|
+
- 官方定义:用于发送邮件通知
|
|
709
|
+
|
|
710
|
+
#### 8.4.1 构造方法
|
|
333
711
|
|
|
334
|
-
|
|
335
|
-
|
|
712
|
+
方法签名:
|
|
713
|
+
|
|
714
|
+
```java
|
|
715
|
+
public SendEmail(UserInfo userInfo)
|
|
336
716
|
```
|
|
337
717
|
|
|
338
|
-
|
|
718
|
+
参数说明:
|
|
719
|
+
|
|
720
|
+
| 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
721
|
+
| ---------- | ---------- | -------- | -------- | -------------------- |
|
|
722
|
+
| `userInfo` | `UserInfo` | 是 | 用户对象 | 用当前上下文发送邮件 |
|
|
723
|
+
|
|
724
|
+
#### 8.4.2 `sendMailFromSystem`
|
|
725
|
+
|
|
726
|
+
方法签名:
|
|
339
727
|
|
|
340
728
|
```java
|
|
341
|
-
|
|
342
|
-
|
|
729
|
+
public boolean sendMailFromSystem(
|
|
730
|
+
String[] toAddress,
|
|
731
|
+
String[] ccAddress,
|
|
732
|
+
String[] bccAddress,
|
|
733
|
+
String subject,
|
|
734
|
+
String content,
|
|
735
|
+
boolean isText)
|
|
736
|
+
```
|
|
343
737
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
738
|
+
参数说明:
|
|
739
|
+
|
|
740
|
+
| 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
741
|
+
| ------------ | ---------- | -------- | -------------------------------------- | --------------------- |
|
|
742
|
+
| `toAddress` | `String[]` | 是 | 收件箱 | 没有收件人不要发送 |
|
|
743
|
+
| `ccAddress` | `String[]` | 是 | 抄送人 | 没有可传空数组 |
|
|
744
|
+
| `bccAddress` | `String[]` | 是 | 密送人 | 没有可传空数组 |
|
|
745
|
+
| `subject` | `String` | 是 | 邮件标题 | 标题要能表达业务动作 |
|
|
746
|
+
| `content` | `String` | 是 | 邮件内容 | HTML 内容要控制复杂度 |
|
|
747
|
+
| `isText` | `boolean` | 是 | 是否采用 TXT 方式发送;`false` 为 HTML | HTML 邮件常用 `false` |
|
|
748
|
+
|
|
749
|
+
返回值:
|
|
750
|
+
|
|
751
|
+
- `boolean`
|
|
752
|
+
|
|
753
|
+
AI 使用规则:
|
|
754
|
+
|
|
755
|
+
- 业务动作成功后再发邮件
|
|
756
|
+
- 不要把邮件发送当成日志系统
|
|
757
|
+
- 同一动作避免重复发送
|
|
758
|
+
|
|
759
|
+
#### 8.4.3 `sendEmailNew`
|
|
760
|
+
|
|
761
|
+
方法签名:
|
|
762
|
+
|
|
763
|
+
```java
|
|
764
|
+
public ServiceResult sendEmailNew(
|
|
765
|
+
String emailtemplateid,
|
|
766
|
+
String[] toaddress,
|
|
767
|
+
String[] ccaddress,
|
|
768
|
+
String[] bcaddress,
|
|
769
|
+
String id) throws Exception
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
参数说明:
|
|
773
|
+
|
|
774
|
+
| 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
775
|
+
| ----------------- | ---------- | -------- | ------------------- | -------------------------------------------------------------------------------- |
|
|
776
|
+
| `emailtemplateid` | `String` | 是 | 邮件模版 id | 优先模板化发送 |
|
|
777
|
+
| `toaddress` | `String[]` | 是 | 收件箱 | 传收件人数组 |
|
|
778
|
+
| `ccaddress` | `String[]` | 是 | 抄送人 | 没有可传空数组 |
|
|
779
|
+
| `bcaddress` | `String[]` | 是 | 密送人 | 官方签名为 `bcaddress`,按签名使用 |
|
|
780
|
+
| `id` | `String` | 是 | 官方文档仅给出 `id` | 结合方法语义,AI 应将其视为模板填充关联记录 ID;此处为基于官方方法名与示例的推断 |
|
|
781
|
+
|
|
782
|
+
返回值:
|
|
783
|
+
|
|
784
|
+
- `ServiceResult`
|
|
785
|
+
|
|
786
|
+
AI 使用规则:
|
|
787
|
+
|
|
788
|
+
- 模板内容能配置就不要在触发器里拼长字符串
|
|
789
|
+
- 对 `id` 的使用要和模板数据来源一致
|
|
790
|
+
|
|
791
|
+
### 8.5 `DevLogger` 详细说明
|
|
792
|
+
|
|
793
|
+
类说明:
|
|
794
|
+
|
|
795
|
+
- 官方定义:运行日志采集
|
|
796
|
+
|
|
797
|
+
#### 8.5.1 构造函数
|
|
798
|
+
|
|
799
|
+
方法签名:
|
|
800
|
+
|
|
801
|
+
```java
|
|
802
|
+
public DevLogger(UserInfo userInfo)
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
参数说明:
|
|
806
|
+
|
|
807
|
+
| 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
808
|
+
| ---------- | ---------- | -------- | -------- | -------------------- |
|
|
809
|
+
| `userInfo` | `UserInfo` | 是 | 用户对象 | 用当前上下文记录日志 |
|
|
810
|
+
|
|
811
|
+
#### 8.5.2 `devLogInfo`
|
|
812
|
+
|
|
813
|
+
方法签名:
|
|
814
|
+
|
|
815
|
+
```java
|
|
816
|
+
public void devLogInfo(String info)
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
参数说明:
|
|
820
|
+
|
|
821
|
+
| 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
822
|
+
| ------ | -------- | -------- | -------- | -------------------------------- |
|
|
823
|
+
| `info` | `String` | 是 | 日志信息 | 记录关键主键、关键分支、关键动作 |
|
|
824
|
+
|
|
825
|
+
返回值:
|
|
826
|
+
|
|
827
|
+
- `void`
|
|
828
|
+
|
|
829
|
+
#### 8.5.3 `devLogError`
|
|
830
|
+
|
|
831
|
+
方法签名:
|
|
832
|
+
|
|
833
|
+
```java
|
|
834
|
+
public void devLogError(String error, Throwable throwable)
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
参数说明:
|
|
838
|
+
|
|
839
|
+
| 参数 | 类型 | 是否必填 | 官方说明 | AI 使用说明 |
|
|
840
|
+
| ----------- | ----------- | -------- | -------- | -------------------------- |
|
|
841
|
+
| `error` | `String` | 是 | 日志信息 | 错误信息必须包含业务上下文 |
|
|
842
|
+
| `throwable` | `Throwable` | 否 | 异常对象 | 有异常时尽量带上 |
|
|
843
|
+
|
|
844
|
+
返回值:
|
|
845
|
+
|
|
846
|
+
- `void`
|
|
847
|
+
|
|
848
|
+
AI 使用规则:
|
|
849
|
+
|
|
850
|
+
- 关键主键要记录
|
|
851
|
+
- 当前触发时机要记录
|
|
852
|
+
- 下游调用结果要记录
|
|
853
|
+
- 异常不能吞
|
|
854
|
+
|
|
855
|
+
### 8.6 `TimeUtil` 详细说明
|
|
856
|
+
|
|
857
|
+
类说明:
|
|
858
|
+
|
|
859
|
+
- 官方说明:`Date` 和 `Calendar` 默认基于本地时区,跨时区处理容易出错
|
|
860
|
+
- 作用:所有和当前时间、用户时区、格式化、Calendar 相关的逻辑,都应优先走
|
|
861
|
+
`TimeUtil`
|
|
862
|
+
|
|
863
|
+
#### 8.6.1 `getNowDate`
|
|
864
|
+
|
|
865
|
+
官方示例:
|
|
866
|
+
|
|
867
|
+
```java
|
|
868
|
+
TpSysTask task = new TpSysTask();
|
|
869
|
+
task.setBeginTime(TimeUtil.getNowDate(userInfo));
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
参数说明:
|
|
873
|
+
|
|
874
|
+
| 参数 | 类型 | 是否必填 | 官方说明 |
|
|
875
|
+
| ---------- | ---------- | -------- | -------- |
|
|
876
|
+
| `userInfo` | `UserInfo` | 是 | 用户对象 |
|
|
877
|
+
|
|
878
|
+
AI 使用规则:
|
|
879
|
+
|
|
880
|
+
- 需要“当前时间”时优先它,不要直接 `new Date()`
|
|
881
|
+
|
|
882
|
+
#### 8.6.2 `getUserTimeZone`
|
|
883
|
+
|
|
884
|
+
方法形式:
|
|
885
|
+
|
|
886
|
+
```java
|
|
887
|
+
TimeUtil.getUserTimeZone(userInfo)
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
参数说明:
|
|
891
|
+
|
|
892
|
+
| 参数 | 类型 | 是否必填 | 官方说明 |
|
|
893
|
+
| ---------- | ---------- | -------- | -------- |
|
|
894
|
+
| `userInfo` | `UserInfo` | 是 | 用户对象 |
|
|
895
|
+
|
|
896
|
+
AI 使用规则:
|
|
897
|
+
|
|
898
|
+
- 需要自己构造时区相关对象时优先先取用户时区
|
|
899
|
+
|
|
900
|
+
#### 8.6.3 `getSimpleDateFormat`
|
|
901
|
+
|
|
902
|
+
官方示例:
|
|
903
|
+
|
|
904
|
+
```java
|
|
905
|
+
SimpleDateFormat myDateFormat = TimeUtil.getSimpleDateFormat(format, userInfo);
|
|
351
906
|
```
|
|
352
907
|
|
|
353
|
-
|
|
908
|
+
参数说明:
|
|
909
|
+
|
|
910
|
+
| 参数 | 类型 | 是否必填 | 默认值 | 官方说明 | AI 使用说明 |
|
|
911
|
+
| ---------- | ---------- | -------- | -------------- | -------- | ---------------------- |
|
|
912
|
+
| `format` | `String` | 是 | `"yyyy-MM-dd"` | 格式化 | 显式传格式串 |
|
|
913
|
+
| `userInfo` | `UserInfo` | 是 | - | 用户对象 | 用用户时区创建格式化器 |
|
|
354
914
|
|
|
355
|
-
|
|
915
|
+
#### 8.6.4 `getCalendar`
|
|
356
916
|
|
|
357
|
-
|
|
917
|
+
官方文档给出两种方式:
|
|
358
918
|
|
|
359
919
|
```java
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
-
|
|
390
|
-
-
|
|
391
|
-
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
-
|
|
398
|
-
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
-
|
|
404
|
-
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
920
|
+
Calendar cal = Calendar.getInstance(TimeUtil.getUserTimeZone(userInfo));
|
|
921
|
+
Calendar cal = TimeUtil.getCalendar(userInfo);
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
AI 使用规则:
|
|
925
|
+
|
|
926
|
+
- 处理日历计算时优先 `TimeUtil.getCalendar(userInfo)`
|
|
927
|
+
- 不要默认 `Calendar.getInstance()` 使用本地时区
|
|
928
|
+
|
|
929
|
+
## 9. 触发器里的代码结构建议
|
|
930
|
+
|
|
931
|
+
### 9.1 采用“判断 -> 查询 -> 校验 -> 执行 -> 记录”的顺序
|
|
932
|
+
|
|
933
|
+
推荐思路:
|
|
934
|
+
|
|
935
|
+
1. 先识别当前触发场景
|
|
936
|
+
2. 再判断是否满足继续执行条件
|
|
937
|
+
3. 再做最小必要查询
|
|
938
|
+
4. 再执行业务动作
|
|
939
|
+
5. 最后记录日志或错误
|
|
940
|
+
|
|
941
|
+
不要一上来就先查很多无关数据。
|
|
942
|
+
|
|
943
|
+
### 9.2 先过滤,再计算
|
|
944
|
+
|
|
945
|
+
如果当前触发器只在某些状态、某些字段、某些记录类型下生效,应先快速退出无关场景。
|
|
946
|
+
|
|
947
|
+
这样做的价值:
|
|
948
|
+
|
|
949
|
+
- 降低无谓查询
|
|
950
|
+
- 降低递归触发影响
|
|
951
|
+
- 降低对其他业务分支的误伤
|
|
952
|
+
|
|
953
|
+
### 9.3 先查一批,再批量处理
|
|
954
|
+
|
|
955
|
+
如果需要操作多条关联记录,优先:
|
|
956
|
+
|
|
957
|
+
- 一次查够
|
|
958
|
+
- 建立映射
|
|
959
|
+
- 合并处理
|
|
960
|
+
|
|
961
|
+
避免:
|
|
962
|
+
|
|
963
|
+
- 在循环里重复查询
|
|
964
|
+
- 在循环里逐条查、逐条更
|
|
965
|
+
|
|
966
|
+
### 9.4 薄触发器 + 服务下沉
|
|
967
|
+
|
|
968
|
+
如果逻辑超过以下规模,优先下沉到自定义类:
|
|
969
|
+
|
|
970
|
+
- 多个对象联动
|
|
971
|
+
- 多层条件嵌套
|
|
972
|
+
- 多处复用
|
|
973
|
+
- 外部接口调用
|
|
974
|
+
- 多步通知和补偿
|
|
975
|
+
|
|
976
|
+
触发器中更推荐:
|
|
977
|
+
|
|
978
|
+
- 只拼装输入
|
|
979
|
+
- 调服务类
|
|
980
|
+
- 根据结果做拦截或记录
|
|
981
|
+
|
|
982
|
+
## 10. 性能、递归与幂等
|
|
983
|
+
|
|
984
|
+
### 10.1 性能原则
|
|
985
|
+
|
|
986
|
+
AI 默认应避免:
|
|
987
|
+
|
|
988
|
+
- 无条件全表查询
|
|
989
|
+
- 过度使用 `select *`
|
|
990
|
+
- 循环内查数据库
|
|
991
|
+
- 循环内大量单条更新
|
|
992
|
+
|
|
993
|
+
AI 应优先:
|
|
994
|
+
|
|
995
|
+
- 只查必要字段
|
|
996
|
+
- 只查必要记录
|
|
997
|
+
- 合并写操作
|
|
998
|
+
- 复用查询结果
|
|
999
|
+
|
|
1000
|
+
### 10.2 递归触发原则
|
|
1001
|
+
|
|
1002
|
+
触发器中最危险的问题之一,是“我更新了一条数据,结果又触发自己或兄弟触发器”。
|
|
1003
|
+
|
|
1004
|
+
AI 每次写更新逻辑时都必须问:
|
|
1005
|
+
|
|
1006
|
+
- 更新的是不是当前对象
|
|
1007
|
+
- 更新的是不是相关联、也挂了触发器的对象
|
|
1008
|
+
- 这次更新是否会反向回写回来
|
|
1009
|
+
|
|
1010
|
+
如果有风险,应优先:
|
|
1011
|
+
|
|
1012
|
+
- 缩小更新字段
|
|
1013
|
+
- 加前置判断
|
|
1014
|
+
- 利用项目已有的低触发/批量更新能力
|
|
1015
|
+
- 将高复杂逻辑下沉到更可控的服务层
|
|
1016
|
+
|
|
1017
|
+
### 10.3 幂等原则
|
|
1018
|
+
|
|
1019
|
+
以下动作最需要幂等判断:
|
|
1020
|
+
|
|
1021
|
+
- 自动生成项目、工作包、子单据
|
|
1022
|
+
- 自动创建共享
|
|
1023
|
+
- 自动发送待办、邮件
|
|
1024
|
+
- 自动推送外部系统
|
|
1025
|
+
|
|
1026
|
+
AI 不得默认“只会触发一次”。
|
|
1027
|
+
|
|
1028
|
+
必须显式考虑:
|
|
1029
|
+
|
|
1030
|
+
- 是否已生成
|
|
1031
|
+
- 是否已发送
|
|
1032
|
+
- 是否已同步
|
|
1033
|
+
- 是否已存在同业务键记录
|
|
1034
|
+
|
|
1035
|
+
## 11. 触发器中的常见业务模式
|
|
1036
|
+
|
|
1037
|
+
基于当前项目本地触发器分析,AI 可优先把需求归入以下模式之一:
|
|
1038
|
+
|
|
1039
|
+
- 保存前校验
|
|
1040
|
+
- 保存前自动赋值
|
|
1041
|
+
- 保存后汇总回写
|
|
1042
|
+
- 创建后自动派生
|
|
1043
|
+
- 审批前校验
|
|
1044
|
+
- 审批后推进流程
|
|
1045
|
+
- 删除前保护
|
|
1046
|
+
- 删除后补偿
|
|
1047
|
+
- 负责人变化后的共享治理
|
|
1048
|
+
- 事件触发后的通知和集成
|
|
1049
|
+
|
|
1050
|
+
一旦能把需求归入模式,时机选择、SDK 选择和代码结构都会更清晰。
|
|
1051
|
+
|
|
1052
|
+
## 12. AI 最容易犯的错误
|
|
1053
|
+
|
|
1054
|
+
### 12.1 把触发器写成大型业务类
|
|
1055
|
+
|
|
1056
|
+
错误表现:
|
|
1057
|
+
|
|
1058
|
+
- 大量查询、更新、通知、外部调用全堆在 SOURCE 区域
|
|
1059
|
+
|
|
1060
|
+
正确做法:
|
|
1061
|
+
|
|
1062
|
+
- 触发器做薄入口
|
|
1063
|
+
- 大逻辑下沉到自定义类
|
|
1064
|
+
|
|
1065
|
+
### 12.2 在错误时机做错误动作
|
|
1066
|
+
|
|
1067
|
+
错误表现:
|
|
1068
|
+
|
|
1069
|
+
- 在 `before*` 里做依赖 ID 的派生创建
|
|
1070
|
+
- 在 `after*` 里做本应提前阻断的校验
|
|
1071
|
+
|
|
1072
|
+
正确做法:
|
|
1073
|
+
|
|
1074
|
+
- 先判断这是拦截型规则还是联动型规则
|
|
1075
|
+
|
|
1076
|
+
### 12.3 没有考虑递归和重复执行
|
|
1077
|
+
|
|
1078
|
+
错误表现:
|
|
1079
|
+
|
|
1080
|
+
- 更新引发再次触发
|
|
1081
|
+
- 自动生成重复记录
|
|
1082
|
+
- 同一通知反复发送
|
|
1083
|
+
|
|
1084
|
+
正确做法:
|
|
1085
|
+
|
|
1086
|
+
- 加前置判断
|
|
1087
|
+
- 做幂等检查
|
|
1088
|
+
- 缩小写入范围
|
|
1089
|
+
|
|
1090
|
+
### 12.4 把可配置项写死
|
|
1091
|
+
|
|
1092
|
+
错误表现:
|
|
1093
|
+
|
|
1094
|
+
- 模板 ID、角色映射、阈值、接口地址直接写死在触发器里
|
|
1095
|
+
|
|
1096
|
+
正确做法:
|
|
1097
|
+
|
|
1098
|
+
- 优先使用自定义设置
|
|
1099
|
+
|
|
1100
|
+
### 12.5 忽视日志和异常
|
|
1101
|
+
|
|
1102
|
+
错误表现:
|
|
1103
|
+
|
|
1104
|
+
- 吞掉异常
|
|
1105
|
+
- 失败无日志
|
|
1106
|
+
- 只有“执行失败”这种低价值日志
|
|
1107
|
+
|
|
1108
|
+
正确做法:
|
|
1109
|
+
|
|
1110
|
+
- 关键分支打日志
|
|
1111
|
+
- 异常带上下文
|
|
1112
|
+
- 错误信息能定位到业务对象和关键动作
|
|
1113
|
+
|
|
1114
|
+
## 13. AI 的交付要求
|
|
1115
|
+
|
|
1116
|
+
当 AI 输出一个触发器方案或实现时,至少应明确:
|
|
1117
|
+
|
|
1118
|
+
- 为什么要用触发器,而不是定时器、页面脚本或纯自定义类
|
|
1119
|
+
- 为什么选这个触发时机
|
|
1120
|
+
- 当前触发器负责什么,不负责什么
|
|
1121
|
+
- 哪些逻辑保留在触发器里
|
|
1122
|
+
- 哪些逻辑应该下沉到自定义类
|
|
1123
|
+
- 使用了哪些 SDK 能力
|
|
1124
|
+
- 如何避免递归、性能和幂等问题
|
|
1125
|
+
- 如何处理日志、异常、通知和时间
|
|
1126
|
+
|
|
1127
|
+
## 14. 最终结论
|
|
1128
|
+
|
|
1129
|
+
对 AI 来说,触发器最正确的定位不是“哪里都能塞逻辑”,而是:
|
|
1130
|
+
|
|
1131
|
+
- 一个跟随对象生命周期执行的服务端规则入口
|
|
1132
|
+
- 一个负责拦截、联动、推进、补偿的即时执行点
|
|
1133
|
+
- 一个必须尽量薄、尽量稳、尽量可控的入口层
|
|
1134
|
+
|
|
1135
|
+
因此,AI 在写触发器时,应始终遵循这条总原则:
|
|
1136
|
+
|
|
1137
|
+
- 用触发器做事件入口
|
|
1138
|
+
- 用 SDK 做规范数据操作
|
|
1139
|
+
- 用自定义类承接复杂逻辑
|
|
1140
|
+
- 用日志、幂等、时区和配置意识保证长期可维护
|