ds2api / docs /web-endpoint-upgrade.md
luckfun233
feat: 新增数美SDK逆向研究报告及Web端接口实现文档
cf4c38d
|
Raw
History Blame Contribute Delete
12.8 kB
# Web端接口实现 & 数美SDK逆向研究报告
## 一、概述
本项目新增了对 DeepSeek **Web端接口**的支持,可通过设置面板在"Android端"和"Web端"之间热切换,无需重启服务。
### 核心功能
| 特性 | Android端 | Web端 |
|------|-----------|-------|
| User-Agent | `DeepSeek/2.1.2 Android/32` | Chrome 137 浏览器UA |
| TLS指纹 | Safari | Chrome (HTTP/2) |
| x-client-platform | `android` | `web` |
| device_id | `"android_device"` | 数美SDK加密字符串 |
| x-hif-dliq | 无 | 动态轮询获取 |
| x-hif-leim | 无 | 动态轮询获取 |
| 浏览器特有头 | 无 | sec-ch-ua, origin, referer等 |
### 架构设计
```
┌─────────────────────────────────────────────────────────────┐
│ 设置面板 (WebUI) │
│ 选择: Android / Web 接口平台 │
└─────────────────────────┬───────────────────────────────────┘
│ PUT /admin/settings
┌─────────────────────────────────────────────────────────────┐
│ Handler.applyPlatformChange() │
│ DS.SetPlatform("web" / "android") │
├─────────────────────────────────────────────────────────────┤
│ 1. dsprotocol.ApplyPlatform() → 切换 BaseHeaders / UA │
│ 2. RefreshTransport() → 切换 TLS 指纹 │
│ 3. hifPoller.Start()/Stop() → 启动/停止 HIF 轮询 │
└─────────────────────────────────────────────────────────────┘
```
切换后立即生效,新请求自动使用对应平台的 UA、TLS 指纹、Headers 和 device_id 算法。
---
## 二、文件变更清单
### 新增文件(8个)
| 文件 | 说明 |
|------|------|
| `internal/deepseek/protocol/constants_web.json` | Web端协议常量定义 |
| `internal/deepseek/hif/hif.go` | HIF头部轮询器 |
| `internal/deepseek/hif/hif_test.go` | HIF单元测试 |
| `internal/shumei/device_id.go` | 数美SDK device_id生成入口 |
| `internal/shumei/crypto.go` | RSA/DES/AES/MD5/Gzip加密工具 |
| `internal/shumei/confusion.go` | DES字段混淆映射表 |
| `internal/shumei/api.go` | 数美设备指纹API调用 |
| `webui/src/features/settings/PlatformSection.jsx` | 平台切换UI组件 |
### 修改文件(18个)
#### Config层
- `internal/config/config.go` — PlatformConfig结构体、PlatformMode()、IsWebPlatform()
- `internal/config/codec.go` — JSON序列化支持
- `internal/config/store_accessors.go` — Store级访问器
- `internal/config/validation.go` — 平台配置校验
#### Protocol层
- `internal/deepseek/protocol/constants.go` — ApplyPlatform()、WebExtraHeaders()、UserAgent()
#### Transport层
- `internal/deepseek/transport/transport.go` — Chrome TLS指纹、NewWithPlatform()
#### Client层
- `internal/deepseek/client/client_core.go` — SetPlatform()、RefreshTransport()、HIF poller集成
- `internal/deepseek/client/client_auth.go` — Web端login(数美device_id)
- `internal/deepseek/client/client_completion.go` — Web端completion headers
- `internal/deepseek/client/client_continue.go` — Web端continue headers
- `internal/deepseek/client/proxy.go` — 平台感知代理客户端
#### Admin API
- `internal/httpapi/admin/shared/deps.go` — DeepSeekCaller接口添加SetPlatform
- `internal/httpapi/admin/settings/handler_settings_*.go` — 设置保存/读取/热加载
#### 前端
- `webui/src/features/settings/SettingsContainer.jsx` — 集成PlatformSection
- `webui/src/features/settings/useSettingsForm.js` — 平台表单数据流
- `webui/src/locales/zh.json` / `en.json` — 国际化翻译
---
## 三、数美SDK逆向分析报告
### 3.1 SDK基本信息
- **SDK名称**:数美科技(Shumei)DeviceFingerprint Web SDK
- **版本**:3.0.0 / subVersion 1.0.0
- **加载方式**:通过 `https://cdn.deepseek.com/static/chat/fp-1.min.js` 动态加载
- **配置来源**`window._smConf`(由DeepSeek页面注入)
- **API端点**`https://fp-it-acc.portal101.cn/deviceprofile/v4`
### 3.2 device_id生成流程
```
1. 生成UUID v4 (uid)
2. 计算priId = md5(uid)[:16] ────→ 作为AES密钥
3. RSA加密uid (PKCS#1 v1.5) ────→ ep字段
4. 收集23项浏览器指纹数据
5. DES-ECB逐字段加密+字段名混淆
6. 计算tn = md5(sorted key=value) ──→ 完整性校验
7. JSON → Gzip压缩
8. AES-CBC加密 (priId + IV="0102030405060708" + ZeroPadding)
9. POST到数美API
10. 解析响应 code=1100 → detail.deviceId
11. getDeviceId() = "B" + deviceId
```
### 3.3 关键配置参数
| 参数 | 值 | 说明 |
|------|-----|------|
| RSA公钥 | `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDetfEgYD4aE1ZjmWJ6/jnPurhzI+yeRoJHWrnNtQMte3stQ4VjG3yu21FuN75E6cDpA9KtDXwcB2M/FiGUAe3G0rNotbWI8+SjZfUbW/OILFTzY0uaeEkmVGW5WyJ6weQbbr1xTCPa2OO3YIMeZljWUYHG5h21WAm/PATg8im8cQIDAQAB` | 用于加密uid |
| organization | `P9usCUBauxft8eAmUXaZ` | 设备标识归属 |
| appId | `default` | 应用ID |
| apiHost | `fp-it-acc.portal101.cn` | 数美API域名 |
### 3.4 指纹数据收集(23项)
| 字段 | 来源 | 说明 |
|------|------|------|
| ua | navigator.userAgent | 浏览器UA |
| platform | navigator.platform | Win32 |
| canvas | canvas 2D绘图 | 绘制'http://www.ishumei.com'文字取哈希 |
| timezone | Date.getTimezoneOffset() | 时区偏移 |
| res | screen.width/height/colorDepth/devicePixelRatio | 屏幕信息 |
| plugins | navigator.plugins | 浏览器插件列表 |
| referer | document.referrer | 来源页面 |
| url | location.href | 当前URL |
| clientSize | screenLeft/screenTop/body尺寸 | 窗口大小 |
| vpw | UUID v4 | 随机值 |
| svm | 时间戳 | 当前时间 |
| trees | UUID v4 | 随机值 |
| pmf | 时间戳 | 当前时间 |
| cpucount | navigator.hardwareConcurrency | CPU核心数 |
| battery | navigator.getBattery() | 电池状态 |
| connectionRtt | navigator.connection.rtt | 网络延迟 |
| maxTouchPoints | navigator.maxTouchPoints | 触控点数 |
| status | Flash检测+webdriver检测 | 环境检测 |
| rtype | 固定值"1" | 类型标识 |
| sdkver | "3.0.0" | SDK版本 |
| subVersion | "1.0.0" | 子版本 |
| appId | "default" | 应用ID |
| organization | 配置值 | 组织标识 |
### 3.5 DES字段混淆
每个指纹字段使用独立的8字节DES密钥进行ECB模式加密,然后Base64编码,最后字段名映射为混淆名称。
**混淆映射表(部分)**
| 原字段 | DES密钥 | 混淆名 |
|--------|---------|--------|
| appId | `q8zb1bs1` | vw |
| organization | `ht432iov` | tj |
| os | `i7g8jtql` | vh |
| ua | `z5jc9qyp` | wr |
| canvas | `qy1d6fmu` | mi |
| plugins | `j916a142` | vc |
| referer | `rf149ntc` | hv |
| res | `rbtd2cl6` | pi |
| timezone | `3w9lg8pr` | oi |
| svm | `mqcala0h` | nu |
| trees | `al8x9zt7` | hc |
| vpw | `xzvnu5jt` | st |
| protocol | (不加密) | protocol |
### 3.6 加密算法详解
#### RSA加密(uid → ep)
- 使用JSEncrypt库
- PKCS#1 v1.5填充
- 输出为Base64字符串
#### DES-ECB加密(字段混淆)
- 每个字段独立使用8字节密钥
- ECB模式,逐块加密
- 输出Base64编码
#### AES-CBC加密(数据体)
- 密钥:`priId`(md5(uid)[:16],UTF-8编码)
- IV:`0102030405060708`
- 模式:CBC
- 填充:ZeroPadding
- 输入:gzip压缩后的JSON
- 输出:hex字符串
#### 完整性校验(tn字段)
```
tn = md5(sorted_key_value_string(data))
```
对所有key排序后拼接`key+value`为字符串,取MD5。
### 3.7 API请求格式
**请求**:
```
POST https://fp-it-acc.portal101.cn/deviceprofile/v4
{
"appId": "default",
"organization": "P9usCUBauxft8eAmUXaZ",
"ep": "RSA加密的uid(hex)",
"data": "AES加密后的hex字符串",
"os": "web",
"encode": 5,
"compress": 2
}
```
**成功响应**
```json
{
"code": 1100,
"detail": {
"deviceId": "XkNeGKe7f4Y5FtZamaxQPqL3ypegujSyds5eQGup13ajuer9XhBAM82w1QWifIm6KUNv2hu3tUTMwoXrVzD0ZA=="
}
}
```
最终返回的device_id为:`"B" + deviceId`
### 3.8 Go实现要点
1. **不需要浏览器**:device_id由数美服务器生成,客户端只负责发送指纹数据和接收结果
2. **指纹数据可伪造**:服务端不校验指纹真实性,只需要格式正确
3. **关键依赖**:crypto/des、crypto/aes、crypto/rsa、crypto/md5、compress/gzip
4. **DES-ECB**:Go标准库不提供ECB模式,需手动逐块实现
5. **AES ZeroPadding**:标准库只提供PKCS7,需手动补零
---
## 四、HIF头部轮询机制
### 4.1 概述
Web端每个请求需要携带两个特殊头部:
- `x-hif-dliq`
- `x-hif-leim`
这两个值通过轮询DeepSeek的两个端点动态获取。
### 4.2 端点
| 类型 | URL | 对应头部 |
|------|-----|----------|
| dliq | `https://hif-dliq.deepseek.com/query` | x-hif-dliq |
| leim | `https://hif-leim.deepseek.com/query` | x-hif-leim |
### 4.3 响应格式
```json
{
"code": 0,
"data": {
"biz_code": 0,
"biz_data": {
"value": "9lspMrX9S0NlRuD7+ChfCz3ENqJPjH4qnMfnlPAwq0wfRuH2nmSepbg=.xuVOOVjUztQikXFf"
}
}
}
```
响应头 `x-hif-ttl` 包含缓存有效期(秒),默认600秒。
### 4.4 轮询策略
```
初始化 → 启动两个goroutine轮询
├── 成功 → 缓存value → 等待TTL秒 → 继续轮询
└── 失败 → 指数退避(1s→2s→4s→...→max 600s) → 重试
```
- 初始重试间隔:1秒
- 退避倍数:2x
- 最大间隔:600秒
- 成功间隔:TTL(从响应头获取)
### 4.5 Go实现
`internal/deepseek/hif/hif.go` 实现了完整的轮询逻辑:
```go
type HIFPoller struct {
mu sync.RWMutex
dliq string
leim string
stopCh chan struct{}
client *trans.Client // 使用Chrome TLS
fallback *http.Client // 标准HTTP fallback
}
func (p *HIFPoller) Start() // 启动两个轮询goroutine
func (p *HIFPoller) Stop() // 停止轮询
func (p *HIFPoller) GetHeaders() map[string]string // 获取当前HIF头部
```
---
## 五、TLS指纹对比
### Android端
```
ClientHello: HelloSafari_Auto
ALPN: 强制 http/1.1 (forceHTTP11ALPN)
```
### Web端
```
ClientHello: HelloChrome_Auto
ALPN: 自然协商 (支持 h2 和 http/1.1)
ForceAttemptHTTP2: true
```
实现位于 `internal/deepseek/transport/transport.go`
```go
func TLSDialerForPlatform(platform string) func(...) {
if platform == "web" {
return chromeTLSDialer(dialContext) // Chrome + HTTP/2
}
return safariTLSDialer(dialContext) // Safari + HTTP/1.1
}
```
---
## 六、使用方式
### 6.1 设置面板切换
访问 WebUI 设置页面,在"接口平台"选项中选择:
- **Android端(默认)**:使用Safari TLS指纹,device_id为固定值
- **Web端(Chrome浏览器)**:使用Chrome TLS指纹+HTTP/2,动态生成数美device_id,轮询HIF头部
切换后**立即生效**,无需重启服务。
### 6.2 环境变量
```bash
# 也可通过环境变量设置默认平台
DS2API_PLATFORM_MODE=web
```
### 6.3 API设置
```bash
curl -X PUT http://localhost:5001/admin/settings \
-H "Authorization: Bearer admin" \
-H "Content-Type: application/json" \
-d '{"platform":{"mode":"web"}}'
```
---
## 七、兼容性说明
- **默认行为不变**:未设置platform时默认为"android"
- **向后兼容**:所有现有配置和API保持不变
- **热切换**:切换平台时自动重建HTTP客户端、更新headers、启停HIF轮询
- **代理兼容**:使用代理时同样应用正确的TLS指纹和headers
---
## 八、测试状态
```
✅ go build ./... 编译通过
✅ go vet (核心包) 静态检查通过
✅ go test deepseek/... deepseek包测试通过
✅ go test shumei/... 数美包测试通过
✅ npm run build webui 前端构建通过
```
> 注意:`TestGetSettingsIncludesCurrentInputFileDefaults` 是预先存在的测试失败,与本次改动无关。