Bmob后端云实战|Python实现SSE流式输出,给备忘录AI加上打字机效果

前言

在之前迭代的备忘录项目中,我们已经实现AI摘要、AI润色功能。但早期方案是一次性返回全部AI结果

当笔记内容较长时,用户点击按钮后页面会空白等待,分不清是网络卡顿还是AI正在计算,产品体验很差。

本次基于Bmob后端云进行改造,使用SSE(Server-Sent Events)服务端单向流式推送,实现类似ChatGPT的打字机逐字输出效果。

核心约束不变:

  1. 调用AI前先校验笔记归属,依托Bmob ACL实现数据隔离,仅可操作自己创建笔记
  2. 流式输出完成后新建笔记入库,不修改、不覆盖原始笔记
  3. 网络中断、接口报错时,禁止将不完整脏数据写入Bmob

一、整体业务流程

复制代码
前端触发请求 → 后端ACL权限校验 → 校验通过建立SSE长连接
→ 请求大模型流式接口 → 后端接收文本分片,实时推送给前端
→ 前端逐字渲染文字(打字机效果)
→ 全部内容接收完毕 → 后端汇总完整文本,新建笔记存入Bmob
→ 异常捕获:断连、超时、模型报错,终止流程并提示用户

二、后端Python核心代码(SSE流式接口)

python 复制代码
import requests
from flask import Flask, Response, stream_with_context, request

app = Flask(__name__)

# Bmob SDK 笔记权限校验函数
def get_note_with_auth(note_id, current_user):
    note = BmobQuery("Note").get_by_id(note_id)
    if not note:
        return {"ok": False, "msg": "笔记不存在"}
    if note["user"]["objectId"] != current_user["objectId"]:
        return {"ok": False, "msg": "无权限访问该笔记"}
    return {"ok": True, "data": note}


@app.route("/api/ai-stream", methods=["POST"])
def ai_stream():
    req_data = request.get_json()
    note_id = req_data.get("note_id")
    op_type = req_data.get("op_type")
    current_user = req_data.get("current_user")

    # 前置鉴权,无权限直接拦截,节省AI token消耗
    auth_result = get_note_with_auth(note_id, current_user)
    if not auth_result["ok"]:
        return {"ok": False, "msg": auth_result["msg"]}

    note_info = auth_result["data"]
    origin_title = note_info["title"]
    origin_content = note_info["content"]

    # 区分摘要 / 润色两种提示词
    if op_type == "summary":
        system_prompt = "你是文本摘要助手,提炼原文核心信息,精简总结输出。"
    elif op_type == "polish":
        system_prompt = "你是文本润色助手,优化语句通顺度,保留原文全部核心信息。"
    else:
        return {"ok": False, "msg": "非法操作类型"}

    # SSE流式生成器
    def stream_generator():
        full_text = ""
        ai_api_url = "你的大模型流式接口地址"
        api_key = "你的API_KEY"
        payload = {
            "messages": [
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"标题:{origin_title}\n内容:{origin_content}"}
            ],
            "stream": True,
            "temperature": 0.6
        }
        headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
        try:
            resp = requests.post(ai_api_url, json=payload, headers=headers, stream=True, timeout=30)
            for chunk in resp.iter_lines():
                if chunk:
                    chunk_data = chunk.decode("utf-8")
                    text_segment = parse_chunk(chunk_data)
                    if text_segment:
                        full_text += text_segment
                        yield f"data: {text_segment}\n\n"
            # 流式结束,完整结果写入Bmob
            BmobQuery("Note").create({
                "title": f"{origin_title}【AI处理】",
                "content": full_text,
                "user": current_user
            })
            yield f"data: [DONE]\n\n"
        except Exception as e:
            yield f"data: [ERROR]{str(e)}\n\n"

    return Response(stream_with_context(stream_generator()), mimetype="text/event-stream")

三、前端JS代码,监听SSE、打字机渲染

ini 复制代码
async function startAiStream(noteId, opType) {
  // 按钮锁,防止重复多次提交请求
  const aiBtn = document.querySelector("#aiBtn");
  const resultDom = document.querySelector("#aiResult");
  aiBtn.disabled = true;
  resultDom.innerText = "";

  const eventSource = new EventSource(`/api/ai-stream?note_id=${noteId}&op_type=${opType}`);

  eventSource.onmessage = (e) => {
    const msg = e.data;
    if(msg === "[DONE]"){
      eventSource.close();
      showToast("生成完成,新笔记已保存至Bmob");
      aiBtn.disabled = false;
      return;
    }
    if(msg.startsWith("[ERROR]")){
      eventSource.close();
      showToast("生成失败:" + msg.replace("[ERROR]",""));
      aiBtn.disabled = false;
      return;
    }
    // 追加文本,实现打字机效果
    resultDom.innerText += msg;
  }

  eventSource.onerror = () => {
    eventSource.close();
    showToast("连接中断,请重试");
    aiBtn.disabled = false;
  }
}

四、核心知识点解析

1. SSE 适用场景

SSE 是服务端单向推送,只需要服务端向客户端发消息,无需双向通信。 相比 WebSocket,开发成本更低,非常适合AI大模型流式输出场景。

2. 为什么等流式全部结束才写入Bmob

流式过程中仅做前端页面预览展示。 只有完整接收全部AI内容,才调用Bmob SDK新建笔记。 一旦中途网络断开、接口报错,不会将残缺文本入库,保障数据库数据干净。

3. 前置鉴权的意义

在调用大模型接口前,校验笔记归属。 未授权用户直接拦截,不会发起昂贵的大模型请求,节约token开销。 用户权限、数据隔离直接复用Bmob自带ACL能力,不用手写复杂权限底层代码。

4. 交互层面优化

  • 按钮锁定,防止用户连续点击重复请求
  • 区分多种状态:AI思考中、生成中、完成、异常失败
  • 异常捕获:超时、网络中断、模型报错,前端给出友好提示

五、为什么选择Bmob后端云

整个项目没有自行搭建数据库、底层服务器。 用户体系、数据表存储、ACL数据权限隔离全部交给Bmob后端云,免费免开发。 开发者可以聚焦业务逻辑、AI交互开发,省去大量底层基础设施开发工作,适合个人练手、简历项目快速落地。

六、总结

很多新手写AI项目,只保证功能跑通,忽略交互反馈。 一次性返回只能叫"能用";加上SSE流式输出之后,项目具备完整产品交互质感。

本Demo包含权限校验、SSE流式接口、异常捕获、云存储,放到简历中对比简单CRUD项目,竞争力更强。

相关推荐
zzzll11111 小时前
LLM 学习第 24 课:Agent Harness
前端·人工智能·学习
nunumaymax2 小时前
【第九章-React Router 6】
前端·react.js
少冰2 小时前
前端埋点如何落地:从事件设计到上线验收
前端·数据分析·数据可视化
toooooop82 小时前
thinkphp查询数据表最后的自增id
前端·javascript·数据库
晚安日记wanna2 小时前
简历写精通浏览器原理一问 URL 到渲染直接哑火...
前端·面试
晚安日记wanna2 小时前
四年前端被问 Code Review90人只看得懂空格和命名
前端·面试
IPdodo_2 小时前
curl 如何测试代理 IP?HTTP、SOCKS5 与认证参数示例
前端·网络·python·https·网络调试
做前端的娜娜子2 小时前
施工蓝图:vite.config.js —— 项目的指挥中心
前端·react native·vite
宸翰2 小时前
解决uni-app 中 SVG 图片在 iOS 端显示模糊问题
前端·uni-app