Files
zt-qms/doc/中铜技术文档/iWork集成说明.md
2025-12-05 14:50:57 +08:00

1192 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 的状态标记是否准确。