当前位置: 首页 > news >正文

2026山东大学项目实训项目博客(八)

宠声健康 AI:一个完整的前后端分离项目实战总结

前言

最近完成了「宠声健康 AI」项目的开发与优化,这是一个功能完整的宠物健康管理应用。整个项目采用了前后端分离的架构,前端使用 Vue 3 + Pinia + Vite,后端使用 Flask + SQLAlchemy + JWT,实现了从用户认证到宠物管理、文件上传等核心功能。

在这篇文章中,我将对整个项目进行一次系统的总结,分享架构设计、核心功能实现以及踩过的坑。


一、项目技术架构

技术栈一览

层级技术选择说明
前端Vue 3 + Pinia + Vite现代化前端技术栈,Composition API 写法
后端Flask + SQLAlchemy + JWT轻量级 Python Web 框架
认证JWT + localStorage无状态认证方案
存储SQLite轻量级关系型数据库

目录结构

travel_agent/ ├── backend/ # 后端服务 │ ├── routes/ # 路由模块 │ │ ├── auth.py # 认证相关 │ │ ├── pet.py # 宠物管理 │ │ └── ... │ ├── models.py # 数据模型 │ ├── utils/ # 工具函数 │ ├── static/ # 静态文件 │ └── app.py ├── frontend/ # 前端应用 │ ├── src/ │ │ ├── api/ # API 接口定义 │ │ ├── components/ # 公共组件 │ │ ├── stores/ # Pinia 状态管理 │ │ ├── utils/ # 工具函数 │ │ ├── views/ # 页面组件 │ │ └── main.js └── docs/ # 文档目录

二、核心功能实现

1. 统一响应格式设计

前后端分离架构中,统一的响应格式是协作的基础。我在后端封装了api_response函数,实现了以下功能:

defsnake_to_camel(snake_str):components=snake_str.split('_')returncomponents[0]+''.join(x.title()forxincomponents[1:])defapi_response(data=None,message="操作成功",code=200,status="success"):converted_data=convert_dict_keys(data)ifdataisnotNoneelseNonereturnjsonify({"status":status,"code":code,"message":message,"data":converted_data})

关键特性

  • 自动将 Python 下划线命名转换为前端习惯的驼峰命名
  • 统一的响应结构:statuscodemessagedata
  • 支持递归转换嵌套数据结构

2. 宠物头像上传功能

这是这次优化的核心功能之一,从最初仅支持 URL 改为支持本地文件上传。

前端实现

1. 模板结构

<divclass="avatar-upload"><divclass="avatar-preview"@click="triggerFileInput"><imgv-if="previewAvatarUrl":src="previewAvatarUrl"alt="头像预览"/><divv-elseclass="avatar-placeholder"><span>点击上传</span></div></div><inputref="fileInputRef"type="file"accept="image/*"@change="handleFileChange"style="display:none"/></div>

2. 文件处理与预览

constfileInputRef=ref(null)constselectedFile=ref(null)constpreviewAvatarUrl=ref('')consthandleFileChange=(event)=>{constfile=event.target.files[0]if(!file)returnselectedFile.value=fileconstreader=newFileReader()reader.onload=(e)=>previewAvatarUrl.value=e.target.result reader.readAsDataURL(file)}

3. 智能上传逻辑

constonSave=async()=>{if(selectedFile.value){constformData=newFormData()formData.append('petName',form.petName)formData.append('avatar',selectedFile.value)awaitaddPet(formData)}else{awaitaddPet({petName:form.petName,avatarUrl:form.avatarUrl})}closeModal()awaitfetchPets()}
后端实现

1. 路由与配置

pet_bp=Blueprint('pet',__name__)UPLOAD_FOLDER=os.path.join(os.path.dirname(os.path.dirname(__file__)),'static','avatars')ALLOWED_EXTENSIONS={'png','jpg','jpeg','gif'}os.makedirs(UPLOAD_FOLDER,exist_ok=True)defallowed_file(filename):return'.'infilenameandfilename.rsplit('.',1)[1].lower()inALLOWED_EXTENSIONS

2. 添加宠物接口

@pet_bp.route('/',methods=['POST'])@jwt_required()defadd_pet():user_id=int(get_jwt_identity())user=User.query.filter_by(user_id=user_id).first()data=request.get_json(silent=True)avatar_file=request.files.get('avatar')# 同时支持 FormData 和 JSON 两种格式pet_name=data.get('pet_name')ordata.get('petName')ifdataelse\ request.form.get('pet_name')orrequest.form.get('petName')# 文件处理final_avatar_url=avatar_urlifavatar_fileandallowed_file(avatar_file.filename):original_filename=secure_filename(avatar_file.filename)unique_filename=f"{uuid.uuid4().hex}_{original_filename}"file_path=os.path.join(UPLOAD_FOLDER,unique_filename)avatar_file.save(file_path)final_avatar_url=unique_filename# 保存宠物信息new_pet=Pet(user_id=user.user_id,pet_name=pet_name,avatar_url=final_avatar_url)db.session.add(new_pet)db.session.commit()returnapi_response(data={"petId":new_pet.pet_id,"avatarUrl":return_avatar_url},code=201),201
request.js 适配

关键!FormData 不能手动设置 Content-Type,要让浏览器自动处理:

request.interceptors.request.use(config=>{consttoken=localStorage.getItem('token')if(token){config.headers.Authorization=`Bearer${token}`}if(!(config.datainstanceofFormData)){config.headers['Content-Type']='application/json'}returnconfig})

3. 401 认证错误处理

Token 过期时,需要给用户友好的提示并自动跳转:

request.interceptors.response.use(response=>{constres=response.dataif(res.code===200||res.status==='success'){returnres.data!==undefined?res.data:res}toast.error(res.message||'请求失败')returnPromise.reject(newError(res.message||'请求失败'))},error=>{if(error.response&&error.response.status===401){localStorage.removeItem('token')localStorage.removeItem('user')if(window.location.pathname!=='/login'){toast.error('登录已过期,请重新登录')setTimeout(()=>window.location.href='/login',1000)}}else{toast.error(error.response?.data?.message||error.message)}returnPromise.reject(error)})

4. Pinia 状态管理

采用 Pinia 的 setup 写法,模块化管理状态:

exportconstuseUserStore=defineStore('user',()=>{constuser=ref(null)constisLoggedIn=ref(false)constrestoreLogin=()=>{consttoken=localStorage.getItem('token')conststoredUser=localStorage.getItem('user')if(token&&storedUser){user.value=JSON.parse(storedUser)isLoggedIn.value=true}}constfetchUserInfo=async()=>{constuserData=awaitgetUserInfo()user.value=userData localStorage.setItem('user',JSON.stringify(userData))}return{user,isLoggedIn,restoreLogin,fetchUserInfo}})

三、踩过的坑与经验总结

1. z-index 层级问题

问题:用户下拉菜单被页面其他元素遮挡。

解决

  • 使用2147483647(浏览器最大值)作为关键元素的 z-index
  • 层级关系:Modal > Navbar > Content
  • 确保有定位属性(relative/absolute/fixed)z-index 才生效
.navbar{position:relative;z-index:2147483647;}

2. FormData 处理问题

问题:发送 FormData 时强制设置Content-Type: multipart/form-data,导致后端解析失败。

解决:FormData 不要手动设置 Content-Type,让浏览器自动处理(会自动加上正确的 boundary)。

3. 文件上传安全

踩坑:直接使用用户上传的文件名,可能存在路径遍历攻击风险。

解决

  • 使用secure_filename处理文件名
  • 配合 UUID 防止文件名冲突

4. 命名规范统一

经验

  • 后端使用 Python 下划线命名(pet_name)
  • 前端使用 JavaScript 驼峰命名(petName)
  • 通过统一响应格式函数自动转换,避免前后端不一致问题

四、项目收尾与成果

经过这轮优化,项目已经具备了:

完善的用户认证体系:登录、登出、Token 管理
便捷的宠物管理功能:增删改查、头像上传
优秀的文件上传体验:本地文件 + URL 两种方式
流畅的前后端交互:统一响应、错误处理完善
清晰的代码架构:模块化设计、易于维护

后续优化方向

虽然核心功能已经完成,但仍有优化空间:

  1. 图片优化:上传前压缩、裁剪、生成缩略图
  2. 性能优化:CDN 加速、请求缓存、异步处理
  3. 功能扩展:更多宠物健康相关功能
  4. 部署上线:生产环境配置、运维自动化
http://www.gsyq.cn/news/1562061.html

相关文章:

  • 企业级大模型私有化部署深度指南:从模型选型到SLA运维
  • 绵阳翻译盖章:2026最新办理流程 - 资讯速览
  • 前端组件库建设实践:提升开发效率的利器
  • 闲置钻石变现避坑!2026 年 6 月上海正规回收机构攻略 - 奢侈品交易观察员
  • 网盘直链下载助手:告别限速,九大网盘高速下载完全指南
  • 面试篇-String、StringBuffer和StringBuilder有什么区别?
  • 2026河源黄金奢侈品回收靠谱门店TOP5|中检双认证河源源奢汇领衔,附避坑指南 - 生活测评小能手
  • HeaderEditor插件:修改HTTP请求头绕过Google人机验证
  • ZenStatesDebugTool:AMD锐龙处理器硬件调试的终极解决方案
  • 基于AI视觉的桌面GUI自动化:UI-TARS Desktop原理与实践
  • GESP7级C++考试语法知识(四、哈希表(4、unordered_map)
  • 线段树算法总结
  • SCF5250微控制器:嵌入式音频系统核心架构与驱动开发实战
  • 电瓶车托运保价别踩坑!2026避坑指南+正确买法 - 快递物流资讯
  • Python毕业设计-基于 Python 的题库资源综合管理系统的设计与实现 基于 Python 的教育题集处理与管理系统(源码+LW+部署文档+全bao+远程调试+代码讲解等)
  • 2026年AI学习机推荐:对比四类产品,奇多多通过了启蒙考验 - 新闻快传
  • 嵌入式GUI开发实战:emWin模拟触摸屏驱动与校准全解析
  • 一人AI公司实战指南:从需求切片到首笔收款的14个关键动作
  • 二手平台哪个更靠谱?不看广告看机制,四大平台实测对比 - 新闻快传
  • 2026南京大牌闲置变现底价指南|不赚差价,实时行情顶格报价回收 - 讯息早知道
  • 商用洗碗机怎么选?苏州本地利宝厨具一站式解决方案 - 新闻快传
  • 二手平台哪个更靠谱?从质检、价格到隐私,2026横向对比见分晓 - 新闻快传
  • 算法入门|埃拉托斯特尼筛法,一张表筛出 1~120 所有质数
  • echarts-for-weixin:微信小程序数据可视化架构设计与企业级应用实践
  • 5秒无损转换B站缓存视频:m4s-converter快速入门指南
  • 广州白蚁防治哪家强?对比5家实测,青林微创探巢完胜 - 博客万
  • 如何3分钟完成Windows与Office永久激活:KMS_VL_ALL_AIO智能激活指南
  • 2026 临沂实木全屋定制口碑 TOP5:回访 5000 + 入住满 1 年业主 - 新闻快传
  • 2026年林芝精密钢管工厂哪家强,冷拔精密无缝钢管/45# 冷拔无缝钢管,精密钢管源头厂家哪家专业 - 品牌推荐师
  • Windows系统文件imm32.dll丢失找不到问题解决