cloudcc-cli 2.3.9 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/bin/cc.js +2 -1
- package/bin/index.js +1 -0
- package/cloudcc-dev-skill/SKILL.md +31 -8
- package/cloudcc-dev-skill/cloudcc-dev-html.md +42 -0
- package/cloudcc-dev-skill/config.json +2 -2
- package/mcp/index.js +10 -4
- package/mcp/tools/JSP Migrator/handler.js +51 -866
- package/mcp/tools/Object Creator/handler.js +14 -4
- package/mcp/tools/Trigger List Retriever/handler.js +24 -8
- package/package.json +1 -1
- package/src/classes/docs/devguide.md +863 -356
- package/src/classes/docs/introduction.md +279 -143
- package/src/jsp/analyze.js +17 -0
- package/src/jsp/doc.js +18 -0
- package/src/jsp/docs/devguide.md +111 -0
- package/src/jsp/docs/introduction.md +50 -0
- package/src/jsp/docs.js +21 -0
- package/src/jsp/index.js +14 -0
- package/src/jsp/migration.js +871 -0
- package/src/jsp/split.js +17 -0
- package/src/object/create.js +36 -10
- package/src/object/docs/devguide.md +6 -3
- package/src/project/docs/devguide.md +1 -1
- package/src/script/docs/devguide.md +22 -2
- package/src/timer/docs/devguide.md +1038 -400
- package/src/timer/docs/introduction.md +343 -231
- package/src/triggers/docs/devguide.md +1027 -329
- package/src/triggers/docs/introduction.md +640 -369
- package/src/triggers/get.js +8 -0
- package/src/version/listModuleCommands.js +6 -0
- package/target/classes/com/cloudcc/core/BaseException.class +0 -0
- package/target/classes/com/cloudcc/core/BusiException.class +0 -0
- package/target/classes/com/cloudcc/core/CCObject.class +0 -0
- package/target/classes/com/cloudcc/core/CCSchedule.class +0 -0
- package/target/classes/com/cloudcc/core/CCService.class +0 -0
- package/target/classes/com/cloudcc/core/CCTrigger.class +0 -0
- package/target/classes/com/cloudcc/core/CCTriggerHandler.class +0 -0
- package/target/classes/com/cloudcc/core/DevLogger.class +0 -0
- package/target/classes/com/cloudcc/core/OperatationEnum.class +0 -0
- package/target/classes/com/cloudcc/core/PeakInterf.class +0 -0
- package/target/classes/com/cloudcc/core/SendEmail.class +0 -0
- package/target/classes/com/cloudcc/core/ServiceResult.class +0 -0
- package/target/classes/com/cloudcc/core/StringUtils.class +0 -0
- package/target/classes/com/cloudcc/core/TimeUtil.class +0 -0
- package/target/classes/com/cloudcc/core/Tool$1.class +0 -0
- package/target/classes/com/cloudcc/core/Tool.class +0 -0
- package/target/classes/com/cloudcc/core/TriggerInvoker.class +0 -0
- package/target/classes/com/cloudcc/core/TriggerMethod.class +0 -0
- package/target/classes/com/cloudcc/core/TriggerTimeEnum.class +0 -0
- package/target/classes/com/cloudcc/core/UserInfo.class +0 -0
- package/test/jsp.cli.test.js +70 -0
- package/test/object.cli.test.js +9 -1
|
@@ -1,541 +1,1048 @@
|
|
|
1
|
-
# CloudCC
|
|
1
|
+
# CloudCC 自定义类 AI 开发规范
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
7
|
+
适用范围:
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
`classes/<类名>/`、不要复制粘贴整套类文件夹、不要私自篡改 `config.json` 里的
|
|
18
|
-
`id` 或破坏版本字段,否则容易导致发布失败、拉取覆盖异常或与线上一致性不一致。
|
|
9
|
+
- 新建自定义类
|
|
10
|
+
- 修改现有自定义类
|
|
11
|
+
- 为按钮、触发器、定时类、自定义页面、组件提供后端能力
|
|
12
|
+
- 编写通用服务类、工具类、集成类、流程编排类
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
标记之间编写与修改业务代码;其余与平台同步相关的操作一律走命令。
|
|
14
|
+
本文档面向 AI,因此重点强调:
|
|
22
15
|
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
- 什么时候应该使用自定义类
|
|
17
|
+
- AI 写代码时必须遵守哪些硬规则
|
|
18
|
+
- SDK 该怎么用
|
|
19
|
+
- 代码结构应该怎么组织
|
|
20
|
+
- 哪些做法必须避免
|
|
25
21
|
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
+
命令:
|
|
66
117
|
|
|
67
118
|
```bash
|
|
68
|
-
|
|
69
|
-
|
|
119
|
+
cloudcc detail classes <name> <id>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
参数规则(实现口径):
|
|
70
123
|
|
|
71
|
-
|
|
72
|
-
|
|
124
|
+
| 参数 | 必填 | 类型 | 说明 |
|
|
125
|
+
| --- | --- | --- | --- |
|
|
126
|
+
| `name` | 条件必填 | `string` | 传 `name` 时优先查本地;本地不完整时再走线上 |
|
|
127
|
+
| `id` | 条件必填 | `string` | 当 `name` 为空时,必须传 `id` 走线上查询 |
|
|
73
128
|
|
|
74
|
-
|
|
75
|
-
cloudcc publish classes MyClass
|
|
129
|
+
等价理解:`name` 与 `id` 至少传一个,优先使用 `name` 路径。
|
|
76
130
|
|
|
77
|
-
|
|
78
|
-
cloudcc pull classes MyClass
|
|
131
|
+
#### 6) 按 ID 拉取并落地到本地目录
|
|
79
132
|
|
|
80
|
-
|
|
81
|
-
cloudcc pullList classes <线上类ID> <projectPath>
|
|
133
|
+
命令:
|
|
82
134
|
|
|
83
|
-
|
|
84
|
-
cloudcc
|
|
135
|
+
```bash
|
|
136
|
+
cloudcc pullList classes <id> <projectPath>
|
|
85
137
|
```
|
|
86
138
|
|
|
87
|
-
|
|
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
|
|
91
|
-
cloudcc doc classes devguide
|
|
151
|
+
cloudcc delete classes <nameOrId> [projectPath]
|
|
92
152
|
```
|
|
93
153
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
---
|
|
154
|
+
参数规则:
|
|
97
155
|
|
|
98
|
-
|
|
156
|
+
| 参数 | 必填 | 类型 | 说明 |
|
|
157
|
+
| --- | --- | --- | --- |
|
|
158
|
+
| `nameOrId` | 是 | `string` | 可传本地目录名或线上 ID;若本地 `classes/<nameOrId>/config.json` 存在且带 `id`,优先使用其中 `id` 删除 |
|
|
159
|
+
| `projectPath` | 否 | `string` | 项目根目录,默认当前目录 |
|
|
99
160
|
|
|
100
|
-
|
|
161
|
+
#### 8) 文档命令
|
|
101
162
|
|
|
102
|
-
|
|
103
|
-
- 可被以下模块调用:
|
|
104
|
-
- 自定义按钮
|
|
105
|
-
- 触发器
|
|
106
|
-
- 定时类 / 定时作业
|
|
107
|
-
- 自定义组件(通过 CCDK 请求自定义类)
|
|
108
|
-
- 典型用途:
|
|
109
|
-
- 封装复杂业务逻辑
|
|
110
|
-
- 查询 / 写入对象数据
|
|
111
|
-
- 调用自定义设置、发送邮件、记录开发日志
|
|
163
|
+
命令:
|
|
112
164
|
|
|
113
|
-
|
|
165
|
+
```bash
|
|
166
|
+
cloudcc doc classes <introduction|devguide>
|
|
167
|
+
```
|
|
114
168
|
|
|
115
|
-
|
|
169
|
+
参数:
|
|
116
170
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
171
|
+
| 参数 | 必填 | 类型 | 说明 |
|
|
172
|
+
| --- | --- | --- | --- |
|
|
173
|
+
| `introduction|devguide` | 是 | `string` | `introduction` 返回概念文档;`devguide` 返回开发规范(并附 SDK 速查) |
|
|
120
174
|
|
|
121
|
-
|
|
175
|
+
## 5. AI 必须遵守的硬规则
|
|
122
176
|
|
|
123
|
-
|
|
177
|
+
### 5.1 只能通过 cloudcc-cli 管理类目录
|
|
124
178
|
|
|
125
|
-
|
|
179
|
+
AI 不得手工创建或复制 `classes/<类名>/` 目录,不得私自改动 `config.json` 中的 `id` 或版本信息。
|
|
126
180
|
|
|
127
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
194
|
+
```java
|
|
195
|
+
// @SOURCE_CONTENT_START
|
|
196
|
+
// @SOURCE_CONTENT_END
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
之间的内容。
|
|
184
200
|
|
|
185
|
-
|
|
201
|
+
不得破坏:
|
|
186
202
|
|
|
187
|
-
|
|
203
|
+
- 包路径
|
|
204
|
+
- 类目录结构
|
|
205
|
+
- `config.json`
|
|
206
|
+
- SOURCE 标记外的框架代码
|
|
188
207
|
|
|
189
|
-
|
|
190
|
-
> ORM)的核心服务类,支持增删改查、自定义设置等。
|
|
208
|
+
### 5.3 必须保留 UserInfo 上下文
|
|
191
209
|
|
|
192
|
-
|
|
210
|
+
自定义类必须显式接收 `UserInfo`,并通过它构造平台能力对象。
|
|
193
211
|
|
|
194
|
-
|
|
212
|
+
标准写法:
|
|
195
213
|
|
|
196
214
|
```java
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
private UserInfo userInfo;
|
|
215
|
+
private UserInfo userInfo;
|
|
216
|
+
private CCService cs;
|
|
200
217
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
218
|
+
public XxxClass(UserInfo userInfo) {
|
|
219
|
+
this.userInfo = userInfo;
|
|
220
|
+
this.cs = new CCService(userInfo);
|
|
205
221
|
}
|
|
206
222
|
```
|
|
207
223
|
|
|
208
|
-
|
|
224
|
+
### 5.4 触发器、定时类、页面、按钮应做薄入口
|
|
209
225
|
|
|
210
|
-
|
|
226
|
+
AI 不能把大量核心逻辑直接堆在按钮入口、触发器入口、页面接口入口中。
|
|
211
227
|
|
|
212
|
-
|
|
213
|
-
2. 调用 `cs.insert(对象)` 持久化到数据库
|
|
228
|
+
推荐模式:
|
|
214
229
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
230
|
+
- 入口层只做参数接收、基础校验、结果返回
|
|
231
|
+
- 复杂逻辑沉到服务方法或独立服务类
|
|
232
|
+
|
|
233
|
+
### 5.5 必须显式处理异常
|
|
234
|
+
|
|
235
|
+
调用 `CCService` 等平台方法时,AI 不能默认“不会失败”。
|
|
218
236
|
|
|
219
|
-
|
|
220
|
-
opp.put("name", "新建机会");
|
|
221
|
-
opp.put("jine__c", 10000); // 示例:金额字段
|
|
237
|
+
必须:
|
|
222
238
|
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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
|
-
|
|
296
|
+
#### 6.1.2 常量字段
|
|
230
297
|
|
|
231
|
-
|
|
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
|
-
|
|
306
|
+
public String getObjectApiName()
|
|
307
|
+
public String put(String apiName, String value)
|
|
237
308
|
```
|
|
238
309
|
|
|
239
|
-
|
|
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
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
"jine__c desc"
|
|
255
|
-
);
|
|
338
|
+
public String getUserId()
|
|
339
|
+
public String getOrgId()
|
|
340
|
+
public String getRoleId()
|
|
256
341
|
```
|
|
257
342
|
|
|
258
|
-
|
|
343
|
+
参数说明:
|
|
344
|
+
|
|
345
|
+
| 方法 | 入参 | 返回 | 说明 |
|
|
346
|
+
| --- | --- | --- | --- |
|
|
347
|
+
| `getUserId` | 无 | `String` | 当前用户 ID,未设置时返回 `null` |
|
|
348
|
+
| `getOrgId` | 无 | `String` | 当前组织 ID,未设置时返回 `null` |
|
|
349
|
+
| `getRoleId` | 无 | `String` | 当前角色 ID,未设置时返回 `null` |
|
|
259
350
|
|
|
260
|
-
|
|
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
|
-
|
|
368
|
+
参数说明:
|
|
369
|
+
|
|
370
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
371
|
+
| --- | --- | --- | --- |
|
|
372
|
+
| `userInfo` | `UserInfo` | 是 | 当前用户对象 |
|
|
373
|
+
|
|
374
|
+
#### 6.3.2 insert
|
|
273
375
|
|
|
274
376
|
```java
|
|
275
|
-
|
|
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
|
-
|
|
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
|
-
|
|
288
|
-
List<CCObject>
|
|
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
|
-
|
|
433
|
+
public List<CCObject> cqlQuery(String apiName, String cql) throws Exception
|
|
299
434
|
```
|
|
300
435
|
|
|
301
|
-
|
|
436
|
+
参数说明:
|
|
437
|
+
|
|
438
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
439
|
+
| --- | --- | --- | --- |
|
|
440
|
+
| `apiName` | `String` | 是 | 对象 APIName |
|
|
441
|
+
| `cql` | `String` | 否 | SQL/CQL 语句,官方说明支持常用 where 条件 |
|
|
442
|
+
|
|
443
|
+
返回说明:
|
|
444
|
+
|
|
445
|
+
- 返回 `List<CCObject>`
|
|
302
446
|
|
|
303
|
-
|
|
447
|
+
AI 使用规则:
|
|
304
448
|
|
|
305
|
-
|
|
449
|
+
- 聚合、join、复杂筛选时再用 `cqlQuery`
|
|
450
|
+
- 尽量只查需要的字段,不要默认 `select *`
|
|
451
|
+
- 必须收窄 where 条件
|
|
452
|
+
- 复杂 SQL 要保持可读性和可维护性
|
|
306
453
|
|
|
307
|
-
|
|
454
|
+
#### 6.3.5 update
|
|
308
455
|
|
|
309
456
|
```java
|
|
310
|
-
|
|
457
|
+
public ServiceResult update(CCObject ccobj) throws Exception
|
|
311
458
|
```
|
|
312
459
|
|
|
313
|
-
|
|
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
|
-
|
|
480
|
+
public ServiceResult delete(CCObject ccobj) throws Exception
|
|
317
481
|
```
|
|
318
482
|
|
|
319
|
-
|
|
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
|
-
|
|
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
|
-
|
|
518
|
+
#### 6.3.8 自定义设置相关方法
|
|
329
519
|
|
|
330
|
-
|
|
520
|
+
官方页列出了 3 类常用能力。
|
|
331
521
|
|
|
332
522
|
```java
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
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
|
-
|
|
349
|
-
// 初始化日志类
|
|
350
|
-
DevLogger cclogger = new DevLogger(userInfo);
|
|
538
|
+
返回说明:
|
|
351
539
|
|
|
352
|
-
|
|
353
|
-
cclogger.devLogInfo("这是 info 日志");
|
|
540
|
+
- 均返回 `Map`
|
|
354
541
|
|
|
355
|
-
|
|
356
|
-
cclogger.devLogError("这是 error 日志");
|
|
542
|
+
AI 使用规则:
|
|
357
543
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
// 业务逻辑
|
|
361
|
-
} catch (Exception e) {
|
|
362
|
-
cclogger.devLogError("发生异常", e);
|
|
363
|
-
throw e;
|
|
364
|
-
}
|
|
365
|
-
```
|
|
544
|
+
- 外部系统地址、业务开关、阈值、角色映射、模板号优先配置化
|
|
545
|
+
- 不要把可配置项直接写死在代码里
|
|
366
546
|
|
|
367
|
-
### 4
|
|
547
|
+
### 6.4 SendEmail 详细说明
|
|
368
548
|
|
|
369
|
-
|
|
549
|
+
`SendEmail` 用于发送邮件通知。
|
|
370
550
|
|
|
371
|
-
|
|
551
|
+
#### 6.4.1 构造方法
|
|
372
552
|
|
|
373
553
|
```java
|
|
374
|
-
|
|
375
|
-
task.setBeginTime(TimeUtil.getNowDate(userInfo));
|
|
554
|
+
public SendEmail(UserInfo userInfo)
|
|
376
555
|
```
|
|
377
556
|
|
|
378
|
-
|
|
557
|
+
参数说明:
|
|
558
|
+
|
|
559
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
560
|
+
| --- | --- | --- | --- |
|
|
561
|
+
| `userInfo` | `UserInfo` | 是 | 当前用户对象 |
|
|
562
|
+
|
|
563
|
+
#### 6.4.2 sendMailFromSystem
|
|
379
564
|
|
|
380
565
|
```java
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
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
|
-
|
|
389
|
-
|
|
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
|
-
|
|
609
|
+
参数说明:
|
|
393
610
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
611
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
612
|
+
| --- | --- | --- | --- |
|
|
613
|
+
| `emailtemplateid` | `String` | 是 | 邮件模板 ID |
|
|
614
|
+
| `toaddress` | `String[]` | 是 | 收件人数组 |
|
|
615
|
+
| `ccaddress` | `String[]` | 是 | 抄送人数组 |
|
|
616
|
+
| `bcaddress` | `String[]` | 是 | 密送人数组 |
|
|
617
|
+
| `id` | `String` | 是 | 业务记录 ID,通常用于模板数据填充 |
|
|
397
618
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
619
|
+
返回说明:
|
|
620
|
+
|
|
621
|
+
- 返回 `ServiceResult`
|
|
401
622
|
|
|
402
|
-
|
|
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
|
-
|
|
406
|
-
|
|
636
|
+
public DevLogger(UserInfo userInfo)
|
|
637
|
+
```
|
|
407
638
|
|
|
408
|
-
|
|
409
|
-
|
|
639
|
+
参数说明:
|
|
640
|
+
|
|
641
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
642
|
+
| --- | --- | --- | --- |
|
|
643
|
+
| `userInfo` | `UserInfo` | 是 | 当前用户对象 |
|
|
410
644
|
|
|
411
|
-
|
|
412
|
-
|
|
645
|
+
#### 6.5.2 devLogInfo
|
|
646
|
+
|
|
647
|
+
```java
|
|
648
|
+
public void devLogInfo(String info)
|
|
413
649
|
```
|
|
414
650
|
|
|
415
|
-
|
|
651
|
+
参数说明:
|
|
416
652
|
|
|
417
|
-
|
|
653
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
654
|
+
| --- | --- | --- | --- |
|
|
655
|
+
| `info` | `String` | 是 | info 级别日志内容 |
|
|
418
656
|
|
|
419
|
-
|
|
657
|
+
#### 6.5.3 devLogError
|
|
420
658
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
659
|
+
```java
|
|
660
|
+
public void devLogError(String error, Throwable throwable)
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
参数说明:
|
|
426
664
|
|
|
427
|
-
|
|
665
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
666
|
+
| --- | --- | --- | --- |
|
|
667
|
+
| `error` | `String` | 是 | 错误日志内容 |
|
|
668
|
+
| `throwable` | `Throwable` | 否 | 异常对象 |
|
|
428
669
|
|
|
429
|
-
|
|
670
|
+
AI 使用规则:
|
|
430
671
|
|
|
431
|
-
|
|
432
|
-
|
|
672
|
+
- 方法入口要记录
|
|
673
|
+
- 关键业务 ID 要记录
|
|
674
|
+
- 外部调用前后要记录
|
|
675
|
+
- 异常必须记录
|
|
676
|
+
- 不要只写无意义日志
|
|
433
677
|
|
|
434
|
-
|
|
678
|
+
### 6.6 TimeUtil 详细说明
|
|
679
|
+
|
|
680
|
+
`TimeUtil` 用于解决跨时区数据处理问题。官方页明确说明:涉及时间写库与比较时,不应直接依赖本地时区的 `Date` 与 `Calendar`。
|
|
681
|
+
|
|
682
|
+
#### 6.6.1 getNowDate
|
|
435
683
|
|
|
436
684
|
```java
|
|
437
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
- 每个 Map 包含:
|
|
447
|
-
- `argType`:参数类型
|
|
448
|
-
- `argValue`:参数值
|
|
449
|
-
- `arglist` / `map`:方法参数
|
|
450
|
-
- 同样包含 `argType` 和 `argValue`
|
|
690
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
691
|
+
| --- | --- | --- | --- |
|
|
692
|
+
| `userInfo` | `UserInfo` | 是 | 当前用户对象 |
|
|
451
693
|
|
|
452
|
-
|
|
694
|
+
返回说明:
|
|
453
695
|
|
|
454
|
-
|
|
696
|
+
- 返回按用户时区获取的当前时间
|
|
697
|
+
|
|
698
|
+
#### 6.6.2 getUserTimeZone
|
|
455
699
|
|
|
456
700
|
```java
|
|
457
|
-
|
|
701
|
+
TimeUtil.getUserTimeZone(userInfo)
|
|
702
|
+
```
|
|
458
703
|
|
|
459
|
-
|
|
704
|
+
参数说明:
|
|
460
705
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
706
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
707
|
+
| --- | --- | --- | --- |
|
|
708
|
+
| `userInfo` | `UserInfo` | 是 | 当前用户对象 |
|
|
464
709
|
|
|
465
|
-
|
|
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
|
-
|
|
717
|
+
TimeUtil.getSimpleDateFormat(format, userInfo)
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
参数说明:
|
|
475
721
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
722
|
+
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
|
723
|
+
| --- | --- | --- | --- | --- |
|
|
724
|
+
| `format` | `String` | 是 | `yyyy-MM-dd` | 格式化模板 |
|
|
725
|
+
| `userInfo` | `UserInfo` | 是 | - | 当前用户对象 |
|
|
480
726
|
|
|
481
|
-
|
|
482
|
-
m.put("name", "Alex");
|
|
727
|
+
返回说明:
|
|
483
728
|
|
|
484
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
|
|
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、把复杂逻辑放在服务端可治理的位置,并让代码具备复用性、可追踪性和可发布性。
|