2026山东大学项目实训个人博客(六)
前面阶段已经完成了注册登录、基础路由、数据库表设计和核心功能模块的代码编写各模块——音频上传、情绪识别、健康评估、历史记录——都已经能够独立运行。但随着开始对接所有页面AI模块的同学也开始频繁调用数据库存储识别结果后端的“体感”一下子变得吃重起来。本阶段我的工作重心不再是“写新接口”而是将已有的后端模块做深度整合、稳定性加固和联调保障确保整个系统在串联运行时依然稳定可靠、数据不出错、体验不卡顿。一、统一返回格式与全局响应封装在前期快速迭代中不同接口的返回格式存在细微差异。有的接口成功时返回 {“code”: 0, “data”: {…}}有的返回 {“code”: 200, “msg”: “success”, “data”: {…}}错误时有的返回 {“error”: “xxx”}有的返回 {“msg”: “xxx”}。{code:200,# 200表示成功其他均为错误码400参数错误、401未登录、403无权限、404资源不存在、500服务器错误msg:操作成功,# 用户友好的提示信息data:{}# 具体业务数据成功时必填失败时为null}随后在 utils/response.py 中封装了三个快捷函数前端同学同步封装了响应拦截器全局判断 res.data.code遇到401自动跳转登录页遇到其他错误码自动弹出 ElMessage.error。改动完成后前端代码中所有接口的 catch 逻辑几乎可以全部删掉代码量缩减了约15%联调效率大幅提升。这件事让我意识到后端定义的不只是数据更是团队协作的契约。二、数据库事务与 flush() 的深度理解保证多表写入原子性联调中最先暴露的问题是数据半残。AI模块的同学在调用 /audio/analyze 接口时有时音频信息写入了 audio_records 表但情绪结果emotion_results却因为枚举类型不匹配就是我上周博客里提到的中文vs英文枚举问题插入失败。由于代码里是分两次 db.session.add() 然后直接 db.session.commit()失败时 audio_records 已经提交成功导致历史记录里出现“孤儿记录”——有音频却没有情绪结果前端列表页渲染时报空指针。我重新审视了这块逻辑利用 SQLAlchemy 的事务机制做了严格改造defsave_analysis_result(pet_id,audio_file,emotion_result):try:# 1. 先创建 audio_record但不提交audio_recordAudioRecord(pet_idpet_id,file_nameaudio_file.filename,file_pathpermanent_path,durationduration,file_sizefile_size)db.session.add(audio_record)db.session.flush()# 仅同步到数据库获取自增 record_id不提交事务# 2. 用拿到的 record_id 创建 emotion_resultemotionEmotionResult(record_idaudio_record.record_id,emotion_typeemotion_enum,confidenceconfidence,suggestionsuggestion)db.session.add(emotion)# 3. 两条记录一起提交要么全成功要么全失败db.session.commit()returnsuccess(data{record_id:audio_record.record_id})exceptExceptionase:db.session.rollback()logger.error(f保存识别结果失败:{str(e)})returnerror(识别结果保存失败请重试)这里的关键是 flush() vs commit() 的区别。flush() 会将 SQL 发送到数据库执行让自增主键 record_id 真正生成并回填到对象上但事务并未结束可以继续添加新的操作而 commit() 才真正提交整个事务。这套机制保证了 audio_records 和 emotion_results 永远成对出现彻底消除了“孤儿记录”。修改后联调中再未出现过数据不一致的情况。三、时区统一与时间格式序列化前端同学在历史记录列表按“近7天”“近30天”筛选时发现筛选结果总是“差8小时”。排查后发现Python 后端使用的 datetime.now() 是系统本地时间东八区存入 MySQL 时被转成了 UTC 时间TIMESTAMP 类型默认会做时区转换而前端从接口拿到时间戳后直接渲染没有做时区转换导致前端展示的时间比实际少了8小时筛选逻辑也因此混乱。这个问题的本质是后端返回给前端的时间格式不规范。我采用了两层解决方案统一时间存储规范所有模型中的时间字段created_at、updated_at统一使用 datetime.utcnow() 存入 UTC 时间确保数据库中所有时间基准一致不受服务器时区影响统一输出格式在序列化返回给前端时统一格式化为 “YYYY-MM-DD HH:mm:ss” 字符串defformat_datetime(dt):ifnotdt:returnNone# 如果是 naive datetime先加上时区信息ifdt.tzinfoisNone:dtdt.replace(tzinfotimezone.utc)local_dtdt.astimezone(timezone(timedelta(hours8)))returnlocal_dt.strftime(%Y-%m-%d %H:%M:%S)修改完所有接口的返回字段后前端同学将所有 created_at 直接展示无需任何额外处理筛选功能也恢复正常。虽然是很小的改动但联调过程中这类“差8小时”的问题往往最耗精力提前规范能省去大量排查时间。四、日志系统搭建让排查问题有迹可循之前调试主要靠 print()控制台一关就什么记录都没了。用户试用时如果出现问题我们完全无法复现原因。本周我接入了 Flask 自带的 logging 模块并配置了文件日志importloggingfromlogging.handlersimportRotatingFileHandler handlerRotatingFileHandler(logs/app.log,maxBytes10*1024*1024,backupCount5)handler.setLevel(logging.INFO)formatterlogging.Formatter([%(asctime)s] %(levelname)s in %(module)s: %(message)s)handler.setFormatter(formatter)app.logger.addHandler(handler)现在所有关键操作——用户注册登录、接口调用、第三方SDK请求、数据库异常——都会写入日志文件。特别在调用大模型和音频识别SDK时我会在发送请求前和收到响应后分别记录日志包括请求参数和响应状态码。这样一旦联调中出现超时或返回格式异常不需要反复复现直接翻日志就能定位到具体是哪一步出了问题。此外我还给日志区分了级别INFO 记录正常业务流程WARNING 记录参数校验失败等可预期问题ERROR 记录异常堆栈。后续用户试用时收集到的反馈我们可以对照日志时间轴精准还原用户操作路径极大提升了问题排查效率。五、分页查询与索引优化历史记录模块在测试初期只有几十条数据响应速度尚可。但随着我们反复测试音频上传和识别前后累计生成200多条记录前端列表页加载时间从300ms飙升到了1.2s体感有明显的卡顿。我通过分析 SQL 日志发现每次查询历史记录都是 SELECT * FROM audio_records ORDER BY created_at DESC并且是全表扫描没有走索引。添加复合索引CREATEINDEXidx_user_createdONaudio_records(user_id,created_atDESC);由于每个用户只能看到自己的记录查询条件必然带 user_id加上按 created_at 倒序排序这个复合索引完美覆盖了查询扫描行数从200多行降到了仅10行每页只取10条响应时间立刻回落到50ms以内。后端分页实现我统一封装了分页工具函数所有列表接口都支持 page 和 page_size 参数默认每页10条最大不超过50条避免一次查询加载过多数据拖慢数据库def paginate(query,page,page_size): pagemax(1,int(page))page_sizemin(50,max(1,int(page_size)))paginatedquery.paginate(pagepage,per_pagepage_size,error_outFalse)return{list: paginated.items,total: paginated.total,page: page,page_size: page_size,pages: paginated.pages }前端同学据此实现了“加载更多”和“分页跳转”两种模式用户查看历史记录时再也感受不到延迟。六、本周总结与感悟技术理解一前后端联调中接口契约的重要性这次联调让我对“接口契约”有了切实体会。前期各自开发的时候前后端基本是各写各的前端按照自己的理解定义请求参数和期望的返回字段后端也按照自己的理解去实现接口。等到真正串起来的时候才发现问题一堆前端传的字段名后端取不到后端返回的字段名前端对不上状态码含义也不统一同一个错误前端可能要做好几种兼容判断。联调的时候报了字段缺失的错误排查了半天才发现是命名没对齐。还有返回格式的问题。技术理解二用户数据隔离与权限校验的逻辑闭环用户登录之后只能操作自己的数据这个逻辑说起来很简单但真正落到代码里涉及到好几个层面。首先是登录态校验每个需要登录的接口都要验证 JWT Token 是否有效这是第一道防线。但光校验 Token 还不够Token 只能证明“你是合法登录的用户”不能证明“你正在操作的数据属于你”。比如删除宠物接口如果只校验了 Token 而没校验宠物归属那用户 A 完全可以通过修改请求参数里的 pet_id 去删除用户 B 的宠物。本周的工作围绕“整合”与“稳定”展开虽然没有开发任何炫酷的新功能但每一项改动都直接提升了系统的健壮性和团队协作效率。从统一返回格式、事务原子性保障、时区规范化到日志系统搭建、索引优化和权限补漏我深刻感受到另外和前端、AI模块同学的密切配合让我意识到规范的价值远大于个人编码技巧。统一返回格式、统一时间格式、统一分页参数这些看似“琐碎”的约定恰恰是多角色协作时的核心内容。