# cloudtentacles 外部履约平台接入设计

## 1. 背景

当前项目里现有链路大致分两层：

- **订单来源层**
  - `agiso`：Webhook 推送型；
  - `91卡券`：开放接口下单型。
- **履约执行层**
  - 当前以内置人工发货、腾讯领取兑换等执行器为主。

现在新增一个发货相关的平台，当前已知入口为：

- `https://123.207.217.176`
- 前端资源来自 `gp.playinjoy.com`
- 前端代码里根实例名显示为 `cloudtentacles`

这个平台**不是订单数据源**，而是**订单创建后用于执行发货/兑换的外部履约平台**。  
它和订单来源平台的系统角色完全不同：

- `91卡券`：订单来源 / 上游平台；
- `cloudtentacles`：履约执行 / 下游发货平台。

它的登录链路独立于订单来源：

- 不是图片验证码识别；
- 是“账号 + 密码 + 手机号 + 短信验证码”登录；
- 登录参数不是明文提交，而是前端先做 `MD5 + RSA-OAEP(SHA-256)` 加密；
- 登录成功后不是 Cookie 会话，而是返回 `token`，后续通过 `Authorization` 请求头访问接口。

因此它应当作为**履约执行器能力**接入，不要按“来源平台”建模。

---

## 2. 当前已确认事实

### 2.1 证据来源

- HAR：`/Users/yml/Desktop/抓包/123.207.217.176.har`
- 前端代码：`tems/test1.js`
- 打包代码：`tems/index.93fe093c.js`

### 2.2 已确认接口链路

1. 发送短信验证码  
   `POST https://123.207.217.176/public/verif_code`

2. 提交登录  
   `POST https://123.207.217.176/public/login`

3. 登录后探活接口  
   - `POST /user/info`
   - `GET /user/get_asset`
   - `GET /user/get_permission`

4. 当前 HAR 里还看到的业务接口  
   - `GET /categories/get`
   - `GET /sku/list`

### 2.3 登录态机制

不是依赖 Cookie。

前端在登录成功后会把 `/public/login` 返回的 `data` 当成 token 保存，并在后续请求里自动带上：

```http
Authorization: <token>
deviceid: -
devicetype: 0
```

### 2.4 登录参数的真实结构

根据 `tems/test1.js`，发验证码与登录都不是直接提交原始字段，而是先调用 `encryptWithPublicKey(...)`。

#### 发验证码原始参数

```js
{
  account: username,
  phone: phone
}
```

#### 登录原始参数

```js
{
  account: username,
  password: MD5(password),
  phone: phone,
  code: smsCode
}
```

#### 加密前最终明文

```js
{
  t: Date.now(),
  r: Math.random().toString(16),
  s: 'ct-client',
  ...payload
}
```

然后：

1. `JSON.stringify`
2. 使用 RSA-OAEP + SHA-256 公钥加密
3. Base64 编码，作为 `covert`

最终请求体：

```json
{
  "covert": "<base64>",
  "t": 1777726021956,
  "r": "0.8601863c152d6"
}
```

### 2.5 密码处理

密码不是明文传输。

前端代码中：

```js
password: cu(password)
```

而 `cu` 实际是：

```js
MD5(password).toString(Hex)
```

即：

- 先 `MD5`
- 再参与 RSA 加密

---

## 3. 设计目标

## 3.1 第一阶段目标

这一版只做“基础登录能力”，不直接做完整发货自动化：

1. 能发送短信验证码；
2. 能使用账号、密码、手机号、短信验证码自动登录；
3. 能持久化保存该平台的履约配置；
4. 能在后台手动测试登录；
5. 能在登录后调用一个轻量探活接口确认登录成功；
6. 输出统一的会话对象，给后续“SKU 查询 / 背包读取 / 发货执行”复用。

## 3.2 第二阶段目标

后续逐步补：

1. token 缓存与自动续登；
2. SKU/分类/背包读取；
3. 发货动作封装；
4. 发货结果记录与截图/证据保存；
5. 接成新的 `executor_key`，纳入现有履约任务执行链路；
6. 由“订单支付成功 / 任务就绪”触发自动发货。

---

## 4. 关键设计决策

## 4.1 领域定位

这里不应该新增 `provider = 'cloudtentacles'` 作为订单来源标识。

更合理的定位是：

- 订单来源 `provider/platform/shopId` 仍然来自 `agiso` / `91卡券`
- `cloudtentacles` 属于**履约执行器 / 外部发货平台**

因此它更适合进入这条链路：

- `fulfillment_profiles.profile_key`
- `fulfillment_profiles.executor_key`
- 任务执行服务

建议后续新增类似：

- `profileKey = 'cloudtentacles_dispatch'`
- `executorKey = 'cloudtentacles_dispatch'`

而不是把它混进订单来源层。

## 4.2 接入模式

这个平台本质上是 **外部履约平台 + token 会话** 模式。

因此结构上可以先放在 `platforms/` 下管理其登录与 API 封装，但业务接入点要落到**履约执行层**：

- 配置服务
- 加密服务
- HTTP 客户端
- 登录/会话服务
- 发货业务服务
- 履约执行适配层

## 4.3 不建议照搬来源平台的原因

历史拉单平台链路核心是：

- 打开登录页
- 拉验证码图片
- OCR
- 表单提交
- Cookie 维持会话

而当前平台核心是：

- 组装明文
- MD5 密码
- RSA 公钥加密
- 请求登录接口
- token 维持会话

所以如果把所有逻辑都塞进一个 `session-service.js`，后面会越来越乱。  
更合理的方式是把“加密”、“请求头”、“token 管理”单独拆开。

## 4.4 第一版不引入浏览器自动化

当前证据显示不需要浏览器：

- HAR 中登录接口是纯 XHR；
- 参数生成逻辑已经从 JS 中还原；
- token 机制清晰；
- 没看到依赖浏览器页面上下文的动态签名。

因此第一版应坚持 **纯 HTTP 实现**。

只有后续遇到这些问题时，才考虑浏览器方案：

- 服务端增加不可还原的前端签名；
- 短信验证码流程强依赖页面态；
- 登录成功但服务端对业务接口做浏览器环境校验。

---

## 5. 推荐目录结构

建议新增目录：

`apps/backend/src/services/platforms/cloudtentacles/`

同时第二阶段补一个执行适配入口，例如：

`apps/backend/src/services/fulfillment/cloudtentacles-dispatch-service.js`

### 5.1 第一阶段实际落地文件

#### `shared.js`

职责：

- 读取运行时配置；
- 规范化 baseUrl / 路径 / timeout；
- 构建 URL；
- 构建公共请求头；
- 统一设备头默认值。

建议内容：

- `resolveCloudtentaclesConfig(overrides)`
- `buildCloudtentaclesUrl(baseUrl, pathname, searchParams?)`
- `buildCloudtentaclesHeaders({ token?, contentType? })`

#### `crypto-service.js`

职责：

- 密码 MD5；
- 公钥 PEM 转 ArrayBuffer / Buffer；
- 组装 `t/r/s` 明文；
- 执行 RSA-OAEP(SHA-256) 加密；
- 返回 `{ covert, t, r }`

建议暴露：

- `md5CloudtentaclesPassword(password)`
- `encryptCloudtentaclesPayload(payload, options?)`

这样后续不仅登录能用，发验证码、改密等其它加密接口也都能复用。

#### `http-client.js`

职责：

- 对 `fetch` 做一层轻封装；
- 统一 timeout；
- 自动带 `Authorization / deviceid / devicetype`；
- 统一解析平台响应格式；
- 统一抛出带平台上下文的错误。

建议暴露：

- `cloudtentaclesRequest(path, options)`

注意：第一版不用做成全局通用 HTTP 框架，只服务这个平台即可。

#### `session-service.js`

职责：

- 发送短信验证码；
- 使用短信验证码登录；
- 登录后调用 `/user/info` 验证会话；
- 返回标准会话对象。

建议暴露：

- `sendCloudtentaclesSmsCode(payload)`
- `loginCloudtentaclesSession(payload)`
- `validateCloudtentaclesSession(payload)`

建议会话对象：

```js
{
  baseUrl,
  token,
  loggedInAt,
  username,
  phone,
  userInfo,
  permissions
}
```

#### `source-config-service.js`

职责：

- 读写履约平台配置文件；
- 管理默认账号、密码、手机号；
- 管理后续自动化开关预留字段。

建议文件：

`apps/backend/data/cloudtentacles-sources.json`

建议结构：

```json
{
  "enabled": true,
  "baseUrl": "https://123.207.217.176",
  "username": "",
  "password": "",
  "phone": "",
  "deviceId": "-",
  "deviceType": 0
}
```

### 5.2 第二阶段预留文件

这些先不一定创建实现，但目录命名先按这个方向约束：

#### `catalog-service.js`

- 分类
- SKU
- 背包
- 库存类读取

#### `delivery-service.js`

- 发货动作
- CDK / 虚拟物品使用
- 回滚 / 失败重试

#### `record-service.js`

- 交易记录
- 发货记录
- 订单对账

#### `auto-sync-service.js`

- 定时探活
- 自动续登
- 预热会话

#### `apps/backend/src/services/fulfillment/cloudtentacles-dispatch-service.js`

职责：

- 接收履约任务；
- 读取任务绑定的库存 / 凭据；
- 调用 `platforms/cloudtentacles/*` 完成实际发货；
- 回写任务状态、发货结果、证据。

---

## 6. 配置设计

## 6.1 运行时配置

建议在：

- `apps/backend/config/default.cjs`
- `apps/backend/src/types/runtime-config.js`
- `apps/backend/src/config/runtime.js`

增加：

```js
platforms: {
  cloudtentacles: {
    baseUrl: 'https://123.207.217.176',
    timeoutMs: 5000,
    sendSmsPath: '/public/verif_code',
    loginPath: '/public/login',
    userInfoPath: '/user/info',
    assetPath: '/user/get_asset',
    permissionPath: '/user/get_permission',
    publicKeyPem: '-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----',
    clientSource: 'ct-client',
    deviceId: '-',
    deviceType: 0,
  },
}
```

### 设计说明

- `publicKeyPem` 放运行时配置，而不是硬编码到 service；
- `clientSource` 默认是 `ct-client`；
- `deviceId / deviceType` 先允许配置，避免后面服务端开始校验时需要改代码。

## 6.2 持久化来源配置

建议先用数据文件落地，不急着进数据库。

建议文件：

`apps/backend/data/cloudtentacles-sources.json`

建议第一版结构：

```json
{
  "enabled": true,
  "baseUrl": "https://123.207.217.176",
  "username": "",
  "password": "",
  "phone": "",
  "deviceId": "-",
  "deviceType": 0
}
```

后续如果要支持多套账号，再演进为：

```json
{
  "sources": [
    {
      "sourceKey": "cloudtentacles-main",
      "enabled": true,
      "baseUrl": "https://123.207.217.176",
      "username": "",
      "password": "",
      "phone": "",
      "deviceId": "-",
      "deviceType": 0
    }
  ]
}
```

第一版先单来源，能明显降低 UI 和逻辑复杂度。

---

## 7. API 设计建议

建议先在管理后台补一组最小接口：

### 7.1 配置读取

```http
GET /api/v1/admin/platform-config/cloudtentacles-source
```

返回：

- 配置文件路径
- 当前履约平台配置

### 7.2 配置保存

```http
POST /api/v1/admin/platform-config/cloudtentacles-source
```

用于保存：

- 账号
- 密码
- 手机号
- baseUrl
- 设备头默认值

### 7.3 发送短信验证码

```http
POST /api/v1/admin/platforms/cloudtentacles/send-sms-code
```

入参支持：

- 使用已保存配置；
- 或临时覆盖账号/手机号/baseUrl。

### 7.4 手动登录测试

```http
POST /api/v1/admin/platforms/cloudtentacles/login
```

入参：

- `username`
- `password`
- `phone`
- `code`

返回：

- `token`
- `userInfo`
- `permissions`
- `loggedInAt`

### 7.5 会话探活测试

```http
POST /api/v1/admin/platforms/cloudtentacles/validate-session
```

入参：

- `token`

返回：

- 是否有效
- 用户信息

---

## 8. 后台 UI 设计建议

第一版 UI 不需要太复杂，应避免把“订单来源配置”和“履约平台配置”混在一起。

建议在“平台配置”页新增独立区块，归类到“履约平台”分组：

### 8.1 履约平台配置区

字段：

- 启用状态
- baseUrl
- 账号
- 密码
- 手机号
- deviceId
- deviceType

操作：

- 保存配置

### 8.2 登录测试区

字段：

- 短信验证码输入框

操作：

- 发送验证码
- 执行登录
- 验证当前 token

### 8.3 调试结果区

显示：

- token 是否返回
- userInfo
- permissions
- 最近一次错误消息

注意：

- 不要把这个平台和订单来源放在同一张混合表单里；
- 页面上要明确区分：
  - 订单来源平台
  - 履约执行平台
- 后面平台越来越多时，这个结构才能撑住。

---

## 9. 错误处理与日志建议

建议统一给这个平台加明确日志前缀：

- `[cloudtentacles/crypto]`
- `[cloudtentacles/session]`
- `[cloudtentacles/http]`

第一版重点记录：

1. 发送验证码是否成功；
2. 登录是否成功；
3. `/user/info` 探活是否成功；
4. 平台返回的 `code/message`；
5. 不记录明文密码、短信验证码、完整 token。

建议在日志和接口返回里都做脱敏：

- 手机号只显示部分；
- token 只显示前后几位；
- password 永不回显。

---

## 10. 与现有履约架构的关系

当前项目里的履约主链路已经是：

1. 订单进入系统；
2. 根据 `provider/platform/shopId/sku` 命中 `order-fulfillment-bindings.json`；
3. 解析到 `fulfillment_profile`；
4. 创建 `delivery task`；
5. 根据 `executor_key` 决定后续执行方式。

所以 `cloudtentacles` 后续正确的接入位置是：

- 第一阶段：只补平台登录能力；
- 第二阶段：新增 `cloudtentacles_dispatch` 执行器；
- 第三阶段：把某些 SKU 的履约绑定切到这个执行器。

也就是说：

- `agiso/91卡券` 决定“订单从哪里来”
- `cloudtentacles` 决定“订单怎么发出去”

---

## 11. 实施顺序

建议按这个顺序落地：

### 第 1 步：后端基础模块

先实现：

- `shared.js`
- `crypto-service.js`
- `http-client.js`
- `session-service.js`
- `source-config-service.js`

目标：

- 本地可完成发送验证码；
- 收到短信后可手动输入验证码登录；
- 登录后可拉 `/user/info`。

### 第 2 步：后台接口

增加：

- 配置读取/保存
- 发送验证码
- 登录测试
- token 探活

### 第 3 步：后台 UI

增加：

- 单独的平台配置区
- 发送验证码与登录测试区
- 调试反馈区

### 第 4 步：后续业务能力

登录能力稳定后，再补：

- SKU/分类/背包读取
- 发货动作
- `cloudtentacles_dispatch` 执行器
- 自动发货链路

---

## 12. 当前阶段结论

这个平台不是新的订单数据源，而是新的**外部履约能力**。

第一版接入重点不是立刻改主流程，而是先把 **可复用的登录能力** 做扎实。

最重要的不是把接口先堆进去，而是先把结构定对：

- 历史拉单型平台：验证码图片 + Cookie
- `cloudtentacles` 型平台：短信码 + RSA 加密 + token

二者都属于“后台登录型平台”，但在系统分层上不同：

- `91卡券`：来源层
- `cloudtentacles`：履约层

因此第一版推荐结论是：

1. 作为独立履约平台能力接入；
2. 先做“配置 + 发验证码 + 登录 + 探活”；
3. 目录结构按“配置 / 加密 / HTTP / 会话 / 业务”拆开；
4. 后续通过新的 `executor_key` 接入履约任务执行，而不是改订单来源模型。
