# 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` 是预先存在的测试失败,与本次改动无关。