带“掌柜有礼”的旅游网站做下来最大的体会是SSM这套经典组合放到今天依然能打但真正决定项目上限的往往不是框架本身而是前端交互和业务模块的设计。这个项目我前后花了三周左右从环境搭建到前后端联调再到把“掌柜有礼”这种营销活动模块完整落地踩了不少坑也积累了一些可以复用的套路。这篇文章就围绕这个项目的完整实现过程来写从技术选型到核心功能拆解再到实际部署中的问题排查尽量把每个环节讲透适合正在做SSM课程设计、毕业设计或者想快速上手前后端分离开发的读者参考。整个项目的代码量不算夸张但麻雀虽小五脏俱全尤其“掌柜有礼”这个模块涉及到优惠券发放、活动状态流转、库存扣减这些核心逻辑非常考验对业务的理解。1. 技术选型与系统整体设计思路1.1 为什么这个年代还在用SSM很多同学看到SSM第一反应是“老古董”但说句实在话SSM在中小型管理系统、课程设计、企业内部工具里依然是出现频率最高的组合之一。Spring管理对象、SpringMVC处理请求路由、MyBatis操作数据库三者各司其职学习曲线平缓社区资料多到看不完遇到问题随便一搜就有答案。相比Spring BootSSM的配置确实繁琐一点但这恰恰是理解Java Web运行原理的好机会——当你手动配过web.xml、配过Spring容器、配过MyBatis的Mapper扫描之后再看Spring Boot的自动配置会有一种豁然开朗的感觉。旅游网站这个场景选SSM还有一层现实考虑项目要演示“掌柜有礼”这类营销功能核心在于业务逻辑的完整性和数据流转的清晰度而不是极致的性能。SSM的声明式事务、AOP拦截、Mapper分层这些能力足够支撑这类业务而且代码结构天然清晰Controller只管接收参数和返回结果Service专注业务规则Mapper只做数据访问这种分层方式特别适合团队协作和后期维护。你想想如果一个活动模块的代码堆在Servlet里改一个优惠券门槛都得从头翻到尾那才是真灾难。1.2 前端选Vue的决定性理由这个项目最开始也纠结过要不要用JSP作为视图层后来果断切到Vue原因有两个。第一旅游网站的页面交互密度远比普通管理系统高首页轮播、景点列表筛选、购物车数量变化、优惠券领取状态切换这些如果全靠JSPAJAX拼字符串渲染代码会变得极其难维护。第二Vue的双向数据绑定能把“数据状态”和“页面展示”彻底解耦比如用户领取优惠券后页面上的领取按钮需要立即变成“已领取”状态在Vue里只需要修改isReceived这个字段就行DOM会自动响应这在传统JSP里要写一堆DOM操作代码。用Vue 2还是Vue 3我是基于生态兼容性做的取舍。这个项目用的是Vue 2原因很实在Element UI对Vue 2的兼容最稳定网上的教程和踩坑文章也最多。如果你是新项目且不依赖老组件库完全可以直接上Vue 3 Vite但如果你是照着大量SSMVue 2的现成案例做那Vue 2反而是最稳妥的。项目采用前后端分离架构前端打包后由Nginx托管后端以纯接口方式提供数据两边通过JSON交换信息职责非常清晰。2. 核心业务模块“掌柜有礼”的深度拆解2.1 需求分析这个模块到底要做什么“掌柜有礼”听着像一个简单活动页实际拆开看里面的业务规则比想象中复杂。我从产品角度重新梳理了一遍需求大概分成了四个子功能活动展示、优惠券领取、可用商家列表、订单核销关联。用户进入活动页面后能看到当前正在进行的活动列表每个活动关联若干张优惠券点击领取后系统判断用户是否登录、是否已领过、库存是否充足全部通过后才发券领到的券会出现在“我的卡券”里去合作景区或酒店消费时可以选择使用。这里有几个容易忽略的隐性需求。一是活动的时间状态管理一个活动会有“未开始”、“进行中”、“已结束”三种状态用户不应该看到已结束的活动更不能领取未开始活动的券。二是库存扣减的并发安全热门景区的优惠券往往几分钟内被抢光如果直接用SELECT判断库存再UPDATE高并发下一定会超发。三是用户与优惠券的绑定关系同一个用户同一张券只能领一次这个唯一性约束必须在数据库层面和代码层面双重保证。2.2 数据库设计五张表的关联关系“掌柜有礼”模块我设计了五张核心表分别是活动表、优惠券表、商家表、用户优惠券表、活动商家关联表。活动表保存活动名称、开始时间、结束时间、状态、封面图这些基础信息优惠券表保存面额、使用门槛、库存总量、已领取数量、有效期商家表保存景区、酒店、餐饮店的名称和地址用户优惠券表则是用户与优惠券的多对多关系表记录了领取时间和使用状态活动商家关联表用来维护每个活动下面有哪些商家参与。建表时有几个细节值得注意。一是所有金额字段都用DECIMAL(10,2)而不是FLOAT避免浮点误差二是时间字段统一用DATETIMEJava端对应LocalDateTime避免时区转换问题三是用户优惠券表要建联合唯一索引(user_id, coupon_id)这是防止重复领取的数据库兜底方案。库存字段我用的是“总量”和“已领量”两个字段领券时对已领量做原子自增配合代码里的事务控制比单独维护一个库存字段更直观也更容易做数据校验。2.3 后端接口设计RESTful风格与返回结构统一后端接口我全部采用RESTful风格活动列表是GET /api/activity/list领取优惠券是POST /api/coupon/receive我的卡券列表是GET /api/coupon/myCoupons。接口的参数和返回值统一使用JSON前端通过Axios调用同时在后端统一处理跨域配置避免开发环境下联调时被CORS拦得怀疑人生。最核心的是统一返回结构的封装。我定义了一个ResultT泛型类包含code、message、data三个字段成功时code200业务失败时返回自定义错误码比如1001表示未登录1002表示优惠券已领完1003表示活动未开始。这样前端只需要在拦截器里判断一次code就能统一弹出错误提示不用每个接口都写一套错误处理逻辑。这个习惯看起来不起眼但实际开发中能省下大量重复代码也让前后端接口对接时沟通成本急剧下降。3. 开发环境搭建与前端项目构建实战3.1 从JDK到Maven的SSM环境配置SSM项目的环境配置是整个开发流程的第一道坎网上教程五花八门但很多都是旧版本照着做容易掉坑里。我用的组合是JDK 1.8 Maven 3.6.3 Tomcat 8.5数据库用MySQL 5.7。JDK一定要配好JAVA_HOME环境变量Maven的settings.xml里配置阿里云镜像源不然下载依赖能等到怀疑人生。IDEA里创建项目时选择Maven骨架或者干脆不用骨架直接建一个普通的Maven项目然后手动补上SSM的结构。SSM整合的关键在于Spring配置和MyBatis配置的分工。spring-mvc.xml只管Controller层的组件扫描、注解驱动、视图解析器spring-mybatis.xml配置数据源、SqlSessionFactory、Mapper扫描和事务管理器。两者要小心不要重复扫描同一个包否则会出现Bean定义冲突的诡异问题。数据源我用的Druid连接池配置了初始连接数5、最大活跃数20还开启了慢查询监控后期排查问题时帮了大忙。3.2 Vue 2项目的创建与必要插件安装前端部分我直接用Vue CLI创建项目命令是vue create travel-web模板选择vue2预设。创建完成后需要安装的项目依赖包括Vue Router路由管理、AxiosHTTP请求、Element UI管理端组件库、Vant移动端组件库用于游客端页面。这里有一个建议游客端页面和掌柜管理后台最好分成两个路由模块虽然共用一个前端工程但代码目录要分开views/tour和views/admin各管各的避免业务代码混在一起。vue.config.js里要配置开发服务器代理把/api前缀的请求转发到后端的http://localhost:8080这样前端开发时就不用关心跨域问题上线后由Nginx统一处理转发。我在这个文件里还配了打包输出路径publicPath: ./避免部署到子目录时静态资源路径错乱。第一次配置时最容易忽略的是后端接口路径要保持一致比如后端是/api/activity/list前端代理配置里就不要写/activity/list差一个前缀就是404。3.3 前后端接口联调的正确打开方式联调阶段是最容易让人崩溃的环节尤其是登录状态的保持。SSM后端默认通过JSESSIONID维持会话前端Vue是跨端口访问的如果不在Axios请求里配置withCredentials: true后端拿不到Cookie用户明明登录了一调接口就提示“未登录”。这个配置搞了我将近两个小时最后在浏览器开发者工具里对比请求头才发现问题。另一个联调经验是善用Mock数据。前后端并行开发时前端可以先定义好接口的返回结构然后在src/mock目录里写死JSON数据后端接口写好后直接切换代理地址即可。这样可以避免前端等后端的尴尬期也让接口字段的变动提前暴露出来。我建议在项目初期就和后端同事或者自己把接口文档先定下来字段名、类型、嵌套结构都写清楚后面联调会顺畅非常多。3.4 用Axios拦截器做统一登录校验和异常处理Axios拦截器是这个项目里性价比最高的代码之一。请求拦截器里从localStorage取出Token并加到请求头响应拦截器里统一判断返回码code200就直接返回数据code1001跳转到登录页其他错误码统一弹出Message提示。这样做的好处是业务代码里完全不用写重复的错误处理逻辑每个接口只需要关心成功分支的数据处理。拦截器还有一个容易被忽视的作用统一处理HTTP状态码。后端接口哪怕业务失败也返回HTTP 200把具体错误码放在JSON里这样前端拦截器不需要对不同HTTP状态码做分支判断。有些后端开发习惯直接返回500表示业务错误前端拦截到之后既拿不到错误详情也不好做用户提示非常被动。项目里后端所有异常都被全局异常处理器捕获并转成统一的Result结构这个设计后来给排查问题省了大力气。4. SSM后端核心编码与业务场景实现4.1 SpringMVC控制器层的参数接收与校验Controller层的代码其实没什么高深技术但写得好不好直接影响调试效率。我习惯把所有接口的参数都用RequestBody接收JSON对象而不是用一堆RequestParam分散接收这样前端传参和字段映射都清晰也方便做参数校验。比如说领取优惠券接口前端传过来的是一个JSON对象包含userId、activityId、couponId三个字段后端用一个ReceiveCouponDTO去接收配合Validated注解做非空校验参数不对直接返回错误码根本不会进入业务逻辑。返回数据方面强调一个原则Controller只做“翻译”和“转发”不写业务逻辑。比如活动列表接口Controller从Service拿到ListActivityVO之后直接塞进Result返回至于这些活动要不要按时间过滤、要不要关联商家信息那都是Service层的事情。我见过不少同学把SQL查询直接写在Controller里十几行的数据拼接看得很头疼这种代码后续完全没法维护。4.2 Service层业务规则与事务边界控制Service层是整个后端最考验功底的地方。以“领取优惠券”这个接口为例我梳理出了完整的业务规则链判断用户是否登录判断活动是否存在且在有效期内判断用户是否已领取过判断优惠券库存是否充足执行用户优惠券记录插入执行优惠券已领数量自增。这六步必须在一个事务里完成任何一步失败都要全部回滚否则就会出现用户领券记录里多了一条数据但库存没减少的脏数据问题。Spring事务管理在这个场景里用的是声明式事务在Service实现类上标注Transactional(rollbackFor Exception.class)注意一定要指定rollbackFor否则只捕获RuntimeException的默认行为会漏掉受检异常。还有一个容易踩的坑事务方法不能通过this调用否则代理失效。比如有一个receiveCoupon方法内部调用了本类里的checkAndLockCoupon如果把后者标注为Transactional它是不会生效的必须把事务方法写在不同类里或者自己注入代理对象。4.3 高并发场景下的优惠券库存防超发处理旅游平台的优惠券活动一旦上了推荐位短时间内可能会涌入几千个并发请求库存扣减如果不做控制一定会超发。我在项目里采用了“乐观锁状态标识”的双保险策略。具体来说优惠券表增加了一个version字段更新库存时用UPDATE coupon SET received_count received_count 1, version version 1 WHERE id ? AND version ? AND received_count total_count这种方式让数据库自己保证并发安全。这种写法的好处是彻底避免了“先查后改”的竞态窗口。如果当前版本号不对或者已领数量达到总量更新语句的受影响行数为0代码检测到后返回“手慢了券已被抢光”。相比直接用SELECT ... FOR UPDATE锁行乐观锁在大多数业务场景下吞吐量更高也完全不需要担心死锁问题。当然如果单张券的并发真的极端到超出数据库承受范围那就需要引入Redis缓存加Lua脚本扣减库存了对于课程设计级别的项目来说乐观锁已经完全够用。4.4 MyBatis动态SQL与多表联查的实操写法MyBatis在这个项目里承担了所有数据库访问逻辑用得最多的就是动态SQL。比如活动列表查询需要根据当前时间过滤出“进行中”的活动SQL大致长这样select idselectActiveActivities resultMapActivityResultMap SELECT a.*, s.name AS shop_name, s.address AS shop_address FROM activity a LEFT JOIN activity_shop_rel r ON a.id r.activity_id LEFT JOIN shop s ON r.shop_id s.id WHERE a.start_time lt; NOW() AND a.end_time gt; NOW() ORDER BY a.sort_order DESC /select这里有一个非常实用的细节多表联查时最好把表名字段全部加上别名前缀避免两张表出现同名字段时MyBatis映射错乱。比如activity表有status字段shop表万一也有status字段不加别名的话ResultMap会拿不到正确的值。另一个坑是lt;gt;这些符号在XML里必须转义直接写会被XML解析器当成标签开头而报错我第一次写的时候就栽在这里。Mapper接口和XML文件的映射规则也很重要。所有XML文件要放到resources/mapper目录下在spring-mybatis.xml里配置mapper-locations指向这个路径同时接口全限定名要和XML的namespace完全一致。我习惯给每个Mapper接口配一个对应的XML文件即使最简单的单表查询也走XML而不是注解SQL这样后续加动态SQL不需要改接口方法签名统一好维护。5. “掌柜有礼”前端页面开发与交互实现5.1 活动首页的轮播图、活动卡片与Vue组件化活动首页是整个项目对外的门面“掌柜有礼”能不能留住用户第一屏至关重要。我把首页拆成了三个核心组件顶部轮播图组件、活动卡片列表组件、商家入口组件。每个组件都是一个独立的.vue文件通过props接收父组件传入的数据通过$emit向父组件抛出事件组件内部完全不关心数据从哪来也保证复用性。轮播图组件用的是Vant的Swipe组件数据从后端活动接口拉取每次页面加载时请求一次把返回的bannerList渲染到轮播图里。这里有个经验图片地址后端返回的是相对路径前端必须拼接完整域名否则图片会全部裂掉。我在Axios的响应拦截器里统一处理过这个问题凡是imageUrl字段的值都以process.env.VUE_APP_BASE_URL为前缀这样换环境部署时只需要改一个环境变量。活动卡片列表用Vue的v-for循环渲染每张卡片展示活动封面、名称、时间范围和剩余券数。剩余券数有一个动态效果库存低于20%时数字变成红色并显示“即将抢光”的标签这个逻辑用计算属性computed实现数据变化时页面自动更新完全不需要手动操作DOM。这种数据驱动的写法就是Vue相比传统jQuery最大的优势理解这个思想后你会发现前端交互的开发效率翻倍。5.2 领取优惠券的交互闭环与页面状态管理领取优惠券的交互是“掌柜有礼”模块的核心体验。用户点击“立即领取”按钮后前端需要做到三层反馈按钮立即变为加载中状态防止重复点击请求发出后根据后端返回结果展示成功或失败提示成功后卡片上的“立即领取”变为“已领取”且按钮置灰禁止再次点击。这个交互我在代码里用了一个简单的状态机来管理。每个优惠券卡片的数据对象里有receiveStatus字段值为0表示可领取1表示已领取2表示售罄。用户点击时先判断状态只有0才能发起请求请求成功后把receiveStatus改成1。如果后端返回售罄错误码则改成2。这个状态机避免了“用户疯狂点击导致发多个请求”的情况也让页面展示逻辑一目了然。有关状态同步还有一个细节用户领取成功后“我的卡券”页面也要同步看到这张券但因为两个页面共享同一个Vuex store我在Vuex里维护了卡券数量的count字段领取成功后commit一个INCREASE_COUPON_COUNT的mutation让整个项目的卡券数量状态处处一致。这种全局状态管理在前端规模变大后几乎是必须的不然A页面改了数据B页面不刷新就看不到体验极差。5.3 路由守卫实现登录态控制与页面访问权限用户没登录就点击领取按钮正确的做法不是等后端返回“未登录”再跳转登录页而是前端直接拦截。我用Vue Router的路由守卫实现了这个逻辑在router.beforeEach里判断目标路由是否需要认证需要的话就检查localStorage里有没有Token没有就跳转登录页并在登录成功后回跳原页面。路由守卫里还做了掌柜后台的权限区分。普通用户即使手动修改URL访问/admin路径也会被重定向到403页面。做法是在路由表定义时给管理后台路由加一个meta: { requiresAdmin: true }字段守卫里判断用户角色是否为管理员。游客端和管理端分开的权限体系在后端接口层面也要同步校验权限但前端先拦截一层可以节省无效请求也提升整体安全性。5.4 使用Vuex管理用户信息、购物车和优惠券状态的实战Vuex在上文提到过这里展开讲讲。游客看旅游产品时会有“加入行程单”的需求行程单里的条目数是全局状态底部导航栏的Badge角标要一直显示这个数字。我用Vuex的state维护tripCount任何组件都能读取、提交变更组件之间不再需要反复通过$emit传数据页面刷新后重新从后端拉取最新数据即可。Vuex也不是万能的钥匙name我反而建议用法大多数页面数据尽量放在组件内部只有多页面共享的状态才进Vuex。比如活动列表数据只在活动首页用就放在组件的data里但用户基本信息、优惠券角标、购物车角标这些需要全局共享的才放进Vuex store。很多新手上来就喜欢把所有接口数据都塞进Vuex结果页面一刷新全没了还得重新加载纯属自找麻烦。6. 项目部署上线与环境配置实战6.1 前端构建打包与Nginx托管部署细节前端部署没什么神秘操作核心命令就一条npm run build。但很多细节可以在这个环节翻车。打包之后生成dist目录要把这个目录拷贝到服务器的/usr/share/nginx/html下然后修改/etc/nginx/nginx.conf配置静态文件服务和接口反向代理。Nginx配置里最核心的是location /api/这一段的转发配置把前端发来的/api请求转发到后端的Tomcat端口。我在Nginx配置里吃过一个大亏try_files指令没配好导致刷新页面时404。SPA应用的思路是只有一个index.html入口所有的路由都由Vue Router在前端处理刷新时服务器必须把任意路径都指向index.html。配置写法是try_files $uri $uri/ /index.html;加了这一行后刷新/activity/3这样的页面就不会404了。这个坑属于“部署一次才知道”的经验提前写在配置文件里能帮读者少走一大段弯路。6.2 后端WAR包构建与Tomcat生产环境配置后端部署我用的是Maven打包成WAR包然后扔进Tomcat的webapps目录。打包前有几个配置需要调整applicationContext.xml里的数据库账号密码要改成生产环境的强密码各数据源的url要指向公网数据库地址日志路径要改成服务器上的绝对路径。Tomcat配置方面我记得修改过两个重要参数一是server.xml里的端口号如果服务器上同时跑着别的应用8080端口容易被占用我改成了8088二是catalina.sh里的JVM内存参数设置为-Xms256m -Xmx512m避免活动高峰期内存溢出导致服务崩溃。如果是低配服务器建议再调低一些如果服务器内存充足也可以调到1G让Tomcat更从容。调完成后用tail -f /usr/local/tomcat/logs/catalina.out监控日志看到Deployed application字样基本就成功了。6.3 线上环境跨域与端口开放的正确处理思路前后端分离部署后还有一个关键问题前端域名是http://travel.example.com后端接口是http://api.example.com:8088浏览器跨域怎么办。有两条路一是后端配置CorsFilter允许指定域名跨域访问二是用Nginx统一中转前端只访问相对路径/apiNginx转发到后端从浏览器视角看根本不存在跨域。我的项目两条路都用了开发环境靠后端CorsFilter生产环境靠Nginx中转。这里重点说明为什么生产环境推荐Nginx中转。第一后端不需要暴露真实端口减少被扫描攻击的风险第二Nginx可以做Gzip压缩和静态资源缓存对页面加载速度有明显提升第三以后如果需要加负载均衡只需要在Nginx层改配置就完成后端完全无感知。这个“Nginx一层代理解决所有问题”的方案在中小型项目里几乎是标准答案。7. 常见问题排查与避坑经验整理7.1 跨域配置无效或请求被拦截的全面排查方法前后端联调时最常见的问题就是“请求发出去了但浏览器报CORS错误”或者“Network里能看到请求但response里没有数据”。我总结了一套排查路径先看后端响应头里有没有Access-Control-Allow-Origin字段没有就说明跨域配置根本没生效再看有没有Access-Control-Allow-Credentials如果设置了true那么Allow-Origin不能是*必须指定确切域名。还有一个容易被忽略的点如果请求中带有自定义请求头比如Token浏览器会自动发起一次OPTIONS预检请求后端必须对这个预检请求也放行。有些同学的CorsFilter只处理了GET和POST忘了放行OPTIONS导致明明配置了跨域实际请求依然被拦。排查时打开开发者工具仔细看Failed请求的General和Request Headers往往一眼就能定位问题。7.2 Maven依赖冲突与Jar包版本问题的根治思路SSM项目里依赖冲突是家常便饭尤其是Spring、MyBatis、Jackson这几个库的新旧版本混在一起时经常出现“明明写了方法却报NoSuchMethodError”的问题。我吃了好几次亏之后养成了一个习惯所有项目的父POM统一用spring-boot-dependencies或者spring-framework-bom来管理版本子模块只引用坐标不写版本号从源头杜绝版本冲突。另外推荐在IDEA里安装Maven Helper插件直接在依赖关系图里搜索冲突可视化地看到哪些jar包被重复引用了。排查到一个原则优先保留版本较新的那个除非新版本有兼容性问题。比如项目里如果同时出现log4j-api和log4j-core版本不一致大概率控制台会疯狂刷ClassNotFound异常这时候去Maven仓库手动统一版本就行。7.3 数据库时区问题导致的日期错乱的解决方案MySQL和Java之间的时区问题是很多人开发时没遇到但上线后立刻爆雷的坑。本地开发时一切正常部署到云服务器后发现活动开始时间和结束时间总是差了8小时。原因很简单数据库连接串里的时区参数没设置对。我用的JDBC连接串是jdbc:mysql://localhost:3306/travel_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai其中serverTimezoneAsia/Shanghai就是关键参数。还有一个配套设置MySQL服务器的默认时区也要改成东八区用命令SET GLOBAL time_zone 8:00即可。如果这两边时区不统一哪怕Java代码里用的是LocalDateTime数据库存进去的值也可能会偏移。排查出来之后最简单的验证方法是在MySQL客户端直接执行SELECT NOW()看返回时间是不是当前本地时间不是就是时区配置有问题再往连接串或者MySQL配置里找原因。7.4 前端白屏、接口404和打包后资源路径错乱的排查前端部署后的常见异常有这三种白屏、接口404、图片样式全部丢失。白屏的第一嫌疑是JS报错打开浏览器控制台看具体的错误信息最常见的是Cannot find module这通常是因为publicPath配置不对导致index.html里引用的JS路径指向了不存在的位置。把vue.config.js里的publicPath改成./即可这样打包出来的资源路径都是相对路径部署在任意子目录下都能正常加载。接口404的排查要分清是前端路由404还是后端接口404。前者表现为页面刷新后404是Nginx的try_files没配置后者表现为接口请求返回404状态码要检查Nginx代理转发路径和后端RequestMapping路径是否匹配。我在项目里出现过一次/api/activity/list被代理转发成/activity/list的惨案改完Nginx配置后记得执行nginx -t检查语法再nginx -s reload重载配置做法很简单但很多人会忘记。8. 项目优化扩展与个人实操心得8.1 从SSM到Spring Boot的升级路径参考这个项目做完之后如果读者想进一步提升我建议尝试把后端从SSM迁移到Spring Boot。迁移难度其实比想象中低因为核心的Service层和Mapper代码基本不用改主要工作是替换配置方式把web.xml删掉把spring-mvc.xml和spring-mybatis.xml里的内容对应换成JavaConfig或者application.yml配置把webapp目录换成Spring Boot的静态资源目录结构。迁移时最大的坑是在依赖引入上。Spring Boot项目一定要引入spring-boot-starter-web和mybatis-spring-boot-starter而不再是原始的spring-webmvc和mybatis。数据源也建议换成HikariCP性能优于Druid且配置更简洁。做完这些改造你会体会到Spring Boot“约定优于配置”的设计哲学整体开发体验会提升一个档次这也是后续面试时能拿得出手的亮点项目经验。8.2 给准备拿这个项目去面试的同学的提醒如果你打算把这个项目写进简历面试官大概率会追问“掌柜有礼”模块的并发处理方案所以请务必把乐观锁防超发的实现细节讲清楚从建表字段到SQL语句再到Service层的判断逻辑都能随口说出。另一个高频问题会是“SSM和Spring Boot的区别”不要背概念要结合这个项目说比如“我在SSM里手动配置了事务和Mapper扫描后来迁移到Spring Boot后这些都由自动配置完成”。还有一个容易被问到但也很容易丢分的点Vue的响应式原理。面试官可能会问为什么修改数据后页面会自动更新这就要讲到Object.defineProperty或者Proxy的拦截机制。建议去翻一遍Vue官方文档里“深入响应式原理”那一节把依赖收集和派发更新的流程理一遍能用自己的话讲出来这个项目就真正变成你自己的了。8.3 这套前后端分离架构的后续扩展想象空间当前项目已经具备旅游网站最基础的能力景点展示、下单、卡券营销。后续如果有余力做扩展我建议从三个方向入手。一是接入地图服务在景点详情页展示基于地理位置的周边酒店和餐饮推荐这是旅游平台的核心需求二是增加订单评价体系让用户对景区和酒店进行星级评分和图文评价提升平台内容的丰富度三是把优惠券系统扩展成满减、折扣、新人券等多种营销玩法并增加后台运营配置界面让“掌柜有礼”真正变成一个可配置的营销中台。在技术架构层面可以把图片和静态资源迁移到对象存储中再把活动页的读请求加上Redis缓存整站的并发承载能力会成倍提升。这些扩展看着很多但都建立在当前清晰的分层架构之上每一步都有迹可循不会伤筋动骨。这也是我强调代码分层要干净的原因——架构清晰了未来才能大胆往前走。
