每年到这时候总能看到大量“XX毕设ssmvue”的标题挂在各大社区和问答平台。家具商城这类题目属于典型的管理信息系统技术栈传统业务模型清晰却又是最容易让毕业生“上手容易、做完费劲”的组合。我此前带过不少毕业设计二稿也接过几个ssmvue的家具商城项目评阅见过的通病出奇一致前端能跑、后端能开、两边接不上或者论文写得像产品说明书。这篇并不是泛泛介绍而是针对“ssmvue家具商城论文程序”这个标题把从选题理由、数据库设计、后端分层、前端工程化、前后端联调、服务器部署到论文写作答辩的全部实操链路串一遍里面有大量是我在实际项目里验证过的方案也包括踩坑后的处理思路。适合正在做这个题目、或者打算照这个方向改一版电商类毕设的人直接参考。1. 毕设选型ssmvue这套组合为什么至今还很常见1.1 “新”与“稳”的取舍技术栈不是越新越好先聊一个很多学生纠结的问题既然现在Spring Boot是主流为什么还要用ssm我的看法是毕设选题首先要考虑的不是技术新潮而是“答辩老师是否熟悉”和“你自己能否讲清楚”。ssm框架在很多高校的Java课程设计里依然是主力尤其是一些偏传统软件工程的院系老师在评审时对SpringSpringMVCMyBatis的分层结构有天然的理解优势提问会更多集中在业务逻辑和数据库设计上而不是揪住某个框架的版本细节不放。另外ssm的代码结构比Spring Boot更“显式”。Spring Boot靠自动配置省掉了大量XML但恰恰因为这些省掉的部分很多学生反而不清楚一个请求从浏览器到数据库再到响应的完整链路。ssm要求你手动配置DispatcherServlet、注解扫描、事务管理器整个过程走一遍以后你对Spring IoC、SpringMVC工作流程、MyBatis代理机制的理解会扎实很多。答辩时老师问“Controller是怎么找到Service的”“MyBatis的Mapper接口为什么不用写实现类”你能流畅回答这比项目本身多跑几个页面更能加分。1.2 ssm与Spring Boot的实际应用对比我并不反对用Spring Boot但既然标题定的是ssmvue就要尊重题目本身的约束。为了让大家看清差异用一个表格来对照两者在毕设场景下的表现对比维度ssmSpringSpringMVCMyBatisSpring Boot配置方式大量XML配置手动控制组件扫描与拦截器自动配置为主起步依赖减少配置量部署形态常打包为war丢到Tomcat webappsjar内嵌Tomcatjava -jar即可运行学习曲线偏陡但能理解底层装配逻辑平缓适合快速出成果答辩友好度老师问题集中但常规易准备老师可能追问自动配置原理答不上来容易扣分代码整洁度分层写得多代码量偏大简化后更清爽资料数量老项目多参考方便新项目多资料更多结论是如果你的学校明确允许自选技术栈且你时间紧、只想快速跑完Spring Boot是高效路线但如果是题库里拿到的题目、或者导师指定了ssm那么老老实实把SSM整合做好效果并不差甚至在某些评分维度上更有优势。家具商城用ssm写不会出现“技术栈撑不起业务”的问题反而因为业务量适中能把每一层的职责体现得很清楚。1.3 家具商城这个业务域的天然优势选家具商城而不是通用的图书商城或零食商城我拆解过原因主要有三点。第一家具商品属性丰富除了基础名称、价格、图片还有材质、尺寸、风格、库存单位等数据库表字段设计有发挥空间论文里可以写出“商品属性设计”的章节不会显得太单薄。第二家具商品有主图、轮播图、详情图前端图片处理和懒加载有话题可以论述。第三家具属于低频高价商品订单流程与会话、购物车、库存扣减的交互比普通小商品更值得写比如库存预占、订单状态机的设计。从工作量角度看一个完整的家具商城里前台用户模块、商品分类浏览、关键字搜索、购物车、结算下单、订单管理、后台商品管理、分类管理、订单处理、用户管理这十个左右的模块刚好能把ssm和vue各自的优势用上又不会多到做不完。这正是大多数毕设想追求的“麻雀虽小五脏俱全”状态。2. 系统设计与数据库建模家具商城的“骨架”2.1 前后台功能模块的完整划分动手写代码之前先别急着建项目把功能边界划清楚能省掉后面大量返工。家具商城的用户角色通常分三种游客、注册用户、管理员。游客只能浏览和搜索商品注册用户除浏览外还能管理购物车、下订单、查看订单状态、维护收货地址和个人信息管理员则进入后台维护商品、处理订单、管理用户和公告。前台用户端注册登录、商品列表、商品搜索、商品详情、购物车增删改、订单提交、订单列表、订单详情、收货地址管理、个人资料修改后台管理员端管理员登录、商品分类管理、商品管理增删改查、上架下架、库存调整、订单管理查看详情、发货、取消、用户管理禁用/启用、公告管理公共部分图片上传、分页组件、统一的登录拦截、统一异常处理这里有一个容易被忽视的设计决策用户端和管理员端是用同一个系统加角色区分还是拆成两个独立的前端工程我的建议是毕设场景下使用同一套前端工程通过路由和权限来区分是更合理的做法。拆成两个前端工程会增加部署和联调成本还容易在论文里把自己绕晕。用一个系统、两套布局前台布局和后台布局的做法既清晰又好写。2.2 核心数据表的结构与字段说明数据库是家具商城的地基表设计得合理后面的Mapper和Service能省下一半力气。按经验来看最少需要这八张核心表我逐个说明字段考虑。用户表user主键id、用户名username唯一索引、密码password推荐MD5或者加盐哈希不要明文、手机号phone、邮箱email、头像avatar、状态status1启用、0禁用、注册时间create_time。这里加一个status字段很关键管理员禁用用户只是改状态而不是物理删除。商品分类表categoryid、分类名称name、父级id parent_id用于实现两级分类、排序sort、是否显示status。虽然家具商城也可以做扁平分类但从论文丰富度考虑做成父子两级分类更好既可以展开成无限极分类的思路又不需要真正实现递归树这么复杂。商品表productid、分类id category_id、商品名称name、副标题subtitle、主图main_image、轮播图carousel_images用逗号分隔的URL字符串、详情详情description、材质material、尺寸size、风格style、价格price用decimal、库存stock用int、销量sales、上架状态status、创建时间create_time、更新时间update_time。这里推荐把副标题、材质、尺寸、风格作为单独字段而不是全部塞进详情文本里这样列表页可以展示更多的商品卡片信息前台查询时也可以按材质或风格过滤论文里能写出的功能点也会更多。购物车表cartid、用户id user_id、商品id product_id、数量quantity、勾选状态checked。有一种设计是直接把购物车存到Session里不建表。但这样用户换个设备购物车就丢了而且没法持久化所以我更建议建表后台管理也更正规。判断加购是否重复的逻辑就是查同一个user_id和product_id下是否已有记录。订单表orders订单号order_no建议用时间戳随机数生成不用自增id直接暴露给用户、用户id user_id、收货地址快照receiver_name、receiver_phone、receiver_address下单时把地址信息复制过来避免用户之后修改地址导致历史订单错乱、订单金额total_price、支付方式pay_type、订单状态status0待支付、1已支付待发货、2已发货、3已完成、4已取消、下单时间create_time、支付时间pay_time、发货时间deliver_time。订单明细表order_itemid、订单id order_id、商品id product_id、商品名称product_name、商品主图product_image、下单时单价price、购买数量quantity。订单明细必须做商品快照不能只存商品id否则以后商品降价或下架订单里的历史价格也跟着变这在电商系统里属于严重的逻辑缺陷。收货地址表addressid、用户id user_id、收货人receiver_name、手机号receiver_phone、省市区省市区、详细地址detail、是否默认is_default。注意“是否默认”字段的更新逻辑当用户设置新的默认地址时要先把该用户所有地址的is_default置为0再更新当前地址为1两步操作包在一个事务里。公告表noticeid、标题title、内容content、创建时间create_time。这块业务量虽然少但可以给后台加一条菜单也方便论文中补充“系统管理模块”的描述。2.3 建表时的几个通用设计细节每次查看学生的表结构总会发现几个共性毛病这里集中提醒。主键统一使用bigint自增不要用int避免造数据时主键不够用。时间字段统一用datetime不要图省事用varchar否则后面做时间范围统计时会非常痛苦。所有可能展示给用户的价格字段都用decimal精度建议decimal(10,2)用float和double会出现精度误差答辩时老师稍微一问就能指出这个问题。所有表都加上create_time和update_time两个字段这属于通用设计任何一张业务表都可以有写论文时还能单独说明“系统对数据审计的通用字段设计”。还有一个建议把SQL建表语句单独保存到一个init.sql文件中项目里包含这个SQL文件论文的附录里也贴上。答辩时老师经常问“你的数据库是怎么建的”能现场打开SQL执行一遍比口头讲有说服力得多。3. 后端ssm开发实战配置、分层与事务处理3.1 Spring、SpringMVC、MyBatis三份配置文件的职责边界ssm项目拿到手最容易让人懵的是配置文件的拆分。网上很多教程的配置写法不同但核心逻辑一致。我在实际项目里习惯分成applicationContext.xmlSpring核心配置、spring-mvc.xmlSpringMVC配置、mybatis-config.xmlMyBatis配置再用web.xml把它们串起来。applicationContext.xml负责组件扫描排除Controller注解、配置数据源DataSource我用的是Druid连接池顺便配置监控、配置SqlSessionFactoryBean指定mybatis-config.xml位置、Mapper.xml文件位置、实体类别名包、配置MapperScannerConfigurer把Mapper接口扫描进来生成代理、配置事务管理器DataSourceTransactionManager并开启事务注解驱动。spring-mvc.xml负责组件扫描只扫描Controller注解、配置注解驱动 mvc:annotation-driven 、配置视图解析器InternalResourceViewResolver、配置静态资源映射 mvc:resources 把js/css/images放行、配置文件上传解析器CommonsMultipartResolver。mybatis-config.xml相对简单配置驼峰映射mapUnderscoreToCamelCase为true、配置日志输出用标准输出到控制台方便排查SQL、可以配置typeAliases或者干脆在Spring里指定别名包。web.xml里要做的核心工作是配置DispatcherServlet并指定加载spring-mvc.xml、配置ContextLoaderListener加载applicationContext.xml、配置CharacterEncodingFilter统一编码为UTF-8。这三样少一个项目跑起来就会出幺蛾子尤其是编码过滤器漏掉之后前端传中文到后端全是乱码排查起来特别耽误时间。3.2 Controller-Service-Mapper三层的代码组织方式实际代码组织上我建议遵循几个约定显得专业且好维护。Controller层不直接写业务代码只做参数接收、参数校验、调用Service、封装结果返回。Service层必须面向接口编程先写IOrderService再写OrderServiceImpl虽然麻烦一点但这是ssm课程的强调点评阅老师会很认可。返回值统一使用Result对象包含code、message、data三个字段不要直接返回散装Map或裸对象否则前端处理各种状态会非常痛苦。分页查询推荐使用PageHelper插件在pom.xml里引入依赖然后在Service层直接PageHelper.startPage(pageNum, pageSize)后面紧跟的查询语句就会自动分页返回的PageInfo对象里带有总记录数、总页数、当前页list等现成数据。这个插件源码简单、演进多年稳定性很好而且论文里能写的点也多比如MyBatis拦截器机制如何实现分页算是一个即插即用的加分项。对于上传图片推荐使用本地磁盘存储加虚拟路径映射的方式。把上传的图片保存到项目外部的一个目录比如D:/furniture/upload然后在SpringMVC配置里加一个虚拟路径映射。用这种方案重启服务器时图片不会随着项目重新部署而被清空这一点我踩过坑以前直接保存到项目内部upload目录每次打完war重新部署图片就全部失效了教训很深刻。3.3 购物车与订单的事务边界设计家具商城里事务处理最核心的是“提交订单”这个动作。因为提交订单时要做的事情不止一条SQL生成订单主表记录、批量生成订单明细、清空购物车中对应商品、扣减商品库存。这四步操作任何一步失败都必须整体回滚不然就会出现“订单创建了但库存没扣”或“购物车清空了但订单没生成”的中间状态。在OrderServiceImpl的createOrder方法上直接标注Transactional并把回滚规则设定为RuntimeException即回滚。方法的处理顺序我测试过多次推荐这样安排根据用户id和购物车勾选状态下查出购物车明细和商品最新价格遍历购物车明细逐个检查库存是否充足不足则throw一个带提示的RuntimeException创建订单主表记录先生成order_no再插入批量生成订单明细同时累加订单总金额更新商品库存SQL中直接写stock stock - #{quantity}并加上stock #{quantity}条件做乐观保护逻辑删除或物理清空购物车记录有一点一定要提醒库存扣减不要在Java代码里先查出来再减而是直接在SQL更新语句中用条件判断。因为高并发场景下先查后减存在并发覆盖问题虽然毕设一般不会遇到真正的并发压力但老师一旦问到“如果两个人同时买同一件库存只剩一件的商品会怎样”你能答出在SQL层面做条件更新这个回答就上了一个档次。3.4 登录拦截器的编写方案ssm里实现登录验证通常有两种做法Servlet的Filter或SpringMVC的HandlerInterceptor。更推荐HandlerInterceptor因为可以注册到SpringMVC的拦截器配置里精确指定哪些URL需要拦截并且可以注入Service做更复杂的校验。比如管理员后台的登录验证可以写一个AdminInterceptor在preHandle方法里判断Session中是否存在loginAdmin对象不存在则重定向到后台登录页。配置时拦截/admin/**路径放行/admin/login相关的路径。而前台用户端使用类似UserInterceptor的方式处理。需要注意不要把所有路径一刀切拦截否则登录页的静态资源也会被拦我见过好几个学生把CSS都拦掉了。关于用户登录信息保存有一个常见选择存Session还是存Token。如果是纯前后端分离且跨域部署推荐用Token方案如果前端工程和后端工程部署在同域或通过nginx反向代理成同域Session方案实现简单、也能应付答辩。家具商城这个题目正规做法是给每个用户登录后生成一个唯一token存到数据库或Redis前端每次请求在Header中携带token后端通过拦截器验证。这块内容我放到后面联调章节详细展开。4. 前端vue项目落地环境、路由、组件通信4.1 开发环境安装与脚手架创建项目的正确流程Vue环境的安装很多新手卡住的地方不是代码本身而是Node环境和依赖安装的顺序问题。建议按如下步骤走实测踩坑最少。Node版本选择上如果项目用Vue2Node不要装最新的大版本建议用14.x或16.x LTS版本更高版本在安装node-sass时会因为编译兼容问题报一堆错。如果换用sass替代node-sass可以规避这个问题但最省心的方案还是直接控制Node版本。推荐使用nvm管理Node版本这样切换非常方便。安装完Node后npm install -g vue/cli全局安装脚手架。然后使用vue create furniture-web创建项目选择手动配置勾选Router和VuexCSS预处理器选择Sass/SCSSlint选择ESLintStandard config。项目创建完成后在自己电脑上先跑一次npm run serve确认默认页面正常显示再继续写代码。这一步能排除大量环境问题。有一个高频问题很多人遇到npm install时网速慢或者部分依赖装不上。解决思路是设置淘宝镜像源npm config set registry https://registry.npmmirror.com。个别包如果还是装不成功删除node_modules后重新npm install或者用cnpm装。装依赖时如果看到npm提示fund和audit信息属于正常现象不用管。如果习惯用VScode作为编辑器装上Vue官方插件Volar和ESLint插件。这里有一个版本差异老项目多用Vetur插件但Vue3以后官方主推Volar如果你的项目是Vue2装Volar同样能正常工作。VScode里如果右下角提示选择Vue的语言服务模式选Volar即可。4.2 Vue Router路由配置、路由参数传递与导航守卫路由设计直接影响整个前端工程的开发效率。家具商城这种系统路由建议分为两部分前台页面路由和后台管理路由后台路由统一挂到一个带AdminLayout布局的父路由下通过嵌套路由实现侧边栏与内容的切换。路由参数传递主要有三种方式我分别说明适用场景路径参数path传参/product/detail/12配置路由时写成path: /product/detail/:id页面里通过this.$route.params.id获取。适合商品详情页、订单详情页这类根据ID查询详情的场景。查询参数query传参/product/list?categoryId3页面里通过this.$route.query.categoryId获取。适合列表页的筛选条件刷新页面后参数还能保留在URL里便于分享。编程式导航加参数this.$router.push({ path: /product/detail, query: { id: 12 } })。适合点击按钮跳转。这里有一个新手的常见误区只有完全不需要在刷新后保留的参数才考虑用Vuex或sessionStorage存凡是详情页需要获得唯一标识的请尽量用URL参数。因为用户刷新后Vuex里的内容会丢失但URL参数不会。另外如果详情页参数没有拼在URL里用户无法复制链接分享商品这对商城类系统来说是个硬伤。导航守卫通常设置全局前置守卫。执行逻辑如下通过router.beforeEach拦截所有路由跳转在路由meta中标记是否需要登录比如meta: { requiresAuth: true }判断当前是否有登录token没有则跳转登录页并携带redirect参数登录成功后从URL参数中取出redirect并跳回到原来想访问的页面另外后台管理的路由可以加一个meta: { role: admin }标记在全局守卫里再校验当前登录用户是否管理员。这样前台后台的权限控制就都收口在同一个守卫里了。4.3 组件间传值的几种手段与选择逻辑Vue项目只要页面一多组件通信绕不开。我总结了一套选择逻辑照着套就能少走弯路父子组件间props向下传数据$emit向上传事件。这是最基础也最常用的方式必须在项目里熟练使用。兄弟组件间推荐先把共享状态抽到它们的共同父组件里父组件通过props传给兄弟兄弟通过$emit通知父组件更新。这种方式逻辑最清晰适合数量少的组件。全局共享状态使用Vuex管理登录用户信息、购物车数量、全局的收货地址。家具商城里登录信息和购物车数量几乎每个页面都要用放Vuex是最合适的选择。注意不要把所有数据都塞进Vuex只放需要跨页面共享的部分否则调试起来会非常累。跨层级或绝对简单的事件通知使用eventBus本质就是一个空的Vue实例通过$on和$emit通信。但要注意使用完销毁监听否则会有重复触发的问题。以购物车数量为例我会在每次登录后拉取一次购物车列表把总数量commit到Vuex中顶部导航栏展示从Vuex中读取的数量。任何页面加购成功后dispatch一个action重新拉取数量这样所有页面都能同步更新不用每个页面都自己管一份数据。这类“从全局store驱动UI更新”的写法在前端面试时也是高频考点。4.4 列表渲染的key原理与图片路径处理写商品列表时:keyitem.id是必须的。因为Vue的diff算法需要通过key精确判断哪些节点是复用的如果用index作为key当列表顺序变化或中间插入数据时Vue会发生错误的DOM复用导致页面显示的数据串行尤其是有输入框或图片懒加载的组件上几乎必定出问题。具体现象我在一个订单明细页见过删除一条明细后下一行数据的内容被复用过来就像“数据没删干净”最后发现是key用了index。图片路径的处理也有讲究。后端返回的商品图片路径如果是一串相对路径或虚拟映射路径前端拼完整URL时要写一个全局方法或过滤器统一处理。比如后端只返回upload/xxx.jpg时前端统一拼接成https://你的域名/upload/xxx.jpg。别把域名硬编码散落得到处都是否则环境一换就全瞎。合理做法是在src目录下建一个config文件导出baseUrl所有接口请求和图片地址都从这里面取。另一个常见问题是“vue对象赋值页面不变”。比如通过this.obj.xxx newVal去修改对象页面却没有任何反应。这是因为Vue2的响应式原理基于Object.defineProperty新增属性并没有被劫持。正确做法是用this.$set(this.obj, xxx, newVal)或者直接整体替换对象。在编辑商品弹窗、修改用户信息这类场景里这个问题比较容易踩到先在项目里定好规范动态增删对象的属性值一律用$set。4.5 UI组件库的选型与自定义样式的边界家具商城的后台管理界面直接用现成的Vue组件库能极大节省开发时间。Vue2项目里最常用的是Element UI表格、表单、弹窗、分页、日期选择器都齐了。前台页面则不建议完全使用组件库因为商城的风格需要按照家居调性定制用组件库默认样式会显得很“管理系统”。通常做法是前台写自定义样式或使用组件库的部分组件再统一覆写主题变量。需要特别注意在使用Element UI的表格或者表单时有很多写法是声明式的比如el-table-column的prop需要和数据结构字段一致el-form的rules校验规则要和model字段对应。如果页面数据没有正常展示先检查这些绑定名是否一致这是出现频率比较高的低级错误。样式方面不要一股脑把所有CSS都写在全局样式表组件内样式加上scoped属性避免样式污染。对于主题色可以用Sass变量统一管理比如把主色定义为$primary-color这样后期如果要换主题改一个变量就行。后台管理如果要适配自己的整体风格还可以通过修改Element UI的SCSS变量来定制主题色实测可行而且效果不错。5. 前后端联调实战axios封装、token与跨域排坑5.1 axios实例的创建与请求/响应拦截器写法前后端分离的项目接口请求统一建议用axios但要封装成一个可复用的request工具不要每个页面都直接this.$http.post这种到处飘。我项目中常用的封装配置如下import axios from axios const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) // 请求拦截器统一携带token service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }, error { return Promise.reject(error) }) // 响应拦截器统一处理业务码 service.interceptors.response.use(response { const res response.data if (res.code ! 200) { if (res.code 401) { // token失效清除本地登录状态并跳转登录页 localStorage.removeItem(token) localStorage.removeItem(userInfo) window.location.href /login } return Promise.reject(new Error(res.message || 请求失败)) } return res }, error { return Promise.reject(error) }) export default service关键点在于请求拦截器把token放到Header的Authorization字段响应拦截器统一判断code码。后端接口如果约定“未登录返回401”前端就能在拦截器里集中处理不用每个页面都写一遍登录态判断。5.2 跨域问题的两种解法代理转发与后端CORS前后端分离联调时跨域是绕不开的第一道坎。前端跑在localhost:8080后端跑在localhost:8085浏览器默认会拦截非相同源来源的请求。我有两种推荐方案开发环境用第一种生产环境用第二种。开发环境使用vue.config.js里的devServer.proxy进行代理转发。配置方式如下module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8085, changeOrigin: true, pathRewrite: { ^/api: } } } } }这样前端请求/api/product/list代理会把请求转发到http://localhost:8085/product/list因为请求发生在服务端不经过浏览器跨域拦截完美解决开发环境跨域。这里注意路径重写后端接口没有/api前缀时要通过pathRewrite把前缀去掉。生产环境通常是nginx反向代理把/api开头的请求统一转发到后端服务。这样前端和后端对外表现为同一个域名和端口天然不存在跨域问题。如果刨掉代理方案后端也可以配置CORS跨域过滤器。SpringMVC项目里写一个CorsFilter设置Access-Control-Allow-Origin、Allowed-Methods、Allowed-Headers并把allowCredentials设为true。这里有一个坑如果使用带凭证的跨域比如要携带CookieAccess-Control-Allow-Origin不能设置为*必须指定具体的来源域名。这一个点我在审查毕设代码时看过很多次写错。5.3 联调阶段高频出现的几个“玄学”问题联调期最常见的错误之一是前端拿到了数据但渲染不出来。发生这种情况优先打开浏览器F12查看响应数据确认数据结构是否与前端读取的一致。比如后端返回data是一个对象前端却用list.forEach去遍历肯定报错。如果返回的结构直接把整个分页对象放在data里那前端就要用res.data.list去拿数据列表而不是res.data.records。第二个高频问题是中文乱码。后端接口返回JSON中文正常但提交订单时前端传的中文地址到后端变乱码。一般先检查数据库连接串是否配置了characterEncodingutf8再检查web.xml的CharacterEncodingFilter是否配置在过滤器链的最前面。还有一点容易被忽略如果前端通过axios提交表单content-type默认是application/json后端Controller接收时参数上需要RequestBody注解如果漏了注解不仅接收不到参数还可能连中文编码都对不上。第三个高频问题是接口404。前后端接口路径明明一样但请求就是404先检查后端项目context-path是否配置了额外的前缀再检查RequestMapping里是否有多余的斜杠。我见过有学生把后端接口写成/product/detail/前端请求写成/product/detail多一个斜杠直接404低级但很致命。第四个问题也是网上搜“vue对象赋值页面不变”比较常见的原因接口返回的数据赋给了data中的对象但页面没有更新。如果这是动态增删对象属性按照之前说的用$set解决如果是整体替换对象换掉整个引用而不是修改已有对象内部属性也能触发响应式更新。5.4 登录注册模块的前后端交互细节登录注册是几乎所有系统的入口模块但细看实现很多学生的代码都存在隐患。注册时前端要校验两次密码一致和手机号格式后端也要做同样的校验不能只依赖前端校验原因是绕过前端直接调接口的情况非常常见。后端收到注册请求后先查询username是否已被占用再执行插入查询和插入建议放到同一个事务方法里避免并发重复注册。登录成功后后端的标准做法是验证用户名密码通过后生成一个随机的UUID或更复杂的token字符串持久化保存到数据库的token字段或单独一张token表然后返回给前端前端把token存到localStorage。后续用户修改密码或管理端强制下线时可以把服务端token清掉前端再次携带旧token访问就返回401。这里要特意提醒一个Session与Token混用的问题。很多学生写代码时一会儿在拦截器里读Session一会儿又从Header里读Token两边各写一套结果行为很不一致。选定一种方案就要全线统一。如果用了Token方案后端Controller需要获取当前登录用户信息时不要从Session取而是通过前端传userId参数或从token解析出来。合理做法是登录成功后把用户信息也存到Vuex和localStorage前端请求时把userId作为参数携带后端在需要时做归属校验。6. 打包部署、论文撰写与答辩弹药6.1 前端npm run build构建与nginx部署配置项目做完联调后下一步就是打包部署。前端工程在项目根目录执行npm run build构建完成后会生成dist目录里面是纯静态文件。把dist目录上传到服务器的某个路径比如/usr/share/nginx/furniture然后在nginx的配置文件中做如下配置server { listen 80; server_name yourdomain.com; root /usr/share/nginx/furniture; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8085/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files那行很重要它的作用是当用户直接访问某个前端路由比如刷新商品详情页而服务器上并没有对应的物理文件时就把请求重写到index.html交给前端路由处理。如果不加这行刷新页面会报404。这个知识点在部署相关的面试和答辩问题里几乎是必问的一定要能讲清楚原理。使用xshell或FinalShell上传dist目录到服务器时如果文件较多建议先本地打成zip再上传解压不然小文件传输又慢又容易损坏。6.2 ssm后端打war包部署到Tomcat的步骤ssm项目最常用的部署形态是war包加外置Tomcat。操作流程是后端项目pom中设置 war使用Maven的package命令打包在target目录下得到war包将war包上传到Tomcat的webapps目录启动Tomcatwar会自动解压需要注意环境的差异本地连数据库localhost服务器上要改成服务器的数据库地址。推荐把数据库连接配置写到properties文件中部署时只改一个文件。Druid的监控页面默认是开启的如果你不想暴露给外网可以关闭或者加上访问权限。Tomcat默认端口是8080如果你想直接用80端口访问就需要改server.xml的Connector端口或者继续用nginx转发。如果用了nginx而且nginx已经配置了/api/转发到后端那么Tomcat端口保持8080即可对外只暴露nginx即可。6.3 论文写作的核心逻辑从项目到文档的转换论文和代码是同一个项目的两个交付物但表述方式截然不同。代码关注“怎么实现”论文关注“为什么这么设计”。论文写作最容易得分的部分是“需求分析”和“系统设计”最容易写得像流水账的是“系统实现”。建议论文大纲如下绪论研究背景与意义、国内外研究现状、研究内容与论文结构相关技术介绍SSM框架各组件原理、Vue框架、前后端分离架构、MySQL数据库。这里不要只是罗列名词要写出每个技术的核心机制和选择理由系统分析可行性分析、需求分析用用例图描述角色和功能、非功能需求系统设计总体架构设计画架构图、功能模块设计、数据库设计给出ER图和核心表结构、接口设计系统实现按模块分小节每节写清楚页面功能、核心代码片段和核心逻辑系统测试测试环境、功能测试用例表、测试结果分析很多学生写系统实现时把大段代码直接贴进来这是最差的写法。论文里的代码只需要贴关键的十几行比如购物车事务处理、MyBatis分页查询这类能体现设计思想的片段并配以文字说明。还有一个写作误区测试章节只写“经过测试系统运行正常”这是毫无价值的空话。要写成表格形式列出每一条测试用例、输入数据、预期结果、实际结果。6.4 答辩场上最容易被追问的四个技术点答辩准备不靠背稿子靠把核心原理吃透。我总结了几道被问到的高频题并给出期望的回答方向。第一题前台和后台的登录状态是怎么隔离的答要点前端通过路由守卫和菜单权限区分后端通过不同的拦截器分别拦截/admin和用户接口Session或Token的key区分存储。第二题提交订单时库存扣减是怎么防止超卖的答要点在SQL中使用带条件更新stock stock - #{quantity} WHERE stock #{quantity}利用数据库行锁保证原子性同时整个下单流程处于同一个事务中。第三题MyBatis的Mapper接口为什么不需要写实现类答要点SqlSessionFactoryBean通过MapperScannerConfigurer扫描接口后为每个接口生成JDK动态代理对象调用接口方法时代理对象根据全限定名去找映射文件中的SQL语句执行。第四题Vue的路由模式为什么用history会刷新404而hash不会答要点hash模式是URL后带#号后端始终返回同一个HTML文件前端根据hash进行路由匹配history模式的URL更美观但刷新时浏览器会按真实路径请求服务器服务器找不到对应文件需要配置try_files回退到index.html.这四个问题的答案背熟之后无论老师从哪个方向追问基本都能绕回到这些核心知识上。最后再给一个实用的小建议。做完项目后把所有接口列成一个API文档表格标注请求地址、请求参数、返回字段放到项目docs目录里。这个操作有两个好处一是写论文时可以直接拿来做接口设计章节的素材二是答辩时老师一旦问到某个功能模块的数据流你翻一下API文档就能快速梳理清楚。整个过程不会超过一个晚上但收益非常直观。