neo-cmp-cli 1.13.13 → 1.13.16
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 +1 -1
- package/dist/neo/neoEnvManager.js +1 -1
- package/dist/neo/neoLogin.js +1 -1
- package/dist/neo/neoRequire.js +1 -1
- package/dist/package.json.js +1 -1
- package/docs//351/200/232/347/224/250/344/273/243/347/220/206/346/216/245/345/217/243/forward.zip +0 -0
- package/docs//351/200/232/347/224/250/344/273/243/347/220/206/346/216/245/345/217/243//350/207/252/345/256/232/344/271/211API:/351/200/232/347/224/250/344/273/243/347/220/206/346/216/245/345/217/243/344/275/277/347/224/250/350/257/264/346/230/216.md +13 -0
- package/package.json +1 -1
- package/template/antd-custom-cmp-template/package.json +1 -1
- package/template/asset-manage-template/package.json +1 -1
- package/template/echarts-custom-cmp-template/package.json +1 -1
- package/template/empty-custom-cmp-template/package.json +1 -1
- package/template/map-custom-cmp-template/package.json +1 -1
- package/template/neo-bi-cmps/package.json +1 -1
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/index.tsx +17 -10
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/model.ts +47 -6
- package/template/neo-bi-cmps/src/components/filterBar__c/index.tsx +21 -7
- package/template/neo-bi-cmps/src/components/forecastChart__c/index.tsx +6 -9
- package/template/neo-bi-cmps/src/components/forecastChart__c/model.ts +2 -1
- package/template/neo-bi-cmps/src/components/forecastGrid__c/model.ts +32 -4
- package/template/neo-bi-cmps/src/components/gapCloser__c/index.tsx +7 -2
- package/template/neo-bi-cmps/src/components/gapCloser__c/model.ts +6 -3
- package/template/neo-bi-cmps/src/components/kpiCards__c/model.ts +18 -3
- package/template/neo-bi-cmps/src/components/oppList__c/index.tsx +70 -13
- package/template/neo-bi-cmps/src/components/oppList__c/model.ts +50 -4
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/index.tsx +3 -1
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/model.ts +28 -4
- package/template/neo-bi-cmps/src/components/stageSwitch__c/index.tsx +21 -6
- package/template/neo-bi-cmps/src/components/stageSwitch__c/model.ts +60 -5
- package/template/neo-bi-cmps/src/components/stageTimeChart__c/model.ts +26 -4
- package/template/neo-custom-cmp-template/package.json +1 -1
- package/template/neo-h5-cmps/package.json +1 -1
- package/template/neo-order-cmps/package.json +1 -1
- package/template/neo-web-entity-grid/package.json +1 -1
- package/template/neo-web-form/package.json +1 -1
- package/template/neo-web-form/src/components/batchAddTable__c/index.tsx +17 -17
- package/template/react-custom-cmp-template/package.json +1 -1
- package/template/react-ts-custom-cmp-template/package.json +1 -1
- package/template/vue2-custom-cmp-template/package.json +1 -1
- package/template/neo-bi-cmps/docs/gartner-pipeline-apis.md +0 -279
- package/template/neo-bi-cmps/docs/gartner-pipeline-prd.md +0 -389
- package/template/neo-bi-cmps/docs/neo-backend-dev/SKILL.md +0 -188
- package/template/neo-bi-cmps/docs/neo-backend-dev/references/01-Trigger/345/274/200/345/217/221.md +0 -183
- package/template/neo-bi-cmps/docs/neo-backend-dev/references/02-/350/207/252/345/256/232/344/271/211API/345/274/200/345/217/221.md +0 -196
- package/template/neo-bi-cmps/docs/neo-backend-dev/references/03-SDK/345/267/245/345/205/267/347/261/273/346/216/245/345/217/243.md +0 -346
- package/template/neo-bi-cmps/docs/neo-backend-dev/references/04-/350/256/241/345/210/222/344/275/234/344/270/232/345/274/200/345/217/221.md +0 -188
- package/template/neo-bi-cmps/docs/neo-backend-dev/references/05-/351/241/265/351/235/242/345/274/200/345/217/221.md +0 -293
- package/template/neo-bi-cmps/docs/neo-backend-dev/references/06-/346/265/201/347/250/213/346/211/251/345/261/225/345/274/200/345/217/221.md +0 -175
- package/template/neo-bi-cmps/docs/neo-backend-dev/references/PaaS/345/271/263/345/217/260/345/274/200/345/217/221/346/211/213/345/206/214/350/247/243/350/257/273.md +0 -313
- package/template/neo-bi-cmps/docs/neo-backend-dev/references/auth-config.md +0 -77
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/deploy_server_script.py +0 -118
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/download_server_script.py +0 -74
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/gen_entity_desc.py +0 -69
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/gen_entitylist.py +0 -87
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/query_crm.py +0 -65
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/uninstall_server_script.py +0 -48
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/update_model_jar.py +0 -49
- package/template/neo-bi-cmps/docs/neo-frontend-dev/SKILL.md +0 -138
- package/template/neo-bi-cmps/docs/neo-frontend-dev/references/auth-config.md +0 -77
- package/template/neo-bi-cmps/docs/neo-frontend-dev/references/component-dev.md +0 -205
- package/template/neo-bi-cmps/docs/neo-frontend-dev/references/entityTable-example.md +0 -167
- package/template/neo-bi-cmps/docs/neo-frontend-dev/references/templates.md +0 -38
- package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/gen_entity_desc.py +0 -69
- package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/gen_entitylist.py +0 -87
- package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/query_crm.py +0 -65
- package/template/neo-bi-cmps/docs/prototype-pipeline-forecasting.html +0 -2453
- package/template/neo-bi-cmps/docs//350/264/246/345/217/267/347/233/270/345/205/263/344/277/241/346/201/257.md +0 -10
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
# PaaS 平台开发手册解读
|
|
2
|
-
|
|
3
|
-
## 一、手册总览
|
|
4
|
-
|
|
5
|
-
本手册是销售易(Xiaoshouyi)PaaS 平台的开发指南,面向需要在销售易 CRM 系统上进行二次开发的开发者。手册共 5 大章节、约 500 页,涵盖业务逻辑开发、页面开发、功能限制和常见问题。
|
|
6
|
-
|
|
7
|
-
### 手册结构
|
|
8
|
-
|
|
9
|
-
| 章节 | 内容 | 页码范围 |
|
|
10
|
-
|------|------|----------|
|
|
11
|
-
| 1. 开发平台简介 | 平台能力概述 | 1 |
|
|
12
|
-
| 2. 业务逻辑开发 | Trigger、流程扩展、SDK 接口、计划作业、自定义 API 等 | 2-432 |
|
|
13
|
-
| 3. 页面开发 | 页面类型、第三方页面集成、自定义页面、JS SDK 2.0 | 433-478 |
|
|
14
|
-
| 4. 开发平台功能限制 | 各版本(专业/企业/旗舰)的资源限制 | 479-483 |
|
|
15
|
-
| 5. 常见问题 | Trigger、计划作业、SDK、页面、日志等 FAQ | 484-497 |
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## 二、开发平台能力
|
|
20
|
-
|
|
21
|
-
销售易 PaaS 平台提供四大开发能力:
|
|
22
|
-
|
|
23
|
-
1. **业务逻辑开发** — 使用 Java 编写自定义业务逻辑代码,集成到 CRM 系统
|
|
24
|
-
2. **页面开发** — 集成第三方页面或自定义开发页面
|
|
25
|
-
3. **开放 API(Open API)** — 系统提供的标准 API 接口
|
|
26
|
-
4. **自定义 API** — 根据业务需求封装的自定义接口
|
|
27
|
-
|
|
28
|
-
### 前提条件
|
|
29
|
-
|
|
30
|
-
- 销售易系统必须是**旗舰版**
|
|
31
|
-
- 开发人员需具备 **Java 开发能力**
|
|
32
|
-
- 建议基于 **JDK 1.8** 运行
|
|
33
|
-
- 需获取 **SDK JAR 包**(sdk.jar)并添加到 Build Path
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## 三、业务逻辑开发(核心)
|
|
38
|
-
|
|
39
|
-
### 3.1 业务逻辑代码概念
|
|
40
|
-
|
|
41
|
-
业务逻辑代码是运行在多租户共享运行时环境上的 Java 可编程脚本,完全托管在销售易系统上,无需维护基础设施。
|
|
42
|
-
|
|
43
|
-
### 3.2 业务逻辑扩展点(12 种)
|
|
44
|
-
|
|
45
|
-
| 序号 | 扩展点 | 说明 |
|
|
46
|
-
|------|--------|------|
|
|
47
|
-
| 1 | **Trigger(触发器)** | 数据操作前/后增加自定义逻辑 |
|
|
48
|
-
| 2 | **新版审批流扩展** | 审批流程各节点的自定义逻辑 |
|
|
49
|
-
| 3 | **自动流扩展** | 自动流程中的自定义逻辑 |
|
|
50
|
-
| 4 | **工作流扩展** | 工作流各节点的自定义逻辑 |
|
|
51
|
-
| 5 | **可视化流扩展** | 可视化流程阶段节点的自定义逻辑 |
|
|
52
|
-
| 6 | **触发规则扩展** | 触发规则中的自定义逻辑 |
|
|
53
|
-
| 7 | **定时调度扩展** | 定时调度任务的自定义逻辑 |
|
|
54
|
-
| 8 | **用户扩展** | 用户操作(分配职能/角色/授权)的扩展 |
|
|
55
|
-
| 9 | **计划作业** | 定时执行的批量任务 |
|
|
56
|
-
| 10 | **Bulk API 回调** | 批量 API 完成后的回调处理 |
|
|
57
|
-
| 11 | **自定义 API** | 封装自定义的 REST API |
|
|
58
|
-
| 12 | **新公海业务扩展** | 公海业务操作的前置/后置扩展 |
|
|
59
|
-
|
|
60
|
-
### 3.3 Trigger 开发详解
|
|
61
|
-
|
|
62
|
-
#### 接口定义
|
|
63
|
-
|
|
64
|
-
```java
|
|
65
|
-
public class ClassName implements Trigger {
|
|
66
|
-
@Override
|
|
67
|
-
public TriggerResponse execute(TriggerRequest request) throws ScriptBusinessException {
|
|
68
|
-
// 业务逻辑
|
|
69
|
-
return new TriggerResponse(Boolean success, String message, List dataList);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
#### TriggerRequest 方法
|
|
75
|
-
|
|
76
|
-
| 方法 | 返回类型 | 说明 |
|
|
77
|
-
|------|----------|------|
|
|
78
|
-
| getPartialSuccess() | Boolean | 是否为部分成功 |
|
|
79
|
-
| getPosition() | String | 获取指定位置(before/after) |
|
|
80
|
-
| getOperate() | String | 获取指定操作(add/update/delete 等) |
|
|
81
|
-
| getDataList() | List\<XObject\> | 获取数据列表 |
|
|
82
|
-
| getUserId() | Long | 获取当前用户 ID |
|
|
83
|
-
| getTriggerContext() | TriggerContext | 获取上下文(用于 before/after 间传参) |
|
|
84
|
-
|
|
85
|
-
#### TriggerResponse 构造
|
|
86
|
-
|
|
87
|
-
| 参数 | 类型 | 说明 |
|
|
88
|
-
|------|------|------|
|
|
89
|
-
| success | Boolean | 是否成功 |
|
|
90
|
-
| message | String | 操作信息 |
|
|
91
|
-
| dataList | List\<DataResult\> | 数据结果(数量必须与请求数据量一致) |
|
|
92
|
-
|
|
93
|
-
#### 配置文件 scriptTrigger.xml
|
|
94
|
-
|
|
95
|
-
```xml
|
|
96
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
97
|
-
<configs>
|
|
98
|
-
<config>
|
|
99
|
-
<trigger>
|
|
100
|
-
<object>customEntity1__c</object> <!-- 对象 API Key -->
|
|
101
|
-
<operate>add</operate> <!-- 操作:add/delete/update/transfer/lock/unlock/recover -->
|
|
102
|
-
<position>before</position> <!-- 时机:before/after -->
|
|
103
|
-
<order>1</order> <!-- 执行顺序(升序) -->
|
|
104
|
-
<class>other.xsy.doc.trigger.Demo</class> <!-- 完整类名 -->
|
|
105
|
-
</trigger>
|
|
106
|
-
</config>
|
|
107
|
-
</configs>
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
#### 注意事项
|
|
111
|
-
|
|
112
|
-
- Java 类名**不能以 "test" 结尾**
|
|
113
|
-
- Java 文件必须以 **UTF-8 无 BOM** 格式编码
|
|
114
|
-
- Package 名称必须**三级且以 other 开始**(如 `other.xsy.demo`)
|
|
115
|
-
- Package 名称中**不能包含 ".sh"**
|
|
116
|
-
|
|
117
|
-
### 3.4 自定义 API 开发
|
|
118
|
-
|
|
119
|
-
使用注解 `@RestApi` 和 `@RestMapping` 定义自定义 API:
|
|
120
|
-
|
|
121
|
-
```java
|
|
122
|
-
@RestApi(baseUrl = "/custom")
|
|
123
|
-
public class CustomApiDemo {
|
|
124
|
-
@RestMapping(value = "/getObjectList", method = RequestMethod.GET)
|
|
125
|
-
public String handleGet() throws ApiEntityServiceException {
|
|
126
|
-
// 业务逻辑
|
|
127
|
-
return result;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### 3.5 计划作业
|
|
133
|
-
|
|
134
|
-
计划作业用于执行定时批量任务,配置步骤:
|
|
135
|
-
1. 编写实现 `ScheduleJob` 接口的 Java 类
|
|
136
|
-
2. 在 scriptTrigger.xml 中配置
|
|
137
|
-
3. 上传代码包并启用
|
|
138
|
-
4. 在系统后台配置计划作业的执行时间和频率
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
## 四、SDK 接口说明
|
|
143
|
-
|
|
144
|
-
### 4.1 开发资源获取
|
|
145
|
-
|
|
146
|
-
| 资源 | 说明 |
|
|
147
|
-
|------|------|
|
|
148
|
-
| **sdk.jar** | 开发 SDK,包含所有接口定义 |
|
|
149
|
-
| **model.jar** | 数据模型 JAR,包含租户自定义对象的模型类 |
|
|
150
|
-
|
|
151
|
-
### 4.2 工具类接口一览
|
|
152
|
-
|
|
153
|
-
| 接口 | 类名 | 说明 |
|
|
154
|
-
|------|------|------|
|
|
155
|
-
| 日志接口 | Logger | 日志输出 |
|
|
156
|
-
| 数据操作接口 | XObjectService | 数据 CRUD 操作 |
|
|
157
|
-
| 内部 HTTP 请求 | RkhdHttpClient | 向销售易系统发起 HTTP 请求 |
|
|
158
|
-
| 外部 HTTP 请求 | CommonHttpClient | 向外部系统发起 HTTP 请求 |
|
|
159
|
-
| XOQL 查询 | XoqlService | 快速查询接口 |
|
|
160
|
-
| 批量数据处理 | BatchJobService | 批量数据处理 |
|
|
161
|
-
| 异步脚本 | FutureTask | 异步任务执行 |
|
|
162
|
-
| 租户级缓存 | CacheService | 缓存读写 |
|
|
163
|
-
| 环境信息 | ScriptRuntimeContext | 获取运行时环境信息 |
|
|
164
|
-
| Webservice 调用 | CommonHttpSoapClient | SOAP 接口调用 |
|
|
165
|
-
| 数据加解密 | SymmetricCryptoUtil | AES 加解密 |
|
|
166
|
-
| 自定义配置 | CustomConfigService | 读取自定义配置项 |
|
|
167
|
-
| 发送邮件 | EmailService | 邮件发送 |
|
|
168
|
-
| 分布式锁 | LockService | 分布式锁操作 |
|
|
169
|
-
|
|
170
|
-
### 4.3 XObjectService 核心方法
|
|
171
|
-
|
|
172
|
-
数据操作接口,用于对销售易系统中的对象数据进行 CRUD 操作:
|
|
173
|
-
|
|
174
|
-
- `query(String sql)` — 查询数据
|
|
175
|
-
- `insert(XObject xObject)` — 插入数据
|
|
176
|
-
- `update(XObject xObject)` — 更新数据
|
|
177
|
-
- `delete(XObject xObject)` — 删除数据
|
|
178
|
-
|
|
179
|
-
### 4.4 CommonHttpClient 核心方法
|
|
180
|
-
|
|
181
|
-
向外部系统发起 HTTP 请求:
|
|
182
|
-
|
|
183
|
-
```java
|
|
184
|
-
CommonHttpClient commonHttpClient = CommonHttpClient.instance();
|
|
185
|
-
CommonData commonData = CommonData.newBuilder()
|
|
186
|
-
.callType("POST")
|
|
187
|
-
.header("Authorization", "token")
|
|
188
|
-
.header("Content-Type", "application/json")
|
|
189
|
-
.callString("https://external-api.com/endpoint")
|
|
190
|
-
.body(jsonString)
|
|
191
|
-
.build();
|
|
192
|
-
HttpResult result = commonHttpClient.execute(commonData);
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### 4.5 RkhdHttpClient 核心方法
|
|
196
|
-
|
|
197
|
-
向销售易系统内部发起 HTTP 请求:
|
|
198
|
-
|
|
199
|
-
```java
|
|
200
|
-
RkhdHttpClient rkhdHttpClient = RkhdHttpClient.instance();
|
|
201
|
-
RkhdHttpData rkhdHttpData = RkhdHttpData.newBuilder()
|
|
202
|
-
.callString("/rest/data/v2/query")
|
|
203
|
-
.callType("POST")
|
|
204
|
-
.build();
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
## 五、页面开发
|
|
210
|
-
|
|
211
|
-
### 5.1 页面类型
|
|
212
|
-
|
|
213
|
-
- **第三方应用页面** — 集成已有的外部页面
|
|
214
|
-
- **自定义开发页面** — 使用 HTML/JS/CSS 开发的自定义页面
|
|
215
|
-
|
|
216
|
-
### 5.2 JS SDK 2.0 API
|
|
217
|
-
|
|
218
|
-
| API | 说明 |
|
|
219
|
-
|-----|------|
|
|
220
|
-
| `ctx.api.openCreateForm()` | 打开标准新建表单页 |
|
|
221
|
-
| `ctx.api.openEditForm()` | 打开标准编辑表单页 |
|
|
222
|
-
| `ctx.api.openCopyForm()` | 打开标准复制表单页 |
|
|
223
|
-
| `ctx.api.openListPage()` | 打开标准对象列表页 |
|
|
224
|
-
| `ctx.api.openDetailPage()` | 打开标准数据详情页 |
|
|
225
|
-
| `ctx.ui.refreshPage()` | 刷新当前列表或详情页 |
|
|
226
|
-
| `ctx.ui.closeIframe()` | 关闭当前弹窗页面 |
|
|
227
|
-
| `ctx.ui.goBack()` | 回退页面(移动端) |
|
|
228
|
-
|
|
229
|
-
### 5.3 页面代码请求(NeoLappSdk.js)
|
|
230
|
-
|
|
231
|
-
```html
|
|
232
|
-
<script type="text/javascript" src="/js/jssdk/neoLapp.js"></script>
|
|
233
|
-
<script>
|
|
234
|
-
lapp.connection.invoke({
|
|
235
|
-
url: url,
|
|
236
|
-
method: method, // GET/POST/PUT/DELETE
|
|
237
|
-
contentType: contentType, // application/json 等
|
|
238
|
-
headers: headers,
|
|
239
|
-
params: params,
|
|
240
|
-
data: data
|
|
241
|
-
}).then(
|
|
242
|
-
res => { console.log(res); },
|
|
243
|
-
err => { console.log(err); }
|
|
244
|
-
)
|
|
245
|
-
</script>
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### 5.4 页面入口地址格式
|
|
249
|
-
|
|
250
|
-
```
|
|
251
|
-
https://lapp-{环境}.xiaoshouyi.com/service/lapp/page/{页面代码 API 名称}
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
## 六、开发平台功能限制(关键数据)
|
|
257
|
-
|
|
258
|
-
### 业务逻辑代码限制
|
|
259
|
-
|
|
260
|
-
| 限制项 | 专业版 | 企业版 | 旗舰版 |
|
|
261
|
-
|--------|--------|--------|--------|
|
|
262
|
-
| 最大 Class 个数 | 5/租户 | 10/租户 | 500/租户 |
|
|
263
|
-
| 单个 Class 最大字节数 | 409,600 | 409,600 | 409,600 |
|
|
264
|
-
| commonHttpClient 调用 API 最大次数 | 50 次/脚本 | 50 次/脚本 | 50 次/脚本 |
|
|
265
|
-
| 同步脚本最大运行时长 | 15 秒 | 15 秒 | 15 秒 |
|
|
266
|
-
| 异步脚本最大运行时长 | 90 秒 | 90 秒 | 90 秒 |
|
|
267
|
-
| 脚本最大内存使用量 | 512 MB | 512 MB | 512 MB |
|
|
268
|
-
| 单次 DML 最大数据量 | 10,000 条 | 10,000 条 | 10,000 条 |
|
|
269
|
-
| 单次 query 最大数据量 | 10,000 条 | 10,000 条 | 10,000 条 |
|
|
270
|
-
|
|
271
|
-
### 计划作业限制
|
|
272
|
-
|
|
273
|
-
| 限制项 | 值 |
|
|
274
|
-
|--------|-----|
|
|
275
|
-
| 最多同时启用 | 30 个/租户 |
|
|
276
|
-
| 自定义类型最多启用 | 5 个/租户 |
|
|
277
|
-
| 最小执行间隔 | 15 分钟 |
|
|
278
|
-
|
|
279
|
-
### CacheService 限制
|
|
280
|
-
|
|
281
|
-
| 限制项 | 值 |
|
|
282
|
-
|--------|-----|
|
|
283
|
-
| 最大缓存大小 | 200 KB |
|
|
284
|
-
| 最大缓存 key 数量 | 64 个 |
|
|
285
|
-
| 同步脚本调用次数 | 50 次 |
|
|
286
|
-
| 异步脚本调用次数 | 100 次 |
|
|
287
|
-
|
|
288
|
-
### API 调用限制
|
|
289
|
-
|
|
290
|
-
| 限制项 | 专业版 | 企业版 | 旗舰版 |
|
|
291
|
-
|--------|--------|--------|--------|
|
|
292
|
-
| 外部并发(用户级) | 15 次/秒 | 15 次/秒 | 15 次/秒 |
|
|
293
|
-
| 外部并发(租户级) | 50 次/秒 | 50 次/秒 | 50 次/秒 |
|
|
294
|
-
| 每日 API 调用次数 | License×250 | License×500 | License×1000 |
|
|
295
|
-
| 查询接口单次最大返回 | 300 条 | 300 条 | 300 条 |
|
|
296
|
-
|
|
297
|
-
---
|
|
298
|
-
|
|
299
|
-
## 七、代码包开发流程总结
|
|
300
|
-
|
|
301
|
-
```
|
|
302
|
-
1. 创建代码包(系统后台 → 开发 → 业务逻辑代码 → 代码包 → 新建代码包)
|
|
303
|
-
↓
|
|
304
|
-
2. 编写 Java 业务逻辑代码(实现 Trigger/ScheduleJob/BulkApiCallBack 等接口)
|
|
305
|
-
↓
|
|
306
|
-
3. 编写配置文件 scriptTrigger.xml(定义扩展点绑定关系)
|
|
307
|
-
↓
|
|
308
|
-
4. 打包上传(将 src/other 文件夹 + scriptTrigger.xml 压缩为 ZIP)
|
|
309
|
-
↓
|
|
310
|
-
5. 启用代码包(安装状态列点击启用)
|
|
311
|
-
↓
|
|
312
|
-
6. 测试验证
|
|
313
|
-
```
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# 授权配置
|
|
2
|
-
|
|
3
|
-
与 NeoCRM 平台交互(push/pull/delete)前需配置授权。
|
|
4
|
-
|
|
5
|
-
## 方式一:OAuth2 登录授权(推荐)
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
neo login
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
执行流程:
|
|
12
|
-
1. 自动打开浏览器访问授权页面(如未自动打开,命令行会输出 URL,手动复制到浏览器)
|
|
13
|
-
2. 输入 NeoCRM 账号密码并选择租户(此处选择的租户即为后续 push/pull/delete 的目标租户)
|
|
14
|
-
3. 授权成功后自动跳转回本地
|
|
15
|
-
4. Token 自动保存到项目目录的 `.neo-cli/token.json`
|
|
16
|
-
|
|
17
|
-
> 此步骤需要用户在浏览器中手动完成,AI 无法自动执行。
|
|
18
|
-
|
|
19
|
-
Token 有效期:
|
|
20
|
-
- access_token:2 小时(过期前 5 分钟自动刷新)
|
|
21
|
-
- refresh_token:30 天(过期需重新 `neo login`)
|
|
22
|
-
|
|
23
|
-
登出:`neo logout`(清除本地 token)
|
|
24
|
-
|
|
25
|
-
### 自定义环境配置
|
|
26
|
-
|
|
27
|
-
在 `neo.config.js` 中配置(`neo login` 选择「自定义环境」时生效):
|
|
28
|
-
|
|
29
|
-
```javascript
|
|
30
|
-
module.exports = {
|
|
31
|
-
neoConfig: {
|
|
32
|
-
neoBaseURL: 'https://crm-xx.xiaoshouyi.com',
|
|
33
|
-
loginURL: 'https://login-xx.xiaoshouyi.com/auc/oauth2/auth',
|
|
34
|
-
tokenURL: 'https://login-xx.xiaoshouyi.com/auc/oauth2/token',
|
|
35
|
-
},
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
> 注意:Token 接口字段名在不同 CLI 版本中可能为 `tokenAPI` 或 `tokenURL`,两者等价。`neo init` 生成的模板中使用 `tokenURL`。
|
|
40
|
-
|
|
41
|
-
## 方式二:密码授权
|
|
42
|
-
|
|
43
|
-
在 `neo.config.js` 中配置:
|
|
44
|
-
|
|
45
|
-
```javascript
|
|
46
|
-
module.exports = {
|
|
47
|
-
neoConfig: {
|
|
48
|
-
neoBaseURL: 'https://crm-cd.xiaoshouyi.com',
|
|
49
|
-
tokenAPI: 'https://login-cd.xiaoshouyi.com/auc/oauth2/token',
|
|
50
|
-
authType: 'password',
|
|
51
|
-
auth: {
|
|
52
|
-
client_id: '<从连接器客户端信息获取>',
|
|
53
|
-
client_secret: '<从连接器客户端信息获取>',
|
|
54
|
-
username: '<销售易系统用户名>',
|
|
55
|
-
password: '<账号密码 + 8位安全令牌,直接拼接>'
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
获取方式:
|
|
62
|
-
- client_id / client_secret:通过 [销售易文档中心](https://doc.xiaoshouyi.com) 创建连接器获取
|
|
63
|
-
- 安全令牌:销售易文档中心 → OAuth安全认证 → 密码模式 → 获取令牌
|
|
64
|
-
|
|
65
|
-
## 两种模式对比
|
|
66
|
-
|
|
67
|
-
| 特性 | OAuth2 | 密码模式 |
|
|
68
|
-
|------|--------|---------|
|
|
69
|
-
| 安全性 | 高(无需存储密码) | 较低 |
|
|
70
|
-
| Token 有效期 | 2 小时(自动刷新) | 永不过期 |
|
|
71
|
-
| 推荐程度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
|
|
72
|
-
|
|
73
|
-
## 常见问题
|
|
74
|
-
|
|
75
|
-
- 浏览器无法自动打开:命令行会输出授权 URL,手动复制到浏览器
|
|
76
|
-
- Token 刷新失败:refresh_token 过期(30 天),重新 `neo login`
|
|
77
|
-
- 授权后未跳回 redirect_uri:可能被浏览器插件(如 Neo UI Extension)影响,关闭后重试
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
打包并上传后端代码包到 NeoCRM 平台。
|
|
4
|
-
用法: python3 deploy_server_script.py <项目目录> <代码包简称> <Java包名>
|
|
5
|
-
示例: python3 deploy_server_script.py NEOTrail 客户管理 other.xsy.account
|
|
6
|
-
"""
|
|
7
|
-
import zipfile, os, json, urllib.request, urllib.error, io, sys
|
|
8
|
-
|
|
9
|
-
if len(sys.argv) < 4:
|
|
10
|
-
print("用法: python3 deploy_server_script.py <项目目录> <代码包简称> <Java包名>")
|
|
11
|
-
print("示例: python3 deploy_server_script.py NEOTrail 客户管理 other.xsy.account")
|
|
12
|
-
sys.exit(1)
|
|
13
|
-
|
|
14
|
-
project_dir = sys.argv[1]
|
|
15
|
-
display_name = sys.argv[2]
|
|
16
|
-
java_package = sys.argv[3]
|
|
17
|
-
package_dir = os.path.join(project_dir, 'src/server-scripts', display_name)
|
|
18
|
-
zip_path = os.path.join(project_dir, display_name + '.zip')
|
|
19
|
-
token_path = os.path.join(project_dir, '.neo-cli/token.json')
|
|
20
|
-
|
|
21
|
-
if not os.path.exists(package_dir):
|
|
22
|
-
print("错误: 代码包目录不存在: %s" % package_dir)
|
|
23
|
-
sys.exit(1)
|
|
24
|
-
|
|
25
|
-
if not os.path.exists(token_path):
|
|
26
|
-
print("错误: 未找到 %s,请先执行 neo login" % token_path)
|
|
27
|
-
sys.exit(1)
|
|
28
|
-
|
|
29
|
-
token = json.load(open(token_path))
|
|
30
|
-
access_token = token['access_token']
|
|
31
|
-
base_url = token['instance_uri'].rstrip('/')
|
|
32
|
-
|
|
33
|
-
# 1. 打包 ZIP
|
|
34
|
-
print("打包 %s ..." % package_dir)
|
|
35
|
-
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
|
|
36
|
-
for root, dirs, files in os.walk(package_dir):
|
|
37
|
-
for f in files:
|
|
38
|
-
full_path = os.path.join(root, f)
|
|
39
|
-
arc_name = os.path.relpath(full_path, package_dir)
|
|
40
|
-
zf.write(full_path, arc_name)
|
|
41
|
-
print(" 添加: %s" % arc_name)
|
|
42
|
-
print("ZIP: %s (%d bytes)" % (zip_path, os.path.getsize(zip_path)))
|
|
43
|
-
|
|
44
|
-
def api_call(method, path, data=None, headers=None):
|
|
45
|
-
url = base_url + path
|
|
46
|
-
req = urllib.request.Request(url, data=data, method=method)
|
|
47
|
-
req.add_header('Authorization', access_token)
|
|
48
|
-
if headers:
|
|
49
|
-
for k, v in headers.items():
|
|
50
|
-
req.add_header(k, v)
|
|
51
|
-
try:
|
|
52
|
-
resp = urllib.request.urlopen(req)
|
|
53
|
-
return json.loads(resp.read().decode('utf-8'))
|
|
54
|
-
except urllib.error.HTTPError as e:
|
|
55
|
-
body = e.read().decode('utf-8')[:500]
|
|
56
|
-
try:
|
|
57
|
-
return json.loads(body)
|
|
58
|
-
except:
|
|
59
|
-
return {"code": str(e.code), "msg": body}
|
|
60
|
-
|
|
61
|
-
# 2. 检查代码包是否存在,不存在则创建
|
|
62
|
-
print("\n检查代码包 ...")
|
|
63
|
-
r = api_call('GET', '/rest/metadata/v2.0/scripts/packages?packageName=' + urllib.request.quote(java_package))
|
|
64
|
-
exists = r.get('code') == '200' and r.get('data', {}).get('records')
|
|
65
|
-
|
|
66
|
-
if not exists:
|
|
67
|
-
print("创建代码包 %s ..." % java_package)
|
|
68
|
-
body = json.dumps({"packageName": java_package, "name": display_name, "content": display_name}).encode()
|
|
69
|
-
r2 = api_call('POST', '/rest/metadata/v2.0/scripts/packages', data=body, headers={'Content-Type': 'application/json'})
|
|
70
|
-
if r2.get('code') != '200':
|
|
71
|
-
print("创建失败: %s" % r2.get('msg', ''))
|
|
72
|
-
os.remove(zip_path)
|
|
73
|
-
sys.exit(1)
|
|
74
|
-
print("创建成功")
|
|
75
|
-
else:
|
|
76
|
-
print("代码包已存在")
|
|
77
|
-
|
|
78
|
-
# 3. 上传文件
|
|
79
|
-
print("\n上传文件 ...")
|
|
80
|
-
boundary = '----PythonBoundary7MA4YWxk'
|
|
81
|
-
body = io.BytesIO()
|
|
82
|
-
body.write(('--%s\r\n' % boundary).encode())
|
|
83
|
-
body.write(('Content-Disposition: form-data; name="file"; filename="%s.zip"\r\n' % display_name).encode())
|
|
84
|
-
body.write(b'Content-Type: application/zip\r\n\r\n')
|
|
85
|
-
with open(zip_path, 'rb') as f:
|
|
86
|
-
body.write(f.read())
|
|
87
|
-
body.write(('\r\n--%s--\r\n' % boundary).encode())
|
|
88
|
-
|
|
89
|
-
upload_url = base_url + '/rest/metadata/v2.0/scripts/packages/files?packageName=' + urllib.request.quote(java_package)
|
|
90
|
-
req = urllib.request.Request(upload_url, data=body.getvalue(), method='POST')
|
|
91
|
-
req.add_header('Authorization', 'Bearer ' + access_token)
|
|
92
|
-
req.add_header('Content-Type', 'multipart/form-data; boundary=%s' % boundary)
|
|
93
|
-
|
|
94
|
-
try:
|
|
95
|
-
resp = urllib.request.urlopen(req)
|
|
96
|
-
result = json.loads(resp.read().decode('utf-8'))
|
|
97
|
-
except urllib.error.HTTPError as e:
|
|
98
|
-
result = {"code": str(e.code), "msg": e.read().decode('utf-8')[:500]}
|
|
99
|
-
|
|
100
|
-
if result.get('code') != '200':
|
|
101
|
-
print("上传失败: %s" % result.get('msg', ''))
|
|
102
|
-
os.remove(zip_path)
|
|
103
|
-
sys.exit(1)
|
|
104
|
-
print("上传成功")
|
|
105
|
-
|
|
106
|
-
# 4. 安装代码包
|
|
107
|
-
print("\n安装代码包 ...")
|
|
108
|
-
install_body = json.dumps({"packageName": java_package}).encode()
|
|
109
|
-
r4 = api_call('POST', '/rest/metadata/v2.0/scripts/packages/installations', data=install_body, headers={'Content-Type': 'application/json'})
|
|
110
|
-
if r4.get('code') == '200':
|
|
111
|
-
print("安装成功")
|
|
112
|
-
else:
|
|
113
|
-
print("安装失败: %s" % r4.get('msg', ''))
|
|
114
|
-
|
|
115
|
-
# 5. 清理临时 ZIP
|
|
116
|
-
os.remove(zip_path)
|
|
117
|
-
print("\n已清理临时文件: %s" % zip_path)
|
|
118
|
-
print("\n完成!")
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
下载服务端代码包到本地 src/server-scripts/ 目录。
|
|
4
|
-
如果本地已存在同名目录,会清空后重新下载(更新本地包)。
|
|
5
|
-
用法: python3 download_server_script.py <项目目录> <Java包名> [代码包简称]
|
|
6
|
-
示例: python3 download_server_script.py NEOTrail other.xsy.account 客户管理
|
|
7
|
-
"""
|
|
8
|
-
import os, json, zipfile, shutil, urllib.request, urllib.error, sys
|
|
9
|
-
|
|
10
|
-
if len(sys.argv) < 3:
|
|
11
|
-
print("用法: python3 download_server_script.py <项目目录> <Java包名> [代码包简称]")
|
|
12
|
-
sys.exit(1)
|
|
13
|
-
|
|
14
|
-
project_dir = sys.argv[1]
|
|
15
|
-
java_package = sys.argv[2]
|
|
16
|
-
display_name = sys.argv[3] if len(sys.argv) > 3 else java_package.split('.')[-1]
|
|
17
|
-
token_path = os.path.join(project_dir, '.neo-cli/token.json')
|
|
18
|
-
output_dir = os.path.join(project_dir, 'src/server-scripts', display_name)
|
|
19
|
-
|
|
20
|
-
if not os.path.exists(token_path):
|
|
21
|
-
print("错误: 未找到 %s,请先执行 neo login" % token_path)
|
|
22
|
-
sys.exit(1)
|
|
23
|
-
|
|
24
|
-
token = json.load(open(token_path))
|
|
25
|
-
access_token = token['access_token']
|
|
26
|
-
base_url = token['instance_uri'].rstrip('/')
|
|
27
|
-
|
|
28
|
-
# 1. 检查本地是否已存在
|
|
29
|
-
if os.path.exists(output_dir):
|
|
30
|
-
print("本地已存在: %s,将更新覆盖" % output_dir)
|
|
31
|
-
shutil.rmtree(output_dir)
|
|
32
|
-
|
|
33
|
-
# 2. 获取代码包信息和下载地址
|
|
34
|
-
print("获取代码包信息: %s ..." % java_package)
|
|
35
|
-
url = base_url + '/rest/metadata/v2.0/scripts/packages?packageName=' + urllib.request.quote(java_package)
|
|
36
|
-
req = urllib.request.Request(url, method='GET')
|
|
37
|
-
req.add_header('Authorization', access_token)
|
|
38
|
-
|
|
39
|
-
try:
|
|
40
|
-
resp = urllib.request.urlopen(req)
|
|
41
|
-
data = json.loads(resp.read().decode('utf-8'))
|
|
42
|
-
except urllib.error.HTTPError as e:
|
|
43
|
-
print("请求失败 HTTP %d: %s" % (e.code, e.read().decode('utf-8')[:500]))
|
|
44
|
-
sys.exit(1)
|
|
45
|
-
|
|
46
|
-
if data.get('code') != '200' or not data.get('data', {}).get('records'):
|
|
47
|
-
print("错误: 代码包不存在或无权限 (%s)" % data.get('msg', data.get('code', '')))
|
|
48
|
-
sys.exit(1)
|
|
49
|
-
|
|
50
|
-
records = data['data']['records']
|
|
51
|
-
download_url = records.get('uploadFile', '')
|
|
52
|
-
if not download_url:
|
|
53
|
-
print("错误: 未找到下载地址")
|
|
54
|
-
sys.exit(1)
|
|
55
|
-
|
|
56
|
-
print("版本: %s" % records.get('version', ''))
|
|
57
|
-
|
|
58
|
-
# 3. 下载 ZIP
|
|
59
|
-
zip_path = os.path.join(project_dir, display_name + '_download.zip')
|
|
60
|
-
print("下载代码包 ...")
|
|
61
|
-
urllib.request.urlretrieve(download_url, zip_path)
|
|
62
|
-
print("已下载: %d bytes" % os.path.getsize(zip_path))
|
|
63
|
-
|
|
64
|
-
# 4. 解压到 src/server-scripts/<display_name>/
|
|
65
|
-
print("解压到 %s ..." % output_dir)
|
|
66
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
67
|
-
with zipfile.ZipFile(zip_path, 'r') as zf:
|
|
68
|
-
zf.extractall(output_dir)
|
|
69
|
-
for name in zf.namelist():
|
|
70
|
-
print(" %s" % name)
|
|
71
|
-
|
|
72
|
-
# 5. 清理临时 ZIP
|
|
73
|
-
os.remove(zip_path)
|
|
74
|
-
print("\n完成: 代码包已下载到 %s" % output_dir)
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
生成实体字段描述:通过 API 获取指定实体的字段信息,输出 <apiKey>.md
|
|
4
|
-
用法: python3 gen_entity_desc.py <项目目录> <实体apiKey>
|
|
5
|
-
示例: python3 gen_entity_desc.py NEOTrail investment__c
|
|
6
|
-
"""
|
|
7
|
-
import sys, os, json, urllib.request
|
|
8
|
-
|
|
9
|
-
if len(sys.argv) < 3:
|
|
10
|
-
print("用法: python3 gen_entity_desc.py <项目目录> <实体apiKey>")
|
|
11
|
-
sys.exit(1)
|
|
12
|
-
|
|
13
|
-
project_dir = sys.argv[1]
|
|
14
|
-
api_key = sys.argv[2]
|
|
15
|
-
token_path = os.path.join(project_dir, '.neo-cli/token.json')
|
|
16
|
-
output_dir = os.path.join(project_dir, 'src/entity-model')
|
|
17
|
-
|
|
18
|
-
if not os.path.exists(token_path):
|
|
19
|
-
print("错误: 未找到 %s,请先执行 neo login" % token_path)
|
|
20
|
-
sys.exit(1)
|
|
21
|
-
|
|
22
|
-
token = json.load(open(token_path))
|
|
23
|
-
access_token = token['access_token']
|
|
24
|
-
base_url = token['instance_uri'].rstrip('/')
|
|
25
|
-
headers = {'Authorization': 'Bearer ' + access_token, 'Content-Type': 'application/json'}
|
|
26
|
-
|
|
27
|
-
def api_get(path):
|
|
28
|
-
req = urllib.request.Request(base_url + path, method='GET')
|
|
29
|
-
for k, v in headers.items():
|
|
30
|
-
req.add_header(k, v)
|
|
31
|
-
return json.loads(urllib.request.urlopen(req).read().decode('utf-8'))
|
|
32
|
-
|
|
33
|
-
# 1. 获取实体信息
|
|
34
|
-
print("获取实体信息: %s ..." % api_key)
|
|
35
|
-
try:
|
|
36
|
-
entity_data = api_get('/rest/metadata/v2.0/xobjects/' + api_key)
|
|
37
|
-
entity = entity_data.get('data', {}).get('records', {})
|
|
38
|
-
entity_label = entity.get('label', api_key)
|
|
39
|
-
except Exception as e:
|
|
40
|
-
print("错误: 获取实体信息失败: %s" % e)
|
|
41
|
-
entity_label = api_key
|
|
42
|
-
|
|
43
|
-
# 2. 获取字段列表
|
|
44
|
-
print("获取字段列表 ...")
|
|
45
|
-
try:
|
|
46
|
-
fields_data = api_get('/rest/metadata/v2.0/xobjects/' + api_key + '/items')
|
|
47
|
-
fields = fields_data.get('data', {}).get('records', [])
|
|
48
|
-
except Exception as e:
|
|
49
|
-
print("错误: 获取字段列表失败: %s" % e)
|
|
50
|
-
sys.exit(1)
|
|
51
|
-
|
|
52
|
-
# 3. 生成 <apiKey>.md
|
|
53
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
54
|
-
|
|
55
|
-
lines = ['# %s (%s)' % (entity_label, api_key), '',
|
|
56
|
-
'| 字段名称 | API Key | 字段类型 | 是否标准字段 |',
|
|
57
|
-
'|----------|---------|----------|------------|']
|
|
58
|
-
for f in fields:
|
|
59
|
-
fk = f.get('apiKey', '')
|
|
60
|
-
label = f.get('label', '')
|
|
61
|
-
ftype = f.get('type', '')
|
|
62
|
-
is_std = '是' if not fk.endswith('__c') else '否'
|
|
63
|
-
lines.append('| %s | %s | %s | %s |' % (label, fk, ftype, is_std))
|
|
64
|
-
|
|
65
|
-
output_path = os.path.join(output_dir, api_key + '.md')
|
|
66
|
-
with open(output_path, 'w', encoding='utf-8') as f:
|
|
67
|
-
f.write('\n'.join(lines))
|
|
68
|
-
|
|
69
|
-
print("完成: %s (%s, %d 个字段)" % (output_path, entity_label, len(fields)))
|