兰 亭 墨 苑
期货 · 量化 · AI · 终身学习
首页
归档
编辑文章
标题 *
URL 别名 *
内容 *
(支持 Markdown 格式)
# Qwen Chat UI vs DeepSeek Proxy 对比分析 --- ## 一、项目定位对比 | 维度 | DeepSeek Proxy | Qwen Chat UI | |------|---------------|--------------| | 目标服务 | chat.deepseek.com | chat.qwen.ai | | 认证方式 | Bearer Token + PoW + HMAC 签名 | Cookie + bx-ua + bx-umidtoken | | 反爬难度 | 高(PoW 求解 + 签名计算) | 中(TLS 指纹 + WAF 滑块) | | 多账号 | ✅ 主备池 + 故障切换 | ❌ 单账号 | | 会话管理 | 服务端维护 session_id 链 | 依赖客户端传入 chat_id | | 代码量 | ~2322 行 Go + 746 行 HTML | ~1451 行 Go | | 外部依赖 | **零**(纯标准库) | fsnotify + uTLS(2个) | **核心差异:** DeepSeek 的逆向难度显著更高(需要破解 PoW 算法 + HMAC 签名),而 Qwen 的难点在于 TLS 指纹伪装和 WAF 绕过。 --- ## 二、架构设计对比 ### DeepSeek:单包扁平结构 ``` package main ├── main.go (1265行 - 路由+业务+配置+OpenAI适配) ├── account.go (189行 - 账号池) ├── pow.go (294行 - PoW算法) ├── sse.go (302行 - SSE解析) └── wechat_push.go (228行 - 告警) ``` 所有代码在 `package main` 中,通过文件名划分职责。简单直接,但 `main.go` 承载过重。 ### Qwen Chat UI:多包分层结构 ``` main.go (107行 - 入口+优雅关闭) ├── config/ │ ├── config.go (355行 - 配置+热加载+Cookie解析) │ └── config_test.go (142行) ├── handlers/ │ ├── handlers.go (538行 - HTTP处理器) │ ├── handlers_test.go (53行) │ └── middleware.go (73行 - Recovery/Logging/CORS) └── proxy/ ├── client.go (142行 - HTTP客户端+uTLS) └── keepalive.go (41行 - 心跳保活) ``` **评价:** Qwen 的分包明显更工程化。`config`、`handlers`、`proxy` 三层各司其职,`main.go` 只有 107 行——这正是我之前建议 DeepSeek 做的拆分。Qwen 项目看起来是**吸取了 DeepSeek 的经验后重新设计的**。 --- ## 三、关键技术差异 ### 3.1 反爬对抗 **DeepSeek — 纯算法破解:** ```go // 自实现 Keccak-f[1600] 的 23 轮变体 func keccakF23(s *[25]uint64) { ... } // 暴力搜索 nonce func SolvePow(ctx, challengeHex, salt, expireAt, difficulty) (int64, error) { ... } // HMAC 签名 func generateHifLeim(token string, answer int) string { ... } ``` **Qwen — TLS 指纹伪装:** ```go // 使用 uTLS 模拟 Chrome TLS 握手 conn := utls.UClient(tcpConn, &utls.Config{ ServerName: host, }, utls.HelloChrome_Auto) ``` DeepSeek 需要"算出正确答案",Qwen 需要"看起来像真浏览器"。两种路线,两种难度。 ### 3.2 配置热加载 **DeepSeek — mtime 轮询:** ```go func reloadConfigIfNeeded() { fi, _ := os.Stat("config.json") if fi.ModTime().Equal(lastMod) { return } // 重新加载... } ``` 每个请求入口检查一次,简单但有最大 1 个请求的延迟。 **Qwen — fsnotify 文件监听 + SIGHUP:** ```go func WatchEnvFiles() { watcher, _ := fsnotify.NewWatcher() watcher.Add(dir) // 300ms 防抖 // 支持 kill -HUP <pid> 强制重载 } ``` Qwen 的方案更优雅:实时监听 + 防抖 + 信号触发,三重保障。但引入了 `fsnotify` 依赖。 ### 3.3 多轮对话 **DeepSeek — 服务端会话缓存(复杂但完整):** - 双层指纹(全量 SHA256 + 尾部锚点) - 每账号独立 64 条 LRU 缓存 - 自动创建 session、维护 parent_message_id 链 - 客户端完全无感知 **Qwen — 客户端传入 chat_id(简单但有局限):** ```go chatID := r.URL.Query().Get("chat_id") if chatID == "" { chatID = r.Header.Get("X-Chat-Id") } if chatID == "" { // 报错:需要先在 chat.qwen.ai 创建对话 } ``` Qwen 要求用户**先在网页端创建对话**,然后把 `chat_id` 传给代理。这意味着: - 无法自动创建新对话 - 多轮对话依赖 Qwen 服务端的 session 管理 - 代理本身是无状态的(更简单,但功能受限) ### 3.4 消息处理 **DeepSeek — 完整的多轮重放:** ```go // 把 OpenAI messages 拆成 system + prefix + newTurn // 缓存命中 → 只发 newTurn // 缓存未命中 → 拼接完整历史重放 func buildPrompt(reuse bool, systemPrompt string, prefix []openAIMsg, newTurn string) string ``` **Qwen — 扁平化拼接:** ```go func FlattenMessages(msgs []openAIMessage) string { // "用户: xxx\n助手: xxx\n用户: xxx" } ``` Qwen 直接把所有消息拼成一段文本发给 Qwen 的 `t2t` 接口。这**丢失了多轮对话的结构化语义**——Qwen 服务端无法区分哪条是历史、哪条是新消息。对于单轮问答够用,多轮对话质量会下降。 --- ## 四、工程质量对比 ### 4.1 中间件模式 **DeepSeek:** 只有一个简单的 `corsMiddleware`,无 panic 恢复、无请求日志。 **Qwen:** 完整的中间件链: ```go h = handlers.RecoveryMiddleware(h) // panic → 500 + 堆栈日志 h = handlers.LoggingMiddleware(h) // 每请求记录 method/path/status/duration h = handlers.CORSMiddleware(h) // CORS + OPTIONS 预检 ``` 还有 `responseWriter` 包装器捕获状态码。这是**生产级 HTTP 服务**的标准做法。 ### 4.2 优雅关闭 **DeepSeek:** ```go log.Fatal(http.ListenAndServe(port, nil)) // 硬退出 ``` **Qwen:** ```go srv := &http.Server{ ReadTimeout: 30 * time.Second, WriteTimeout: 300 * time.Second, IdleTimeout: 120 * time.Second, } // SIGINT/SIGTERM → 10s 优雅关闭 quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // srv.Shutdown(ctx) ``` Qwen 有完整的超时配置和优雅关闭,DeepSeek 没有。 ### 4.3 保活机制 **DeepSeek:** 无(依赖请求触发) **Qwen:** ```go func StartKeepalive() { // 每 2-5 分钟随机间隔 // GET /api/v2/config(保持 Cookie 活跃) // POST /api/analytics(模拟前端心跳) } ``` 这很关键——Qwen 的 Cookie 可能因为长时间不活跃而过期,keepalive 模拟浏览器心跳延长有效期。DeepSeek 的 Token 过期机制不同(固定有效期),所以不需要。 ### 4.4 配置持久化 **DeepSeek:** 通过 API 写入 config.json(原子写入) **Qwen:** 通过 API 修改 .env 文件(逐行查找替换) ```go func UpdateEnvFile(key, value string) { // 找到 KEY= 开头的行,替换值 // 找不到则追加 } ``` 两者都支持运行时修改配置,但 Qwen 的 `.env` 格式更通用(兼容 Docker、systemd EnvironmentFile)。 --- ## 五、测试对比 | | DeepSeek | Qwen Chat UI | |--|--|--| | 测试行数 | 186 行 | 195 行 (142+53) | | 覆盖模块 | 账号池状态机 | 配置解析 + 消息处理 | | 测试质量 | 高(含边界条件、去重逻辑) | 中(主要是 happy path) | | 缺失 | PoW、SSE 解析 | HTTP handler 集成测试、uTLS | 两者测试覆盖度相当,都覆盖了核心逻辑但缺少端到端测试。 --- ## 六、安全性对比 | 风险点 | DeepSeek | Qwen Chat UI | |--------|----------|--------------| | 管理接口认证 | 硬编码 `deepseek` | **无认证**(/api/config 裸奔) | | CORS | `*` 全开放 | `*` 全开放 | | 凭据存储 | config.json (0600) | .env 文件 (0644 ⚠️) | | Token 暴露 | /admin/config 返回明文 | /api/config 不返回 Cookie(只返回 `cookie_configured: bool`)✅ | | WAF 检测 | 无(DeepSeek 无 WAF) | 有检测(aliyun_waf 识别)✅ | **Qwen 做得更好的地方:** `/api/config` GET 不返回 Cookie 原文,只返回 `cookie_configured: true/false`。DeepSeek 的 `/admin/config` 会返回完整 Token。 **Qwen 做得更差的地方:** `.env` 文件权限是 `0644`(所有用户可读),DeepSeek 的 config.json 是 `0600`。 --- ## 七、Qwen 项目的独特亮点 ### 7.1 Cookie 多格式兼容 ```go func ParseCookie(input string) string { // 支持三种格式: // 1. JSON 数组: [{"name":"cna","value":"xxx"},...] // 2. JSON 对象: {"cna":"xxx","token":"eyJ..."} // 3. 原始字符串: "cna=xxx; token=eyJ..." } ``` 这非常实用——不同浏览器扩展导出的 Cookie 格式不同,一次性兼容避免了用户手动转换。 ### 7.2 WAF 识别 ```go waf := strings.Contains(snippet, "aliyun_waf") || strings.Contains(snippet, "_waf_") if waf { errMsg = "blocked by Qwen WAF (slider challenge) -- refresh Cookie/bx-ua or check IP geo" } ``` 当 Qwen 返回 WAF 滑块验证时,代理能识别并给出明确提示,而不是返回一个莫名其妙的 HTML 页面。 ### 7.3 随机抖动 ```go func RandomJitter() time.Duration { return time.Duration(rand.Intn(JitterMax-JitterMin)+JitterMin) * time.Millisecond } // 代理请求前 sleep 300-2000ms time.Sleep(config.RandomJitter()) ``` 模拟人类操作间隔,降低被频控检测的概率。DeepSeek 没有这个(因为 PoW 本身就是"证明我是人")。 ### 7.4 运行时配置 API ``` GET /api/config → 查看当前模型、温度等 POST /api/config → 修改模型、温度、max_tokens、top_p ``` 无需重启、无需编辑文件,HTTP 调用即可切换模型。DeepSeek 只有 Token 管理 API,没有推理参数调整。 --- ## 八、Qwen 项目的不足 ### 8.1 多轮对话能力弱 `FlattenMessages` 把所有历史拼成一段文本,Qwen 服务端无法正确理解对话结构。对比 DeepSeek 的 session 缓存 + parent_message_id 链,Qwen 的多轮体验会明显差。 ### 8.2 无故障恢复 单账号设计意味着 Cookie 过期 = 服务完全不可用。没有告警、没有自动恢复、没有备用账号。 ### 8.3 无流式解析 Qwen 的流式路径直接透传上游 SSE: ```go proxy.CopyWithFlush(w, resp.Body, make([]byte, 4096)) ``` 不做任何解析、不提取 citation、不处理 thinking 标签。非流式路径才做 SSE 解析拼装。DeepSeek 对流式/非流式都有完整的解析和 citation 处理。 ### 8.4 管理接口无认证 `/api/config` POST 可以修改模型、base_url 等关键配置,但**没有任何认证**。如果暴露在内网以外,任何人都能篡改配置。 --- ## 九、综合评分对比 | 维度 | DeepSeek | Qwen Chat UI | |------|:---:|:---:| | 逆向难度 | ★★★★★ | ★★★☆☆ | | 架构设计 | ★★★★☆ | ★★★★★ | | 代码质量 | ★★★★☆ | ★★★★☆ | | 多轮对话 | ★★★★★ | ★★☆☆☆ | | 容错/高可用 | ★★★★★ | ★★☆☆☆ | | 安全性 | ★★★☆☆ | ★★★☆☆ | | 可运维性 | ★★★★★ | ★★★★☆ | | 测试 | ★★★☆☆ | ★★★☆☆ | | 文档 | ★★★★★ | ★★☆☆☆(无 README) | | 实用性 | ★★★★★ | ★★★★☆ | --- ## 十、总结 这两个项目是**同一作者、同一思路、不同阶段**的产物: > **DeepSeek Proxy 是"先跑起来再说"的实战派;Qwen Chat UI 是"想清楚再动手"的工程派。** 从 Qwen 项目中能看到明显的"吸取教训"痕迹: - DeepSeek 的 `main.go` 1265 行 → Qwen 拆成 4 个包,main 只有 107 行 - DeepSeek 无优雅关闭 → Qwen 有完整的 signal handling + timeout - DeepSeek 无中间件 → Qwen 有 Recovery + Logging + CORS 三件套 - DeepSeek 无 keepalive → Qwen 有随机间隔心跳 - DeepSeek 配置用 mtime 轮询 → Qwen 用 fsnotify + SIGHUP 但 DeepSeek 在**业务复杂度**上远超 Qwen:多账号池、PoW 求解、会话指纹缓存、citation 解析、微信告警——这些都是 Qwen 没有的。Qwen 目前更像一个"轻量透传代理",而 DeepSeek 是一个"完整的 AI 网关"。 如果要用一句话概括: > **DeepSeek 是"功能驱动"的极致,Qwen 是"架构驱动"的起点。** 理想状态是把 Qwen 的架构 + DeepSeek 的功能合在一起。🎯
配图 (可多选)
选择新图片文件或拖拽到此处
标签
更新文章
删除文章