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