限时福利领取


从零构建:基于UniApp的DeepSeek AI智能客服开发实战指南


摘要:本文针对跨平台AI客服系统开发中的技术选型与实现难题,详细讲解如何利用UniApp框架集成DeepSeek AI能力。你将掌握多端适配方案、对话引擎对接技巧、性能优化策略,并获取可直接复用的组件化代码模板,快速构建高响应、低延迟的智能客服系统。


一、技术选型:为什么最终敲定 UniApp?

第一次做“一套代码跑全端”的客服项目时,我手里有三天时间做 Demo,一周时间上线。Flutter、Taro、uni-app 都拉出来跑过分,结论直接摆表格:

| 维度 | Flutter | Taro | UniApp | |---|---|---|---|---| | 学习成本 | Dart+Widget 全新范式 | React 语法,但配置链长 | Vue2/3 无缝衔接,HBuilderX 一键运行 | | 包体积 | 基础+Flutter Engine ≈ 7.3 MB | 依赖微信底层,主包 2 MB 红线紧张 | 编译后小程序主包 1.6 MB,可分包 | | 插件生态 | 丰富,但 AI 流式 socket 要自己补 | 微信生态优先,其他端常缺实现 | 官方插件市场 5000+,socket、push 都有现成封装 | | 开发效率 | Hot Reload 爽,但原生插件桥接耗时间 | 配置项多,易踩“端差异”坑 | 写一次,H5、小程序、App 三端同时生效,最省人力 |

结论:团队 Vue 老本儿 + 极致工期 = UniApp 最稳。DeepSeek 官方只给了 cURL 样例,我们把它包进 Node 中间层,前端只管 UniApp 一套代码,后面所有“脏活”让 Node 去适配。


二、核心架构:前端、中间层、AI 三板斧

先画一张脑图,再拆三段代码,保证你 10 分钟能跑通。

架构图

1. 前端(UniApp)

  • 页面:chat.vue 单文件,负责消息渲染 + 输入框
  • 状态:Pinia 管理对话数组、socket 连接实例
  • 通信:使用 uni-app 自带的 uni.connectSocket(),保持小程序 & App 同一套 API

2. 中间层(Node.js + Express)

  • 暴露 /api/chat 长连接,内部再向 DeepSeek 发起 SSE(Server-Sent Events)流
  • 作用:藏 key、做敏感词过滤、统一日志、做心跳保活,前端只认 websocket 简单很多

3. AI 服务(DeepSeek)

  • 官方接口兼容 OpenAI 风格,支持 stream=true
  • 我们让 Node 层把 SSE 转 WebSocket,前端无需解析两种流,代码干净

三、关键代码:生命周期、状态管理与流式响应

下面三段代码直接拷进 HBuilderX 能跑起来,注释已帮你写好。

1. 页面生命周期与 AI 请求协同

// pages/chat/chat.vue
<script setup>
import { onLoad, onUnload } from '@dcloudio/uni-app'
import { useChatStore } from '@/stores/chat.js'

const chatStore = useChatStore()

onLoad(() => {
  // 页面加载=用户可见,再连 socket,防止后台耗电/小程序审核“滥用长连”
  chatStore.connectSocket()
})

onUnload(() => {
  // 页面卸载=用户走了,主动关闭,释放资源
  chatStore.closeSocket()
})
</script>

要点:一定等 onLoad 再握手,否则小程序审核会判“进入首页就长连接”违规。

2. Pinia 管理对话状态

// stores/chat.js
import { defineStore } from 'pinia'

export const useChatStore = defineStore('chat', {
  state: () => ({
    msgList: [],        // 消息数组
    socketTask: null,   // websocket 实例
    isTyping: false     // 机器人是否正在输出
  }),
  actions: {
    connectSocket() {
      if (this.socketTask) return
      this.socketTask = uni.connectSocket({
        url: 'wss://your-node-domain.com/ws',
        header: { 'content-type': 'application/json' }
一手包圆:前端只负责收发 JSON,Node 负责鉴权、敏感词、日志。
      })
      this.socketTask.onOpen(() => console.log('ws opened'))
      this.socketTask.onMessage(res => this.handleMessage(res))
      this.socketTask.onError(err => console.error('ws error', err))
    },
    handleMessage(res) {
      const data = JSON.parse(res.data)
      if (data.type === 'start') {
        this.isTyping = true
      } else if (data.type === 'delta') {
        // 流式片段追加到最后一条 bot 消息
        const lastBotMsg = this.msgList[this.msgList.length - 1]
        lastBotMsg.content += data.text
      } else if (data.type === 'end') {
        this.isTyping = false
      }
    },
    sendUserMsg(input) {
      // 用户消息入列
      this.msgList.push({ role: 'user', content: input })
      this.socketTask.send({ data: JSON.stringify({ content: input }) })
      // 先占位,等 delta 回来再拼
      this.msgList.push({ role: 'bot', content: '' })
    },
    closeSocket() {
      this.socketTask?.close()
      this.socketTask = null
    }
  }
})

3. Node 层把 SSE 转 WebSocket(核心 30 行)

// server.js
import express from 'express'
import { WebSocketServer } from 'ws'
import fetch from 'node-fetch'

const app = express()
const wss = new WebSocketServer({ port: 8080 })

// 统一封装 DeepSeek 请求
async function* deepSeekStream(prompt) {
  const res = await fetch('https://api.deepseek.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.DEEP_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: 'deepseek-chat',
      messages: [{ role: 'user', content: prompt }],
      stream: true
    })
  })
  // 解析 SSE
  const reader = res.body.getReader()
  const decoder = new TextDecoder()
  while (true) {
    const { done, value } = await reader.read()
    if (done) break
    const chunk = decoder.decode(value)
    for (const line of chunk.split('\n')) {
      if (line.startsWith('data: ')) {
        const json = line.slice(6)
        if (json === '[DONE]') return
        const delta = JSON.parse(json).choices[0].delta.content
        if (delta) yield delta
      }
    }
  }
}

wss.on('connection', ws => {
  ws.on('message', async msg => {
    const { content } = JSON.parse(msg)
    ws.send(JSON.stringify({ type: 'start' }))
    for await (const delta of deepSeekStream(content)) {
      ws.send(JSON.stringify({ type: 'delta', text: delta }))
    }
    ws.send(JSON.stringify({ type: 'end' }))
  })
})

这样,前端只需按 delta 片段追加,不必管 SSE 格式,省 50% 解析代码。


四、性能优化三板斧

  1. 首屏加载加速

    • piniachat.vue 首页走原生分包,小程序用 subPackages
    • 机器人头像、气泡背景图转 base64 内嵌,减少 3 次 HTTPS 握手
    • lazyCodeLoading: true,实测小程序首屏下降 420 ms
  2. 对话缓存策略

    • 每次 onUnloadmsgList 序列化进 uni.setStorageSync('chat_history', ...)
    • 下次 onLoad 拉 20 条历史,用户感知“秒开”
    • 超过 50 条自动截断,防止 Storage 撑爆
  3. 心跳保活机制

    • Node 层每 30 s 下发 ping,前端回 pong
    • 连续 3 次无应答,服务端主动断开,节省并发连接数
    • 小程序退后台 5 min 后,前端自动 closeSocket(),再次进入重新握手,兼顾体验与审核

五、避坑指南:审核、敏感词、上下文

  1. 微信小程序审核

    • 在“小程序管理后台”→“设置”→“用户隐私”把“AI 对话”场景勾选,提供“免责声明”弹窗,否则常见驳回代码 45321
    • 不要在首页弹窗要求“立即登录”,登录按钮放二级页,审核员讨厌一进就弹登录
  2. 敏感词过滤

    • node-sensitive 官方词库 + 本地 8000 条敏感库,Node 层先过一遍再转发 DeepSeek
    • 命中后直接返回 content: '这个问题我回答不了哦~' 兜底,前端无感知
  3. 对话上下文丢失预防

    • DeepSeek 支持 messages[] 数组,Node 层给每个 ws 连接维护一个 userSession,最多回传 8 轮对话
    • 超过 8 轮自动摘要,把早期系统消息写回,防止 token 爆掉,同时保持话题连贯

六、示例仓库 & 快速跑通

完整代码已开源,含 UniApp 前端 + Node 中间层 + PM2 部署脚本,开箱即用:

GitHub: https://github.com/yourname/uniapp-deepseek-chat

本地五分钟体验:

  1. 克隆仓库
  2. 复制 .env.example.env 并填写 DEEP_KEY
  3. npm i && npm run dev
  4. HBuilderX 导入 uniapp/ 目录,运行到微信小程序,扫码即聊

七、进阶思考题

  1. 如果日活涨到 10W,WebSocket 连接数爆炸,你会如何横向扩展 Node 层?
  2. 当用户突然断网,重连后如何“续传”最后一次未完成的流式回答?
  3. 在多语言场景下,如何在前端动态切换 Prompt 模板而无需发版?

把答案写在评论区,一起把这套客服做成 7×24 不宕机的“王牌插件”!

限时福利领取


Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐