资讯中心

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

📅 2026/6/20 18:51:59
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):componentssnake_str.split(_)returncomponents[0].join(x.title()forxincomponents[1:])defapi_response(dataNone,message操作成功,code200,statussuccess):converted_dataconvert_dict_keys(data)ifdataisnotNoneelseNonereturnjsonify({status:status,code:code,message:message,data:converted_data})关键特性自动将 Python 下划线命名转换为前端习惯的驼峰命名统一的响应结构status、code、message、data支持递归转换嵌套数据结构2. 宠物头像上传功能这是这次优化的核心功能之一从最初仅支持 URL 改为支持本地文件上传。前端实现1. 模板结构divclassavatar-uploaddivclassavatar-previewclicktriggerFileInputimgv-ifpreviewAvatarUrl:srcpreviewAvatarUrlalt头像预览/divv-elseclassavatar-placeholderspan点击上传/span/div/divinputreffileInputReftypefileacceptimage/*changehandleFileChangestyledisplay:none//div2. 文件处理与预览constfileInputRefref(null)constselectedFileref(null)constpreviewAvatarUrlref()consthandleFileChange(event){constfileevent.target.files[0]if(!file)returnselectedFile.valuefileconstreadernewFileReader()reader.onload(e)previewAvatarUrl.valuee.target.result reader.readAsDataURL(file)}3. 智能上传逻辑constonSaveasync(){if(selectedFile.value){constformDatanewFormData()formData.append(petName,form.petName)formData.append(avatar,selectedFile.value)awaitaddPet(formData)}else{awaitaddPet({petName:form.petName,avatarUrl:form.avatarUrl})}closeModal()awaitfetchPets()}后端实现1. 路由与配置pet_bpBlueprint(pet,__name__)UPLOAD_FOLDERos.path.join(os.path.dirname(os.path.dirname(__file__)),static,avatars)ALLOWED_EXTENSIONS{png,jpg,jpeg,gif}os.makedirs(UPLOAD_FOLDER,exist_okTrue)defallowed_file(filename):return.infilenameandfilename.rsplit(.,1)[1].lower()inALLOWED_EXTENSIONS2. 添加宠物接口pet_bp.route(/,methods[POST])jwt_required()defadd_pet():user_idint(get_jwt_identity())userUser.query.filter_by(user_iduser_id).first()datarequest.get_json(silentTrue)avatar_filerequest.files.get(avatar)# 同时支持 FormData 和 JSON 两种格式pet_namedata.get(pet_name)ordata.get(petName)ifdataelse\ request.form.get(pet_name)orrequest.form.get(petName)# 文件处理final_avatar_urlavatar_urlifavatar_fileandallowed_file(avatar_file.filename):original_filenamesecure_filename(avatar_file.filename)unique_filenamef{uuid.uuid4().hex}_{original_filename}file_pathos.path.join(UPLOAD_FOLDER,unique_filename)avatar_file.save(file_path)final_avatar_urlunique_filename# 保存宠物信息new_petPet(user_iduser.user_id,pet_namepet_name,avatar_urlfinal_avatar_url)db.session.add(new_pet)db.session.commit()returnapi_response(data{petId:new_pet.pet_id,avatarUrl:return_avatar_url},code201),201request.js 适配关键FormData 不能手动设置 Content-Type要让浏览器自动处理request.interceptors.request.use(config{consttokenlocalStorage.getItem(token)if(token){config.headers.AuthorizationBearer${token}}if(!(config.datainstanceofFormData)){config.headers[Content-Type]application/json}returnconfig})3. 401 认证错误处理Token 过期时需要给用户友好的提示并自动跳转request.interceptors.response.use(response{constresresponse.dataif(res.code200||res.statussuccess){returnres.data!undefined?res.data:res}toast.error(res.message||请求失败)returnPromise.reject(newError(res.message||请求失败))},error{if(error.responseerror.response.status401){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 写法模块化管理状态exportconstuseUserStoredefineStore(user,(){constuserref(null)constisLoggedInref(false)constrestoreLogin(){consttokenlocalStorage.getItem(token)conststoredUserlocalStorage.getItem(user)if(tokenstoredUser){user.valueJSON.parse(storedUser)isLoggedIn.valuetrue}}constfetchUserInfoasync(){constuserDataawaitgetUserInfo()user.valueuserData localStorage.setItem(user,JSON.stringify(userData))}return{user,isLoggedIn,restoreLogin,fetchUserInfo}})三、踩过的坑与经验总结1. z-index 层级问题问题用户下拉菜单被页面其他元素遮挡。解决使用2147483647浏览器最大值作为关键元素的 z-index层级关系Modal Navbar Content确保有定位属性relative/absolute/fixedz-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 两种方式流畅的前后端交互统一响应、错误处理完善清晰的代码架构模块化设计、易于维护后续优化方向虽然核心功能已经完成但仍有优化空间图片优化上传前压缩、裁剪、生成缩略图性能优化CDN 加速、请求缓存、异步处理功能扩展更多宠物健康相关功能部署上线生产环境配置、运维自动化