1192 lines
36 KiB
Markdown
1192 lines
36 KiB
Markdown
# iWork 集成 API 与使用指南
|
||
|
||
本文面向 **调用方后端开发者**,介绍如何在 System 模块中使用现有的 iWork 集成能力:流程发起/作废、附件回调、人力组织代理与全量同步。文档按“流程调用能力 / 组织同步能力”拆分,并包含 Mermaid 流程图、字段映射表及禁用策略说明,便于快速自助集成。
|
||
|
||
---
|
||
|
||
## 背景与适用范围
|
||
|
||
- 所有接口位于 `zt-module-system-server` 的 `IWorkIntegrationController` 下,统一前缀 `/system/integration/iwork`。
|
||
- Service 层提供 `IWorkIntegrationService`、`IWorkOrgRestService`、`IWorkSyncService` 三个 Bean,可在任意业务模块注入调用。
|
||
- 调用 iWork 之前会实时执行 register + apply-token,不保留本地缓存;组织同步则通过 key+ts 鉴权代理 iWork HR 接口并写入本地组织/用户表。
|
||
|
||
---
|
||
|
||
## 快速开始清单
|
||
|
||
- [ ] `application.yml` 或配置中心已填写 `iwork.*` 参数。
|
||
- [ ] 确认所在网络能访问 iWork register/apply-token/HR 接口。
|
||
- [ ] 明确调用方向:流程调用 or 组织同步,两种能力可独立使用。
|
||
- [ ] 若使用文件回调,已约定业务附件的 `businessCode` 与租户。
|
||
- [ ] 管理端 `admin-api/system/integration/iwork/**` 已放通权限。
|
||
|
||
---
|
||
|
||
## 配置清单
|
||
|
||
### YAML 示例
|
||
|
||
```yaml
|
||
iwork:
|
||
base-url: https://iwork.example.com
|
||
app-id: my-iwork-app
|
||
client-public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...
|
||
user-id: system
|
||
workflow:
|
||
seal-workflow-id: 54
|
||
paths:
|
||
register: /api/ec/dev/auth/regist
|
||
apply-token: /api/ec/dev/auth/applytoken
|
||
user-info: /api/workflow/paService/getUserInfo
|
||
create-workflow: /api/workflow/paService/doCreateRequest
|
||
void-workflow: /api/workflow/paService/doCancelRequest
|
||
token:
|
||
ttl-seconds: 3600
|
||
client:
|
||
connect-timeout: 5s
|
||
response-timeout: 30s
|
||
org:
|
||
token-seed: 5936562a-d47c-4a29-9b74-b310e6c971b7
|
||
paths:
|
||
subcompany-page: /api/hrm/resful/getHrmsubcompanyWithPage
|
||
department-page: /api/hrm/resful/getHrmdepartmentWithPage
|
||
job-title-page: /api/hrm/resful/getJobtitleInfoWithPage
|
||
user-page: /api/hrm/resful/getHrmUserInfoWithPage
|
||
sync-subcompany: /api/hrm/resful/synSubcompany
|
||
sync-department: /api/hrm/resful/synDepartment
|
||
sync-job-title: /api/hrm/resful/synJobtitle
|
||
sync-user: /api/hrm/resful/synHrmresource
|
||
```
|
||
|
||
| 配置键 | 说明 | 必填 | 默认值 |
|
||
| --- | --- | --- | --- |
|
||
| `iwork.base-url` | iWork 网关地址,需与网络互通 | 是 | - |
|
||
| `iwork.app-id` | 平台分配的 AppId,用于申请 token | 是 | - |
|
||
| `iwork.client-public-key` | iWork 端提供的 RSA 公钥,系统用其加密凭证 | 是 | - |
|
||
| `iwork.workflow.seal-workflow-id` | 盖章流程模板 ID,其他流程可扩展 | 是 | - |
|
||
| `iwork.paths.*` | 控制器访问 iWork 的后端路由 | 是 | - |
|
||
| `iwork.token.ttl-seconds` | token 缓存时长,单位秒 | 否 | 3600 |
|
||
| `iwork.client.connect-timeout` | OkHttp 建连超时 | 否 | 5s |
|
||
| `iwork.org.token-seed` | HR 组织同步签名盐值 | 是 | - |
|
||
|
||
## 流程调用 API
|
||
|
||
### 端到端互动图
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Client as 调用方服务
|
||
participant Gateway as ZTCloud /system 接口
|
||
participant IWork as iWork 平台
|
||
Client->>Gateway: POST /auth/register
|
||
Gateway->>IWork: /api/ec/dev/auth/regist
|
||
IWork-->>Gateway: 返回 secret + spk
|
||
Client->>Gateway: POST /auth/token
|
||
Gateway->>IWork: /api/ec/dev/auth/applytoken
|
||
IWork-->>Gateway: token
|
||
Client->>Gateway: POST /workflow/create
|
||
Gateway->>IWork: /api/workflow/paService/doCreateRequest
|
||
IWork-->>Gateway: requestId
|
||
IWork-->>Gateway: 文件回调
|
||
Gateway-->>Client: 业务附件 ID
|
||
```
|
||
|
||
### HTTP 接口分组
|
||
|
||
| 功能 | Method & Path | 对应 iWork 接口 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 注册凭证 | POST `/system/integration/iwork/auth/register` | `/api/ec/dev/auth/regist` | 生成 secret + spk,用于后续 token 申请 |
|
||
| 申请 token | POST `/system/integration/iwork/auth/token` | `/api/ec/dev/auth/applytoken` | 以 secret + operatorUserId 换取 token |
|
||
| 用户解析 | POST `/system/integration/iwork/user/resolve` | `/api/workflow/paService/getUserInfo` | 通过工号/手机号等标识查询 iWork 用户 ID |
|
||
| 发起流程 | POST `/system/integration/iwork/workflow/create` | `/api/workflow/paService/doCreateRequest` | 将本地业务单推送至 iWork 指定流程 |
|
||
| 作废流程 | POST `/system/integration/iwork/workflow/void` | `/api/workflow/paService/doCancelRequest` | 针对 requestId 触发终止/回收 |
|
||
| 文件回调 | POST `/system/integration/iwork/callback/file` | iWork 自定义回调 | 将 iWork 附件落地并与业务编码关联 |
|
||
|
||
### 关键参数与 VO
|
||
|
||
| 接口 | 请求体 | 核心字段 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 注册凭证 | `IWorkAuthRegisterReqVO` | `forceRefreshRegistration` | true 时会重新向 iWork 注册,常用于公钥被重置的场景 |
|
||
| 申请 token | `IWorkAuthTokenReqVO` | `operatorUserId`、`forceRefreshToken` | `operatorUserId` 默认读取 `iwork.user-id`,必要时可传入办件人 ID |
|
||
| 用户解析 | `IWorkUserInfoReqVO` | `identifierKey`、`identifierValue` | 支持 `workCode`、`mobile`、`loginId` 等键值组合 |
|
||
| 创建流程 | `IWorkWorkflowCreateReqVO` | `workflowId`、`mainData`、`attachments` | `workflowId` 缺省则采用 `iwork.workflow.seal-workflow-id`,`mainData` 为流程主表字段 |
|
||
| 作废流程 | `IWorkWorkflowVoidReqVO` | `requestId`、`reason` | reason 最长 400 字,系统会自动截断超长字符 |
|
||
| 文件回调 | `IWorkFileCallbackReqVO` | `fileUrl`、`businessCode` | 将远程文件下载后与业务附件绑定,`businessCode` 对应已有 `BusinessFile` 记录 |
|
||
|
||
### 发起流程示例
|
||
|
||
```bash
|
||
curl -X POST https://{host}/system/integration/iwork/workflow/create ^
|
||
-H "Content-Type: application/json" ^
|
||
-H "Authorization: Bearer <your-token>" ^
|
||
-d @payload.json
|
||
```
|
||
|
||
payload 示意:
|
||
|
||
```json
|
||
{
|
||
"operatorUserId": "1001",
|
||
"forceRefreshToken": false,
|
||
"jbr": "1001",
|
||
"yybm": "2001",
|
||
"fb": "3001",
|
||
"sqsj": "2025-01-01",
|
||
"yyqx": "寄送客户",
|
||
"yyfkUrl": "https://oss.example.com/依据附件.pdf",
|
||
"yysy": "与 XX 公司签订框架合同",
|
||
"xyywjUrl": "https://oss.example.com/材料附件.pdf",
|
||
"xyywjFileName": "材料附件.pdf",
|
||
"yysx": "合同用印",
|
||
"ywxtdjbh": "DJ-2025-00018"
|
||
}
|
||
```
|
||
|
||
### Java 调用片段
|
||
|
||
```java
|
||
IWorkWorkflowCreateReqVO req = new IWorkWorkflowCreateReqVO();
|
||
req.setWorkflowId(54);
|
||
req.setOperatorUserId("1001");
|
||
req.setMainData(Map.of("title", "盖章申请", "amount", 128000));
|
||
req.setForceRefreshToken(false);
|
||
|
||
CommonResult<IWorkOperationRespVO> resp = restTemplate.postForObject(
|
||
baseUrl + "/system/integration/iwork/workflow/create",
|
||
req,
|
||
new ParameterizedTypeReference<>() {});
|
||
|
||
if (resp == null || !resp.isSuccess()) {
|
||
throw new IllegalStateException("iWork 调用失败" + Optional.ofNullable(resp).map(CommonResult::getMsg).orElse(""));
|
||
}
|
||
String requestId = resp.getData().getRequestId();
|
||
```
|
||
|
||
### 通用请求字段(IWorkBaseReqVO)
|
||
|
||
| 字段 | 类型 | 必填 | 说明 | 示例 |
|
||
| --- | --- | --- | --- | --- |
|
||
| `appId` | string | 否 | 覆盖默认的 iWork 应用编号;为空时取配置 | `iwork-app` |
|
||
| `operatorUserId` | string | 否 | 作为 iWork 操作人的用户编号;为空时取 `iwork.user-id` | `1001` |
|
||
| `forceRefreshToken` | boolean | 否 | true 时忽略本地缓存并强制重新申请 token | `false` |
|
||
|
||
> 所有接口均返回 `CommonResult<T>` 结构,通用包体为 `{ "code": 0, "data": T, "msg": "success", "traceId": "..." }`。下文的响应字段默认落在 `data` 节点内。
|
||
|
||
### 流程接口字段详解
|
||
|
||
#### POST `/system/integration/iwork/auth/register`
|
||
|
||
##### 请求字段(/auth/register)
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `forceRefreshRegistration` | boolean | 否 | true 时即使已有注册信息也会重新向 iWork 注册,以获取新的 secret + serverPublicKey |
|
||
|
||
##### 响应字段(/auth/register)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `appId` | string | 实际使用的 iWork 应用编号 |
|
||
| `clientPublicKey` | string(Base64) | 系统用于加密 secret 的客户端公钥 |
|
||
| `clientPrivateKey` | string(Base64) | 仅在动态生成公钥时回传,用于后续持久化 |
|
||
| `serverPublicKey` | string(Base64) | iWork 返回的服务端公钥,需用于加密 secret 与 userId |
|
||
| `secret` | string | iWork 发放的密钥,结合公钥加密后才能申请 token |
|
||
|
||
##### JSON 示例(/auth/register)
|
||
|
||
请求:
|
||
|
||
```http
|
||
POST /system/integration/iwork/auth/register HTTP/1.1
|
||
Host: api.example.com
|
||
Content-Type: application/json
|
||
Authorization: Bearer ***
|
||
|
||
```
|
||
|
||
响应:
|
||
|
||
```http
|
||
HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
失败示例(iWork 接口异常):
|
||
|
||
```http
|
||
HTTP/1.1 502 Bad Gateway
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
#### POST `/system/integration/iwork/auth/token`
|
||
|
||
##### 请求字段(/auth/token)
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `operatorUserId` | string | 否 | 不填则取配置的默认操作者 |
|
||
| `forceRefreshToken` | boolean | 否 | true 时忽略缓存直接调 iWork 申请新 token |
|
||
| `forceRefreshRegistration` | boolean | 否 | true 时在申请 token 前重新注册,以应对 serverPublicKey 失效 |
|
||
|
||
##### 响应字段(/auth/token)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `appId` | string | 本次 token 绑定的应用编号 |
|
||
| `operatorUserId` | string | 实际用于发起流程的 iWork 用户 |
|
||
| `token` | string | iWork 访问令牌 |
|
||
| `encryptedUserId` | string | 使用 serverPublicKey 加密后的 userId,调用 iWork 接口时需要写入 Header |
|
||
| `expiresAtEpochSecond` | long | 预计过期时间(Epoch 秒) |
|
||
| `serverPublicKey` | string | token 对应的 server 公钥 |
|
||
|
||
##### JSON 示例(/auth/token)
|
||
|
||
请求:
|
||
|
||
```http
|
||
POST /system/integration/iwork/auth/token HTTP/1.1
|
||
Host: api.example.com
|
||
Content-Type: application/json
|
||
Authorization: Bearer ***
|
||
|
||
```
|
||
|
||
响应:
|
||
|
||
```http
|
||
HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
失败示例(secret 失效):
|
||
|
||
```http
|
||
HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
#### POST `/system/integration/iwork/user/resolve`
|
||
|
||
##### 请求字段(/user/resolve)
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `identifierKey` | string | 是 | 写入 iWork 所需的字段名,如 `workcode`、`mobile`、`loginid` |
|
||
| `identifierValue` | string | 是 | 对应字段的取值 |
|
||
| `payload` | map | 否 | 附加在请求体的扩展参数,会与识别字段一起发送 |
|
||
| `queryParams` | map | 否 | 附加在 URL 上的查询参数 |
|
||
|
||
##### 响应字段(/user/resolve)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `payload` | map | iWork 原始返回 JSON |
|
||
| `success` | boolean | 是否判定为成功(根据 code/success 等字段自动推断) |
|
||
| `message` | string | 友好提示或错误信息 |
|
||
| `userId` | string | 从返回体解析出的 iWork 用户编号 |
|
||
|
||
##### JSON 示例(/user/resolve)
|
||
|
||
请求:
|
||
|
||
```http
|
||
POST /system/integration/iwork/user/resolve HTTP/1.1
|
||
Host: api.example.com
|
||
Content-Type: application/json
|
||
Authorization: Bearer ***
|
||
|
||
```
|
||
|
||
响应:
|
||
|
||
```http
|
||
HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
失败示例(未找到用户):
|
||
|
||
```http
|
||
HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
#### POST `/system/integration/iwork/workflow/create`
|
||
|
||
##### 请求字段(/workflow/create)
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `jbr` | string | 是 | 用印经办人(iWork 用户 ID) |
|
||
| `yybm` | string | 是 | 用印部门 ID |
|
||
| `fb` | string | 是 | 用印单位(分部 ID) |
|
||
| `sqsj` | string(yyyy-MM-dd) | 是 | 申请时间 |
|
||
| `yyqx` | string | 是 | 用印去向/流向 |
|
||
| `yyfkUrl` | string | 否 | 用印依据附件 URL |
|
||
| `yysy` | string | 否 | 用印事由 |
|
||
| `xyywjUrl` | string | 是 | 待用印材料附件 URL,系统会封装成 iWork 需要的数组结构 |
|
||
| `xyywjFileName` | string | 否 | 待用印材料附件名称,默认从 URL 截取 |
|
||
| `yysx` | string | 是 | 用印事项(流程标题关键字段) |
|
||
| `ywxtdjbh` | string | 是 | 业务系统单据编号,用于生成流程标题与追溯 |
|
||
|
||
##### 响应字段(/workflow/create)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `payload.code` | string | iWork 返回的业务状态,如 `SUCCESS` |
|
||
| `payload.data.requestId` | long | iWork 流程请求编号,后续作废/查询需要使用 |
|
||
| `payload.errMsg` | map | 错误附加信息(失败时) |
|
||
| `payload.reqFailMsg.keyParameters` | map | 失败时的关键参数回显 |
|
||
| `success` | boolean | SDK 根据 code/success 判断的结果 |
|
||
| `message` | string | 统一的提示文案 |
|
||
|
||
##### JSON 示例(/workflow/create)
|
||
|
||
请求:
|
||
|
||
```http
|
||
POST /system/integration/iwork/workflow/create HTTP/1.1
|
||
Host: api.example.com
|
||
Content-Type: application/json
|
||
Authorization: Bearer ***
|
||
|
||
```
|
||
|
||
响应:
|
||
|
||
```http
|
||
HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
失败示例(字段缺失):
|
||
|
||
```http
|
||
HTTP/1.1 400 Bad Request
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
#### POST `/system/integration/iwork/workflow/void`
|
||
|
||
##### 请求字段(/workflow/void)
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `requestId` | string | 是 | 需要作废的 iWork 流程编号 |
|
||
| `reason` | string | 否 | 作废原因,将映射到 iWork 的 `remark` 字段(超长会被截断) |
|
||
| `extraParams` | map | 否 | 直接透传给 iWork 的附加 JSON 字段 |
|
||
| `formExtras` | map<string,string> | 否 | 会被追加到 payload 中的额外 form 字段 |
|
||
|
||
##### 响应字段(/workflow/void)
|
||
|
||
与 `/workflow/create` 使用的 `IWorkOperationRespVO` 结构完全一致。
|
||
|
||
##### JSON 示例(/workflow/void)
|
||
|
||
请求:
|
||
|
||
```http
|
||
POST /system/integration/iwork/workflow/void HTTP/1.1
|
||
Host: api.example.com
|
||
Content-Type: application/json
|
||
Authorization: Bearer ***
|
||
|
||
```
|
||
|
||
响应:
|
||
|
||
```http
|
||
HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
失败示例(requestId 不存在):
|
||
|
||
```http
|
||
HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
#### POST `/system/integration/iwork/callback/file`
|
||
|
||
##### 请求字段(/callback/file)
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `fileUrl` | string | 是 | iWork 可访问的文件下载地址 |
|
||
| `businessCode` | string | 是 | 已存在的业务附件编码,将使用该编码定位租户与业务ID |
|
||
| `fileName` | string | 否 | 覆盖默认文件名,不填则从 URL 末尾截取 |
|
||
|
||
##### 响应字段(/callback/file)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `data` | long | 成功创建的业务附件关联 ID |
|
||
|
||
##### JSON 示例(/callback/file)
|
||
|
||
请求:
|
||
|
||
```http
|
||
POST /system/integration/iwork/callback/file HTTP/1.1
|
||
Host: api.example.com
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
响应:
|
||
|
||
```http
|
||
HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
失败示例(业务编码不存在):
|
||
|
||
```http
|
||
HTTP/1.1 400 Bad Request
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
## 组织同步 API
|
||
|
||
### 数据流示意
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
IWork[(iWork HR)] -->|分页接口| RestProxy[/IWorkOrgRestService/]
|
||
RestProxy -->|BatchResult| SyncService{{IWorkSyncService}}
|
||
SyncService -->|Dept/Post/UserSaveReqVO| LocalDB[(ZTCloud 系统库)]
|
||
SyncService -->|统计| 调用方
|
||
```
|
||
|
||
### 接口矩阵
|
||
|
||
| 功能 | Method & Path | 请求体 | 备注 |
|
||
| --- | --- | --- | --- |
|
||
| 分部分页 | POST `/system/integration/iwork/hr/subcompany/page` | `IWorkSubcompanyQueryReqVO` | 透传 `subcompanycode/subcompanyname` 条件 |
|
||
| 部门分页 | POST `/system/integration/iwork/hr/department/page` | `IWorkDepartmentQueryReqVO` | 支持按照 `subcompanyId`、名称过滤 |
|
||
| 岗位分页 | POST `/system/integration/iwork/hr/job-title/page` | `IWorkJobTitleQueryReqVO` | 可按岗位编码、名称筛选 |
|
||
| 人员分页 | POST `/system/integration/iwork/hr/user/page` | `IWorkUserQueryReqVO` | 支持工号、手机号、状态等组合查询 |
|
||
| 分部全量同步 | POST `/system/integration/iwork/hr/subcompanies/full-sync` | `IWorkFullSyncReqVO` | 默认 `scopes=subcompany`,按分页批量落库 |
|
||
| 部门全量同步 | POST `/system/integration/iwork/hr/departments/full-sync` | `IWorkFullSyncReqVO` | 自动处理父子依赖,多次遍历确保 parent ready |
|
||
| 岗位全量同步 | POST `/system/integration/iwork/hr/job-titles/full-sync` | `IWorkFullSyncReqVO` | 岗位编码固定为 `IWORK_JOB_${id}` |
|
||
| 人员全量同步 | POST `/system/integration/iwork/hr/users/full-sync` | `IWorkFullSyncReqVO` | 工号优先映射为用户名,自动建档岗位 |
|
||
|
||
### Full Sync 请求参数
|
||
|
||
| 字段 | 默认值 | 说明 |
|
||
| --- | --- | --- |
|
||
| `startPage` | 1 | 开始拉取的页码,对应 iWork `curpage` |
|
||
| `pageSize` | 100 | 单页数量(1-500) |
|
||
| `maxPages` | null | 限制最大页数,null 表示直到 iWork 返回空页 |
|
||
| `scopes` | 全量 | 枚举 `subcompany`、`department`、`jobTitle`、`user`,可多选 |
|
||
| `id` | null | 指定单个 iWork 实体 ID,主要用于补偿 |
|
||
| `includeCanceled` | false | `true` 时同步 iWork 失效记录并将其禁用 |
|
||
| `allowUpdate` | false | 是否允许更新本地已有但来源为 iWork 的记录 |
|
||
|
||
> 系统内部始终启用 `createIfMissing=true`,因此全量同步会自动建档缺失的分部/部门/岗位/人员。
|
||
|
||
### 字段映射(组织)
|
||
|
||
| iWork 字段 | 本地字段 | 说明 |
|
||
| --- | --- | --- |
|
||
| `subcompanyname` / `departmentname` | `DeptDO.name` | 自动截断到 30 字符,缺失时使用“未命名*” |
|
||
| `subcompanycode` / `departmentcode` | `DeptDO.code` | 空字符串会被置空,保持与 iWork 编码一致 |
|
||
| `supsubcomid` / `supdepid` | `DeptDO.parentId` | 优先使用部门父级,退化到分部或根节点 |
|
||
| `showorder` | `DeptDO.sort` | 为空则落地 999 |
|
||
| `canceled` | `DeptDO.status` | `1/true/yes` 视为禁用(CommonStatusEnum.DISABLE) |
|
||
|
||
### 字段映射(人员与岗位)
|
||
|
||
| iWork 字段 | 本地字段 | 处理规则 |
|
||
| --- | --- | --- |
|
||
| `jobtitleid` | `PostDO.code=IWORK_JOB_${id}` | 若本地不存在则自动建档,`jobtitlename` 作为名称 |
|
||
| `lastname` | `AdminUserDO.nickname` | 超过 30 字符会截断 |
|
||
| `workcode` → `loginid` | `AdminUserDO.username` | 首选工号,缺失时使用登录账号,均为空则跳过 |
|
||
| `departmentid` → `subcompanyid1` | `AdminUserDO.deptIds` | 优先部门,空则落地分部,仍为空则允许为空继续 |
|
||
| `jobtitleid` / `jobtitlename` | `AdminUserDO.postIds` | 通过岗位编码命中缓存,否则按名称动态创建 |
|
||
| `email` / `mobile` | `AdminUserDO.email/mobile` | 同步时跳过重复验证,兼容历史数据 |
|
||
| `password` | `AdminUserDO.password` | 直接使用 iWork 原始密码,避免哈希不一致 |
|
||
| `status` | `AdminUserDO.status` | `0` 视为启用,其余状态视为禁用 |
|
||
| `sex` | `AdminUserDO.sex` | 自动适配 `0/1/2` 与 `男/女/M/F` 等多种格式 |
|
||
|
||
### 组织同步禁用策略
|
||
|
||
| 场景 | 判定条件 | 本地处理 | 备注 |
|
||
| --- | --- | --- | --- |
|
||
| 分部/部门被禁用 | `canceled` = `1`/`true`/`yes` | 若 `includeCanceled=false`:跳过;否则创建/更新并设置为禁用 | 可通过 `includeCanceled` 控制是否同步 |
|
||
| 岗位被禁用 | `canceled` 标记为真 | 同步后 `PostDO.status=DISABLE`,同时记入统计 | |
|
||
| 人员离职 | `status` ≠ `0` | 创建/更新为 `DISABLE`,并统计在 `disabled` 数量 | `status` 字段通常为字符串 |
|
||
| 非 iWork 数据 | `deptSource`/`userSource` ≠ iWork | 永远跳过修改,保证手工数据安全 | 相关日志输出 `Skipped` 提示 |
|
||
|
||
### 鉴权与签名
|
||
|
||
组织接口统一由 `IWorkOrgRestService` 代理,鉴权方式为:
|
||
|
||
1. 读取 `iwork.org.token-seed`。
|
||
2. 运行时生成 `ts = System.currentTimeMillis()`。
|
||
3. 计算 `key = MD5(tokenSeed + ts)` 并转大写。
|
||
4. 将 `{ key, ts }` 作为 `token` 字段写入请求体,iWork 会基于相同算法校验。
|
||
|
||
请确保 ZTCloud 所在网络与 iWork 可直接互通,否则需在接口网关上放通上述 URL。
|
||
|
||
### 组织接口字段详解
|
||
|
||
#### 通用分页请求字段(`IWorkOrgBaseQueryReqVO`)
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `curpage` | integer | 否 | 当前页码,默认 1 |
|
||
| `pagesize` | integer | 否 | 每页条数,默认 20,最大 500 |
|
||
| `params` | map | 否 | 额外查询条件,直接透传给 iWork |
|
||
|
||
#### POST `/system/integration/iwork/hr/subcompany/page`
|
||
|
||
##### 请求字段(subcompany/page)
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `curpage` | integer | 否 | 继承自通用分页字段 |
|
||
| `pagesize` | integer | 否 | 同上 |
|
||
| `subcompanyCode` | string | 否 | 按分部编码模糊查询 |
|
||
| `subcompanyName` | string | 否 | 按分部名称模糊查询 |
|
||
|
||
##### 响应字段(`IWorkHrSubcompanyPageRespVO`)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `code` | string | iWork 返回码 |
|
||
| `message` | string | 提示信息 |
|
||
| `success` | boolean | 是否成功 |
|
||
| `totalSize` | integer | 总记录数 |
|
||
| `totalPage` | integer | 总页数 |
|
||
| `pageSize` | integer | 单页条数 |
|
||
| `pageNumber` | integer | 当前页码 |
|
||
| `dataList[]` | array | 分部列表 |
|
||
|
||
`dataList[]` 主要字段:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | integer | 分部 ID(也是本地部门 ID) |
|
||
| `subcompanycode` | string | 分部编码 |
|
||
| `subcompanyname` | string | 分部名称 |
|
||
| `companyid` / `companyname` | integer/string | 总部标识 |
|
||
| `supsubcomid` / `supsubcomname` | integer/string | 上级分部信息 |
|
||
| `showorder` | integer | 排序号 |
|
||
| `description` | string | 描述 |
|
||
| `canceled` | string | 是否失效(0/1/true/false) |
|
||
| `alllevel` | string | 层级路径 |
|
||
| `attributes` | map | iWork 额外字段的承载容器 |
|
||
|
||
##### JSON 示例(/hr/subcompany/page)
|
||
|
||
请求:
|
||
|
||
```http
|
||
POST /system/integration/iwork/hr/subcompany/page HTTP/1.1
|
||
Host: api.example.com
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
响应:
|
||
|
||
```http
|
||
HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
失败示例(鉴权失败):
|
||
|
||
```http
|
||
HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
#### POST `/system/integration/iwork/hr/department/page`
|
||
|
||
##### 请求字段(department/page)
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `curpage` | integer | 否 | 分页参数 |
|
||
| `pagesize` | integer | 否 | 分页参数 |
|
||
| `departmentCode` | string | 否 | 部门编码 |
|
||
| `departmentName` | string | 否 | 部门名称 |
|
||
| `subcompanyId` | string | 否 | 所属分部 ID |
|
||
|
||
##### 响应字段(`IWorkHrDepartmentPageRespVO`)
|
||
|
||
顶层字段:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `code` | string | iWork 返回码 |
|
||
| `message` | string | 提示信息 |
|
||
| `success` | boolean | 是否成功 |
|
||
| `data.totalSize` | integer | 总记录数 |
|
||
| `data.totalPage` | integer | 总页数 |
|
||
| `data.pageSize` | integer | 每页条数 |
|
||
| `data.page` | integer | 当前页码 |
|
||
| `data.dataList[]` | array | 部门列表 |
|
||
|
||
`data.dataList[]` 关键字段:部门 ID(`id`)、部门编码(`departmentcode`)、部门名称(`departmentname`)、所属分部(`subcompanyid1` / `subcompanyname`)、父部门(`supdepid`)、显示顺序(`showorder`)、失效标记(`canceled`)等,其余原样透传在 `attributes` 中。
|
||
|
||
##### JSON 示例(/hr/department/page)
|
||
|
||
请求:
|
||
|
||
```http
|
||
POST /system/integration/iwork/hr/department/page HTTP/1.1
|
||
Host: api.example.com
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
响应:
|
||
|
||
```http
|
||
HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
#### POST `/system/integration/iwork/hr/job-title/page`
|
||
|
||
##### 请求字段(job-title/page)
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `curpage` | integer | 否 | 分页参数 |
|
||
| `pagesize` | integer | 否 | 分页参数 |
|
||
| `jobTitleCode` | string | 否 | 岗位编码 |
|
||
| `jobTitleName` | string | 否 | 岗位名称 |
|
||
|
||
##### 响应字段(`IWorkHrJobTitlePageRespVO`)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `code` | string | iWork 返回码 |
|
||
| `message` | string | 提示信息 |
|
||
| `success` | boolean | 是否成功 |
|
||
| `totalSize` / `totalPage` / `pageSize` / `pageNumber` | integer | 分页信息 |
|
||
| `dataList[]` | array | 岗位集合 |
|
||
|
||
`dataList[]` 字段包含 `id`、`jobtitlecode`、`jobtitlename`、`jobgroupid`、`jobgroupname`、`jobfunction`、`showorder`、`canceled` 等,可直接映射到本地岗位表。
|
||
|
||
#### POST `/system/integration/iwork/hr/user/page`
|
||
|
||
##### 请求字段(user/page)
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `curpage` / `pagesize` | integer | 否 | 分页参数 |
|
||
| `workCode` | string | 否 | 人员工号 |
|
||
| `lastName` | string | 否 | 姓名模糊匹配 |
|
||
| `departmentId` / `subcompanyId` | string | 否 | 组织过滤 |
|
||
| `jobTitleId` | string | 否 | 岗位过滤 |
|
||
| `status` | string | 否 | 人员状态(0:在职,其余为非在职) |
|
||
| `mobile` / `email` | string | 否 | 联系方式过滤 |
|
||
|
||
##### 响应字段(`IWorkHrUserPageRespVO`)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `code` / `message` / `success` | string/string/boolean | 通用响应字段 |
|
||
| `totalSize` / `totalPage` / `pageSize` / `pageNumber` | integer | 分页信息 |
|
||
| `dataList[]` | array | 人员数据 |
|
||
|
||
`dataList[]` 主要字段:`id`、`lastname`、`loginid`、`workcode`、`sex`、`departmentid`、`subcompanyid1`、`jobtitleid`、`mobile`、`email`、`status`、`password`、`hiredate`、`leavedate` 等,以及 `attributes` 中的扩展数据。同步服务会根据这些字段计算用户名、岗位与状态。
|
||
|
||
##### JSON 示例(/hr/user/page)
|
||
|
||
请求:
|
||
|
||
```http
|
||
POST /system/integration/iwork/hr/user/page HTTP/1.1
|
||
Host: api.example.com
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
响应:
|
||
|
||
```http
|
||
HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
#### Full Sync 请求参数(`IWorkFullSyncReqVO`)
|
||
|
||
| 字段 | 类型 | 默认值 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `startPage` | integer | 1 | 起始页码,对应 iWork `curpage` |
|
||
| `pageSize` | integer | 100 | 单页记录数 (1-500) |
|
||
| `maxPages` | integer | null | 限制最大处理页数,null 表示遍历至空页 |
|
||
| `scopes` | `array<string>` | `["subcompany","department","jobTitle","user"]` | 指定同步范围 |
|
||
| `id` | string | null | 仅同步指定 iWork 主键,常用于补偿 |
|
||
| `includeCanceled` | boolean | false | true 时会同步并禁用 iWork 标记为失效的数据 |
|
||
| `allowUpdate` | boolean | false | true 时允许覆盖已有的 iWork 来源记录 |
|
||
|
||
> 系统内部始终启用 `createIfMissing=true`,因此全量同步会自动建档缺失的分部/部门/岗位/人员。
|
||
|
||
#### Full Sync 响应字段(`IWorkFullSyncRespVO`)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `processedPages` | integer | 实际处理的页数总和 |
|
||
| `pageSize` | integer | 每批次请求的页大小回显 |
|
||
| `subcompanyStat` / `departmentStat` / `jobTitleStat` / `userStat` | `IWorkSyncEntityStatVO` | 各实体的累计统计 |
|
||
| `batches[]` | `IWorkSyncBatchStatVO` | 每个分页批次的明细 |
|
||
|
||
`IWorkSyncEntityStatVO` 字段:`pulled`(拉取条数)、`created`(新建)、`skippedExisting`(跳过)、`disabled`(禁用)、`failed`(失败)。
|
||
|
||
`IWorkSyncBatchStatVO` 字段:`entityType`(subcompany/department/job_title/user)、`pageNumber`、`pulled`、`created`、`skippedExisting`、`disabled`、`failed`。借助这些字段可以直观看到失败批次并进行定向补偿。
|
||
|
||
##### JSON 示例(/hr/users/full-sync)
|
||
|
||
请求:
|
||
|
||
```http
|
||
POST /system/integration/iwork/hr/users/full-sync HTTP/1.1
|
||
Host: api.example.com
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
响应:
|
||
|
||
```http
|
||
HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
失败示例(iWork 远端异常):
|
||
|
||
```http
|
||
HTTP/1.1 500 Internal Server Error
|
||
Content-Type: application/json
|
||
|
||
```
|
||
|
||
## 故障排查与常见问题
|
||
|
||
### 1. Token 申请失败
|
||
|
||
- 现象:`IWORK_APPLY_TOKEN_FAILED`,日志提示 “返回缺少 token”。
|
||
- 处理:
|
||
1. 确认 `iwork.base-url` 是否指向正式域名;
|
||
2. 核对 appId、client 公钥是否与 iWork 后台一致;
|
||
3. 如需重新注册,将 `/auth/register` 与 `/auth/token` 的 `forceRefreshRegistration=true`、`forceRefreshToken=true` 同时置为 true。
|
||
|
||
### 2. 组织分页 401 或“key 无效”
|
||
|
||
- 现象:iWork 返回 `code=401` 或 `success=false`,message 包含 `key invalid`。
|
||
- 处理:
|
||
- 校验服务器时间是否与 iWork 相差 < 60s;
|
||
- 确保 `token-seed` 未被误填为空;
|
||
- 通过日志中输出的 cURL(`[iWork-Org] curl`)复现请求,核对 key/ts 是否被代理层篡改。
|
||
|
||
### 3. 人员同步缺少岗位
|
||
|
||
- 现象:`BatchResult` 中 `failed` 大量增加,日志提示 “岗位缺少标识”。
|
||
- 处理:
|
||
- 确认先执行岗位全量同步;
|
||
- 若 iWork 未维护岗位,可在本地预建岗位并记录在字典,再由 iWork 返回 `jobtitlename` 供系统创建。
|
||
|
||
### 4. 附件回调 404
|
||
|
||
- 现象:iWork 回调 `/callback/file` 返回 “业务编码不能为空”。
|
||
- 处理:
|
||
- 需要提前在业务流程中调用 `BusinessFileApi` 建立 `businessCode`;
|
||
- 回调只接受 POST JSON,并且必须包含 `fileUrl`、`businessCode`。
|
||
|
||
### 诊断 checklist
|
||
|
||
1. 查看 `logs/system/iwork*.log`,定位请求链路与响应体;
|
||
2. 通过 `IWorkFullSyncRespVO` 的 `batches` 字段复盘每页统计;
|
||
3. 若同步停在某一批次,可使用 `id` + `scopes` 方式做定向补偿;
|
||
4. 启用 `includeCanceled=true` 可帮助确认 iWork 的状态标记是否准确。
|