ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flask+Vue宠物领养平台开发实战:前后端分离与JWT认证完整实现

Flask+Vue宠物领养平台开发实战:前后端分离与JWT认证完整实现 最近后台好多同学在问宠物领养平台怎么做今天我就把这个项目从头到尾拆一遍。先说明白这个项目用的是 Flask 做后端、Vue 做前端、PyCharm 做开发工具标题里的 Django 其实是很多人在搜索过程中参考过的框架实际项目里不会两个同时当主力后端用我后面会详细说这个矛盾点怎么处理。这套组合特别适合做毕业设计或者课程项目的同学需求清晰、技术栈经典、演示效果也好看而且 Flask 和 Vue 都相对轻量短时间能跑通全流程属于性价比很高的选题方向。这个平台解决的核心问题是线下宠物领养信息不透明、流程难追踪的痛点。用户可以在网页上浏览待领养宠物列表、查看宠物详情、提交领养申请管理员可以在后台审核申请、管理宠物信息、管理用户。涉及的技术点包括 RESTful API 设计、ORM 数据建模、前后端分离、CORS 跨域、JWT 身份认证、文件上传处理等每一个都是当前 Web 开发岗位面试的高频考点做完这个项目简历上能写的内容就很扎实了。1. 项目整体设计与技术选型思路1.1 为什么是 Flask Vue而不是其他组合先解决标题里那个最容易让新手懵的问题Flask 和 Django 到底怎么选。这两个都是 Python 生态里的 Web 框架但设计哲学完全不同。Django 是全家桶路线自带 Admin 后台、ORM、表单验证、认证系统适合大型项目但学习曲线陡峭灵活性相对低。Flask 是微框架路线核心只处理路由和请求其他模块自己按需集成代码写起来自由度高非常适合毕设这种规模的项目。标题里两个都出现大概率是前期调研时都看了实际开发中你只需要选一条路走通。我推荐 Flask理由很简单宠物领养平台的核心功能其实不复杂用 Flask 能把每个接口的逻辑都看清楚答辩时老师问起来你也讲得明白。Vue 这边的理由更直接——它是目前国内中小型前端项目占有率最高的框架之一官方文档中文完善社区资料多遇到问题基本都能搜到答案。React 也很好但对新手来说 JSX 的门槛高于 Vue 的模板语法。Vue 的响应式数据绑定、组件化开发模式和 Flask 的轻量后端配合起来前后端职责清晰调试起来不用折腾复杂的构建链PyCharm 一个工具就能同时搞定 Python 和前端代码的编辑。1.2 项目功能模块规划整个平台拆成用户端和管理端两条线。用户端包含注册登录、宠物浏览、宠物筛选搜索、宠物详情查看、提交领养申请、查看申请进度、个人资料管理这些功能管理端包含宠物信息发布与下架、领养申请审核、用户列表查看、数据统计概览。听起来模块不少但归类之后就清晰了本质上是三类操作——一是对宠物信息的增删改查二是对领养申请的流转处理三是对用户身份的管理。分类清楚之后数据库表的设计和 API 的设计就都有了依据。这里分享一个小经验开始敲代码之前先把自己当成真实用户把界面流程画一遍。不需要画得多精美术就是简单的线框图把用户在哪个页面能干什么事标注清楚。这个环节花半天时间后面写代码至少少走两三天弯路。特别是申请领养的流程从用户提交到管理员审核中间状态怎么流转不提前想清楚写后台接口的时候会来回改。1.3 开发环境与版本选型这个项目的开发环境我建议这样搭操作系统Windows 10/11 或 macOS 均可开发工具PyCharm 专业版或社区版均可社区版免费够用Python 版本3.9 或 3.10Flask 版本2.3.xVue 版本Vue 2.7 或 Vue 3.x建议 Vue 3 Vite 组合启动速度快数据库MySQL 5.7 或 8.0也可以用 SQLite 做本地快速开发接口联调工具Postman 或 Apifox版本选择上有一个关键提醒Flask 2.x 和 1.x 在依赖处理上有细微差别Vue 3 和 Vue 2 的语法差异更大。如果不想被版本问题折腾直接在 PyCharm 里新建虚拟环境然后统一安装指定版本。后续我会把具体安装命令写出来照着执行就不会踩版本坑。2. 数据库设计与后端核心接口实现2.1 数据库表结构设计宠物领养平台我用四张核心表加两张辅助表。先说四张核心表用户表users要存的字段是用户ID主键自增、用户名、密码加密后的哈希值、手机号、邮箱、用户角色用户/管理员、注册时间。密码必须加密存储这里用 Werkzeug 自带的 generate_password_hash 函数就够了不要自己写加密算法也不要明文存储这是答辩时评委老师一定会关注的点。宠物表pets宠物ID、宠物名称、宠物种类狗/猫/其他、品种、年龄、性别、是否绝育、疫苗状态、描述信息、照片路径、领养状态待领养/已申请/已领养、发布时间、发布人。发布人这个字段可以关联用户表表示这是哪个管理员录入的也可以设计成匿名发布看你自己需求。领养申请表adoptions申请ID、宠物ID外键关联宠物表、申请用户ID外键关联用户表、申请原因、居住情况、养宠经验、申请状态待审核/通过/拒绝、申请时间、审核时间、审核备注。收藏表favorites收藏ID、用户ID、宠物ID、收藏时间。这是最基础的表结构后面前端写页面的时候所有接口的数据来源都是这些表。设计表的时候要多留一个心眼字段命名统一用下划线风格Python 代码里用 SQLAlchemy 映射的时候会舒服很多。提示表结构设计阶段多花一小时后面写代码少花三天。字段设计遗漏是后期改代码最头疼的问题尤其外键关联的表一旦中间改字段名所有引用它的代码都要跟着动。2.2 Flask 项目初始化与配置后端代码的组织结构推荐按照功能模块来分组而不是把所有路由堆在一个文件里。我的建议目录结构是这样的pet_adoption_backend/ ├── app.py # 应用入口 ├── config.py # 配置文件 ├── extensions.py # 扩展初始化db, jwt等 ├── models/ │ ├── __init__.py │ ├── user.py │ ├── pet.py │ └── adoption.py ├── api/ │ ├── __init__.py # 蓝图注册 │ ├── auth.py # 登录注册接口 │ ├── pets.py # 宠物管理接口 │ ├── adoptions.py # 领养申请接口 │ └── users.py # 用户信息接口 ├── utils/ │ ├── __init__.py │ ├── response.py # 统一响应格式 │ └── auth_decorator.py # 登录验证装饰器 └── requirements.txt第一次做 Flask 项目的同学最容易犯的错误是——把所有路由全部写在 app.py 里一个文件几百行最后自己都找不到接口在哪里。用蓝图Blueprint把不同模块的路由区分开在 app.py 里统一注册代码清晰度直接提升一个档次。创建 Flask 应用之后接着做三件事配置数据库连接、初始化 SQLAlchemy、配置 CORS。这里给出核心初始化逻辑# extensions.py from flask_sqlalchemy import SQLAlchemy from flask_cors import CORS db SQLAlchemy() def init_extensions(app): db.init_app(app) CORS(app, resources{r/api/*: {origins: *}})# app.py from flask import Flask from extensions import init_extensions, db def create_app(): app Flask(__name__) app.config.from_object(config.Config) init_extensions(app) # 注册蓝图 from api.auth import auth_bp from api.pets import pets_bp from api.adoptions import adoptions_bp from api.users import users_bp app.register_blueprint(auth_bp, url_prefix/api/auth) app.register_blueprint(pets_bp, url_prefix/api/pets) app.register_blueprint(adoptions_bp, url_prefix/api/adoptions) app.register_blueprint(users_bp, url_prefix/api/users) with app.app_context(): db.create_all() return app if __name__ __main__: app create_app() app.run(debugTrue, port5000)CORS 这里要特别说明一下前后端分离项目前端跑在 5173 端口Vite 默认后端跑在 5000 端口浏览器会因为跨域限制拦截请求。Flask-CORS 这个扩展就是专门解决这个问题的上面代码里origins*表示允许所有来源访问开发阶段这么配没问题上线部署时最好改成具体的域名安全一些。2.3 宠物管理接口实现详解宠物模块是整个平台的核心接口设计围绕前端页面需要的功能展开。具体来说是这样几个接口GET /api/pets宠物列表支持分页、按品种筛选、按关键字搜索GET /api/pets/ 宠物详情POST /api/pets新增宠物管理员权限PUT /api/pets/ 修改宠物信息管理员权限DELETE /api/pets/ 删除宠物管理员权限PUT /api/pets/ /status修改宠物领养状态其中一个很实用的细节是列表接口的筛选参数设计。前端页面通常有按种类筛选、关键字搜索、分页加载这些需求后端接口可以直接把过滤逻辑内置到查询里from flask import request from models.pet import Pet pet_bp.route(, methods[GET]) def get_pets(): page request.args.get(page, 1, typeint) per_page request.args.get(per_page, 10, typeint) category request.args.get(category, ) keyword request.args.get(keyword, ) status request.args.get(status, ) query Pet.query if category: query query.filter(Pet.category category) if keyword: query query.filter(Pet.name.like(f%{keyword}%)) if status: query query.filter(Pet.status status) pagination query.paginate(pagepage, per_pageper_page, error_outFalse) return { code: 0, data: { items: [pet.to_dict() for pet in pagination.items], total: pagination.total, page: page, pages: pagination.pages } }这样设计的好处是前端传不传参数都不影响接口正常运行不传就返回全部数据的第一页传了就做条件过滤。分页参数用typeint声明后Flask 会自动做类型转换即使前端传了非数字值也不会报 500 错误而是取默认值。2.4 领养申请流程设计领养申请是整个平台业务流程的核心这里我用了一个状态机模型。状态流转是用户提交申请状态变为待审核管理员审核通过状态变为已通过同时宠物状态变为已领养管理员审核拒绝状态变为已拒绝宠物状态保持不变这个状态流转逻辑看似简单但实现时必须把两件表的更新放在同一个事务里防止出现申请通过了宠物还是待领养状态的数据不一致问题。SQLAlchemy 里用db.session.commit()一次性提交如果中途出错就db.session.rollback()adoption_bp.route(/int:adoption_id/approve, methods[POST]) def approve_adoption(adoption_id): adoption Adoption.query.get_or_404(adoption_id) adoption.status approved adoption.review_time datetime.now() pet Pet.query.get(adoption.pet_id) pet.status adopted db.session.commit() return {code: 0, message: 审核通过}这里有一个容易被忽略的业务规则同一只宠物在待领养状态下只能有一条待审核的申请记录。防止多个用户同时申请同一只宠物做申请的时候要先查一下宠物状态# 提交申请前校验 pet Pet.query.get(data[pet_id]) if pet.status ! available: return {code: 1, message: 该宠物已被申请或已领养} adoption Adoption.query.filter_by( pet_iddata[pet_id], statuspending ).first() if adoption: return {code: 1, message: 该宠物已有待审核的申请}2.5 用户认证与权限控制登录认证我用 JWTJSON Web Token。流程是用户登录成功后后端生成一个包含用户ID和角色的 token 返回给前端前端把 token 存到 localStorage 里之后每个请求的请求头都带上Authorization: Bearer token后端的装饰器从 token 里解析出用户信息判断是否有权限访问。JWT 的好处是服务器不需要保存会话状态天然适合前后端分离架构。Flask 里可以用 PyJWT 或者 flask-jwt-extended 这个扩展。flask-jwt-extended 封装了创建 token、验证 token、获取当前用户这几个常用操作省不少事from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity auth_bp.route(/login, methods[POST]) def login(): data request.get_json() user User.query.filter_by(usernamedata.get(username)).first() # 使用 werkzeug 安全校验密码 if user and check_password_hash(user.password_hash, data.get(password)): token create_access_token(identitystr(user.id)) return {code: 0, data: {token: token, user: user.to_dict()}} return {code: 1, message: 用户名或密码错误} # 管理员权限校验装饰器 from functools import wraps from flask_jwt_extended import verify_jwt_in_request, get_jwt def admin_required(fn): wraps(fn) def wrapper(*args, **kwargs): verify_jwt_in_request() claims get_jwt() if claims.get(role) ! admin: return {code: 403, message: 需要管理员权限}, 403 return fn(*args, **kwargs) return wrapper注意JWT 的 secret key 要单独放在配置变量里开发环境可以用随机字符串部署到服务器时用环境变量注入不要写死在代码里。这个属于代码安全的基本素养答辩时加分项。3. 前端 Vue 页面搭建与前后端联调3.1 Vue 项目初始化与环境配置前端部分我用 Vue 3 Vite 来搭建。很多教程还在用 Vue CLI 创建项目但 Vite 已经是目前官方推荐的构建工具启动速度比 Webpack 快一个量级配置也更简洁。环境配置前需要先装好 Node.js建议装 16 以上的版本npm 会跟着一起装好。打开命令行工具执行下面这几步# 创建 Vue 项目 npm create vitelatest pet_adoption_frontend -- --template vue # 进入项目目录 cd pet_adoption_frontend # 安装依赖 npm install # 安装路由和请求库 npm install vue-router axios # 启动开发服务器 npm run devVite 创建的项目目录结构很干净src下面有components、views、router等文件夹。如果是第一次用 Vue 3建议先看看src/main.js里的挂载逻辑和src/App.vue的组件结构理解了这两个文件再把页面往里填。PyCharm 里写前端代码也完全没问题社区版就支持 Vue 文件的语法高亮。装一个 Vue.js 官方插件写单文件组件体验会好很多。调试的时候Chrome 浏览器装一下 Vue Devtools 扩展可以在浏览器里直观看到组件的响应式数据状态排查数据绑定的问题非常有用。3.2 前端页面组件划分宠物领养平台的前端页面我按用户操作路径拆成了这几个页面首页Home.vue宠物卡片流式展示顶部导航栏含搜索框宠物详情页PetDetail.vue展示宠物所有信息 领养申请入口登录/注册页Auth.vue登录注册切换个人中心Profile.vue我的申请、我的收藏、个人资料修改管理后台Admin.vue宠物信息管理、领养审核列表每个页面拆成更小的子组件比如首页的宠物卡片可以抽成PetCard.vue这样宠物列表和详情页的推荐列表都能复用同一个卡片组件。组件化开发的核心优势就是复用和隔离数据变化只触发对应组件的重新渲染不会整个页面都刷新。前端路由用 vue-router 管理页面路径和组件的映射关系写在路由配置文件里。需要特别注意的路由守卫管理后台的页面路由要检查当前用户是不是管理员不是管理员就重定向到首页。这个判断的依据是 localStorage 里存的用户信息中的 role 字段。// router/index.js import { createRouter, createWebHistory } from vue-router const routes [ { path: /, component: () import(../views/Home.vue) }, { path: /pet/:id, component: () import(../views/PetDetail.vue) }, { path: /login, component: () import(../views/Auth.vue) }, { path: /profile, component: () import(../views/Profile.vue), meta: { requiresAuth: true } }, { path: /admin, component: () import(../views/Admin.vue), meta: { requiresAdmin: true } } ] const router createRouter({ history: createWebHistory(), routes }) // 全局前置守卫 router.beforeEach((to, from, next) { const user JSON.parse(localStorage.getItem(user) || null) if (to.meta.requiresAuth !user) { next(/login) } else if (to.meta.requiresAdmin (!user || user.role ! admin)) { next(/) } else { next() } })3.3 API 请求封装与 Axios 拦截器前后端通信统一通过 Axios 发起 HTTP 请求。但有个细节要注意直接在组件里写axios.get(/api/pets)会有两个问题——一是每个请求都要拼完整的 BaseURL二是每个请求都要手动加 Authorization 请求头。我的做法是先在src/utils/request.js里创建一个 Axios 实例import axios from axios const request axios.create({ baseURL: http://127.0.0.1:5000/api, timeout: 10000 }) // 请求拦截器自动附加 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截器统一处理错误码 request.interceptors.response.use( response { const res response.data if (res.code ! 0) { // 业务错误统一提示 alert(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res.data }, error { if (error.response error.response.status 401) { // token 过期跳转到登录页 localStorage.removeItem(token) localStorage.removeItem(user) window.location.href /login } alert(error.message || 网络错误) return Promise.reject(error) } ) export default request封装好之后每个接口调用只需要写一行代码const pets await request.get(/pets, { params: { page: 1, per_page: 10 } })所有请求自动带上 token所有业务错误统一弹窗提示401 自动跳登录页。这个封装看着简单实际联调的时候能帮你省掉大量重复代码。3.4 图片上传与文件路径处理宠物信息录入的时候需要上传宠物照片这是前后端联调时坑最多的环节。后端的处理方式是这样的Flask 接收前端传来的 multipart/form-data 文件保存到一个专门的 uploads 目录然后把文件路径存到数据库的 photo_url 字段里。import os from flask import request, current_app from werkzeug.utils import secure_filename pet_bp.route(/upload, methods[POST]) def upload_image(): file request.files.get(file) if not file: return {code: 1, message: 没有接收到文件} # 校验文件类型 allowed_extensions {png, jpg, jpeg, gif, webp} filename secure_filename(file.filename) extension filename.rsplit(., 1)[1].lower() if . in filename else if extension not in allowed_extensions: return {code: 1, message: 不支持的图片格式} # 生成唯一文件名防止重名覆盖 import uuid new_filename f{uuid.uuid4().hex}.{extension} upload_dir current_app.config[UPLOAD_FOLDER] os.makedirs(upload_dir, exist_okTrue) file.save(os.path.join(upload_dir, new_filename)) return { code: 0, data: { url: f/uploads/{new_filename} } }注意这几点secure_filename是为了过滤掉文件名里的特殊字符防止路径穿越攻击用uuid生成新文件名是为了避免不同用户上传了同名文件互相覆盖图片格式白名单校验是防止上传木马文件。但这里有一个细节光把文件保存到 uploads 目录还不够还需要让 Flask 能把 uploads 目录下的文件作为静态资源对外提供访问。在 app.py 里加上from flask import send_from_directory app.route(/uploads/path:filename) def uploaded_file(filename): return send_from_directory(current_app.config[UPLOAD_FOLDER], filename)前端上传文件的组件用input typefile加 FormData 实现const handleFileChange async (e) { const file e.target.files[0] if (!file) return const formData new FormData() formData.append(file, file) const result await request.post(/pets/upload, formData, { headers: { Content-Type: multipart/form-data } }) petForm.photoUrl result.url }前后端联调时遇到图片显示不出来90% 是路径问题。要么是前端拼的 URL 不对要么是后端的 uploads 静态路由没配好。开发环境直接用http://127.0.0.1:5000/uploads/xxx.jpg访问图片能访问就说明后端没问题剩下的就是前端拼路径的事。3.5 核心页面逻辑代码展示给一个宠物卡片组件为例看 Vue 3 的script setup写法template div classpet-card clickgoDetail img :srcpet.photoUrl || defaultImage :altpet.name / div classpet-info h3{{ pet.name }}/h3 span classtag{{ pet.category }}/span span classtag{{ pet.age }}岁/span p classstatus :classpet.status {{ statusMap[pet.status] }} /p /div /div /template script setup import { useRouter } from vue-router const props defineProps({ pet: { type: Object, required: true } }) const router useRouter() const statusMap { available: 待领养, pending: 已被申请, adopted: 已领养 } const goDetail () { router.push(/pet/${props.pet.id}) } /script style scoped .pet-card { border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; cursor: pointer; transition: transform 0.2s; } .pet-card:hover { transform: translateY(-4px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .pet-card img { width: 100%; height: 200px; object-fit: cover; } /styleVue 3 的组合式 API 用ref和reactive管理数据computed计算派生状态watch监听变化生命周期用onMounted取代 Vue 2 的mounted。这些核心概念掌握之后写页面就是往模板里填数据的过程了。4. 常见问题排查与实操避坑记录4.1 问题的快速定位数据库连接失败后端项目在 PyCharm 里刚跑起来最常见的报错是Cant connect to MySQL server on 127.0.0.1。这个问题的排查顺序是第一步确认 MySQL 服务有没有启动Windows 下可以在服务管理里查看 MySQL 服务状态第二步确认连接配置里的用户名密码和 MySQL 的一致第三步确认数据库有没有创建。前两步没问题还报错那大概率是数据库没建在 MySQL 里执行一下CREATE DATABASE pet_adoption DEFAULT CHARACTER SET utf8mb4;这里强调一下MySQL 8.0 默认字符集已经是 utf8mb4但如果你用的 5.7 版本建库时一定要指定 utf8mb4否则存中文会出现乱码。Python 连接 MySQL 还需要先装驱动pip install pymysql然后在配置文件里把连接字符串写成SQLALCHEMY_DATABASE_URI mysqlpymysql://root:your_passwordlocalhost:3306/pet_adoption有些环境还需要显式设一下SQLALCHEMY_ENGINE_OPTIONS把连接池的编码指定为 utf8mb4。这些细节不处理后面展示中文数据出现问号时就很难受。4.2 Pycharm 里的依赖安装和环境管理用 PyCharm 跑 Flask 项目不建议直接往系统的 Python 里装包而是应该在项目里创建虚拟环境。PyCharm 新建项目时可以选 Virtualenv创建后Terminal里执行 pip 安装的命令会自动装到当前项目的虚拟环境里。但很多同学遇到过这个问题Pycharm 的终端里执行pip install flask后运行项目还是提示 ModuleNotFoundError这是因为解释器没选对。到File - Settings - Project - Python Interpreter里确认一下当前项目用的解释器是不是虚拟环境里的那个 Python。依赖安装还有一个值得注意的点一定要生成一份requirements.txt文件方便换电脑或者最后部署的时候一键安装所有依赖。生成方式是在项目根目录执行pip freeze requirements.txt恢复环境的时候只需要pip install -r requirements.txt4.3 前后端联调最常见的跨域报错前端写好了后端也写好了打开浏览器访问 Vue 提供的地址打开 F12 控制台最常见的一行红字就是Access to XMLHttpRequest at http://127.0.0.1:5000/api/... from origin http://localhost:5173 has been blocked by CORS policy。这个报错的原因就是跨域。解决方案前面提过了后端加 Flask-CORS。但有个细节容易忽略Flask-CORS 的配置要放在注册蓝图之前还是之后都没关系关键是CORS(app)这一行必须执行到。如果你用了应用工厂模式记得在init_extensions(app)里调用。另外一个可以前端解决的方案是在 Vite 的配置文件里配置代理把/api开头的请求代理到后端地址// vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { proxy: { /api: { target: http://127.0.0.1:5000, changeOrigin: true } } } })这样前端代码里所有请求地址直接写/api/xxx就行不需要写完整的后端地址也顺便解决了跨域问题。代理模式下浏览器看到的请求是同源的不会触发 CORS 拦截。不过要注意如果用这种方式前端 Axios 实例的baseURL就不要带后端端口号了写成/api就行。4.4 记忆犹新的一个 Bug上传图片路径对但前端显示 404这个坑我印象很深上传接口返回了图片路径数据库里也存上了前端拼接出来的图片地址在浏览器里直接回车也能访问但是页面上的img标签就是显示不出来控制台报 404。排查了半天发现问题是前端把图片路径里的斜杠处理错了。最后定位到原因Axios 响应拦截器里我返回的是res.data而这个res.data字段里正好有一个data对象图片路径是嵌套在里边的url。前端拿的时候用的是pet.photoUrl但后端把这个字段返回时命名成了photo_url前端和后端字段名对不上最终导致 photoUrl 是 undefined图片 src 拼成了undefined。这个其实不是复杂的 Bug但花了将近两个小时排查。从那以后我总结出一个习惯前后端约定字段命名风格。我的做法是后端接口返回的 JSON 直接用驼峰命名比如photoUrl这样跟前端 JS 的命名习惯保持一致省去中间转换的麻烦。如果你后端坚持用下划线命名那前端拿到数据后要做一个字段映射二选一就行混着用一定会出错。4.5 必坑经验列表接口的 SQL 注入防御Flask-SQLAlchemy 的 ORM 查询本身对 SQL 注入有较好的防御因为查询参数会被转义。但有一个地方容易忽略直接用db.session.execute(text())拼接 SQL 时如果参数直接拼进字符串就会有注入风险。正确做法是用 SQLAlchemy 的绑定参数。如果业务场景允许用 ORM 就尽量用 ORM不要图省事写原生 SQL。比如模糊搜索用原生 SQL 可能想写# 错误示范直接拼接用户输入 db.session.execute(text(fSELECT * FROM pets WHERE name LIKE %{keyword}%))如果用户输入% OR 11 --这个 SQL 就变成了查询所有数据安全性直接崩掉。正确做法db.session.execute( text(SELECT * FROM pets WHERE name LIKE :keyword), {keyword: f%{keyword}%} )4.6 常见问题速查表问题现象可能原因解决方案MySQL 连接失败MySQL 服务未启动或配置错误检查 MySQL 服务、数据库名、用户名密码中文乱码数据库字符集不是 utf8mb4建库时指定 utf8mb4连接串加编码参数CORS 报错跨域未配置或配置未生效后端加 Flask-CORS 或前端配 Vite 代理上传图片 404图片保存路径或静态路由配置错误检查 uploads 目录和send_from_directory路由图片字段 undefined前后端字段名不一致统一命名字段风格严格按接口文档联调401 循环跳转路由守卫和响应拦截器冲突检查拦截器逻辑避免无限重定向虚拟环境安装的包找不到PyCharm 解释器未切换到虚拟环境Settings - Python Interpreter 选择正确解释器分页 total 始终为 0查询条件写错或未提交事务检查过滤条件和db.session.commit()前端启动端口占用Vite 默认 5173 被占用修改 vite.config.js 的 port 或关闭占用进程5. 项目部署与其他实践扩展5.1 本地打包与部署思路项目做完之后如果要在毕业设计答辩现场做演示部署是最后一道关卡。这里给两条路。一条是只在本机部署后端 Flask 直接跑前端npm run build把静态文件构建出来然后想办法让 Nginx 或者 Flask 自己托管前端静态文件。另外一条是部署到云服务器把代码上传到服务器用 gunicorn 或 waitress 作为 WSGI 服务器跑 FlaskNginx 做反向代理同时托管前端静态文件和转发后端 API 请求。Nginx 反向代理的配置核心思想就一句话把所有/api请求转发到后端端口其他请求都走前端静态文件。一个最小可用的配置大概是这样的server { listen 80; server_name your_domain_or_ip; # 前端静态文件 root /var/www/pet_adoption_frontend/dist; index index.html; # 前端路由 history 模式需要 location / { try_files $uri $uri/ /index.html; } # 后端 API 反向代理 location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 上传图片访问 location /uploads/ { proxy_pass http://127.0.0.1:8000; } }有个容易踩的坑Vue Router 如果用的是 history 模式刷新非首页路由时会 404。因为 Nginx 找不到对应的文件路径需要配置try_files $uri $uri/ /index.html把所有路径都 fallback 到 index.html由前端路由自己决定渲染什么内容。如果是 hash 模式就没有这个问题但 URL 里会有#符号视觉效果不太美观所以我还是推荐 history 模式加 try_files 配置。5.2 功能扩展方向如果想让这个项目在答辩时更有亮点可以考虑加这么几个功能第一个是申请进度通知。当管理员通过了用户的领养申请用户在个人中心能看到状态变化但目前没有推送提醒。可以加一个站内消息表审核通过时生成一条消息记录用户登录后拉取未读消息数。思路不复杂但能体现你考虑了用户体验。第二个是我的收藏功能。用户可以收藏心仪的宠物方便后续对比和决定。数据库加一张收藏表前端在详情页加一个收藏按钮接口无非是增删查三个操作。这类多对多关系的表设计是数据库设计的经典考点答辩时老师问起来也很好回答。第三个是数据可视化。管理员后台加一个简单的统计页面展示不同种类宠物的数量占比、每周新增的领养申请数量趋势、各品种的领养率排行。前端可以用 ECharts 画柱状图、饼图、折线图。后端不需要太多工作聚合查询几行代码就能完成但视觉效果好很多还能证明你有一点数据可视化能力。5.3 这个项目带给我的技术收获做完这个项目我最大的收获不是学会了 Flask 或 Vue 的某个具体 API而是彻底理解了前后端分离架构下一个完整业务功能从前端页面到后端接口再到数据库的整个数据流转过程。从用户在浏览器点击申请领养按钮到前端发起 POST 请求到后端校验身份和业务规则到修改数据库中的两条记录再到前端收到响应更新页面状态全链路清晰了就没那么怕开发了。数据库设计上我深刻体会到了表结构设计不只是列字段那么简单。状态字段怎么定、外键关联怎么设置、索引该建在哪一列、哪些查询能用上索引、哪些查不出来这些在上手之前以为都是后面再说的问题。真正开始做才发现前三张表设计得有没有道理直接决定了后面所有接口和页面的开发效率。比如领养申请表的联合唯一约束没设计好就会出现多个人同时申请同一只宠物的脏数据。这类问题到后期才补救代价非常高。还有一个心得是开发这个项目时一定要用 Git 做版本管理。哪怕是个人项目每次大功能做完就提交一次写坏了不至于整个项目回炉。我第一次做的时候偷懒没建 Git 仓库改坏了三个文件连带没备份最后只能手动重写丢失的代码那种感觉记忆犹新。建议一开始就git init每完成一个模块就提交一次。最后分享一个小经验如果你是用这个项目做毕业设计答辩演示前一定要准备一条完整的演示路径。比如注册一个新用户 - 浏览宠物列表 - 搜索某类宠物 - 查看详情 - 提交领养申请 - 切换到管理员账号 - 审核通过 - 切回普通用户查看状态。把这条路径跑顺比准备十页 PPT 都管用。我在实际演示中测试过走完整条流程大概是三分钟期间可以在关键节点停顿说一下该接口的工作逻辑比如在申请审核环节顺口提一句这里我用事务保证了宠物状态和申请状态的一致性效果非常好比自己干讲技术点自然得多。
返回列表