当前位置: 首页 > 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.jsqmd.com/news/1050146/

相关文章:

  • 2026年阿里云618 Hermes Agent/OpenClaw配置Token Plan详细步骤一文讲清
  • 南京靠谱宠物店整理,新手买宠可以先看看 - 园友3800037
  • 影刀RPA子流程设计:让复杂流程变清晰
  • 2026年6月脉冲除尘滚振清理筛供货厂家怎么选择,脉冲除尘滚振清理筛/滚振组合清理筛,脉冲除尘滚振清理筛制造企业哪家专业 - 品牌推荐师
  • 2026年6月最新格拉苏蒂中国官方售后电话热线网点地址客服服务 - 亨得利官方服务中心
  • 果速修服务流程全透明:先检测再报价后维修,全程录像+旧件归还,热线400-811-2953 - 博客万
  • emWin显示驱动与VNC服务器集成:嵌入式GUI开发实战指南
  • 3分钟解决iPhone USB网络共享问题:Windows驱动一键安装方案
  • 重访Jahnke与Emde函数手册:从查表插值到现代数值计算
  • Windows风扇控制神器FanControl:5分钟打造静音高效散热系统
  • Python毕设选题推荐:基于 Django 的校园跳蚤市场交易平台设计与实现 智能化校园二手商品交易管理系统【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 企业级大模型私有化部署深度指南:从模型选型到SLA运维
  • 2026年6月最新格拉苏蒂中国官方售后电话网点地址及客户服务热线 - 亨得利官方服务中心
  • 2026深度实测!主流AI编程助手横向对比,开发者真实选型指南
  • 南充翻译盖章:2026最新办理流程 - 资讯速览
  • 无锡本地买宠避坑指南,附几家宠物店参考 - 园友3800037
  • 前端组件库建设实践:提升开发效率的利器
  • 第17周学习总结
  • PIC17CXX外部SRAM接口设计:时序计算、硬件连接与调试实战
  • 绵阳翻译盖章:2026最新办理流程 - 资讯速览
  • 果速修2026年品牌发展全景:从上海首店到全国200+门店,官方热线400-811-2953 - 博客万
  • 面试篇-String、StringBuffer和StringBuilder有什么区别?
  • 闲置钻石变现避坑!2026 年 6 月上海正规回收机构攻略 - 奢侈品交易观察员
  • 2026河源黄金奢侈品回收靠谱门店TOP5|中检双认证河源源奢汇领衔,附避坑指南 - 生活测评小能手
  • 2026年6月20日郴州金价大跌!最新回收行情+变现时机+靠谱门店排名 - 小仙贝贝
  • 终极网盘下载加速方案:一键解锁八大平台满速下载
  • 网盘直链下载助手:告别限速,九大网盘高速下载完全指南
  • 台州怎么登报?办理流程详解 - 资讯速览
  • 宁波本地买宠避坑指南,附几家宠物店参考 - 园友3800037
  • HeaderEditor插件:修改HTTP请求头绕过Google人机验证