跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScriptNode.jsAI大前端

Qwen3-32B 集成实战:Clawdbot Web 网关配置与 CORS 问题解决

Qwen3-32B 模型通过 Ollama 服务部署时,前端 Clawdbot 页面常因跨域策略无法直接调用 API。介绍使用轻量级 Node.js 网关代理转发请求,解决浏览器同源限制导致的 CORS 报错。核心步骤包括编写 gateway.js 脚本处理 POST 请求转换、注入 Access-Control-Allow-Origin 头、支持流式响应 SSE,以及配置前端指向新端口。同时涵盖预检请求 OPTIONS 处理、多模型路由扩展及健康检查端点实现,确保本地开发环境稳定运行,无需 Nginx 或 Docker 依赖。

NodeJser发布于 2026/4/9更新于 2026/5/2217 浏览

Qwen3-32B 集成实战:Clawdbot Web 网关配置与 CORS 问题解决

1. 为什么需要 Web 网关与跨域处理

你是不是也遇到过这样的情况:本地跑通了 Qwen3-32B 模型,Ollama 服务正常响应,Clawdbot 前端页面也能打开,但一点击发送按钮,控制台就报错——CORS policy: No 'Access-Control-Allow-Origin' header is present? 这不是模型没跑起来,也不是代码写错了,而是浏览器在'多管闲事':它默认禁止网页向不同源(协议、域名、端口任一不同)的后端发起请求。而我们典型的开发结构是——

  • 前端页面运行在 http://localhost:3000(Clawdbot Web 界面)
  • Ollama API 默认监听 http://localhost:11434/api/chat
  • 中间又加了一层代理转发到 18789 端口

三者端口全不一致,浏览器直接拦截请求,连请求都发不出去。 所以,网关不是可选项,而是必选项;CORS 不是小问题,而是阻断整个交互链路的关键门槛。本文不讲抽象理论,只聚焦一件事:怎么用最轻量、最稳定、最易维护的方式,把 Qwen3-32B 真正'接进'你的 Web 聊天界面。

2. 整体架构与角色分工

2.1 各组件职责一目了然
组件运行位置职责默认端口是否暴露给前端
Qwen3-32B 模型本地服务器执行推理,生成文本——(由 Ollama 托管)❌ 不直连
Ollama 服务localhost提供标准 /api/chat 接口11434❌ 浏览器无法直调
Clawdbot Web 前端浏览器渲染聊天界面,发送请求3000(开发)或 80(生产)用户直接访问
Web 网关代理localhost接收前端请求,转发至 Ollama,注入 CORS 头18789前端唯一通信目标

关键理解:Clawdbot 前端只和网关说话,网关再替它去和 Ollama'交涉'。网关的核心任务有三个——把 POST /v1/chat/completions 这类前端请求,改写成 Ollama 能认的 /api/chat 格式;在响应里加上 Access-Control-Allow-Origin: * 等必要头,让浏览器放行;处理流式响应(SSE),把 Ollama 返回的 data: {...} 分块正确透传给前端。

2.2 为什么选 18789 端口?不是随便定的

你可能注意到,文档里反复出现 18789。这不是一个玄学数字,而是经过实测验证的'安全端口':

  • 它避开了常见服务端口(如 8080 常被其他开发服务占用,3000/5000 是前端默认端口);
  • 它高于 1024,无需 root 权限即可绑定(Linux/macOS 下普通用户可直接启动);
  • 它在 10000–20000 区间内,既不冲突又便于记忆(18789 → '要发吧久',谐音提醒这是'对外发请求'的端口)。 实际部署时,你完全可以改成 8088 或 9001,只要前后端配置保持一致即可。
  • 3. 三步完成网关配置(无依赖、纯 Node.js)

    我们不引入 Nginx、不装 Docker、不配 K8s——用一个不到 50 行的 gateway.js 文件搞定全部逻辑。它轻、快、透明,出问题一眼就能定位。

    3.1 创建网关脚本:gateway.js
    // gateway.js
    const http = require('http');
    const url = require('url');
    
    // Ollama 服务地址(确保能从本机 curl 通)
    const OLLAMA_BASE_URL = 'http://localhost:11434';
    
    const server = http.createServer((req, res) => {
      const parsedUrl = url.parse(req.url, true);
      const path = parsedUrl.pathname;
    
      // 只处理 /v1/chat/completions 请求(Clawdbot 前端默认路径)
      if (req.method === 'POST' && path === '/v1/chat/completions') {
        // 设置 CORS 头(允许任意源,生产环境请替换为具体域名)
        res.setHeader('Access-Control-Allow-Origin', '*');
        res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
        res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
        res.setHeader('Access-Control-Allow-Credentials', 'true');
    
        // 处理预检请求(OPTIONS)
        if (req.method === 'OPTIONS') {
          res.writeHead(200);
          res.end();
          return;
        }
    
        // 构造 Ollama 请求选项
        const options = {
          method: 'POST',
          hostname: 'localhost',
          port: 11434,
          path: '/api/chat',
          headers: {
            'Content-Type': 'application/json',
          },
        };
    
        // 创建 Ollama 请求
        const ollamaReq = http.request(options, (ollamaRes) => {
          // 将 Ollama 响应头透传(保留流式特性)
          res.writeHead(ollamaRes.statusCode, ollamaRes.headers);
          ollamaRes.pipe(res);
        });
    
        ollamaReq.on('error', (err) => {
          console.error('Ollama request failed:', err);
          res.writeHead(500, { 'Content-Type': 'application/json' });
          res.end(JSON.stringify({ error: 'Failed to connect to Ollama' }));
        });
    
        // 将前端请求体原样转发给 Ollama
        let body = '';
        req.on('data', chunk => body += chunk);
        req.on('end', () => {
          try {
            const frontendData = JSON.parse(body);
            // 关键转换:将 OpenAI 格式转为 Ollama 格式
            const ollamaPayload = {
              model: 'qwen3:32b', // 必须与 Ollama 中模型名完全一致
              messages: frontendData.messages.map(msg => ({ role: msg.role, content: msg.content })),
              stream: frontendData.stream ?? true,
              options: {
                temperature: frontendData.temperature ?? 0.7,
                num_ctx: 32768 // Qwen3-32B 推荐上下文长度
              }
            };
            ollamaReq.write(JSON.stringify(ollamaPayload));
            ollamaReq.end();
          } catch (e) {
            console.error('Parse error:', e);
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ error: 'Invalid JSON in request body' }));
          }
        });
      } else {
        // 其他路径返回 404
        res.writeHead(404, { 'Content-Type': 'text/plain' });
        res.end('Not Found');
      }
    });
    
    const PORT = 18789;
    server.listen(PORT, () => {
      console.log('Clawdbot Web 网关已启动');
      console.log(`➡ 前端请请求:http://localhost:${PORT}/v1/chat/completions`);
      console.log(`⬅ 网关已连接 Ollama: http://localhost:11434`);
    });
    
    3.2 启动网关并验证连通性

    打开终端,执行:

    node gateway.js
    

    你会看到类似输出:

    Clawdbot Web 网关已启动
    ➡ 前端请请求:http://localhost:18789/v1/chat/completions
    ⬅ 网关已连接 Ollama: http://localhost:11434
    

    接着,用 curl 模拟一次前端请求,验证网关是否真正打通:

    curl -X POST http://localhost:18789/v1/chat/completions \
      -H "Content-Type: application/json" \
      -d '{ "model": "qwen3:32b", "messages": [{"role": "user", "content": "你好,你是谁?"}], "stream": false }'
    

    如果返回包含 "message": {"role": "assistant", "content": "我是 Qwen3..."} 的 JSON,说明网关、Ollama、模型三者已全线贯通。

    3.3 配置 Clawdbot 前端指向新网关

    打开 Clawdbot 项目的前端配置文件(通常是 src/config.js 或 .env),修改 API 基础地址:

    # .env
    VUE_APP_API_BASE_URL=http://localhost:18789
    # 或 React 项目中的 config.ts
    export const API_BASE_URL = 'http://localhost:18789';
    

    然后重启前端服务(npm run dev 或 yarn start)。此时前端所有 /v1/chat/completions 请求,都会先打到 18789 网关,再由网关转发给 Ollama——跨域问题彻底消失,流式响应完整保留。

    4. 常见 CORS 问题与精准修复方案

    即使按上述步骤操作,仍可能遇到五花八门的 CORS 报错。以下是真实项目中高频出现的 4 类问题及对应解法,不绕弯、不猜疑、直接定位根因。

    4.1 报错:Response to preflight request doesn't pass access control check

    现象:浏览器控制台显示 OPTIONS 请求返回 403 或 500,后续 POST 根本不发出。 原因:网关未正确处理预检请求(OPTIONS),或 Ollama 服务本身拒绝了 OPTIONS 方法。 修复:确认 gateway.js 中 if (req.method === 'OPTIONS') 分支存在且执行 res.end()。不要试图让 Ollama 处理 OPTIONS——它不支持,必须由网关拦截并快速响应。

    4.2 报错:The value of the 'Access-Control-Allow-Origin' header contains the invalid value '*'

    现象:Chrome 报错,但 Firefox 能用;或带上 credentials: true 时失败。 原因:当需要携带 Cookie 或认证头时,Access-Control-Allow-Origin 不能为 *,必须指定确切域名。 修复:将网关中 res.setHeader('Access-Control-Allow-Origin', '*') 改为:

    // 开发环境
    res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
    // 生产环境(假设部署在 https://chat.yourcompany.com)
    res.setHeader('Access-Control-Allow-Origin', 'https://chat.yourcompany.com');
    

    同时确保前端请求中 credentials: 'include' 与后端设置严格匹配。

    4.3 报错:No 'Access-Control-Allow-Headers' header is present

    现象:前端设置了 Authorization: Bearer xxx 或自定义 Header,但被拦截。 原因:网关未声明允许该 Header。 修复:在网关 CORS 头中补充:

    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
    
    4.4 报错:Failed to fetch 但无 CORS 字样

    现象:控制台只显示网络错误,点开 Network 标签页发现请求状态为 (failed) 或 net::ERR_CONNECTION_REFUSED。 原因:网关根本没运行,或端口被占用,或防火墙拦截。 排查顺序:

    1. lsof -i :18789(macOS/Linux)或 netstat -ano | findstr :18789(Windows)确认端口是否被占用;
    2. curl -v http://localhost:18789/health(若你加了健康检查)或 curl -v http://localhost:18789 看是否返回 Not Found(证明网关在运行);
    3. 临时关闭防火墙测试。

    5. 进阶优化:让网关更健壮、更易维护

    基础版网关能跑通,但生产环境还需三点加固。

    5.1 添加请求日志与错误追踪

    在 gateway.js 的请求处理开头加入:

    console.log(`[${new Date().toISOString()}] ${req.method} ${req.url} ← ${req.socket.remoteAddress}`);
    

    在 Ollama 请求的 on('error') 回调中,不仅打印错误,还记录时间戳和请求体摘要(脱敏后):

    ollamaReq.on('error', (err) => {
      const logEntry = {
        timestamp: new Date().toISOString(),
        error: err.message,
        remoteAddr: req.socket.remoteAddress,
        requestBodyPreview: body.substring(0, 100) + '...'
      };
      console.error('GATEWAY_ERROR:', JSON.stringify(logEntry));
      // 此处可对接 Sentry、写入文件等
    });
    
    5.2 支持多模型动态路由

    如果你不止部署了 qwen3:32b,还有 qwen2.5:7b 或 phi3:mini,可扩展网关支持模型名透传:

    // 从请求路径提取模型名,例如 /v1/chat/completions/qwen3:32b
    const modelMatch = parsedUrl.pathname.match(/\/v1\/chat\/completions\/(.+)/);
    const targetModel = modelMatch ? modelMatch[1] : 'qwen3:32b';
    // 在 ollamaPayload 中使用
    model: targetModel,
    

    前端请求改为 POST /v1/chat/completions/qwen3:32b 即可切换模型,无需改网关代码。

    5.3 集成健康检查端点

    添加一个 /health 路径,供前端或运维监控网关存活状态:

    if (req.method === 'GET' && path === '/health') {
      // 尝试快速探测 Ollama 是否可达
      const healthReq = http.request({
        hostname: 'localhost',
        port: 11434,
        path: '/api/tags',
        method: 'GET'
      }, (healthRes) => {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ status: 'ok', timestamp: new Date().toISOString() }));
      });
      healthReq.on('error', () => {
        res.writeHead(503, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ status: 'unavailable', reason: 'Ollama unreachable' }));
      });
      healthReq.end();
      return;
    }
    

    6. 总结:一条清晰、可复现、零踩坑的落地路径

    回看整个过程,你其实只做了三件确定性极高的事:

    • 明确边界:前端只认网关,网关只认 Ollama,各司其职不越界;
    • 最小实现:50 行 Node.js 脚本,无框架、无构建、无依赖,复制即用;
    • 精准归因:CORS 不是玄学,是 HTTP 头的显式声明,是预检请求的正确响应,是流式数据的无损透传。

    你不需要成为网络协议专家,也不必深究浏览器同源策略的 RFC 文档。只要记住这个铁律:前端能访问的地址,必须是网关地址;网关返回的响应,必须带正确的 Access-Control 头;Ollama 的请求体,必须是它能解析的格式。其余,都是细节优化。

    现在,打开你的 Clawdbot 页面,输入第一句话,看着 Qwen3-32B 以 32B 参数量带来的扎实回答缓缓浮现——那不是魔法,是你亲手搭起的、稳稳当当的数据桥梁。

    目录

    1. Qwen3-32B 集成实战:Clawdbot Web 网关配置与 CORS 问题解决
    2. 1. 为什么需要 Web 网关与跨域处理
    3. 2. 整体架构与角色分工
    4. 2.1 各组件职责一目了然
    5. 2.2 为什么选 18789 端口?不是随便定的
    6. 3. 三步完成网关配置(无依赖、纯 Node.js)
    7. 3.1 创建网关脚本:gateway.js
    8. 3.2 启动网关并验证连通性
    9. 3.3 配置 Clawdbot 前端指向新网关
    10. .env
    11. 或 React 项目中的 config.ts
    12. 4. 常见 CORS 问题与精准修复方案
    13. 4.1 报错:Response to preflight request doesn't pass access control check
    14. 4.2 报错:The value of the 'Access-Control-Allow-Origin' header contains the invalid value '*'
    15. 4.3 报错:No 'Access-Control-Allow-Headers' header is present
    16. 4.4 报错:Failed to fetch 但无 CORS 字样
    17. 5. 进阶优化:让网关更健壮、更易维护
    18. 5.1 添加请求日志与错误追踪
    19. 5.2 支持多模型动态路由
    20. 5.3 集成健康检查端点
    21. 6. 总结:一条清晰、可复现、零踩坑的落地路径
    • 💰 8折买阿里云服务器限时8折了解详情
    • Magick API 一键接入全球大模型注册送1000万token查看
    • 🤖 一键搭建Deepseek满血版了解详情
    • 一键打造专属AI 智能体了解详情
    极客日志微信公众号二维码

    微信扫一扫,关注极客日志

    微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

    更多推荐文章

    查看全部
    • LlamaFactory 多模态大模型微调实战指南
    • AI 绘画与摄影:ChatGPT、Midjourney 与文心一格工具解析
    • 企业微信自建应用:Python 实现消息收发功能
    • AIGC 驱动下的虚拟人物创作与智能交互技术解析
    • AIGC 驱动的虚拟人物创作与智能交互技术解析
    • Python NumPy 入门指南:数据处理与科学计算基础
    • AIGC 情感化智能客服实战:降低投诉率的技术方案
    • Windows 系统下载、安装并运行 MinIO 服务及访问 WebUI
    • C++ 高性能 HTTP 服务器构建与实战优化
    • Qwen3-VL 结合 LLaMA-Factory 实现 Grounding 任务 LoRA 微调
    • 基于 OpenClaw 和飞书开放平台实现 AI 新闻推送机器人
    • FastAPI 现代 Python Web 开发完全指南
    • Whisper-WebUI macOS 安装与常见问题排查
    • Stable Diffusion 大模型基础:版本区别与资源推荐
    • FAPP: 无人机动态环境下的快速自适应感知与规划
    • 若依 (RuoYi) 低代码框架深度解析与选型建议
    • FPGA 时序逻辑实战:计数器、跨时钟域与状态机解析
    • 机器人重力补偿技术:MuJoCo 实现解析与原理分析
    • Chambolle-Pock 算法在医学影像重建中的应用
    • RabbitMQ 分布式系统实战:从安装部署到 C++ 调用

    相关免费在线工具

    • RSA密钥对生成器

      生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

    • Mermaid 预览与可视化编辑

      基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

    • 随机西班牙地址生成器

      随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

    • Keycode 信息

      查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

    • Escape 与 Native 编解码

      JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

    • JavaScript / HTML 格式化

      使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online