文本笔记接口开发与前后端联调实战
上一篇我们做好了文本笔记的编辑页面,但保存只是 console.log 模拟,数据关掉页面就没了。
这一篇来解决数据持久化的问题:怎样用 AI 开发后台接口、怎样把前端的 mock 调用替换为真实接口、以及联调过程中踩了哪些坑。
一、先理清需要哪些接口
在动手之前,我先对照前端页面的功能,列出需要后台提供哪些接口:
理清了,开始给 AI 发提示词。
二、第一个提示词:设计数据表
后台开发的第一步是设计数据库表。我给 AI 发了这样的提示词:
提示词内容:
请参照前端文本笔记编辑页面(edit.vue)的内容,设计对应的数据库表。表名用 digital_note,生成建表 SQL 文件并加上字段注释,生成后直接导入到数据库。
真实场景:不需要手动列出每个字段,AI 会自动读取前端页面的内容--标题输入框、正文文本域、分类选择、标签输入、字数统计,根据这些功能自行规划数据库需要哪些字段,包括字段类型、长度、默认值,还会自动加上创建人、更新人、创建时间、更新时间、逻辑删除标记这些通用字段。
AI 做了什么:
读取前端编辑页面的代码,分析出需要存储的数据:标题、正文、分类、标签、字数
自动补充了通用字段:主键 id(雪花算法)、创建人、更新人、创建时间、更新时间、逻辑删除标记
生成了建表 SQL 文件,包含完整的字段定义和注释
自动执行 SQL 将表导入到数据库,可以直接使用了
什么是逻辑删除?:逻辑删除不是真的把数据删掉,而是把 del_flag 字段从 '0' 改成 '-1'。查询时只查 del_flag='0' 的数据,被标记为 '-1' 的数据不会出现在结果里,但数据库里还保留着。好处是误删可以恢复,坏处是数据会越来越多。对于笔记类 App,逻辑删除比物理删除更安全。
什么是雪花算法?:雪花算法(Snowflake)是 Twitter 开源的一种 ID 生成算法,生成的 ID 是一个 64 位整数,保证分布式环境下全局唯一、趋势递增。简单说就是每条数据都有一个不会重复的数字 ID,不需要数据库自增。
三、第二个提示词:生成后端各层代码
表建好了,接下来生成 Java 后端代码。这个项目基于 PIG 后台框架(Spring Boot + MyBatis-Plus),有成熟的代码规范和目录结构。我给 AI 发了很简单的提示词:
提示词内容:
请参照 PIG 后台框架的相关规范,生成文本笔记页面需要的接口,以及后台管理页面可能需要的接口。用户端的控制器需要和管理端的控制器区分开,以便做权限控制。
真实场景:不需要手动列出每个接口的请求方式和路径,AI 会自动参照 PIG 框架的目录结构和代码规范,读取前端页面的功能需求,自行规划需要哪些接口。PIG 框架本身有完善的代码生成体系,AI 会遵循框架的分层规范(Entity -> Mapper -> Service -> Controller),自动生成标准代码。
顺便说说为什么把三个项目放在同一个目录下?:最开始我们说过把后台服务、后台管理页面、前端 uniapp 这三个项目放在同一个目录下,再导入到 Trae 编辑器里。这样做最大的好处是:AI 可以同时读取三个项目的代码。当我在前端页面里调用了某个还不存在的接口时,直接告诉 AI「参照前端页面的功能生成对应的接口」,AI 就会自动读取前端代码,分析出需要哪些字段、哪些接口,然后在后台服务里生成对应的 Controller、Service、Entity。不需要自己切换编辑器窗口、复制粘贴代码、重新描述需求。AI 在处理前端页面时如果发现缺少需要的接口,甚至会主动提醒你并自动生成。三个项目在一个工作区里,AI 全都能看到,全程无缝衔接。
AI 做了什么:
参照 PIG 框架规范,自动生成了 Entity、Mapper、Service、Controller 各层代码
生成了两个 Controller:用户端控制器(路径前缀 /note/text)给 App 调用,管理端控制器(路径前缀 /admin/note/text)给后台管理页面调用
两个控制器共用同一套 Service、Entity、Mapper,避免代码重复
用户端控制器:按当前登录用户过滤数据(用户只能看到自己的笔记),支持分页查询、详情、新增、修改、删除、统计
管理端控制器:加了 @PreAuthorize 权限注解,可以查看所有用户的数据,需要对应权限才能操作
Entity 类用了 MyBatis-Plus 注解(@TableName、@TableId、@TableLogic),逻辑删除自动处理
Service 额外加了 countToday 方法,统计当前用户今日新增数量
为什么要区分用户端和管理端?:用户端接口给 App 用,用户只能操作自己的数据;管理端接口给后台管理页面用,管理员可以查看所有用户的数据。两套接口共用同一套 Service 逻辑,但 Controller 层的权限控制和数据过滤不一样。这样既保证了数据安全,又避免了代码重复。
什么是 MyBatis-Plus?:MyBatis-Plus 是 MyBatis 的增强版,最大的好处是不用写基础 SQL。继承 BaseMapper 就自动有了增删改查方法,继承 IService 就自动有了分页查询方法。PIG 框架内置了 MyBatis-Plus,AI 生成的代码会自动遵循这套规范。
AI 生成的后端文件一览
AI 参照 PIG 框架的分层规范,自动生成了以下文件:
实体类的核心注解(省略字段细节):

两个控制器的核心结构(省略方法体):

提示:以上代码只展示了类的核心结构和路径声明,方法内部的实现逻辑没有贴出来。实际开发中 AI 会参照 PIG 框架现有的 Controller(如 pig-upms 里的代码)生成完整的方法体,包括参数校验、分页封装、返回值统一格式等,不需要自己写。
四、第三个提示词:同步数据到聚合索引表
这个项目之前设计了一张聚合索引表,所有类型的笔记(文本/语音/图片/手绘)都有一份索引记录在这张表里,首页和列表页统一从这张表查询。所以文本笔记的新增、修改、删除都需要同步到聚合表。我给 AI 发了提示词:
提示词内容:
文本笔记的增删改需要同步到聚合索引表(NoteBaseService):
新增笔记后:调用 noteBaseService.syncSave,传入类型"text"、笔记 id、标题、正文摘要(截取前 200 字)、分类、标签
修改笔记后:调用 noteBaseService.syncUpdate,参数同上
删除笔记后:调用 noteBaseService.syncDelete,传入笔记 id
正文摘要用 StrUtil.maxLength(content, 200) 截取,避免存太长
同步逻辑放在 Controller 层,在主表操作成功后执行
AI 做了什么:
在 save 方法里,noteTextService.save 成功后调用 noteBaseService.syncSave
在 update 方法里,updateById 成功后先查出完整数据再调用 syncUpdate
在 remove 方法里,removeBatchByIds 成功后遍历 id 数组逐个调用 syncDelete
正文摘要用 StrUtil.maxLength 截取前 200 字
为什么要同步到聚合表?:假设有 4 种笔记类型,分别存在 4 张表里。首页要展示所有笔记的列表,如果不用聚合表,就要查 4 张表再合并,很慢。有了聚合表,每条笔记在新增时同步一份索引到聚合表,首页只查聚合表一张就够了。这是典型的「空间换时间」优化。
五、后台接口测试
后台代码写完了,先不急着对接前端,先用接口测试工具验证一下接口是否正常。我用的是 Apifox(类似 Postman 的接口测试工具)。
测试流程:
先调用登录接口获取 token
把 token 放到后续请求的 Header 里(Authorization: Bearer xxx)
调用 POST /note/text 新建一条笔记,验证返回的 id
调用 GET /note/text/{id} 查询刚建的笔记,验证内容正确
调用 PUT /note/text 修改笔记,再查一次验证修改生效
调用 GET /note/text/page 分页查询,验证列表能查到
调用 DELETE /note/text 删除,再查验证查不到了
调用 GET /note/text/count 验证统计数据
测试通过后,后台接口就算完成了。接下来进入前后端联调阶段。
六、第四个提示词:前端对接保存接口
后台接口好了,现在要把前端的 mock 调用替换成真实接口。第一个要替换的是保存功能。我给 AI 发了提示词:
提示词内容:
把编辑页的 doSave 函数从 mock 改为调用真实接口:
新建笔记:调用 POST /note/text,提交标题、正文、分类、标签、字数
修改笔记:调用 PUT /note/text,提交 id + 标题、正文、分类、标签、字数
新建成功后,从返回数据里拿到笔记 id,保存到 noteId 变量(后续自动保存就变成修改了)
保存中状态显示「保存中...」,成功显示「已保存」,失败显示「保存失败」
请求头带 token(Authorization: Bearer xxx)
在 api/note.ts 里新增 saveText 和 updateText 两个函数
AI 做了什么:
在 api/note.ts 里新增了 saveText(POST)和 updateText(PUT)两个函数
doSave 函数改为:如果有 noteId 就调 updateText,没有就调 saveText
新建成功后从返回数据取 id 赋值给 noteId.value
保存状态在 try/catch/finally 里更新
在 api/note.ts 中新增的接口函数:

编辑页面调用接口的关键片段:

七、第五个提示词:前端对接查询接口
保存搞定了,接下来是查询。打开已有笔记时需要从后台加载数据。我给 AI 发了提示词:
提示词内容:
把 loadNote 函数从 mock 改为调用真实接口:
调用 GET /note/text/{id} 获取笔记详情
把返回的 title、content、category、tags 填充到页面变量
加载中显示 loading 状态,加载完关闭
加载失败显示「加载失败」提示
在 api/note.ts 里新增 getTextById 函数
加载成功后记录一条浏览历史(调用 recordView 接口)
AI 做了什么:
在 api/note.ts 里新增了 getTextById 函数
loadNote 改为调用 getTextById,把返回数据填充到 title、content、category、tagText
加载成功后调用 recordView 记录浏览历史
用 isLoading 变量控制加载状态,加载中不触发自动保存
在 api/note.ts 中新增的查询函数:

编辑页面加载数据的关键片段:

注意:这里的 isLoading.value = true 就是后面踩坑篇「加载数据触发自动保存」的预防措施。如果不加这行,给 title、content 赋值时会触发 watch 监听,导致刚打开页面就触发了一次保存。
八、第六个提示词:分类字典对接
分类标签之前用的是 mock 数据,现在要改为从字典接口加载。但这里有个问题:PIG 框架原本只提供了查询单个字典的接口,如果页面需要多个字典(比如分类字典 + 标签字典),就要发多次请求,网络不好的时候体验很差。所以我让 AI 在字典控制器里加了一个批量查询方法:
在字典控制器里新增一个 getDictByTypes 方法,支持批量查询多个字典:传入一个字典 key 的数组(比如 note_category,note_tag),一次返回所有字典数据。前端调用 GET /dict/types?types=note_category,note_tag 就能同时获取多个字典。
AI 做了什么:
在字典控制器新增了批量查询接口,接收逗号分隔的字典 key,一次返回多个字典数据
前端在 onLoad 里调用 getDictByTypes('note_category') 获取分类字典
如果以后需要多个字典,只需传
getDictByTypes('note_category,note_tag')一次搞定把返回的数据映射为 { label, value } 格式赋值给 categories
mock 数据删除,完全由接口驱动
前端调用字典接口的片段:

什么是字典接口?:字典就是「选项列表」。分类(杂记/想法/工作/学习/生活)就是一种字典,存在数据库的字典表里。后台提供字典接口,前端调用后拿到选项列表。好处是:要加新分类不用改代码,在后台管理页面加一条字典记录就行。
为什么要批量查询?:框架原本只支持一次查一个字典,如果页面需要 3 个字典就要发 3 次请求。在弱网环境下,3 次请求意味着 3 倍的等待时间,还可能有的成功有的失败。批量查询一次返回所有字典,请求次数从 N 次变成 1 次,体验好很多。
九、踩坑时刻:联调过程中的问题
9.1 问题:新建笔记后自动保存变重复新建
现象:新建笔记第一次保存成功了,但继续编辑触发自动保存时,又创建了一条新笔记,导致重复。
原因:新建保存成功后,没有把返回的 id 存下来,noteId 还是 null,下一次自动保存又走了新建逻辑。
怎么告诉 AI 修复:
新建笔记保存成功后,从返回数据里取 id 赋值给 noteId.value。这样下一次自动保存时 noteId 有值,就会走 updateText 修改逻辑,不会重复新建。
9.2 问题:加载笔记时触发了自动保存
现象:打开已有笔记,数据刚加载到输入框里,还没等用户操作呢,自动保存就触发了,保存状态显示「保存中...」。
原因:loadNote 给 title、content 赋值时,触发了 watch 监听,watch 调用了 scheduleSave。
怎么告诉 AI 修复:
加一个 isLoading 变量,加载笔记时设为 true,加载完设为 false。scheduleSave 里检查 isLoading,如果是 true 就直接 return,不触发保存。这样数据填充到输入框时不会误触发自动保存。
9.3 问题:跨域请求被拦截
现象:前端请求后台接口,浏览器控制台报跨域错误(CORS)。
原因:前端开发服务器端口和后台端口不一样,浏览器默认拦截跨域请求。
怎么告诉 AI 修复:
前端开发时配置代理,把 /api 开头的请求代理到后台地址。在 vite.config 里配置 server.proxy,把 /api 代理到 http://localhost:9999。
什么是跨域?:浏览器有个安全策略叫「同源策略」,只允许网页请求和自己在同一域名、同一端口的接口。前端在 localhost:5173,后台在 localhost:9999,端口不一样就是跨域,浏览器会拦截。解决办法是配置代理,让前端请求先发给前端服务器,前端服务器再转发给后台,绕过浏览器的同源策略。
vite 代理配置片段:

9.4 问题:保存接口返回 401
现象:保存笔记时后台返回 401 未授权。
原因:请求头没带 token,后台不知道是谁在操作。
怎么告诉 AI 修复:
请求头需要带 Authorization: Bearer + token。token 从本地存储获取(uni.getStorageSync('access_token'))。请在 http 请求拦截器里统一添加,不用每个请求手动加。
AI 在请求拦截器里统一加了 Authorization 头,所有请求自动带上 token。关键片段:

十、前后端联调的完整流程
最后总结一下前后端联调的完整流程:
十一、总结
后台接口开发和前后端联调的几个关键经验:
后台先测试再联调:先用接口测试工具验证后台接口正常,再对接前端,能快速定位问题在前端还是后台
MyBatis-Plus 省了大量 SQL:继承 BaseMapper 就有 CRUD,LambdaQuery 拼条件,大部分接口零 SQL
新建成功后一定要存 id:不存 id 就分不清是新建还是修改,会导致重复创建
加载数据时要屏蔽自动保存:用 isLoading 标记,加载中不触发 watch 的保存逻辑
token 在拦截器统一添加:不用每个请求手动加,拦截器自动处理
跨域用代理解决:开发时配置 vite 代理,上线后同域名就没有跨域问题
同步聚合表保证数据一致:主表增删改时同步到聚合表,首页和列表页统一查聚合表
下一篇预告:下一篇将展示语音笔记的开发过程,包括录音功能、音频文件上传、语音转文字等功能。
夜雨聆风