day35 法宝AI法律助手全栈项目开发

1. 今天做了什么

  1. 最初用 Trae 创建 Vue3+Vite 前端项目(首页三模块卡片、深蓝+金色主题、Element Plus)
  2. 用 WorkBuddy 接手后补充三个功能页(AI法律咨询、文书生成、合同审查)
  3. 前端直连 FastGPT 实现AI对话流式输出(SSE、思维链实时展示、appId 放请求体)
  4. 搭建 FastAPI 后端(FastAPI+SQLAlchemy2.0+MySQL,5张表,4个API模块,JWT认证)
  5. 前端从直连FastGPT改为调后端接口(axios实例、token自动携带、统一响应解包)
  6. 后端三个服务接入FastGPT(ai_service对话、document_service文书生成、contract_service合同审查)
  7. 文书生成从纯文本改为结构化JSON返回(8种section type,确定性Word排版)
  8. 合同审查报告下载实现(python-docx生成Word,RFC 5987中文文件名编码)
  9. 文书下载实现(从简陋逐行写入 → 专业docx排版服务,按文书类型差异化版式)
  10. 百分比进度条替换为阶段骨架提示(4/5阶段清单+旋转图标,覆盖5-15s等待窗口)
  11. 前端历史记录UI(对话侧栏、文书历史卡、审查历史卡,下载原文件/报告)
  12. FastGPT chatId跨用户/跨对话串台修复(硬编码→按用户+记录唯一)
  13. Word文档全文改纯黑字体、登录状态变化时历史列表自动刷新
  14. 线上部署(CF Pages前端+花生壳内网穿透后端),CORS跨域修复
  15. Python环境问题:系统3.15 beta无预编译包 → 安装3.12.10绕过

2. 核心知识点

  • FastGPT OpenAI兼容接口:/v1/chat/completions,chatId是会话主键+记忆开关+落库开关,appId放请求体内(非Header拼接)
  • FastGPT chatId隔离:三个模块的chatId必须按”模块前缀+用户ID+表主键”命名,否则跨用户记忆串台
  • SSE流式解析:前端用fetch+getReader()逐行解析data: {...},处理reasoning_content(思维链)和content(正文)两种字段
  • FastAPI分层架构:models→schemas→services→api四层,依赖注入管理DB会话和当前用户
  • JWT认证流程:后端python-jose签发token → 前端存localStorage → axios拦截器自动加Bearer头 → 后端get_current_user依赖校验
  • python-docx中文字体:必须同时设置ascii和eastAsia两套字体族,否则Word打开中文变方块
  • 结构化文书JSON schema:{title, sections: [{type, ...}]},8种type(party/heading/numbered/paragraph/center/signature/blank/notice),每种对应确定性Word样式
  • RFC 5987中文文件名:Content-Disposition: attachment; filename="fallback.docx"; filename*=UTF-8''%E4%B8%AD%E6%96%87.docx,前端需Access-Control-Expose-Headers
  • Vue watch监听全局状态:useAuth()返回的user是全局单例ref,组件内watch(isLoggedIn)可在登录状态变化时自动触发操作
  • Vite代理仅开发环境生效:生产构建后/api请求直接打到静态服务器返回405,需用VITE_API_BASE_URL环境变量拼完整后端地址
  • import.meta.env编译时替换:Vite打包时把import.meta.env.VITE_API_BASE_URL直接替换成字符串常量,改环境变量后必须重新build才生效
  • CORS预检机制:非简单请求浏览器先发OPTIONS(preflight),后端响应头必须带Access-Control-Allow-Origin才放行;本地走Vite代理是同源根本不触发CORS
  • Vue Router驱动页面切换:router-view算组件+component :is渲染,pushState只改地址栏不发请求(SPA无刷新切换),currentRoute响应式对象是URL与页面间的桥
  • URL驱动vs状态驱动:手动activeIndex不支持前进后退/刷新恢复/分享;路由方案每个页面有独立URL,懒加载按需下载,扩展只需加路由+组件

3. 深度思考

  • 为什么文书生成需要结构化JSON:纯文本靠正则猜排版(len<=12 and no冒号),不可靠且不可扩展;JSON让每种section type对应确定性的Word样式,AI和降级模板共用同一schema,前后端对齐零成本
  • chatId硬编码的隐蔽性:前端展示完全正确(后端按conversation_id隔离了消息),问题藏在FastGPT侧——所有用户共用一条”记忆”,AI能准确复述别人的咨询内容。这种bug不在自己系统内,极难发现
  • 占位符长度区分:人名6下划线、身份证18、电话11……比统一____合理得多,打印出来手写填空时视觉宽度与实际数据匹配
  • 进度条vs骨架提示:el-progress百分比进度条无法预估等待时间,1秒到100%然后卡住是 Worse UX;阶段清单让用户知道”在做什么”而非”做了百分之几”
  • 先生成后入库 vs 先入库后生成:文书生成原来先调FastGPT再存记录,导致调用时没有主键可用作chatId;改为先建空记录拿主键→生成→回填,一次额外commit换来隔离保障
  • 本地没问题一上线就跨域:本质是两条链路不同——本地Vite代理让浏览器认为是同源(CORS根本不触发),线上前端和后端分属不同域名,浏览器才执行同源策略检查。所以本地验证通过不等于线上没问题
  • pushState vs 整页跳转:router-link拦截了<a>的默认行为,只改地址栏+更新响应式状态,浏览器不重新加载。这就是SPA”切页无刷新”的全部秘密,也是为什么URL变了但F12里看不到任何网络请求

4. 踩过的坑

  • FastGPT appId is empty:Key是通用Key而非应用专属Key,三种Auth格式试错(纯Key ❌、Bearer appId-key ❌、appId放请求体 ✅)
  • Python 3.15 beta无预编译包:pydantic-core、pyyaml没有wheel,源码编译需Rust且rustup安装失败;最终用uv下载CPython 3.12.10安装到项目目录
  • 双气泡问题:发送后预插空AI气泡+”正在输入”独立一行=两组头像;合并为同一气泡内显示打字动效
  • CORS 400 Disallowed CORS origin:改.env后残留python进程仍用旧配置(6个进程两组多开),必须杀干净重启
  • 合同审查500:review_contract()是async但调用缺await,coroutine对象被直接JSON序列化
  • DataGrip看不到新建库:schema列表缓存未刷新,需手动Synchronize或在Properties→Schemas里勾选
  • WinError 10013端口占用:冒烟测试启动的uvicorn后台未退出,占着8000端口
  • 文书下载第一轮丢AI内容:DownloadRequest改成只收form_data,下载变成模板拼接;正确做法是保留AI生成内容走智能排版
  • npm空文件遮蔽:C:\WINDOWS\system32\npm是0字节空文件,优先级高于Node.js安装目录,导致npm命令无任何输出
  • 历史列表登录后不加载:onMounted时未登录直接return,之后登录状态变化无人监听;需watch(isLoggedIn)
  • CF Pages 405 Method Not Allowed:前端没配VITE_API_BASE_URL,请求发给静态托管→POST方法不被允许;解法:配环境变量让请求直接打到花生壳
  • npm run preview连不上后端:preview不加载server.proxy(那是dev专用),build产物里没有代理逻辑
  • .env改了但”假重启”:Windows上关窗口杀不干净python进程,6个残留进程占着8000用旧配置;必须Get-Process python | Stop-Process -Force清场

5. 遗留问题 / 想不通

  • FastGPT后台旧记录(fabao-backend等硬编码chatId产生的)需手动删除,代码不会自动清理
  • 前端删除对话时后端只删MySQL,FastGPT侧chat记录残留(chatId唯一化后不再被读写,无害但不干净)
  • 合同审查同一份合同重新审查时FastGPT会带上上次审查的历史(chatId相同),如果希望每次审查完全无记忆需加时间戳
  • FastGPT后台”使用者”列全显示Owner(API Key属主),OpenAPI方式无法区分真实用户