Qwen Chat UI vs DeepSeek Proxy 对比分析

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 的分包明显更工程化。confighandlersproxy 三层各司其职,main.go 只有 107 行——这正是我之前建议 DeepSeek 做的拆分。Qwen 项目看起来是吸取了 DeepSeek 的经验后重新设计的


三、关键技术差异

3.1 反爬对抗

DeepSeek — 纯算法破解:

  
// 自实现 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 指纹伪装:

  
// 使用 uTLS 模拟 Chrome TLS 握手
  
conn := utls.UClient(tcpConn, &utls.Config{
  
    ServerName: host,
  
}, utls.HelloChrome_Auto)
  

DeepSeek 需要"算出正确答案",Qwen 需要"看起来像真浏览器"。两种路线,两种难度。

3.2 配置热加载

DeepSeek — mtime 轮询:

  
func reloadConfigIfNeeded() {
  
    fi, _ := os.Stat("config.json")
  
    if fi.ModTime().Equal(lastMod) { return }
  
    // 重新加载...
  
}
  

每个请求入口检查一次,简单但有最大 1 个请求的延迟。

Qwen — fsnotify 文件监听 + SIGHUP:

  
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(简单但有局限):

  
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 — 完整的多轮重放:

  
// 把 OpenAI messages 拆成 system + prefix + newTurn
  
// 缓存命中 → 只发 newTurn
  
// 缓存未命中 → 拼接完整历史重放
  
func buildPrompt(reuse bool, systemPrompt string, prefix []openAIMsg, newTurn string) string
  

Qwen — 扁平化拼接:

  
func FlattenMessages(msgs []openAIMessage) string {
  
    // "用户: xxx\n助手: xxx\n用户: xxx"
  
}
  

Qwen 直接把所有消息拼成一段文本发给 Qwen 的 t2t 接口。这丢失了多轮对话的结构化语义——Qwen 服务端无法区分哪条是历史、哪条是新消息。对于单轮问答够用,多轮对话质量会下降。


四、工程质量对比

4.1 中间件模式

DeepSeek: 只有一个简单的 corsMiddleware,无 panic 恢复、无请求日志。

Qwen: 完整的中间件链:

  
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:

  
log.Fatal(http.ListenAndServe(port, nil))  // 硬退出
  

Qwen:

  
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:

  
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 文件(逐行查找替换)

  
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 多格式兼容

  
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 识别

  
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 随机抖动

  
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:

  
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 的功能合在一起。🎯