
好这个标题一看就是典型的毕设/课设项目但我得说一句SSM Vue 的组合到现在依然是 Java Web 入门阶段最能打的一套搭配。很多同学纠结要不要换 Spring Boot其实如果你能把 SSM 的原理吃透后面上手 Spring Boot 基本就是降维打击。这篇文章我就基于“SSM218的宠物商城及领养管理系统vue”这个项目把整套系统的设计思路、核心模块、实操要点和踩坑记录全部拆开讲清楚希望能给正在做类似项目的朋友一些参考。1. 项目整体设计与技术选型思路1.1 项目背景与需求定位宠物商城及领养管理系统从名字就能看出两大业务主线一是宠物用品的电商交易二是流浪宠物的领养管理。这类系统在现实中非常常见比如宠物店自营的线上商城、动物救助机构的领养平台都需要把“卖货”和“领养”两条业务逻辑整合在一起。从需求层面拆解系统核心要解决三个问题商品交易用户浏览宠物食品、玩具、用品等商品加入购物车下单购买管理员处理订单。宠物领养管理员发布可领养的宠物信息用户浏览并提交领养申请管理员审核并记录领养结果。后台管理管理员对商品、宠物、订单、用户、领养申请进行统一管理。这类系统非常适合作为毕业设计或者项目实战练手因为它既有电商的标准流程商品、购物车、订单又有业务审核流程领养申请、审核、状态流转覆盖了业务系统开发中相当全面的场景。1.2 为什么选 SSM Vue 而不是别的组合技术选型上SSMSpring SpringMVC MyBatis Vue 是经过权衡的选择。后端用 SSM 的理由很实在这套框架组合在 Java 企业级开发里统治了很多年虽然现在 Spring Boot 已经成了主流但 SSM 能让你更清楚地看到 Spring 的 IoC 容器是怎么管理对象的、SpringMVC 的请求分发流程是怎么走的、MyBatis 的 SQL 映射和参数绑定是怎么回事。这些东西在 Spring Boot 里被高度封装后反而很难看清。对于学习阶段来说SSM 是一套很好的“解剖教材”。而且这个项目涉及的表结构、业务逻辑复杂度适中用 SSM 来写不会像大型分布式系统那样繁琐代码量可控。前端选 Vue 则是因为它的生态成熟、上手曲线平缓。Vue 2 的 Options API 语法对新手非常友好模板语法直观双向绑定能省掉大量 DOM 操作。特别是做后台管理界面Vue Element UI 的组合几乎成了标配。如果你用的是 Vue 3那 Composition API 的写法更灵活配合 Vite 构建速度也快很多。前后端分离是这套架构的另一个关键点。后端只提供 RESTful API前端通过 axios 异步请求数据。这样做的好处是职责清晰前端专注页面交互后端专注业务逻辑和数据处理分工明确调试也方便。提示如果你是在校生做毕设强烈建议前后端分离。一方面答辩时能展示的内容更多另一方面你只需要把项目跑起来前端 npm run dev、后端 Tomcat 启动整个项目就能运转老师演示时效果很好。1.3 系统角色与功能模块全景整个系统按用户角色划分主要有三类游客、普通用户、管理员。游客可以浏览商品列表、宠物列表但不能下单和提交领养申请需要注册登录。普通用户注册登录后可以管理个人信息浏览商品、加入购物车、下单购买、查看订单状态可以浏览待领养宠物、提交领养申请、查看审核进度。管理员登录后台管理界面管理商品分类和商品信息、管理宠物信息、处理订单发货、完成、审核领养申请、管理用户账号、查看数据统计。从功能模块角度看整个系统可以拆成六大块用户模块注册、登录、个人信息管理、密码修改。商品模块商品分类、商品列表、商品详情、商品搜索。购物车与订单模块购物车增删改查、下单、订单列表、订单状态管理。宠物领养模块宠物发布、宠物列表、领养申请提交、申请审核。后台管理模块商品管理、宠物管理、订单管理、用户管理、领养审核。数据统计模块商品销量统计、宠物领养统计可选锦上添花。2. 后端核心模块设计与数据库建模2.1 数据库表结构设计数据库设计是整个系统的地基表结构设计得合理后面写代码会顺畅很多设计得不好后期改起来极为痛苦。我基于常见的业务需求设计了以下核心数据表。用户表t_user字段名类型说明idint主键自增usernamevarchar(50)用户名唯一passwordvarchar(100)密码建议 MD5 加密存储nicknamevarchar(50)昵称phonevarchar(20)手机号emailvarchar(50)邮箱avatarvarchar(255)头像地址roleint角色1管理员0普通用户create_timedatetime注册时间商品表t_product字段名类型说明idint主键namevarchar(100)商品名称category_idint分类ID关联分类表pricedecimal(10,2)价格stockint库存imagevarchar(255)商品图片descriptiontext商品描述salesint销量statusint状态1上架0下架商品分类表t_category字段名类型说明idint主键namevarchar(50)分类名称remarkvarchar(255)备注购物车表t_cart字段名类型说明idint主键user_idint用户IDproduct_idint商品IDquantityint数量create_timedatetime添加时间订单表t_order字段名类型说明idint主键order_novarchar(50)订单编号user_idint用户IDtotal_amountdecimal(10,2)总金额statusint状态0待付款1待发货2已发货3已完成4已取消receiver_namevarchar(50)收货人姓名receiver_phonevarchar(20)收货人电话receiver_addressvarchar(255)收货地址create_timedatetime下单时间订单明细表t_order_item字段名类型说明idint主键order_idint订单IDproduct_idint商品IDproduct_namevarchar(100)商品名称快照product_imagevarchar(255)商品图片快照pricedecimal(10,2)成交单价quantityint数量subtotaldecimal(10,2)小计宠物表t_pet字段名类型说明idint主键namevarchar(50)宠物名称categoryvarchar(50)宠物类别猫、狗、兔子等breedvarchar(50)品种ageint年龄月genderint性别0公1母health_statusvarchar(255)健康状况imagevarchar(255)宠物图片descriptiontext详细描述statusint状态0待领养1已被申请2已领养create_timedatetime发布时间领养申请表t_adopt_apply字段名类型说明idint主键pet_idint宠物IDuser_idint申请用户IDreasontext领养理由experiencetext养宠经验statusint状态0待审核1通过2拒绝apply_timedatetime申请时间audit_timedatetime审核时间audit_remarkvarchar(255)审核备注这里特别说一下订单明细表。很多新手设计订单表时只存一个总金额订单里的商品信息不管了。这是大忌。订单明细表里的 product_name、product_image、price 字段我特意标注了“快照”意思是在下单那一刻把这些信息原样复制一份存下来。为什么因为商品信息是动态的管理员可以改价、改名字甚至删商品如果订单里只关联商品ID等用户回头查订单时看到的商品信息可能已经变了。使用快照可以保证订单历史记录永久不变这在电商系统里是基本要求。2.2 SSM 整合与分层架构后端代码结构按照 SSM 标准分层来组织这是 Java Web 开发最常见的分包方式。com.pet ├── controller # 控制层接收前端请求 ├── service # 业务层处理业务逻辑 │ └── impl # 业务实现 ├── mapper # 数据访问层MyBatis的Mapper接口 ├── entity # 实体类对应数据库表 ├── common # 公共类如统一返回结果、分页工具 └── config # 配置类如拦截器、跨域配置SSM 整合的核心是配置文件牵一发动全身。简单说一下关键配置。spring-mvc.xml主要配置注解驱动、静态资源映射、视图解析器。前后端分离后主要返回 JSON所以配置好消息转换器即可。spring-mybatis.xml负责整合 MyBatis配置数据源、SqlSessionFactory、Mapper扫描等。数据库连接用 Druid 连接池配置文件里写数据库地址、用户名、密码。这里有个细节数据库连接参数里建议加上useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai否则中文乱码和时区问题会把你折腾够呛。Controller 层接收前端请求调用 Service 层处理业务Service 层调用 Mapper 层操作数据库。所有接口统一返回 JSON 格式我习惯定义一个 Result 类包含 code、message、data 三个字段code200 表示成功其他表示各种异常。这样做前端统一处理响应时非常方便不用每个接口都写一堆判断逻辑。2.3 核心接口设计与 RESTful 风格接口设计遵循 RESTful 风格用 HTTP 方法区分操作类型语义清晰。模块方法接口路径说明用户POST/api/user/register注册用户POST/api/user/login登录用户GET/api/user/info获取当前用户信息商品GET/api/product/list商品列表支持分页、关键字搜索商品GET/api/product/detail/{id}商品详情购物车GET/api/cart/list购物车列表购物车POST/api/cart/add加入购物车购物车PUT/api/cart/update修改购物车商品数量购物车DELETE/api/cart/delete/{id}删除购物车项订单POST/api/order/create创建订单订单GET/api/order/list订单列表宠物GET/api/pet/list宠物列表领养POST/api/adopt/apply提交领养申请领养GET/api/adopt/my我的领养申请后台GET/api/admin/order/list管理员查看所有订单后台PUT/api/admin/order/status管理员更新订单状态后台GET/api/admin/pet/list管理员查看宠物列表后台POST/api/admin/pet/add管理员发布宠物后台PUT/api/admin/adopt/audit审核领养申请注意接口路径里的/api前缀强烈建议保留。后期如果你需要做网关、统一权限控制或者接口版本管理有前缀会方便很多。3. Vue 前端项目搭建与核心页面实现3.1 前端项目初始化和环境配置前端用的是 Vue具体版本看你自己的选择。Vue 2 Element UI 老牌稳定资料多Vue 3 Element Plus 是现在的趋势新项目建议直接用 Vue 3。别被网上那些“Vue 3 生态不完善”的旧说法误导了现在主流组件库基本都支持 Vue 3 了。创建项目的命令很简单# 如果你用的 Vue 2 npm install -g vue/cli vue create pet-frontend # 如果你用的 Vue 3 Vite推荐速度更快 npm create vitelatest pet-frontend -- --template vue创建完成后进入项目目录安装必要的依赖cd pet-frontend npm install # 安装基础依赖 npm install axios # HTTP请求库 npm install element-plus element-plus/icons-vue # Element Plus组件库Vue 3 npm install vue-router4 # 路由 npm install pinia # 状态管理Vue 3 推荐用 Pinia 替代 Vuex注意安装 Element Plus 后需要在 main.js 里全局注册import { createApp } from vue import ElementPlus from element-plus import element-plus/dist/index.css import App from ./App.vue const app createApp(App) app.use(ElementPlus) app.mount(#app)如果你完全按这个流程走到这一步前端项目就能启动起来了。跑npm run dev看到默认欢迎页面说明环境没问题。3.2 前端项目目录结构与路由设计前端项目结构我习惯按照功能模块来组织src ├── api # 存放所有接口请求文件 │ ├── user.js # 用户相关接口 │ ├── product.js # 商品相关接口 │ ├── cart.js # 购物车相关接口 │ ├── order.js # 订单相关接口 │ └── adopt.js # 领养相关接口 ├── assets # 静态资源 ├── components # 公共组件 │ ├── Header.vue # 头部导航 │ └── Footer.vue # 底部 ├── router # 路由配置 │ └── index.js ├── store # 状态管理 │ └── user.js ├── views # 页面组件 │ ├── home # 首页 │ ├── product # 商品相关页面 │ ├── cart # 购物车 │ ├── order # 订单 │ ├── adopt # 领养 │ ├── user # 个人中心 │ └── admin # 后台管理 ├── App.vue └── main.js路由配置上有一点特别重要区分前台页面和后台管理页面。前台页面用普通布局后台管理页面需要登录且只有管理员能访问所以要配置路由守卫。// router/index.js import { createRouter, createWebHistory } from vue-router const routes [ { path: /, component: () import(../views/home/Home.vue) }, { path: /product/detail/:id, component: () import(../views/product/ProductDetail.vue) }, { path: /cart, component: () import(../views/cart/Cart.vue) }, { path: /adopt, component: () import(../views/adopt/AdoptList.vue) }, { path: /admin, component: () import(../views/admin/AdminLayout.vue), meta: { requiresAuth: true, isAdmin: true }, children: [ { path: product, component: () import(../views/admin/AdminProduct.vue) }, { path: pet, component: () import(../views/admin/AdminPet.vue) }, { path: order, component: () import(../views/admin/AdminOrder.vue) }, { path: adopt, component: () import(../views/admin/AdminAdopt.vue) } ] } ] const router createRouter({ history: createWebHistory(), routes }) // 路由守卫验证登录状态和管理员权限 router.beforeEach((to, from, next) { const userInfo localStorage.getItem(userInfo) if (to.meta.requiresAuth !userInfo) { next(/login) } else if (to.meta.isAdmin) { const user JSON.parse(userInfo || {}) if (user.role ! 1) { next(/) } else { next() } } else { next() } }) export default router路由守卫这块特别容易出错我多说两句。判断登录状态最常见的方式是把用户信息或 token存到 localStorage 里路由跳转前检查一下。但要注意 localStorage 里的数据用户可以手动改所以真正安全的权限校验必须在后端做前端路由守卫只是体验层面的拦截。我在后端专门写了一个拦截器校验请求头里带的 token校验失败直接返回 401前端拿到 401 后跳转登录页。双保险才靠谱。3.3 axios 封装与 API 调用axios 请求封装是前端项目里最值得花时间做的一件事。如果不封装每个页面都直接写axios.get(...)那么遇到 token 过期、接口报错、加载状态这些通用逻辑你要在几十个文件里重复处理。我推荐的做法是建立一个统一的请求模块// api/request.js import axios from axios import { ElMessage } from element-plus import router from ../router const request axios.create({ baseURL: /api, // 通过 Vite 代理解决跨域 timeout: 10000 }) // 请求拦截器自动附加 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization token } return config }) // 响应拦截器统一处理错误码 request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { ElMessage.error(登录已过期请重新登录) localStorage.removeItem(token) localStorage.removeItem(userInfo) router.push(/login) } else { ElMessage.error(网络异常请稍后重试) } return Promise.reject(error) } ) export default request请求封装好了之后具体接口模块写起来就清爽了// api/product.js import request from ./request export function getProductList(params) { return request.get(/product/list, { params }) } export function getProductDetail(id) { return request.get(/product/detail/${id}) }这里有个很关键的开发小技巧跨域问题。前后端分离后前端跑在 5173Vite 默认后端跑在 8080Tomcat 默认直接发请求会被浏览器拦截。解决方式有两种后端加 CORS 配置或者前端配置代理。我推荐前端配置代理开发环境不需要动后端代码。Vite 的代理配置// vite.config.js export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这样前端请求/api/product/list就会被代理转发到后端的http://localhost:8080/api/product/list浏览器看起来是同源的跨域问题自然解决。3.4 核心页面实现商城、购物车、领养首页和商品列表页商品列表用卡片栅格布局Element Plus 的el-row和el-col做响应式排列每行显示 4 个商品。搜索功能用el-input绑定关键字通过keyup.enter触发搜索请求后端接口接收keyword参数实现模糊查询。分页用el-pagination组件切换页码时重新请求数据。商品详情页主要展示大图、商品名称、价格、库存、商品描述。加入购物车的核心逻辑是判断用户是否登录未登录跳转登录页调用/cart/add接口把商品 ID 和数量传给后端。这里有一个前后端都要做的双重校验前端判断库存是否足够后端下单时再次校验库存防止超卖。购物车页面用表格展示购物车商品支持修改数量、删除商品右侧实时计算总金额。数量修改用el-input-number监听 change 事件后调用更新接口同时重新计算合计。下单流程用户从购物车结算时需要填写收货人、电话、地址然后调创建订单接口。我把创建订单逻辑放在后端 Service 层里用Transactional事务注解包裹同时处理三件事生成订单记录、生成订单明细、扣减库存。这里必须加事务否则库存扣了订单没生成或者订单生成了库存没扣数据就乱了。领养模块宠物列表页展示待领养的宠物卡片每张卡片上有宠物照片、名字、品种、健康状态底部放一个“申请领养”按钮。点击后弹窗出来让用户填写领养理由和养宠经验提交后状态变成“待审核”。4. 关键业务逻辑与实操要点4.1 登录注册与权限控制用户注册时后端需要对密码做处理绝对不能明文存数据库。我用的是 MD5 加盐的方式虽然现在安全级别算不高但对这个项目足够了。Spring Security 这种重量级框架在这个场景反而显得笨重引入它光是配置就够你喝一壶的。权限控制分前端和后端两层。前端通过路由守卫控制页面跳转后端通过拦截器控制接口访问。这两个是独立的前端控制只影响用户体验后端控制才是真正的安全屏障。后端的拦截器实现逻辑很简单写一个 HandlerInterceptor在 preHandle 方法里从请求头取出 token校验通过就放行否则返回 401 状态码。注册、登录、商品列表这些公共接口要放行其他接口都拦截。注意拦截器配置时一定要用excludePathPatterns把公共接口排除掉否则用户还没登录就访问不了登录接口直接逻辑死锁。我一开始就踩过这个坑注册接口也被拦了前端怎么调都报 401折腾了半天才发现问题出在这。4.2 购物车到订单的完整链路购物车和订单的衔接是这个项目中最容易出错的地方。我先把流程捋清楚用户在购物车页面点击“去结算”前端检查收货信息是否填写。前端把购物车选中的商品 ID 列表传给后端后端先查询这些商品的最新价格和库存。后端生成订单号格式建议为时间戳加随机数比如2025010112000012345保证唯一性。后端在事务内创建订单主表和订单明细表逐条扣减库存。成功后返回订单 ID前端跳转到订单详情页同时清空购物车中已下单的商品。这里有几个容易忽略的细节价格以后端为主前端计算的总价只是展示真正下单时后端必须重新从数据库查价格计算。否则用户篡改前端请求里的价格就出大事了。库存要加判断扣库存前判断库存是否大于下单数量不够则报错“库存不足”。并发问题正常做完扣库存的逻辑已经能应付作业场景了。但如果想做得更严谨可以在商品表加一个 version 字段做乐观锁防止两个用户同时买最后一个商品导致超卖。4.3 领养申请与审核状态流转领养模块的业务逻辑比商城简单但状态流转要设计清楚。宠物有三种状态待领养0、已被申请1、已领养2。领养申请有三种状态待审核0、已通过1、已拒绝2。当用户提交领养申请时宠物的状态从“待领养”变成“已被申请”。注意这里有一个业务规则需要考虑一只宠物允许被多个用户申请还是只有一个用户能申请我做的版本是允许多人申请管理员在后台审核时选择其中一个通过其他申请自动失效宠物状态变成“已领养”。这个设计更贴近真实场景也给管理员更多的筛选空间。管理员审核的核心操作是看到申请列表查看申请人信息和领养理由决定通过或拒绝。通过时更新申请状态为 1同时把宠物状态改为 2已领养拒绝时更新申请状态为 2宠物状态改回 0待领养方便其他用户继续申请。用户在个人中心可以看到自己的申请记录以及审核状态和备注。这块建议做一个申请进度的状态展示比如“待审核”是黄色、“已通过”是绿色、“已拒绝”是红色用户一眼就能看清。4.4 图片上传与文件存储方案商城商品需要图片宠物信息也需要图片图片上传是绕不开的功能。最简单的实现方式前端用 Element Plus 的el-upload组件设置 action 为后端的文件上传接口。后端接口用 MultipartFile 接收文件保存到服务器的某个目录下然后把访问路径返回给前端。后端核心代码大致如下PostMapping(/api/admin/upload) public Result upload(RequestParam(file) MultipartFile file) { String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName UUID.randomUUID().toString() suffix; String savePath D:/pet-upload/ fileName; file.transferTo(new File(savePath)); return Result.success(/images/ fileName); }保存路径和访问路径要区分开。服务器磁盘路径是文件真正存放的位置而访问路径是前端用来加载图片的 URL。这里涉及到静态资源映射配置需要在 SpringMVC 配置里把/images/**映射到实体文件夹路径。一个实际的坑是上传的图片过大时Tomcat 默认限制请求体大小为 2MB会报MaxUploadSizeExceededException。需要在配置文件里调大限制spring.servlet.multipart.max-file-size10MB spring.servlet.multipart.max-request-size10MB4.5 数据统计与可视化加分项如果你的项目想要在答辩时加分可以加一个数据统计模块。管理员后台首页展示几个核心数据商品总数、用户总数、订单总数、待审核领养申请数下面放一个图表展示最近 7 天订单量趋势。图表的实现可以用 ECharts安装方式npm install echarts前端用法很简单通过 ref 获取 DOM 容器初始化图表实例设置 option 数据。后端提供统计接口返回最近七天的订单数据前端渲染成折线图。这个功能做起来不难但视觉效果好、展示性强评审老师一看就觉得系统完整度高。很多同学的毕设项目都只做到“能增删改查”你能多做一步数据可视化价值立刻就不一样了。5. 部署上线与常见问题排查5.1 项目打包与部署项目完成后要打包部署。前端打包执行npm run build生成 dist 静态文件目录。部署方案有两种方案一前后端分开部署。前端 dist 目录用 Nginx 托管后端打成 war 包扔到 Tomcat 的 webapps 目录。Nginx 需要配置反向代理把/api开头的请求转发到后端 Tomcat。同时前端配置的 baseURL 改为域名地址。方案二前后端合并部署。把前端构建后的 dist 目录直接放到后端的 webapp 目录下由 SpringMVC 同时提供页面和接口服务。但这种方案破坏了前后端分离的架构我只在课程设计老师要求“单机运行演示”时才推荐。实际生产基本不用这种方式。Nginx 配置反向代理的核心语句server { listen 80; server_name yourdomain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }注意try_files这一行非常关键。Vue 是单页应用路由是前端控制的。如果用户直接访问/product/detail/3这个地址Nginx 默认会去找服务器上对应的物理文件找不到就 404。加了try_files $uri $uri/ /index.html后找不到资源时统一返回 index.html由前端路由接管。5.2 常见问题与解决方案速查表做这个项目过程中我积累了一些常见问题的排查经验整理成下面这个表遇到类似问题可以直接对着处理问题现象可能原因解决方案前端请求接口报 404后端接口路径和前端请求路径不一致检查 RequestMapping 注解的路径和 axios 请求的路径是否完全一致前端请求接口报 405请求方法不对确认 GET、POST、PUT、DELETE 是否和后端接口定义一致中文乱码数据库连接参数未指定编码连接 URL 加上 characterEncodingutf8中文乱码POST请求Tomcat 未配置 UTF-8 编码过滤器SpringMVC 配置 CharacterEncodingFilterforceEncodingtrue跨域请求被拦截前后端未配置跨域处理前端配 Vite 代理或后端加 CORS 配置登录后刷新页面状态丢失用户信息只存内存把用户信息和 token 存入 localStorage图片上传报 MaxUploadSizeExceededTomcat 或 Spring 文件大小限制调大 max-file-size 和 max-request-size页面刷新后 404Nginx 未配置 try_files配置 location / 时加上 try_files $uri $uri/ /index.htmlmybatis 查询结果字段为 null数据库字段名和属性名不一致开启 mapUnderscoreToCamelCasetrue或写 resultMap 映射购物车添加商品报空指针用户 ID 获取失败检查登录状态拦截器是否放行 /cart/add检查 token 是否正确解析5.3 动态化配置前端和后端的联调技巧联调是前后端分离项目里最磨人的环节。我提供一个很实用的调试思路优先使用 Logback 日志输出。后端每个接口的入口和关键业务步骤都打上日志联调时打开控制台看日志请求进来了没有、参数对不对、执行到哪一步报错了一目了然。新手最容易犯的毛病是“瞎猜”前端报错了不知道后端是没收到请求还是处理出错了有了日志就不用猜了。前端用浏览器开发者工具的 Network 面板。看到请求的状态码、请求头、响应体能很快判断问题层次。状态码 4xx 通常是参数或权限问题5xx 是后端代码异常断网层面的问题则要看请求是否发出去了。数据库操作不熟练时先手动执行 SQL。写 MyBatis 的 SQL 时先在 Navicat 或命令行里跑一遍确认 SQL 本身没问题再贴到 XML 映射文件里。我见过太多同学把时间浪费在 SQL 语法错误这种低级问题上。5.4 代码规范与项目文档代码规范这块我多啰嗦几句。既然是拿来当毕设或者项目经验的项目代码质量本身就是打分的一部分。命名规范类名大驼峰、方法名小驼峰、常量全大写这些最基本的约定不要违反。接口统一风格所有 Controller 的返回值用统一的 Result 类不要有的返回 Map、有的返回 String、有的直接返回实体类。注释清晰核心业务逻辑加上注释但不用每行都写废话。实体类与数据库字段对应写好 Lombok 的 Data 注解减少 getter/setter 的样板代码。分层清晰Controller 里不要写 SQLService 里不要写 JSON 处理每层各司其职。项目文档建议包含需求分析文档系统要解决什么问题、数据库设计文档表结构和关系、接口文档每个接口的请求方式、参数、返回结果。这些内容写到毕设论文里也要用到早做准备后来轻松。6. 系统测试与优化建议6.1 功能测试用例设计功能测试不用搞得太正式但至少要把每个模块的完整流程走一遍。我一般会按业务场景设计测试用例用户模块注册成功、用户名重复、密码太短、登录成功、密码错误、用户不存在、修改个人信息。商品模块商品列表展示、关键字搜索、分页切换、商品详情查看、下架商品不应出现在列表。购物车模块添加商品、修改数量、删除商品、清空购物车。订单模块正常下单、库存不足下单、未登录下单应被拦截、取消订单。领养模块提交申请、重复申请同一宠物、审核通过后宠物状态变化、审核拒绝后宠物恢复待领养。后台模块管理员登录、商品增删改查、宠物发布、订单状态管理、领养审核。每个用例都要验证正常流程和异常流程。比如下单这个操作正常流程是库存够、信息完整直接下单成功异常流程就是库存不够要给出友好提示而不是直接报 500。这些边边角角的处理才是系统完整度的体现。6.2 前端性能优化与体验优化项目做到能跑只是第一步做得好才是加分项。图片懒加载宠物和商品的图片比较多时用懒加载避免一次加载几十张图片拖慢首屏速度。Vue 3 里可以用v-lazy指令或者简单点用原生loadinglazy属性。列表数据分页不要一次性查出全部数据前端分页也好、后端分页也好数据量大的时候体验差距明显。加载状态反馈每次发请求时显示 loading 效果特别是列表加载和订单提交这种耗时操作。没有加载提示用户点了按钮以为没反应就会重复点击后端就可能重复下单。空数据展示列表为空时显示“暂无数据”的占位图而不是一片空白。按钮防重复提交下单、提交领养申请这类提交操作点击后把按钮置为 loading 状态并禁用防止用户多次点击。6.3 后端性能与安全性增强虽然这个项目规模不大但一些基本的安全和性能要求还是要做到。SQL 注入方面使用 MyBatis 的#{}参数占位符可以有效防止注入。注意不要图方便用${}拼接 SQL尤其是用户直接传入参数的场景。比如排序字段这种不得不动态拼接的地方要对传入值做白名单校验。XSS 攻击方面前端不要用v-html直接渲染用户提交的内容后端也可以对用户输入做 HTML 转义。接口限流方面领养申请和注册这两个接口容易被恶意刷可以在后端用一个简单的计数器限制单个 IP 的请求频率。不过对于毕设项目这属于锦上添花有时间再做。数据库索引方面商品表按分类查询时给 category_id 加索引订单表按 user_id 查询时给 user_id 加索引。数据量大了以后这个效果非常明显。7. 写在最后的个人经验做这个项目下来我最大的体会是SSM 虽然老但它是理解 Java Web 后端最好的教材。Spring 的依赖注入让你知道对象是怎么被管理的SpringMVC 的请求流程让你明白一个 HTTP 请求是怎么从前端到控制器再到业务层最后落到数据库的MyBatis 让你亲手写 SQL、调参数、处理结果集。这些底层认知在以后用 Spring Boot、MyBatis-Plus 时都会变成你的内功。Vue 这边也是一样刚开始可能会被双向绑定、组件通信、路由守卫这些东西搞晕但跟着项目走一遍从搭环境到写页面再到调接口整套流程跑通了以后再看其他前端框架心态会完全不一样。如果你正准备做类似的项目我最后再分享一个小建议不要只满足于“能跑”。把功能做完整、把异常处理到位、把界面做得好看一点、多写几句注释这些细节加在一起才是一个项目真正和别人拉开差距的地方。毕竟一个好的项目不是代码写得多高级而是每个细节都经得起推敲。