# 星企接口与客户端接入

星企业务接口由共享聊天服务提供，正式目标来源配置为 `https://chat.thestarline.cn`。请求使用聊天服务签发的 Bearer 会话，企业范围写入路由路径。不要使用 Account Cookie、OIDC Access Token 或开放平台 HMAC 密钥直接调用企业路由。模块边界见[技术概览](https://document.thestarline.cn/xingqi/overview.md)。

## 登录与本机恢复

当前 iOS、Android 都打开 `GET /auth/start?app=xingqi`。聊天服务保存自己的 OIDC state、verifier，并把用户送到 Account；回调在服务端换取账户资料，再生成短时 ticket，重定向到 `xingqiworkbench://auth?ticket=…`。客户端调用 `POST /auth/exchange`，正文为 `{ticket}`，成功返回 `accessToken,expiresAt,user,eventCursor`。eventCursor 是共享聊天会话返回字段，企业频道本身不使用它分页。

ticket 为单次使用，当前服务端有效期 60 秒；聊天会话有效期 24 小时。星企两端未接入 `/auth/native/start` 的原生 codeChallenge/state/codeVerifier 附加证明，不能把星线 App 的完整原生 PKCE 方案照搬为当前星企事实。回调错误、丢票或过期应重新开始登录，不保存或分享回调 URL。

iOS 把会话保存到独立安全存储，并用退出意图标记阻止旧会话恢复；恢复验证失败会清除当前登录。退出先做本机清理，再请求 `/auth/logout`，网络失败会提示服务端注销尚未确认。Android 也区分本机退出与远程请求结果。退出界面成功不能证明旧服务端会话已撤销；Account 改密码同样不自动撤销独立聊天会话。

## 工作区初始化与基础路由

先 `GET /enterprise/organizations`，选择组织后调用 `GET /enterprise/organizations/:org`。聚合响应包含 organization、members、departments、channels、tasks、announcements、services；管理者还收到 joinRequests、invitations，普通成员对应数组为空。客户端切换企业时应取消旧请求、清除旧状态，再处理新响应。

| 方法与路径 | 主要输入 | 结果与权限 |
| --- | --- | --- |
| `POST /enterprise/organizations` | `name`，1–80 字符 | 201 `{organization}`，创建者为 owner |
| `PATCH /enterprise/organizations/:org/members/me/card` | `name`，字符串或 null | 200 `{member}`，只允许本人名片字段 |
| `POST /enterprise/organizations/:org/departments` | `name` | 201 `{department}`，管理者 |
| `PATCH /enterprise/organizations/:org/departments/:id` | `name` | 修改本企业部门 |
| `DELETE /enterprise/organizations/:org/departments/:id` | 无业务正文 | 删除部门，清空成员部门关联 |
| `PATCH /enterprise/organizations/:org/members/:id` | 可选 `role,departmentId,jobTitle,cardName` | 管理者操作，角色另受 owner 约束 |
| `DELETE /enterprise/organizations/:org/members/:id` | 无业务正文 | 受保护的移除，不能移除所有者 |

没有独立公开的“跳过审批加入组织”接口。邀请创建为 `POST /enterprise/organizations/:org/invitations`，返回 `id,code,expiresAt`；撤销用相同前缀加 `/:id` 的 DELETE。接收者先 POST `/enterprise/invitations/preview` 提交 `{code}`，再 POST `/enterprise/join-requests` 提交同一结构。已有成员返回 joined，有效申请为 pending。

申请列表 `GET /enterprise/join-requests` 最多返回 100 项，状态还可能为 approved、rejected、invalid、removed。管理者以 `POST /enterprise/organizations/:org/join-requests/:id` 提交 `{approve:true}` 或 false。重复相同决定可成功，相反决定返回冲突；申请被同意后才建立成员与全员群资格。

## 企业消息协议

创建频道为 `POST /enterprise/organizations/:org/channels`，输入 `kind:"direct"|"group"`、`memberIds`，群还需要 name。memberIds 输入 1–200 项，不能重复，服务端自动包含发起人；这不是“最终频道人数固定最多 200”的等价表达。单聊最终必须两人，群至少两人，所有成员均须属于当前企业。

```sh
curl 'https://chat.thestarline.cn/enterprise/organizations/<组织ID>/channels/<频道ID>/messages' \
  -H 'Authorization: Bearer <聊天会话令牌>' \
  -H 'Content-Type: application/json' \
  --data '{"body":"请核对合成示例任务","clientId":"example_message_001"}'
```

上例是结构示范，不对应真实组织或令牌。消息正文为清理首尾空白后的非空文本，最多 2,000 个 Unicode 字符；请求 JSON 上限 32,768 字节。clientId 为 1–128 位字母、数字、下划线或连字符，应在本次发送操作生成并持续复用。同一正文重试成功为 200，首次创建为 201；二者均返回 `{message}`。

消息字段包含字符串 `id`、channelId、senderId、senderName、body、clientId、createdAt。读取使用相同路径 GET，可加 `?before=<正整数消息序号>`，每次最多 100 条、本页升序。before 必须能表示为安全整数；不可传 ISO 时间或普通聊天事件 cursor。`POST .../channels/:id/read` 用 JSON 对象标记到服务器当前最新消息，无客户端 lastMessageId 参数。

## 待办、公告和服务

| 操作 | 路由尾部（均在 `/enterprise/organizations/:org` 下） | 输入与规则 |
| --- | --- | --- |
| 创建待办 | `POST /tasks` | `title,notes,dueAt,assigneeId`；创建时未完成 |
| 更新待办 | `PATCH /tasks/:id` | 管理者/创建者可改内容，被指派的普通成员仅可传 `isCompleted` |
| 删除待办 | `DELETE /tasks/:id` | 管理者或创建者 |
| 发布公告 | `POST /announcements` | `title,body`，管理者；请求上限 131,072 字节 |
| 公告已读 | `PUT /announcements/:id/read` | JSON 对象，重复不增加读者 |
| 删除公告 | `DELETE /announcements/:id` | 管理者 |
| 添加服务 | `POST /services` | `name,url`，管理者，仅合规 HTTPS URL |
| 删除服务 | `DELETE /services/:id` | 管理者 |

待办 notes 可为空字符串，dueAt 可以为 null 或非负安全整数毫秒值，assigneeId 必须是有效企业成员；待办请求上限 65,536 字节。不要发送网页日期字符串或把“未设置”写成 undefined 后期待服务器自动填充。服务链接只保存地址，不接收账户令牌，也不让外部页面继承企业会话。

## 失败与客户端一致性

错误形状为 `{error:{code,message}}`，应结合 HTTP 状态判断。401 表示聊天会话无效；403 `FORBIDDEN` 可能是组织资格、频道成员或管理权限变化，需要刷新相关作用域。400 `INVALID_MEMBER`、`INVALID_ASSIGNEE`、`INVALID_DEPARTMENT` 表示对象不属于当前企业；不能以另一企业 ID 重试。

400 `INVALID_CURSOR`、`INVALID_CLIENT_ID` 要修正参数；409 `CLIENT_ID_REUSED` 表示同一发送编号用于不同正文或加入前旧消息，需要核对待发送记录后决定新操作。409 `DUPLICATE_DEPARTMENT`、`DECISION_CONFLICT`、`INVITATION_ALREADY_USED` 应读取最新状态，不自动覆盖。400 `INVALID_INVITATION` 可能为格式错误、到期或撤销，需新的有效邀请。

企业工作区通过轮询更新，iOS 工作区约 8 秒、当前频道约 3 秒；Android 以 3 秒节拍更新频道、约每三个节拍刷新工作区。它不是强实时推送承诺。异步结果需校验当前会话和企业代次；被移除、切换企业和退出后返回的旧响应不能重新填充页面。消息未知结果可使用原 clientId 重试，审批未知结果先查申请状态。

## 验证与发布要求

应覆盖不同企业同名对象、越权 ID、移除后读取、重新加入历史边界、消息重试及换正文冲突、邀请过期和反向审批、待办角色权限、公告重复已读、退出存储失败、切换企业迟到响应及外部网站隔离。测试入口可从 `xingqi-ios-app/Tests/`、`xingqi-ios-app/UITests/`、`xingqi-android-app/app/src/test/` 与 `chat-service/tests/` 查阅，历史报告只按原日期解释。

模拟器样例不能验证正式 Account 回调、真实组织数据、跨网络轮询或商店发布。生产 `/__preview/enterprise/session` 明确不存在，不向接入者提供绕过登录的演示凭据。发布前需分别取得真实身份回调、企业业务与原生设备验收记录。
