# 扣子智能体 API 调用前后端部署实战:基于对象存储与云函数的 Serverless 方案

# 一、 问题的提出

在有关智能体的研究中 我们需要将制作的智能体给学生使用 但是必须考虑以下问题:

  1. 终端限制:校园环境下的计算机机房通常受限于系统还原机制或安全策略,不便于安装各类客户端 App。因此,基于浏览器的 Web 网页成为学生访问智能体的最佳载体。
  2. 数据采集需求:研究核心在于分析学生与 AI 的对话行为。我们需要通过 API 调用,结合扣子(Coze)的数据提取功能,实时获取学生身份信息(班级、学号)及完整的对话上下文,以供后续的教育数据挖掘。
  3. 并发与性能考量:教学场景具有典型的高并发特征。上课期间,50-100 名学生会在短时间内同时发起请求,这对系统的稳定性提出了要求。

# 二、 技术栈的选择:Serverless 架构的优势

在方案规划初期,我们对比了传统云服务器与 Serverless 架构,最终选择了 ** 对象存储(COS/OSS)+ 云函数(SCF/FC)** 的组合,其核心动机如下:

  1. 极低成本与免运维:传统服务器需配置环境且闲置成本高;Serverless 方案按量计费,无须管理操作系统底层,极大地降低了研究经费支出。
  2. 规避备案周期:国内云服务器若绑定域名访问,通常需要漫长的工信部备案流程。利用对象存储的静态托管功能及云函数的 API 网关,可以快速搭建实验环境,符合研究的时间节点。
  3. 弹性扩展能力:云函数原生支持高并发触发,能轻松应对百人级别的瞬间请求峰值。
  4. 伪动态网页(Pseudo-dynamic Web):前端采用静态 HTML 托管在对象存储中,通过 JavaScript 调用后端云函数。这种架构既拥有静态网页的加载速度,又具备动态交互的逻辑能力。

# 三、 技术实现:从架构雏形到深度隔离

# 3.1 系统架构设计:走向彻底的 Serverless

为了实现 “开箱即用” 且 “免运维”,我们确立了前后端彻底解耦的 Serverless(无服务器)架构

  • 前端:采用对象存储(COS)。将编写好的 HTML、CSS 和 JS 文件作为静态资源直接托管。这种 “扁平化” 的存储结构天然自带 CDN 属性,能够利用边缘计算节点实现秒级加载,彻底消除了服务器带宽瓶颈。
  • 后端:采用云函数(SCF)。作为前端与扣子(Coze)服务器之间的桥梁,云函数仅在学生点击 “开始实验” 时触发运行。它负责处理所有高风险的加密计算和令牌申请,确保敏感信息(如私钥)永远不会暴露在客户端网络中。

# 3.2 初步最小化测试(MVP):个人密钥

在架构搭建初期,为了快速跑通前后端的数据交互链路,构建了一个最小化可行性产品(MVP),并使用 ** 个人访问令牌(PAT - Personal Access Token)** 进行了初步测试。

  • 测试过程与链路打通:为了快速验证 Serverless 架构的连通性,我们首先构建了一个最小化测试(MVP)链路。

    • 前端构造与请求发起:我们在前端快速搭建了包含 “班级” 和 “学号” 输入框的基础交互页面。在逻辑层,我们利用原生的 fetch API 构造了一个标准的 HTTP POST 请求,将学生填写的表单数据封装为 JSON 载荷(Payload),定向推送到腾讯云函数暴露的 API 网关接口。
    • 后端代理与请求转发:在接收端,云函数作为中间层解析并提取了前端传入的参数。为了在测试期避开复杂的签名校验以追求快速跑通,我们在云函数代码中直接硬编码了开发者的个人访问令牌(PAT)。云函数在 HTTP 标头中挂载该 PAT 后,直接作为代理向扣子(Coze)的底层 API 发起调用。
    • 状态反馈与数据闭环:云函数在获取到扣子服务器的流式或结构化响应后,将其原封不动地作为 Response 返回给前端。前端接收到反馈参数并进行解析,最终将对话结果渲染至 UI 界面,成功完成了
      “前端数据采集 \rightarrow 云端中转 \rightarrow 扣子响应 \rightarrow 页面渲染” 的完整数据闭环。
  • 测试结果与暴露的问题
    虽然单用户测试表现良好,但在模拟多名学生同时在线的压力测试中,系统遭遇了的会话串台

    1. 权限越界:PAT 拥有开发者账户的全局权限,一旦被恶意抓包,整个智能体后台将面临风险。
    2. 上下文污染:由于 PAT 无法区分调用来源,扣子服务器会将所有学生的请求视为 “同一个开发者” 在发言。导致学生 A 的界面上出现了学生 B 的回答,需要进一步调整

阶段性结论:MVP 测试证明了 Serverless 架构的可行与高连通性,但也彻底否定了 PAT 在多用场景下的可用性。我们必须重构鉴权链路,引入基于 OAuth 2.0 的 JWT(JSON Web Token)动态加密体系。

# 3.3 鉴权与加密部署 对话隔离的实现

根据扣子(Coze)官方文档介绍,将智能体接入自有 Web 环境时,我们需要采用 OAuth 2.0 的 JWT(JSON Web Token)鉴权来实现用户的分发以及对话的隔离。为了防止 RSA 私钥暴露在前端网页中被恶意抓包,我们将加密签名的主战场转移到了后端的云函数(SCF)。

具体的实现思路与部署如下:

# 1. 技术选型与实现思路

要生成符合扣子官方规范的 JWT 令牌,我们需要在后端进行 RS256(非对称加密)签名。

  • 核心依赖库:我们选用了 Python 的 cryptography 库。这是目前处理 PEM 格式密钥和数字签名最成熟的工业级库,能够完美支撑 RSA 算法的底层需求。
  • 整体思路:前端仅负责传递学生的基本信息(班级、学号);云函数接收信息后,在本地计算并拼装出 JWT 的 Header 和 Payload,利用私钥对其进行数字签名;随后,云函数带着这个签名向扣子服务器 “换票”,拿到有时效的 Access Token 后再下发给前端。

# 2. JWT 加密的核心代码逻辑

在云函数内部,我们纯手工构建了 JWT 的签发引擎,核心步骤分为三块:

  • 组装 Header:声明加密算法,即 {"alg": "RS256", "typ": "JWT"}
  • 组装 Payload:这是最关键的一步。我们填入应用的 Client ID( iss ),通过时间戳设置 2 小时的过期时间( exp ),并生成毫秒级时间戳( jti )防止重放攻击。更为核心的是,我们在这一步将前端传来的学生信息经过 MD5 加密后,作为 session_name 注入到 Payload 中,为后续的对话隔离打下基础。
  • 执行签名:将 Header 和 Payload 分别进行 URL 安全的 Base64 编码,中间用 . 连接。调用 cryptography 库的 padding.PKCS1v15()hashes.SHA256() ,利用我们在云端保存的 RSA 私钥对这段字符串进行签名,最终拼接成完整的 JWT 字符串。

# 3. 云端部署与接口化

代码逻辑写好后,我们将整个工程打包部署到了腾讯云函数(SCF)。

  • 在云函数控制台,我们为其配置了 API 网关触发器,生成了一个可供公网访问的 HTTPS 接口(URL)。
  • 由于这套逻辑完全是事件驱动的(Serverless),只有当学生请求访问时,云端容器才会被唤醒并执行上述加密流程,极大节省了服务器常驻成本。

# 4. 前端对应与跨域打通

后端接口就绪后,前端页面的对接逻辑就变得非常轻量化了。

  • 请求换票:学生点击 “开始实验” 后,前端 startChat 函数利用内置的 fetch API,向我们的云函数网关发起 POST 请求,请求体中携带 ${班级}_${学号}_${课次}
  • SDK 初始化:云函数返回 czs_ 开头的最终 Token 后,前端直接将该 Token 传入 CozeWebSDK.WebChatClientauth 配置项中。前端彻底摆脱了复杂的密钥计算,只负责 “进场”。

# 四、 坑点排查与系统优化

在整个部署过程中,我们并不是一帆风顺的。尤其是 JWT 鉴权更是花费了整整两个小时去排查各种问题。因此做这个其实也不是那么的简单,也记录下来供避坑使用。

# 4.1 核心坑点复盘

# 1. 云函数底层环境冲突

  • 问题描述:在腾讯云函数(SCF)中部署签名库 cryptography 时,系统报错提示找不到底层 C 语言的 GLIBC 库。
  • 原因分析:云函数默认的 Python 3.9 环境较新,其预编译的二进制包与云端容器的老旧底层库版本不匹配。
  • 解决手段:手动将云函数的运行环境降级为 Python 3.7,并在本地打包时锁定安装 cryptography==3.4.8 版本,从而确保了加密环境的稳定性。

# 2. 私钥格式解析失败

  • 问题描述:最初尝试将 RSA 私钥存放在云函数的环境变量中,但代码读取时始终报错 Could not deserialize key data
  • 原因分析:环境变量在保存长文本时会破坏 PEM 格式严格的换行符结构,导致 RSA 算法无法识别私钥。
  • 解决手段:放弃环境变量传参,改在 Python 脚本顶部使用 """ (三引号) 将私钥直接以多行字符串的形式硬编码在代码里。

# 3. 浏览器缓存导致的 “串台”

  • 问题描述:即便后端实现了会话隔离,部分学生在更换学号登录后,依然能看到上一个人的聊天记录。
  • 原因分析:扣子(Coze)的 Web SDK 会在浏览器的 IndexedDB 数据库中缓存旧的 conversation_id
  • 解决手段:在前端执行 “物理清场”。在点击开始实验时,通过代码强制执行 localStorage.clear() 并删除 IndexedDB 中的 coze_sdk_db 数据库,确保环境纯净。

# 4. 跨域请求(CORS)拦截

  • 问题描述:前端网页调用云函数接口时,浏览器报跨域资源共享错误。
  • 原因分析:前端托管在对象存储(COS),后端在云函数(SCF),两者域名不一致触发了浏览器的安全策略。
  • 解决手段:在云函数代码中手动增加了针对 OPTIONS 预检请求 的放行逻辑,并在响应头中加入 Access-Control-Allow-Origin: *

# 4.2 优化

在解决掉生存问题后,为了适配真实的课堂教学,我们又在前端做了几项针对性的优化。

# 1. 交互:从输入到点选

  • 优化策略:将最初的文本输入框全部替换为受控下拉列表
  • 实施细节:预设好 1-10 班、1-99 号以及具体的实验课次。
  • 效果:初中生不再需要打字输入班级学号,不仅降低了认知负荷,更从源头上避免了因学生输入格式不统一(如 “1 班” 和 “一班”)导致的后端 MD5 匹配失败。

# 2. 心理反馈优化:动效与视觉引导

  • 优化策略:引入毛玻璃视觉风格,并增加状态流转动画。
  • 实施细节:利用 backdrop-filter: blur(40px) 打造卡片质感;在点击登录后,通过卡片消失与对话框平滑展开的过渡效果,掩盖后端申请 Token 的等待时间。
  • 效果:给了学生明确的 “系统正在准备独立房间” 的暗示,增强了实验的沉浸感。

更新于