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.
Files changed (66) hide show
  1. package/.cursor/skills/{cloudcc-cli-dev → dev-guide}/SKILL.md +5 -1
  2. package/README.md +82 -1
  3. package/bin/cc.js +2 -1
  4. package/bin/index.js +1 -0
  5. package/cloudcc-dev-skill/SKILL.md +31 -8
  6. package/cloudcc-dev-skill/cloudcc-dev-html.md +42 -0
  7. package/cloudcc-dev-skill/config.json +2 -2
  8. package/mcp/index.js +27 -3
  9. package/mcp/tools/JSP Migrator/handler.js +51 -866
  10. package/mcp/tools/Object Creator/handler.js +14 -4
  11. package/mcp/tools/Object Fields Creator/handler.js +149 -3
  12. package/package.json +1 -1
  13. package/src/classes/docs/devguide.md +758 -364
  14. package/src/classes/docs/introduction.md +279 -143
  15. package/src/fields/buildFieldData.js +692 -0
  16. package/src/fields/create.js +10 -170
  17. package/src/fields/detail.js +37 -0
  18. package/src/fields/docs/devguide.md +168 -44
  19. package/src/fields/docs/introduction.md +2 -0
  20. package/src/fields/fields/A.js +3 -2
  21. package/src/fields/fields/AD.js +4 -2
  22. package/src/fields/fields/B.js +8 -5
  23. package/src/fields/fields/C.js +13 -5
  24. package/src/fields/fields/D.js +4 -4
  25. package/src/fields/fields/E.js +10 -5
  26. package/src/fields/fields/ENC.js +27 -8
  27. package/src/fields/fields/ENCD.js +27 -8
  28. package/src/fields/fields/F.js +4 -4
  29. package/src/fields/fields/FL.js +8 -4
  30. package/src/fields/fields/H.js +4 -4
  31. package/src/fields/fields/IMG.js +23 -5
  32. package/src/fields/fields/J.js +21 -6
  33. package/src/fields/fields/L.js +32 -8
  34. package/src/fields/fields/LT.js +23 -6
  35. package/src/fields/fields/M.js +2 -2
  36. package/src/fields/fields/MR.js +2 -2
  37. package/src/fields/fields/N.js +31 -8
  38. package/src/fields/fields/P.js +13 -5
  39. package/src/fields/fields/Q.js +42 -12
  40. package/src/fields/fields/S.js +19 -7
  41. package/src/fields/fields/SCORE.js +9 -4
  42. package/src/fields/fields/T.js +4 -4
  43. package/src/fields/fields/U.js +18 -5
  44. package/src/fields/fields/X.js +20 -6
  45. package/src/fields/fields/Y.js +17 -4
  46. package/src/fields/index.js +2 -0
  47. package/src/fields/update.js +148 -0
  48. package/src/jsp/analyze.js +17 -0
  49. package/src/jsp/doc.js +18 -0
  50. package/src/jsp/docs/devguide.md +111 -0
  51. package/src/jsp/docs/introduction.md +50 -0
  52. package/src/jsp/docs.js +21 -0
  53. package/src/jsp/index.js +14 -0
  54. package/src/jsp/migration.js +871 -0
  55. package/src/jsp/split.js +17 -0
  56. package/src/object/create.js +36 -10
  57. package/src/object/docs/devguide.md +6 -3
  58. package/src/project/docs/devguide.md +1 -1
  59. package/src/timer/docs/devguide.md +849 -400
  60. package/src/timer/docs/introduction.md +343 -231
  61. package/src/triggers/docs/devguide.md +929 -352
  62. package/src/triggers/docs/introduction.md +640 -369
  63. package/src/version/listModuleCommands.js +6 -0
  64. package/test/fields.cli.test.js +3 -1
  65. package/test/jsp.cli.test.js +70 -0
  66. package/test/object.cli.test.js +9 -1
@@ -1,541 +1,935 @@
1
- # CloudCC 自定义类开发指南
1
+ # CloudCC 自定义类 AI 开发规范
2
2
 
3
- > 本文档参考 CloudCC
4
- > 官方「自定义类」文档整理而成:[自定义类](https://help.cloudcc.cn/product03/zi-ding-yi-lei/)。\
5
- > 目标:指导开发者在 CloudCC 平台上编写 Java 自定义类,熟练使用
6
- > `CCService`、`CCObject`、`SendEmail`、`DevLogger`、`TimeUtil`
7
- > 等工具类,并理解类在触发器、定时作业、自定义组件中的调用方式。
3
+ ## 1. 目的
8
4
 
9
- ---
5
+ 本文档用于指导 AI 在 CloudCC 项目中编写自定义类时,如何做出稳定、可维护、可发布的实现。
10
6
 
11
- ## 0. 必须使用 CLI 管理本地类文件
7
+ 适用范围:
12
8
 
13
- 自定义类在本项目中的目录结构、`config.json`、主类中的 `// @SOURCE_CONTENT_START`
14
- `// @SOURCE_CONTENT_END` 区域,均与 **cloudcc-cli**
15
- 的创建、发布、拉取逻辑绑定。**必须通过下列命令**
16
- 完成新建目录、与服务器同步、发布与删除;不要手工新建
17
- `classes/<类名>/`、不要复制粘贴整套类文件夹、不要私自篡改 `config.json` 里的
18
- `id` 或破坏版本字段,否则容易导致发布失败、拉取覆盖异常或与线上一致性不一致。
9
+ - 新建自定义类
10
+ - 修改现有自定义类
11
+ - 为按钮、触发器、定时类、自定义页面、组件提供后端能力
12
+ - 编写通用服务类、工具类、集成类、流程编排类
19
13
 
20
- **允许的做法**:在 CLI 已生成的主类 `*.java` 中,仅在上述 SOURCE
21
- 标记之间编写与修改业务代码;其余与平台同步相关的操作一律走命令。
14
+ 本文档面向 AI,因此重点强调:
22
15
 
23
- 执行命令前请确认:已完成 `cloudcc doc project devguide`
24
- 中的环境初始化,项目根目录配置可用且包含 `accessToken`。
16
+ - 什么时候应该使用自定义类
17
+ - AI 写代码时必须遵守哪些硬规则
18
+ - SDK 该怎么用
19
+ - 代码结构应该怎么组织
20
+ - 哪些做法必须避免
25
21
 
26
- ### 0.1 命令总览(以代码实现为准)
22
+ ## 2. 编写依据
23
+
24
+ 本文档基于以下材料整理:
25
+
26
+ - CloudCC 官方后端 SDK 参考:`CCObject`、`UserInfo`、`CCService`、`SendEmail`、`DevLogger`、`TimeUtil`
27
+
28
+ ## 3. AI 的总体职责
29
+
30
+ AI 在编写 CloudCC 自定义类时,应把自己视为“服务端业务能力设计者”,而不是“只补一段能跑代码的脚本工具”。
31
+
32
+ AI 需要同时满足以下目标:
33
+
34
+ - 功能正确
35
+ - 结构清晰
36
+ - 可复用
37
+ - 易排错
38
+ - 易发布
39
+
40
+ ## 4. 什么时候应该使用自定义类
41
+
42
+ 满足以下任一情况时,AI 应优先选择自定义类实现:
43
+
44
+ - 逻辑需要在服务端执行
45
+ - 一个动作会影响多个对象
46
+ - 逻辑会被多个入口复用
47
+ - 需要复杂规则计算
48
+ - 需要流程状态推进
49
+ - 需要调用外部系统
50
+ - 需要处理附件、文件、目录
51
+ - 需要统一日志、异常、时间处理
52
+ - 需要自动通知、提醒、待办协同
53
+ - 需要接入 AI 或分析能力
54
+
55
+ 以下场景不要优先上自定义类:
56
+
57
+ - 只是简单展示
58
+ - 只是轻量页面交互
59
+ - 只是简单字段联动
60
+ - 标准配置即可完成
61
+
62
+ ## 5. AI 必须遵守的硬规则
63
+
64
+ ### 5.1 只能通过 cloudcc-cli 管理类目录
65
+
66
+ AI 不得手工创建或复制 `classes/<类名>/` 目录,不得私自改动 `config.json` 中的 `id` 或版本信息。
67
+
68
+ 必须使用以下命令:
27
69
 
28
70
  ```bash
29
71
  cloudcc create classes <name>
30
72
  cloudcc publish classes <name>
31
73
  cloudcc pull classes <name>
32
- cloudcc get classes [listQueryJson] [projectPath]
33
- cloudcc detail classes <name>
34
- cloudcc detail classes "" <id>
35
- cloudcc pullList classes <id> <projectPath>
36
- cloudcc delete classes <nameOrId> [projectPath]
37
- cloudcc doc classes <introduction|devguide>
74
+ cloudcc delete classes <nameOrId>
38
75
  ```
39
76
 
40
- 参数约定:
41
-
42
- - `name`:Java 类名,与 `classes/<name>/` 目录名一致。
43
- - `listQueryJson`:可选;列表查询条件的 JSON 经 `encodeURI(JSON.stringify(...))`
44
- 编码后传入。不传时使用默认分页参数。
45
- - `projectPath`:项目根路径,可选;不传则使用当前工作目录。
46
- - `id`:线上类 ID。`detail`
47
- 在仅查服务器时可将类名置空:`cloudcc detail classes "" <id>`。`pullList` 用 ID
48
- 将线上类落到指定项目的 `classes/` 下。
49
- - `nameOrId`:本地目录名,或类 ID。若本地存在 `config.json` 且含
50
- `id`,删除时会优先用其中的服务器 ID。
51
-
52
- ### 0.2 命令说明摘要
53
-
54
- | 命令 | 作用 |
55
- | ---------- | ----------------------------------------------------------------------------------- |
56
- | `create` | 在 `classes/<name>/` 生成主类、测试类、`config.json` 模板 |
57
- | `publish` | 将本地 SOURCE 区域源码提交到服务器;首次成功后会写回 `config.json` 的 `id` |
58
- | `pull` | 按本地 `config.json` 的 `id` 从服务器拉取源码,覆盖主类中 SOURCE 区域(需已发布过) |
59
- | `get` | 查询线上类列表(JSON 输出) |
60
- | `detail` | 按类名优先读本地;或仅按 `id` 读服务器详情 |
61
- | `pullList` | 按类 `id` 将线上类拉取到指定 `projectPath` 下的 `classes/<类名>/` |
62
- | `delete` | 调用接口删除服务器上的类;参数可为本地类名或类 ID |
63
- | `doc` | 输出 `introduction` 或 `devguide`(`devguide` 含附录 SDK 速查) |
64
-
65
- ### 0.3 推荐操作顺序
77
+ ### 5.2 只能在 SOURCE 区域内写业务代码
66
78
 
67
- ```bash
68
- # 1) 查看线上已有类(可选,避免重名或确认 ID)
69
- cloudcc get classes
79
+ AI 修改现有类时,只能编辑主类中的:
70
80
 
71
- # 2) 新建类(仅通过 create 生成目录与文件)
72
- cloudcc create classes MyClass
81
+ ```java
82
+ // @SOURCE_CONTENT_START
83
+ // @SOURCE_CONTENT_END
84
+ ```
73
85
 
74
- # 3) 编辑 classes/MyClass/MyClass.java 中 SOURCE 区域后发布
75
- cloudcc publish classes MyClass
86
+ 之间的内容。
76
87
 
77
- # 4) 需要与服务器对齐时,拉取覆盖本地 SOURCE 区域
78
- cloudcc pull classes MyClass
88
+ 不得破坏:
79
89
 
80
- # 5) 按已知线上类 ID 拉到指定项目(迁移、批量)
81
- cloudcc pullList classes <线上类ID> <projectPath>
90
+ - 包路径
91
+ - 类目录结构
92
+ - `config.json`
93
+ - SOURCE 标记外的框架代码
82
94
 
83
- # 6) 不再使用时删除线上类
84
- cloudcc delete classes MyClass
85
- ```
95
+ ### 5.3 必须保留 UserInfo 上下文
86
96
 
87
- ### 0.4 文档子命令
97
+ 自定义类必须显式接收 `UserInfo`,并通过它构造平台能力对象。
88
98
 
89
- ```bash
90
- cloudcc doc classes introduction
91
- cloudcc doc classes devguide
99
+ 标准写法:
100
+
101
+ ```java
102
+ private UserInfo userInfo;
103
+ private CCService cs;
104
+
105
+ public XxxClass(UserInfo userInfo) {
106
+ this.userInfo = userInfo;
107
+ this.cs = new CCService(userInfo);
108
+ }
92
109
  ```
93
110
 
94
- 仅支持 `introduction` 与 `devguide`,其他子命令会报错。
111
+ ### 5.4 触发器、定时类、页面、按钮应做薄入口
95
112
 
96
- ---
113
+ AI 不能把大量核心逻辑直接堆在按钮入口、触发器入口、页面接口入口中。
97
114
 
98
- ## 1. 概念与使用场景
115
+ 推荐模式:
99
116
 
100
- ### 1.1 什么是自定义类
117
+ - 入口层只做参数接收、基础校验、结果返回
118
+ - 复杂逻辑沉到服务方法或独立服务类
101
119
 
102
- - 自定义类本质上是运行在 CloudCC 平台上的 Java 类。
103
- - 可被以下模块调用:
104
- - 自定义按钮
105
- - 触发器
106
- - 定时类 / 定时作业
107
- - 自定义组件(通过 CCDK 请求自定义类)
108
- - 典型用途:
109
- - 封装复杂业务逻辑
110
- - 查询 / 写入对象数据
111
- - 调用自定义设置、发送邮件、记录开发日志
120
+ ### 5.5 必须显式处理异常
112
121
 
113
- **注意**:调用 `CCService` 的方法时,需要对异常进行显式抛出或捕获。
122
+ 调用 `CCService` 等平台方法时,AI 不能默认“不会失败”。
114
123
 
115
- ### 1.2 入口:进入类开发页面
124
+ 必须:
116
125
 
117
- 1. 登录 CloudCC 系统
118
- 2. 点击右上角头像,选择「开发者平台」(仅对管理员简档用户开放)
119
- 3. 左侧菜单:`扩展 → 类`,进入类开发页面
126
+ - 显式 `throws Exception` 或 `try/catch`
127
+ - 在关键路径记录日志
128
+ - 保留失败原因
129
+ - 对外返回明确结果,而不是悄悄吞错
120
130
 
121
- ---
131
+ ### 5.6 涉及时间必须使用 TimeUtil
122
132
 
123
- ## 2. 示例:查询客户对象数据(AccountClass)
133
+ AI 编写代码时,凡是时间写库、时间比较、格式化、Calendar 处理,都不能默认直接用本地时区对象。
124
134
 
125
- 下面是一个官方示例类:查询客户对象数据并返回 JSON。
135
+ 必须优先使用:
126
136
 
127
- ```java
128
- import net.sf.json.JSONObject;
129
- import net.sf.json.JSONArray;
130
- import net.sf.json.JSON;
131
-
132
- /**
133
- * author:*** on 2022-10-12
134
- * 客户类
135
- */
136
- public class AccountClass {
137
- private CCService cs;
138
- private UserInfo userInfo;
139
-
140
- public AccountClass(UserInfo userInfo) {
141
- this.userInfo = userInfo;
142
- cs = new CCService(userInfo);
143
- }
137
+ - `TimeUtil.getNowDate(userInfo)`
138
+ - `TimeUtil.getUserTimeZone(userInfo)`
139
+ - `TimeUtil.getSimpleDateFormat(format, userInfo)`
140
+ - `TimeUtil.getCalendar(userInfo)`
144
141
 
145
- public JSONObject selectAccount() throws Exception {
146
- JSONObject rtninfo = new JSONObject(); // 返回结果
147
- JSONArray dataList = new JSONArray(); // 数据列表
148
- boolean flag = false;
149
- try {
150
- // 查询客户对象数据
151
- String sql = "select name, leixing, fenji, hangye from Account where is_deleted='0' ";
152
- List<CCObject> accountList = cs.cqlQuery("Account", sql);
153
-
154
- for (int i = 0; i < accountList.size(); i++) {
155
- String name = accountList.get(i).get("name") == null ? "" : accountList.get(i).get("name").toString();
156
- String type = accountList.get(i).get("leixing") == null ? "" : accountList.get(i).get("leixing").toString();
157
- String grade = accountList.get(i).get("fenji") == null ? "0" : accountList.get(i).get("fenji").toString();
158
- String industry = accountList.get(i).get("hangye") == null ? "0" : accountList.get(i).get("hangye").toString();
159
-
160
- JSONObject data = new JSONObject();
161
- data.put("name", name);
162
- data.put("type", type);
163
- data.put("grade", grade);
164
- data.put("industry", industry);
165
- data.put("no", (i + 1) + ""); // 序号
166
- dataList.add(data);
167
- }
168
- flag = true;
169
- } catch (Exception e) {
170
- throw e;
171
- }
172
- rtninfo.put("status", flag);
173
- rtninfo.put("data", dataList.toString());
174
- return rtninfo;
175
- }
176
- }
177
- ```
142
+ 禁止默认使用:
178
143
 
179
- 要点:
144
+ - `new Date()` 直接作为业务时间来源
145
+ - `Calendar.getInstance()` 不带用户时区
146
+ - `new SimpleDateFormat()` 后不设置时区
180
147
 
181
- - 构造函数接收 `UserInfo`,通过它实例化 `CCService`。
182
- - 查询结果使用 `CCObject` 列表封装,再转为 `JSONObject`/`JSONArray` 返回。
183
- - 方法签名抛出 `Exception`,调用方需要处理。
148
+ ## 6. SDK 详细参考与使用规范
184
149
 
185
- ---
150
+ 本节用于解决一个核心问题:
186
151
 
187
- ## 3. CCService:对象数据读写核心
152
+ - AI 不能只知道“该用哪个类”,还必须知道“这个 API 具体怎么调、参数是什么、哪些是官方明确说明的”。
188
153
 
189
- > `CCService` 是操作 CloudCC 对象(类似
190
- > ORM)的核心服务类,支持增删改查、自定义设置等。
154
+ 本节内容基于官方页面:
191
155
 
192
- ### 3.1 在类中实例化 CCService
156
+ 使用原则:
193
157
 
194
- 通用写法:
158
+ - 只优先使用官方页面已明确给出签名和参数说明的 API
159
+ - 如果官方页没有说明某个方法的参数或返回结构,AI 不得臆造
160
+ - 遇到未文档化能力时,先参考当前仓库相邻类的现有写法,再决定是否使用
161
+ - 对 `ServiceResult` 这类官方页只给出返回类型、但未完整说明字段结构的对象,AI 不得猜字段名;如果确需读取内部字段,必须先参考本项目已有代码模式
195
162
 
196
- ```java
197
- public class DemoClass {
198
- private CCService cs;
199
- private UserInfo userInfo;
163
+ ### 6.1 CCObject 详细说明
200
164
 
201
- public DemoClass(UserInfo userInfo) {
202
- this.userInfo = userInfo;
203
- this.cs = new CCService(userInfo);
204
- }
205
- }
165
+ `CCObject` CloudCC 对象数据载体,继承自 `java.util.HashMap`,用于新增、更新、删除以及承载查询结果。
166
+
167
+ #### 6.1.1 构造方法
168
+
169
+ ```java
170
+ public CCObject()
171
+ public CCObject(String ccobj)
172
+ public CCObject(String ccobj, String isShared)
206
173
  ```
207
174
 
208
- 在触发器中则通常不需要手动实例化,可直接使用平台提供的 `cs`(见触发器指南)。
175
+ 参数说明:
209
176
 
210
- ### 3.2 新增记录:insert
177
+ | 方法 | 参数 | 类型 | 必填 | 说明 |
178
+ | --- | --- | --- | --- | --- |
179
+ | `CCObject(String ccobj)` | `ccobj` | `String` | 是 | 对象 APIName |
180
+ | `CCObject(String ccobj, String isShared)` | `ccobj` | `String` | 是 | 对象 APIName |
181
+ | `CCObject(String ccobj, String isShared)` | `isShared` | `String` | 是 | 共享标识,官方建议使用 `CCObject.IS_SHARED` |
211
182
 
212
- 1. 通过 `CCObject` 构造要插入的记录
213
- 2. 调用 `cs.insert(对象)` 持久化到数据库
183
+ #### 6.1.2 常量字段
214
184
 
215
- ```java
216
- // 构造一个数据对象,传入对象 API 名称
217
- CCObject opp = new CCObject("Opportunity");
185
+ | 常量 | 默认值 | 说明 |
186
+ | --- | --- | --- |
187
+ | `CCObject.OBJECT_API` | `CCObjectAPI` | 对象 API |
188
+ | `CCObject.IS_SHARED` | `isShared` | 共享对象标识 |
218
189
 
219
- // CCObject 赋值,key 为字段 API 名称
220
- opp.put("name", "新建机会");
221
- opp.put("jine__c", 10000); // 示例:金额字段
190
+ #### 6.1.3 常用方法
222
191
 
223
- // 插入数据库
224
- cs.insert(opp);
192
+ ```java
193
+ public String getObjectApiName()
194
+ public String put(String apiName, String value)
225
195
  ```
226
196
 
227
- **注意**:ID、自动编号、创建人、创建时间等系统字段不需要也不应该手动赋值。
197
+ 参数说明:
198
+
199
+ | 方法 | 参数 | 类型 | 必填 | 说明 |
200
+ | --- | --- | --- | --- | --- |
201
+ | `put` | `apiName` | `String` | 是 | 字段 APIName |
202
+ | `put` | `value` | `String` | 是 | 字段值 |
203
+
204
+ AI 使用规则:
228
205
 
229
- ### 3.3 查询记录:cquery
206
+ - 新增或更新对象时优先使用 `new CCObject("ObjectApiName")`
207
+ - 修改记录时必须写入 `id`
208
+ - 字段赋值一律使用字段 APIName,不用字段显示名
209
+ - 共享对象按官方方式传共享标识,不要自己拼魔法字符串
210
+ - 官方页示例把 `put` 记为 `String` 参数;项目里也广泛传入数字、日期、布尔等对象。AI 如果不确定类型,优先参考同对象的现有项目代码,不要自行猜测
230
211
 
231
- `cquery` 提供多种重载,用于按条件和排序查询数据。
212
+ ### 6.2 UserInfo 详细说明
232
213
 
233
- - 返回对象全部记录:
214
+ `UserInfo` 是当前登录用户上下文对象,是构造 `CCService`、`SendEmail`、`DevLogger`、`TimeUtil` 的基础。
215
+
216
+ #### 6.2.1 构造方法
234
217
 
235
218
  ```java
236
- List<CCObject> opps = cs.cquery("Opportunity");
219
+ public UserInfo()
237
220
  ```
238
221
 
239
- - 按条件查询(where 表达式):
222
+ #### 6.2.2 官方页明确列出的常用方法
240
223
 
241
224
  ```java
242
- List<CCObject> opps = cs.cquery(
243
- "Opportunity",
244
- "khmc__c = '" + record_new.get("id") + "'"
245
- );
225
+ public String getUserId()
226
+ public String getOrgId()
227
+ public String getRoleId()
246
228
  ```
247
229
 
248
- - 按条件 + 排序:
230
+ 参数说明:
231
+
232
+ | 方法 | 入参 | 返回 | 说明 |
233
+ | --- | --- | --- | --- |
234
+ | `getUserId` | 无 | `String` | 当前用户 ID,未设置时返回 `null` |
235
+ | `getOrgId` | 无 | `String` | 当前组织 ID,未设置时返回 `null` |
236
+ | `getRoleId` | 无 | `String` | 当前角色 ID,未设置时返回 `null` |
237
+
238
+ AI 使用规则:
239
+
240
+ - 所有服务端类都应把 `UserInfo` 当作基础上下文
241
+ - 不要自己构造“当前用户”概念
242
+ - 不要使用硬编码用户替代 `UserInfo`
243
+ - 如果需要官方页未列出的 `UserInfo` 方法,先去当前仓库搜索已有用法,再决定是否复用
244
+
245
+ ### 6.3 CCService 详细说明
246
+
247
+ `CCService` 是 CloudCC 服务端核心工具类,负责增删改查、自定义设置等核心能力。
248
+
249
+ #### 6.3.1 构造函数
249
250
 
250
251
  ```java
251
- List<CCObject> opps = cs.cquery(
252
- "Opportunity",
253
- "khmc__c = '" + record_new.get("id") + "'",
254
- "jine__c desc"
255
- );
252
+ public CCService(UserInfo userInfo)
256
253
  ```
257
254
 
258
- **注意**:条件与排序中的自定义字段 API 名称要加 `__c` 后缀。
255
+ 参数说明:
256
+
257
+ | 参数 | 类型 | 必填 | 说明 |
258
+ | --- | --- | --- | --- |
259
+ | `userInfo` | `UserInfo` | 是 | 当前用户对象 |
259
260
 
260
- ### 3.4 修改记录:update
261
+ #### 6.3.2 insert
261
262
 
262
263
  ```java
263
- // 先查询到目标记录
264
- List<CCObject> list = cs.cquery("Opportunity", "id = '" + record_id + "'");
265
- if (!list.isEmpty()) {
266
- CCObject opp = list.get(0);
267
- opp.put("name", "修改后的机会名称");
268
- cs.update(opp);
269
- }
264
+ public ServiceResult insert(CCObject ccobj) throws BusiException
270
265
  ```
271
266
 
272
- ### 3.5 删除记录:delete
267
+ 参数说明:
268
+
269
+ | 参数 | 类型 | 必填 | 说明 |
270
+ | --- | --- | --- | --- |
271
+ | `ccobj` | `CCObject` | 是 | 待新增的对象数据 |
272
+
273
+ 返回说明:
274
+
275
+ - 返回 `ServiceResult`
276
+ - 官方页未完整展开 `ServiceResult` 字段结构
277
+
278
+ AI 使用规则:
279
+
280
+ - 用于单条新增
281
+ - 新增前补齐必填字段
282
+ - 不要默认插入一定成功
283
+ - 若需要读取 `ServiceResult` 内部字段,必须先参考当前项目现有写法,不得猜字段名
284
+
285
+ #### 6.3.3 cquery
286
+
287
+ 官方页给出两个重载。
273
288
 
274
289
  ```java
275
- List<CCObject> list = cs.cquery("Opportunity", "id = '" + record_id + "'");
276
- if (!list.isEmpty()) {
277
- CCObject opp = list.get(0);
278
- cs.delete(opp);
279
- }
290
+ public List<CCObject> cquery(String apiName, String condtion, String order) throws Exception
291
+ public List<CCObject> cquery(String apiName, String condtion, boolean isDataObject) throws Exception
280
292
  ```
281
293
 
282
- ### 3.6 操作共享表
294
+ 参数说明:
295
+
296
+ | 方法 | 参数 | 类型 | 必填 | 说明 |
297
+ | --- | --- | --- | --- | --- |
298
+ | `cquery(apiName, condtion, order)` | `apiName` | `String` | 是 | 对象 APIName |
299
+ | `cquery(apiName, condtion, order)` | `condtion` | `String` | 否 | 查询条件;官方说明使用字段 APIName,自定义字段要加 `__c` |
300
+ | `cquery(apiName, condtion, order)` | `order` | `String` | 否 | 排序字段;自定义字段要加 `__c` |
301
+ | `cquery(apiName, condtion, isDataObject)` | `apiName` | `String` | 是 | 对象 APIName |
302
+ | `cquery(apiName, condtion, isDataObject)` | `condtion` | `String` | 否 | 查询条件;自定义字段要加 `__c` |
303
+ | `cquery(apiName, condtion, isDataObject)` | `isDataObject` | `boolean` | 否 | `true` 查询普通数据,`false` 查询共享数据 |
304
+
305
+ 返回说明:
306
+
307
+ - 返回 `List<CCObject>`
308
+
309
+ AI 使用规则:
310
+
311
+ - 简单单表查询优先 `cquery`
312
+ - 条件和排序统一使用字段 APIName
313
+ - 自定义字段按官方要求补 `__c`
314
+ - 不要无条件全表扫描
315
+ - 当需求明确查询共享数据时,才使用带 `isDataObject` 的重载
283
316
 
284
- #### 查询共享表
317
+ #### 6.3.4 cqlQuery
285
318
 
286
319
  ```java
287
- // isDataObject false,表示查询共享表
288
- List<CCObject> shares = cs.cquery(
289
- "Opportunity",
290
- "userid = '" + userId + "'",
291
- false
292
- );
320
+ public List<CCObject> cqlQuery(String apiName, String cql) throws Exception
293
321
  ```
294
322
 
295
- #### 删除共享表记录
323
+ 参数说明:
324
+
325
+ | 参数 | 类型 | 必填 | 说明 |
326
+ | --- | --- | --- | --- |
327
+ | `apiName` | `String` | 是 | 对象 APIName |
328
+ | `cql` | `String` | 否 | SQL/CQL 语句,官方说明支持常用 where 条件 |
329
+
330
+ 返回说明:
331
+
332
+ - 返回 `List<CCObject>`
333
+
334
+ AI 使用规则:
335
+
336
+ - 聚合、join、复杂筛选时再用 `cqlQuery`
337
+ - 尽量只查需要的字段,不要默认 `select *`
338
+ - 必须收窄 where 条件
339
+ - 复杂 SQL 要保持可读性和可维护性
340
+
341
+ #### 6.3.5 update
296
342
 
297
343
  ```java
298
- cs.deleteShareObjectBySql("Opportunity", "userid = '" + userId + "'");
344
+ public ServiceResult update(CCObject ccobj) throws Exception
299
345
  ```
300
346
 
301
- **注意**:共享表中表达式里的字段 **不需要加 `__c` 后缀**。
347
+ 参数说明:
302
348
 
303
- ### 3.7 自定义设置(CustomSetting)
349
+ | 参数 | 类型 | 必填 | 说明 |
350
+ | --- | --- | --- | --- |
351
+ | `ccobj` | `CCObject` | 是 | 待更新对象 |
304
352
 
305
- #### 列表类型自定义设置(List)
353
+ 返回说明:
306
354
 
307
- - 返回某个 API 名称下的全部数据:
355
+ - 返回 `ServiceResult`
356
+ - 官方页未完整展开 `ServiceResult` 字段结构
357
+
358
+ AI 使用规则:
359
+
360
+ - 更新前必须带 `id`
361
+ - 只更新必要字段,避免无意义全量覆盖
362
+ - 更新动作应和状态校验绑定
363
+
364
+ #### 6.3.6 delete
308
365
 
309
366
  ```java
310
- Map listSettings = cs.getListCustomSetting("MyListSettingApiName");
367
+ public ServiceResult delete(CCObject ccobj) throws Exception
311
368
  ```
312
369
 
313
- - 返回指定 `Name` 的单条数据:
370
+ 参数说明:
371
+
372
+ | 参数 | 类型 | 必填 | 说明 |
373
+ | --- | --- | --- | --- |
374
+ | `ccobj` | `CCObject` | 是 | 待删除对象,通常至少包含 `id` |
375
+
376
+ 返回说明:
377
+
378
+ - 返回 `ServiceResult`
379
+
380
+ AI 使用规则:
381
+
382
+ - 删除前必须确认条件准确
383
+ - 删除前后最好有日志
384
+ - 删除共享关系时优先使用共享专用能力,不要混用普通删除
385
+
386
+ #### 6.3.7 deleteShareObjectBySql
314
387
 
315
388
  ```java
316
- Map singleSetting = cs.getListCustomSetting("MyListSettingApiName", "SomeName");
389
+ public void deleteShareObjectBySql(String objectApiName, String expression) throws Exception
317
390
  ```
318
391
 
319
- #### 层次结构类型自定义设置(Hierarchy)
392
+ 参数说明:
393
+
394
+ | 参数 | 类型 | 必填 | 说明 |
395
+ | --- | --- | --- | --- |
396
+ | `objectApiName` | `String` | 是 | 对象 APIName |
397
+ | `expression` | `String` | 是 | SQL 表达式;官方说明这里字段不需要加 `__c` |
398
+
399
+ AI 使用规则:
400
+
401
+ - 仅用于共享关系删除
402
+ - 普通对象删除不要误用这个接口
403
+ - 注意这里的字段规则和 `cquery` 条件规则不同
404
+
405
+ #### 6.3.8 自定义设置相关方法
406
+
407
+ 官方页列出了 3 类常用能力。
320
408
 
321
409
  ```java
322
- // id 为简档 ID 或用户 ID
323
- Map hiSetting = cs.getCustomSetting("MyHierarchySettingApiName", profileOrUserId);
410
+ public Map getListCustomSetting(String objectApiName)
411
+ public Map getListCustomSetting(String apiName, String name)
412
+ public Map getCustomSetting(String objectApiName, String id)
324
413
  ```
325
414
 
326
- ---
415
+ 参数说明:
416
+
417
+ | 方法 | 参数 | 类型 | 必填 | 说明 |
418
+ | --- | --- | --- | --- | --- |
419
+ | `getListCustomSetting(String objectApiName)` | `objectApiName` | `String` | 是 | 列表型自定义设置对象 APIName |
420
+ | `getListCustomSetting(String apiName, String name)` | `apiName` | `String` | 是 | 列表型自定义设置对象 APIName |
421
+ | `getListCustomSetting(String apiName, String name)` | `name` | `String` | 是 | 自定义设置属性名称 |
422
+ | `getCustomSetting(String objectApiName, String id)` | `objectApiName` | `String` | 是 | 层次结构型自定义设置对象 APIName |
423
+ | `getCustomSetting(String objectApiName, String id)` | `id` | `String` | 是 | 简档 ID 或用户 ID |
424
+
425
+ 返回说明:
426
+
427
+ - 均返回 `Map`
428
+
429
+ AI 使用规则:
430
+
431
+ - 外部系统地址、业务开关、阈值、角色映射、模板号优先配置化
432
+ - 不要把可配置项直接写死在代码里
327
433
 
328
- ## 4. 常用工具类
434
+ ### 6.4 SendEmail 详细说明
329
435
 
330
- ### 4.1 SendEmail:发送邮件
436
+ `SendEmail` 用于发送邮件通知。
437
+
438
+ #### 6.4.1 构造方法
331
439
 
332
440
  ```java
333
- SendEmail sendEmail = new SendEmail(userInfo);
334
- sendEmail.sendMailFromSystem(
335
- new String[]{"to@example.com"}, // 收件人
336
- new String[]{"cc@example.com"}, // 抄送
337
- new String[]{"bcc@example.com"}, // 密送
338
- "邮件主题",
339
- "邮件内容",
340
- true // 是否纯文本
341
- );
441
+ public SendEmail(UserInfo userInfo)
342
442
  ```
343
443
 
344
- ### 4.2 DevLogger:开发日志
444
+ 参数说明:
445
+
446
+ | 参数 | 类型 | 必填 | 说明 |
447
+ | --- | --- | --- | --- |
448
+ | `userInfo` | `UserInfo` | 是 | 当前用户对象 |
345
449
 
346
- > 用于在自定义类中记录开发日志,便于排查问题。
450
+ #### 6.4.2 sendMailFromSystem
347
451
 
348
452
  ```java
349
- // 初始化日志类
350
- DevLogger cclogger = new DevLogger(userInfo);
453
+ public boolean sendMailFromSystem(
454
+ String[] toAddress,
455
+ String[] ccAddress,
456
+ String[] bccAddress,
457
+ String subject,
458
+ String content,
459
+ boolean isText
460
+ )
461
+ ```
351
462
 
352
- // 打印 info 级别日志
353
- cclogger.devLogInfo("这是 info 日志");
463
+ 参数说明:
354
464
 
355
- // 打印 error 级别日志
356
- cclogger.devLogError("这是 error 日志");
465
+ | 参数 | 类型 | 必填 | 说明 |
466
+ | --- | --- | --- | --- |
467
+ | `toAddress` | `String[]` | 是 | 收件人数组 |
468
+ | `ccAddress` | `String[]` | 是 | 抄送人数组 |
469
+ | `bccAddress` | `String[]` | 是 | 密送人数组 |
470
+ | `subject` | `String` | 是 | 邮件标题 |
471
+ | `content` | `String` | 是 | 邮件正文 |
472
+ | `isText` | `boolean` | 是 | `false` 表示 HTML 邮件,`true` 表示纯文本 |
357
473
 
358
- // 打印异常堆栈
359
- try {
360
- // 业务逻辑
361
- } catch (Exception e) {
362
- cclogger.devLogError("发生异常", e);
363
- throw e;
364
- }
365
- ```
474
+ 返回说明:
475
+
476
+ - 返回 `boolean`
366
477
 
367
- ### 4.3 TimeUtil:多时区日期时间
478
+ AI 使用规则:
368
479
 
369
- > 为保证跨时区时间的准确性,推荐使用 `TimeUtil` 获取当前时间与时间格式化工具。
480
+ - 无抄送/密送时传空数组,不要盲目传 `null`
481
+ - HTML 邮件时 `isText=false`
482
+ - 发信动作应建立在业务动作成功之后
370
483
 
371
- - 获取当前时间(根据用户时区):
484
+ #### 6.4.3 sendEmailNew
372
485
 
373
486
  ```java
374
- TpSysTask task = new TpSysTask();
375
- task.setBeginTime(TimeUtil.getNowDate(userInfo));
487
+ public ServiceResult sendEmailNew(
488
+ String emailtemplateid,
489
+ String[] toaddress,
490
+ String[] ccaddress,
491
+ String[] bcaddress,
492
+ String id
493
+ ) throws Exception
376
494
  ```
377
495
 
378
- - 使用 `SimpleDateFormat` 时设置时区:
496
+ 参数说明:
497
+
498
+ | 参数 | 类型 | 必填 | 说明 |
499
+ | --- | --- | --- | --- |
500
+ | `emailtemplateid` | `String` | 是 | 邮件模板 ID |
501
+ | `toaddress` | `String[]` | 是 | 收件人数组 |
502
+ | `ccaddress` | `String[]` | 是 | 抄送人数组 |
503
+ | `bcaddress` | `String[]` | 是 | 密送人数组 |
504
+ | `id` | `String` | 是 | 业务记录 ID,通常用于模板数据填充 |
505
+
506
+ 返回说明:
507
+
508
+ - 返回 `ServiceResult`
509
+
510
+ AI 使用规则:
511
+
512
+ - 模板邮件优先用这个接口
513
+ - 模板 ID 不要硬编码,优先配置化
514
+ - 如需解析 `ServiceResult`,先参考现有项目代码,不要自行猜字段
515
+
516
+ ### 6.5 DevLogger 详细说明
517
+
518
+ `DevLogger` 用于运行日志采集。
519
+
520
+ #### 6.5.1 构造函数
379
521
 
380
522
  ```java
381
- SimpleDateFormat myDateFormat = new SimpleDateFormat("yyyy-MM-dd");
382
- myDateFormat.setTimeZone(TimeUtil.getUserTimeZone(userInfo));
523
+ public DevLogger(UserInfo userInfo)
383
524
  ```
384
525
 
385
- - 使用工具方法直接获取带时区的 `SimpleDateFormat`:
526
+ 参数说明:
527
+
528
+ | 参数 | 类型 | 必填 | 说明 |
529
+ | --- | --- | --- | --- |
530
+ | `userInfo` | `UserInfo` | 是 | 当前用户对象 |
531
+
532
+ #### 6.5.2 devLogInfo
386
533
 
387
534
  ```java
388
- SimpleDateFormat myDateFormat =
389
- TimeUtil.getSimpleDateFormat("yyyy-MM-dd", userInfo);
535
+ public void devLogInfo(String info)
390
536
  ```
391
537
 
392
- - 使用 `Calendar` 时设置时区:
538
+ 参数说明:
393
539
 
394
- ```java
395
- // 方式一
396
- Calendar cal = Calendar.getInstance(TimeUtil.getUserTimeZone(userInfo));
540
+ | 参数 | 类型 | 必填 | 说明 |
541
+ | --- | --- | --- | --- |
542
+ | `info` | `String` | 是 | info 级别日志内容 |
397
543
 
398
- // 方式二(推荐使用工具方法)
399
- Calendar cal2 = TimeUtil.getCalendar(userInfo);
544
+ #### 6.5.3 devLogError
545
+
546
+ ```java
547
+ public void devLogError(String error, Throwable throwable)
400
548
  ```
401
549
 
402
- ### 4.4 CCObject:数据载体
550
+ 参数说明:
403
551
 
404
- ```java
405
- // 构造一个数据对象,传入对象 API 名称
406
- CCObject opp = new CCObject("Opportunity");
552
+ | 参数 | 类型 | 必填 | 说明 |
553
+ | --- | --- | --- | --- |
554
+ | `error` | `String` | 是 | 错误日志内容 |
555
+ | `throwable` | `Throwable` | 否 | 异常对象 |
407
556
 
408
- // 构造一个共享表对象
409
- CCObject oppshare = new CCObject("Opportunity", CCObject.IS_SHARED);
557
+ AI 使用规则:
410
558
 
411
- // 赋值
412
- opp.put("name", "value");
413
- ```
559
+ - 方法入口要记录
560
+ - 关键业务 ID 要记录
561
+ - 外部调用前后要记录
562
+ - 异常必须记录
563
+ - 不要只写无意义日志
564
+
565
+ ### 6.6 TimeUtil 详细说明
414
566
 
415
- ---
567
+ `TimeUtil` 用于解决跨时区数据处理问题。官方页明确说明:涉及时间写库与比较时,不应直接依赖本地时区的 `Date` 与 `Calendar`。
416
568
 
417
- ## 5. 自定义类与其他模块的配合
569
+ #### 6.6.1 getNowDate
418
570
 
419
- ### 5.1 触发器中使用类
571
+ ```java
572
+ TimeUtil.getNowDate(userInfo)
573
+ ```
420
574
 
421
- - 触发器可以直接调用自定义类中的方法,也可以直接使用 `CCService`。
422
- - 触发器由 CloudCC 平台动态编译,调用写法与普通 Java 方法调用类似。
423
- - 典型模式:
424
- - 在触发器中只做入口和参数准备
425
- - 将复杂逻辑委托给自定义类方法
575
+ 参数说明:
426
576
 
427
- (触发器具体语法与最佳实践详见 `custom-trigger-dev.md`。)
577
+ | 参数 | 类型 | 必填 | 说明 |
578
+ | --- | --- | --- | --- |
579
+ | `userInfo` | `UserInfo` | 是 | 当前用户对象 |
428
580
 
429
- ### 5.2 自定义类调用其他自定义类(PageClsInvoker)
581
+ 返回说明:
430
582
 
431
- > 当需要在一个自定义类中调用另一个自定义类的方法时,可使用 `PageClsInvoker` 的
432
- > `invoker` 方法。
583
+ - 返回按用户时区获取的当前时间
433
584
 
434
- `invoker` 有两个常用重载:
585
+ #### 6.6.2 getUserTimeZone
435
586
 
436
587
  ```java
437
- Object invoker(String className, String method, List<Map> conlist, List<Map> arglist);
438
- Object invoker(String className, String method, List<Map> conlist, Map map);
588
+ TimeUtil.getUserTimeZone(userInfo)
439
589
  ```
440
590
 
441
591
  参数说明:
442
592
 
443
- - `className`:目标自定义类名称
444
- - `method`:要调用的方法名
445
- - `conlist`:构造器参数列表(可为 `null`)
446
- - 每个 Map 包含:
447
- - `argType`:参数类型
448
- - `argValue`:参数值
449
- - `arglist` / `map`:方法参数
450
- - 同样包含 `argType` 和 `argValue`
593
+ | 参数 | 类型 | 必填 | 说明 |
594
+ | --- | --- | --- | --- |
595
+ | `userInfo` | `UserInfo` | 是 | 当前用户对象 |
451
596
 
452
- #### 5.2.1 使用 Map 作为方法参数
597
+ 返回说明:
453
598
 
454
- 目标类:
599
+ - 返回用户时区对象
600
+
601
+ #### 6.6.3 getSimpleDateFormat
455
602
 
456
603
  ```java
457
- public class Hello {
604
+ TimeUtil.getSimpleDateFormat(format, userInfo)
605
+ ```
458
606
 
459
- public Hello() {}
607
+ 参数说明:
460
608
 
461
- public Hello(UserInfo userInfo) {
462
- System.out.print("测试");
463
- }
609
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
610
+ | --- | --- | --- | --- | --- |
611
+ | `format` | `String` | 是 | `yyyy-MM-dd` | 格式化模板 |
612
+ | `userInfo` | `UserInfo` | 是 | - | 当前用户对象 |
464
613
 
465
- public void test5(Map map) throws Exception {
466
- System.out.print("获取 map 中的值" + map.get("name"));
467
- }
468
- }
469
- ```
614
+ 返回说明:
615
+
616
+ - 返回带用户时区的 `SimpleDateFormat`
470
617
 
471
- 调用方:
618
+ #### 6.6.4 Calendar 获取方式
619
+
620
+ 官方页给出两种方式:
472
621
 
473
622
  ```java
474
- List<Map> conlist = new ArrayList<Map>();
623
+ Calendar cal = Calendar.getInstance(TimeUtil.getUserTimeZone(userInfo));
624
+ Calendar cal = TimeUtil.getCalendar(userInfo);
625
+ ```
475
626
 
476
- Map c = new HashMap();
477
- c.put("argType", UserInfo.class);
478
- c.put("argValue", userInfo);
479
- conlist.add(c);
627
+ AI 使用规则:
480
628
 
481
- Map m = new HashMap();
482
- m.put("name", "Alex");
629
+ - 只要涉及时间写库、比较、提醒、截止、统计,一律优先 `TimeUtil`
630
+ - 禁止默认用 `new Date()` 或不带时区的 `Calendar`
483
631
 
484
- new PageClsInvoker(userInfo).invoker("Hello", "test5", conlist, m);
485
- ```
632
+ ### 6.7 AI 不得猜测的 SDK 内容
633
+
634
+ 以下内容如果官方页没有明确说明,AI 不得自己编:
635
+
636
+ - 未在官方页列出的 SDK 方法名
637
+ - 未文档化的参数顺序
638
+ - `ServiceResult` 的内部字段结构
639
+ - 未确认的 `UserInfo` 扩展方法
640
+ - 外部能力类的构造方法和返回值
641
+
642
+ 正确做法:
643
+
644
+ 1. 先查官方文档
645
+ 2. 再查当前仓库已有同类实现
646
+ 3. 仍不确定时,不生成“看起来像真的”代码
647
+
648
+ ## 7. 代码结构规范
649
+
650
+ ### 7.1 分层建议
651
+
652
+ AI 编写自定义类时,优先按职责分层:
653
+
654
+ - `Controller`:入口编排、参数接收、结果返回
655
+ - `Service`:核心业务逻辑
656
+ - `Util/Utils`:通用工具、通用查询、通用转换
657
+ - `Client`:外部系统调用封装
658
+ - `Handler`:触发器或事件逻辑下沉层
659
+
660
+ 如果项目已有既定命名习惯,优先跟随相邻模块命名。
661
+
662
+ ### 7.2 方法粒度
663
+
664
+ AI 不应默认把所有逻辑塞进一个超长方法。
665
+
666
+ 推荐拆分:
667
+
668
+ - `validateXxx`
669
+ - `queryXxx`
670
+ - `calculateXxx`
671
+ - `updateXxx`
672
+ - `sendXxx`
673
+ - `buildXxxResult`
674
+
675
+ 原则:
676
+
677
+ - 一个方法只负责一类动作
678
+ - 复杂编排方法负责串联,不负责所有细节
679
+
680
+ ### 7.3 返回值规范
681
+
682
+ AI 需要根据调用场景选择返回值类型:
683
+
684
+ - 页面/按钮/组件接口:优先 `JSONObject`、`Map` 或稳定结构对象
685
+ - 内部服务方法:优先返回明确业务结果
686
+ - 仅做写操作的方法:可返回 `void`,但必须通过异常或日志暴露失败
687
+
688
+ 规则:
689
+
690
+ - 返回值结构要稳定
691
+ - 不要同一方法一会儿返回字符串、一会儿返回对象
692
+ - 对外接口最好带成功标识和错误信息
693
+
694
+ ## 8. 数据与查询规范
695
+
696
+ ### 8.1 字段名统一使用 API 名称
697
+
698
+ AI 不得使用字段显示名直接拼查询条件。
699
+
700
+ 必须:
701
+
702
+ - 使用对象 APIName
703
+ - 使用字段 APIName
704
+ - 自定义字段按文档规则补 `__c`
705
+
706
+ ### 8.2 查询要收敛
707
+
708
+ AI 默认应避免:
709
+
710
+ - 无条件全表查询
711
+ - 无限制 `select *`
712
+ - 在循环里反复查同一批数据
713
+
714
+ 应优先:
715
+
716
+ - 一次查够
717
+ - 只查必要字段
718
+ - 提前构造索引或映射
719
+ - 把批量处理合并到同一逻辑块
720
+
721
+ ### 8.3 写操作要可回溯
486
722
 
487
- #### 5.2.2 使用 List<Map> 作为方法参数列表
723
+ 涉及新增、更新、删除时:
488
724
 
489
- 目标类:
725
+ - 要明确主键或条件
726
+ - 要记录关键业务键
727
+ - 要尽量保证幂等
728
+ - 要避免重复插入
729
+
730
+ ## 9. 外部集成规范
731
+
732
+ AI 编写集成类时,必须把外部系统调用与业务逻辑适度隔离。
733
+
734
+ 规则:
735
+
736
+ - 接口地址、密钥、系统标识优先配置化
737
+ - 不要在多个业务方法中重复拼接 URL
738
+ - 外部调用前后必须记录日志
739
+ - 调用失败要保留错误上下文
740
+ - 文件上传、下载、同步逻辑要单独封装
741
+
742
+ 推荐模式:
743
+
744
+ - `Client` 负责调用外部接口
745
+ - `Service` 负责业务编排
746
+ - `Controller` 负责入口输出
747
+
748
+ ## 10. 通知与协同规范
749
+
750
+ AI 编写通知能力时,应遵循:
751
+
752
+ - 先完成业务动作,再触发通知
753
+ - 通知内容应基于真实业务结果生成
754
+ - 收件人/待办对象来源应可追溯
755
+ - 同一动作不要重复发送
756
+
757
+ 如果通知逻辑复杂:
758
+
759
+ - 单独提取通知方法
760
+ - 不要把大段 HTML 拼接散落到多处
761
+
762
+ ## 11. 权限与共享规范
763
+
764
+ AI 处理权限相关需求时,应优先使用服务端判断,而不是把关键限制交给前端。
765
+
766
+ 规则:
767
+
768
+ - 角色判断放服务端
769
+ - 审批权限放服务端
770
+ - 共享写入放服务端
771
+ - 删除共享关系要使用正确接口
772
+
773
+ 当需求和共享、角色、负责人变化相关时,AI 应主动考虑:
774
+
775
+ - 是否要补共享
776
+ - 是否要删旧共享
777
+ - 是否要校验当前用户是否有权执行
778
+
779
+ ## 12. 异常与日志规范
780
+
781
+ AI 写代码时必须假设以下事情都可能失败:
782
+
783
+ - 查询为空
784
+ - 字段为空
785
+ - 配置缺失
786
+ - 外部接口超时
787
+ - 文件不存在
788
+ - 写操作失败
789
+ - 状态不满足前置条件
790
+
791
+ 因此必须:
792
+
793
+ - 对空值做保护
794
+ - 对异常做显式处理
795
+ - 对关键节点打日志
796
+ - 对外返回明确错误
797
+
798
+ 不允许:
799
+
800
+ - 空 `catch`
801
+ - 吞异常后继续假装成功
802
+ - 失败时只返回 `false` 而不给任何上下文
803
+
804
+ ## 13. 命名规范
805
+
806
+ 优先遵循以下模式:
807
+
808
+ - `XxxController`:入口编排类
809
+ - `XxxService`:核心业务服务类
810
+ - `XxxUtil` / `XxxUtils`:通用工具类
811
+ - `XxxClient`:外部系统客户端
812
+ - `XxxHandler`:触发逻辑处理类
813
+
814
+ 方法命名优先使用动词开头:
815
+
816
+ - `queryXxx`
817
+ - `createXxx`
818
+ - `updateXxx`
819
+ - `deleteXxx`
820
+ - `sendXxx`
821
+ - `calculateXxx`
822
+ - `buildXxx`
823
+
824
+ ## 14. AI 禁止事项
825
+
826
+ AI 不得:
827
+
828
+ - 手工新建 `classes/<类名>/` 目录
829
+ - 修改 `config.json` 的 `id` 或版本字段
830
+ - 在 SOURCE 区域外写业务代码
831
+ - 默认使用 `new Date()` 作为业务时间
832
+ - 使用字段显示名代替 API 名称
833
+ - 无条件全量查询大对象
834
+ - 将关键逻辑全部堆在入口方法里
835
+ - 在多个地方复制同样的外部接口调用代码
836
+ - 把地址、角色、阈值、模板 ID 等全部硬编码
837
+ - 吞异常不报错
838
+ - 只写“成功/失败”而没有上下文日志
839
+
840
+ ## 15. AI 生成代码前的检查清单
841
+
842
+ 在开始写代码前,AI 应先确认:
843
+
844
+ 1. 这是页面/按钮/触发器/定时类/组件中的哪一种入口
845
+ 2. 这段逻辑是否真的应该放到自定义类
846
+ 3. 会影响哪些对象
847
+ 4. 是否需要外部系统调用
848
+ 5. 是否需要附件或文件处理
849
+ 6. 是否涉及时区
850
+ 7. 是否涉及权限、共享或审批
851
+ 8. 哪些参数适合配置化而不是硬编码
852
+
853
+ ## 16. AI 交付前的自检清单
854
+
855
+ AI 完成代码后,必须自检:
856
+
857
+ 1. 是否只修改了 SOURCE 区域
858
+ 2. 是否保留了 `UserInfo` + `CCService`
859
+ 3. 是否正确使用对象/字段 API 名称
860
+ 4. 是否优先使用了 `cquery` / `cqlQuery` 的正确场景
861
+ 5. 是否对写操作做了失败处理
862
+ 6. 是否对关键步骤加了日志
863
+ 7. 是否避免了直接 `new Date()`
864
+ 8. 是否把复杂逻辑拆成了可读的方法
865
+ 9. 是否避免了不必要硬编码
866
+ 10. 是否让返回结果对调用方足够清晰
867
+
868
+ ## 17. 推荐骨架
869
+
870
+ 下面是一个适合 AI 生成新类时参考的通用骨架:
490
871
 
491
872
  ```java
492
- public class Hello {
873
+ import com.cloudcc.core.*;
874
+ import net.sf.json.JSONObject;
875
+ import java.util.List;
493
876
 
494
- public Hello() {}
877
+ public class XxxService {
878
+ private final UserInfo userInfo;
879
+ private final CCService cs;
880
+ private final DevLogger logger;
495
881
 
496
- public ServiceResult test3(UserInfo userInfo, String leadName) throws Exception {
497
- CCService cs = new CCService((UserInfo) userInfo);
498
- CCObject co = new CCObject("Lead");
499
- co.put("name", leadName);
500
- ServiceResult sr = cs.insert(co);
501
- return sr;
882
+ public XxxService(UserInfo userInfo) {
883
+ this.userInfo = userInfo;
884
+ this.cs = new CCService(userInfo);
885
+ this.logger = new DevLogger(userInfo);
502
886
  }
503
- }
504
- ```
505
887
 
506
- 调用方:
888
+ public JSONObject execute(String recordId) throws Exception {
889
+ JSONObject result = new JSONObject();
890
+ logger.devLogInfo("XxxService.execute start, recordId=" + recordId);
891
+ try {
892
+ validate(recordId);
507
893
 
508
- ```java
509
- List<Map> arglist = new ArrayList<Map>();
894
+ List<CCObject> records = queryData(recordId);
895
+ JSONObject calcResult = calculate(records);
896
+ updateData(recordId, calcResult);
510
897
 
511
- Map u = new HashMap();
512
- u.put("argType", UserInfo.class);
513
- u.put("argValue", userInfo);
514
- arglist.add(u);
898
+ result.put("success", true);
899
+ result.put("data", calcResult);
900
+ logger.devLogInfo("XxxService.execute success, recordId=" + recordId);
901
+ return result;
902
+ } catch (Exception e) {
903
+ logger.devLogError("XxxService.execute failed, recordId=" + recordId, e);
904
+ throw e;
905
+ }
906
+ }
515
907
 
516
- Map n = new HashMap();
517
- n.put("argType", String.class);
518
- n.put("argValue", "Alex");
519
- arglist.add(n);
908
+ private void validate(String recordId) throws Exception {
909
+ if (recordId == null || "".equals(recordId.trim())) {
910
+ throw new Exception("recordId is required");
911
+ }
912
+ }
520
913
 
521
- new PageClsInvoker(userInfo).invoker("Hello", "test3", null, arglist);
914
+ private List<CCObject> queryData(String recordId) throws Exception {
915
+ return cs.cquery("ObjectApiName", "id = '" + recordId + "'", null);
916
+ }
917
+
918
+ private JSONObject calculate(List<CCObject> records) {
919
+ JSONObject result = new JSONObject();
920
+ result.put("count", records == null ? 0 : records.size());
921
+ return result;
922
+ }
923
+
924
+ private void updateData(String recordId, JSONObject calcResult) throws Exception {
925
+ CCObject obj = new CCObject("ObjectApiName");
926
+ obj.put("id", recordId);
927
+ obj.put("last_execute_time", TimeUtil.getNowDate(userInfo));
928
+ cs.update(obj);
929
+ }
930
+ }
522
931
  ```
523
932
 
524
- ---
525
-
526
- ## 6. 自定义类开发 Checklist
527
-
528
- - [ ] 类目录与平台同步仅通过 `cloudcc create` / `cloudcc pull` / `cloudcc pullList` /
529
- `cloudcc publish` / `cloudcc delete` 等命令操作,不手工造目录或改 `config.json` 的
530
- `id`
531
- - [ ] 类名、方法名与调用方(按钮/触发器/组件/定时作业)约定一致
532
- - [ ] 构造函数正确接收 `UserInfo` 并实例化 `CCService` 等依赖
533
- - [ ] 所有 `CCService` 操作(`insert`/`update`/`delete`/`cquery`)使用正确的对象
534
- API 名称和字段 API 名称(自定义字段加 `__c`)
535
- - [ ] 访问共享表时,表达式中的字段未错误地添加 `__c` 后缀
536
- - [ ] 涉及时间处理的逻辑统一使用 `TimeUtil`,避免时区问题
537
- - [ ] 异常统一捕获并按需抛出,同时使用 `DevLogger` 记录关键错误信息
538
- - [ ] 使用 `SendEmail` 时确认收件地址与内容来源安全可靠
539
- - [ ] 使用 `PageClsInvoker` 跨类调用时,`argType` 与 `argValue` 类型匹配
540
- - [ ] 对外暴露的方法返回结构(如
541
- `JSONObject`)与前端/调用方约定一致,字段清晰、语义明确
933
+ ## 18. 一句话结论
934
+
935
+ AI 编写 CloudCC 自定义类时,最重要的不是“把需求翻译成几段 Java”,而是遵守 CloudCC 的类管理方式、正确使用 SDK、把复杂逻辑放在服务端可治理的位置,并让代码具备复用性、可追踪性和可发布性。