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