
1. 项目背景与需求分析为什么是社区生鲜团购1.1 社区生鲜市场的痛点与机遇做毕设选这个题目其实挺贴合当下实际的。社区生鲜团购这个赛道过去几年经历了从野蛮生长到精细化运营的转变。我见过不少案例最开始大家觉得这玩意儿就是个微信群接龙手动记账规模一上来就全乱套了——订单漏记、库存对不上、配送路线全凭记忆最后不是团长跑路就是用户投诉。从技术角度看这个需求天然适合用Web系统来解决。核心矛盾在于生鲜商品具有高时效性、非标品多、配送窗口短而传统电商的订单管理系统直接搬过来根本用不了。你需要一个能处理“今天下单明天到”、“部分商品缺货自动退款”、“按社区维度打包配送”这些特殊场景的系统。1.2 毕设选这个题目的技术价值从技术层面拆解这个项目覆盖了Java Web开发的主流技术栈后端SSM框架Spring SpringMVC MyBatis——虽然现在Spring Boot更流行但SSM的底层原理必须吃透面试时一问IoC容器怎么实现的、AOP怎么织入的光会用Spring Boot注解的人往往答不上来前端Vue全家桶Vue Vue Router Vuex/Pinia Element UI——前后端分离是主流API接口设计、跨域处理、状态管理这些都得实操数据库MySQL Redis——MySQL做持久化Redis做热点数据缓存和购物车临时存储这个组合对毕设来说既不会简单到拿不到分也不会复杂到做不完。而且涉及到电商的核心流程商品管理、购物车、下单、支付、订单状态流转这些都是面试高频题。1.3 系统核心功能清单基于实际运营场景我把系统拆成了两个端用户端前端Vue页面商品浏览、按分类/社区筛选、加入购物车、下单结算、订单查询、地址管理、评价管理端后台SSM商品上架/下架、库存管理、订单处理确认/配货/配送/完成、用户管理、社区管理、数据统计每个功能点的设计都要考虑生鲜团购的特殊性。比如商品列表要支持按“今日特价”“社区推荐”排序购物车要能自动提示某商品库存不足订单状态要能处理“部分退款”这种场景。2. 技术选型与架构设计为什么是SSM Vue2.1 SSM框架的选择逻辑刚开始做这个项目的时候我也在纠结要不要直接用Spring Boot毕竟配置少、起步快。但后来想清楚一件事毕设的本质不是让你快速搭一个能跑的东西而是让你展示对底层框架的理解。SSM框架虽然配置繁琐但每个配置文件都有明确的目的Spring的applicationContext.xml管理Bean的生命周期控制依赖注入SpringMVC的springmvc-servlet.xml配置控制器映射、视图解析器、拦截器MyBatis的mybatis-config.xml连接数据库、配置SQL映射如果你能讲清楚为什么SpringMVC的前端控制器DispatcherServlet要拦截所有请求以及MyBatis的Mapper代理是怎么把接口和XML映射起来的面试官基本不会追问太多。2.2 前后端分离的架构取舍这个项目采用了前后端分离架构后端只提供RESTful API前端用Vue构建单页应用。这样做的好处很明显开发效率高前后端可以并行开发约定好接口文档我是用Swagger生成的后端写接口前端调接口部署灵活后端打war包扔Tomcat前端build后放Nginx或者直接合并在一个Tomcat的webapps下扩展性好以后想加个移动端App直接复用后端接口前端不用动但也别低估前后端分离的坑。最大的坑是跨域问题CORS。我一开始写接口时前端Vue发请求一直报403后来发现是后端没配置允许跨域。解决方案是在SpringMVC拦截器里添加CORS支持或者用Nginx做反向代理统一处理。2.3 Vue版本选择与组件化设计在Vue版本上我选了Vue 2 Element UI的组合。虽然Vue 3已经挺成熟了但考虑到Element UI对Vue 2的支持更稳定而且毕设项目不需要用到Vue 3的Composition API那些高级特性选Vue 2更稳妥。组件化设计上我拆分了几个核心组件GoodsCard.vue商品卡片组件接收商品ID、图片、名称、价格、库存等props支持点击跳转详情页CartControl.vue购物车加减控件可以单独使用也可以嵌入商品列表OrderList.vue订单列表组件支持按状态筛选路由配置也很关键。我用Vue Router实现了页面跳转前端路由配置为const routes [ { path: /, component: Home }, { path: /goods/:id, component: GoodsDetail }, { path: /cart, component: Cart }, { path: /order, component: OrderList }, { path: /admin, component: AdminLayout, children: [ { path: goods, component: AdminGoods }, { path: order, component: AdminOrder } ]} ]2.4 数据库设计的核心原则生鲜团购系统的数据库设计最核心的是处理好几个关键表的关系。我画了大概6张核心表user表用户ID昵称手机号密码地址所属社区IDgoods表商品ID名称图片描述原价团购价库存上下架状态分类IDcategory表分类ID分类名称排序cart表购物车ID用户ID商品ID数量添加时间order表订单ID用户ID订单状态待支付/待发货/待收货/已完成/已取消总金额下单时间收货地址order_item表订单项ID订单ID商品ID数量单价设计时特别注意了几点订单和订单项是1对N关系方便处理部分退款购物车表用用户ID商品ID做联合唯一索引防止重复添加商品库存要加乐观锁防止超卖3. 核心功能模块详细实现与踩坑记录3.1 商品列表与分类筛选性能优化实战商品列表页是所有用户访问量最大的页面性能优化是关键。我一开始直接写了个简单的SELECT * FROM goods然后前端分页结果上线测试时发现商品数据2000条每次请求返回所有数据页面加载慢得不行。优化方案后端分页MyBatis配合PageHelper插件每次只返回当前页的数据。配置其实很简单在pom.xml加依赖然后在service层调用PageHelper.startPage(pageNum, pageSize)就行。缓存热门商品首页推荐的商品、今日特价这些不会频繁变动的数据放到Redis里缓存。我用的RedisTemplate设置key为“goods:hot:list”过期时间30分钟。分类筛选前端传categoryId后端用MyBatis动态SQL拼接。这里有个坑如果传了空字符串MyBatis会误判为条件成立。所以我用 来处理。实现代码片段MyBatis Mapper XMLselect idselectGoodsByCondition resultTypecom.leerle.pojo.Goods SELECT * FROM goods where if testcategoryId ! null and categoryId ! AND category_id #{categoryId} /if if teststatus ! null AND status #{status} /if /where ORDER BY create_time DESC /select3.2 购物车模块复杂状态管理购物车是电商系统里最考验前端状态管理的模块。用户可能同时操作多个商品加数量、减数量、删除、全选、结算。这些操作如果都靠后端接口实现每次请求都要刷新整个页面用户体验极差。我的做法是购物车数据在前端用Vuex管理只在下单结算时向后端提交最终数据。Vuex的store设计如下// store/modules/cart.js const state { carts: [] // 购物车列表 } const mutations { ADD_CART(state, goods) { const exist state.carts.find(item item.goodsId goods.id) if (exist) { exist.count } else { state.carts.push({ goodsId: goods.id, name: goods.name, price: goods.price, count: 1, checked: true }) } }, UPDATE_CART_COUNT(state, { goodsId, count }) { const item state.carts.find(item item.goodsId goodsId) if (item) item.count count }, DELETE_CART(state, goodsId) { state.carts state.carts.filter(item item.goodsId ! goodsId) } }这里有个坑刷新页面后Vuex数据会丢失。解决方案是用localStorage做持久化每次mutation触发时同步更新localStorage。或者更优雅的做法用户登录后把购物车数据同步到后端数据库但前端还是用Vuex管理只有结算时才调接口。3.3 订单生成与状态流转防止超卖和重复下单订单模块是系统的核心也是最容易出bug的地方。我主要处理了三个问题1. 防止超卖用户下单时如果库存只剩1件但两个人同时下单不能都成功。我的做法是在生成订单时用MySQL的UPDATE语句加上库存条件判断UPDATE goods SET stock stock - #{count} WHERE id #{goodsId} AND stock #{count}然后检查受影响的行数如果为0说明库存不足回滚事务。这样利用数据库的行锁简单有效不用引入分布式锁。2. 防止重复下单用户网络不好时可能连续点击提交按钮导致生成多个相同订单。我用Redis的分布式锁解决以用户ID商品ID作为keysetnx命令过期时间设为5秒如果返回1才允许下单返回0说明请求已处理。3. 订单状态机订单的状态流转是有固定顺序的待支付 - 待发货 - 待收货 - 已完成中间可以取消从待支付或待发货取消。我设计了一个状态机用枚举类控制状态转换public enum OrderStatus { UNPAID, // 待支付 PAID, // 已支付待发货 SHIPPED, // 已发货待收货 COMPLETED, // 已完成 CANCELED // 已取消 }每次状态变更时检查当前状态是否允许转换到目标状态不合法的转换直接抛异常。3.4 前后端联调与接口规范前后端联调是项目最多坑的阶段。为了减少沟通成本我提前定义了接口规范统一返回格式{code: 200, message: 操作成功, data: {...}}分页返回格式{code: 200, data: {total: 100, rows: [...]}}异常时返回{code: 500, message: 服务器内部错误, data: null}后端用SpringMVC的ControllerAdvice做全局异常处理前端用axios的拦截器统一处理响应遇到code!200的情况alert弹窗提示或跳转登录页。这里有个实用技巧前端调试时用Vue Devtools插件查看组件状态和Vuex数据比console.log好用一百倍。Chrome浏览器装一个能直接看到state、mutations、actions的调用栈。4. 项目部署与性能优化从开发到线上4.1 环境配置与部署方案项目开发环境我用的是Windows 10 IntelliJ IDEA MySQL 8.0部署到云服务器上用的是CentOS 7。后端部署步骤用Maven的package命令打出war包把war包放到Tomcat的webapps目录下启动Tomcat自动解压部署修改数据库连接配置改成线上MySQL的地址前端部署步骤运行npm run build生成dist目录把dist目录下的所有文件复制到Tomcat的webapps/ROOT目录下或者新建一个目录配置前端的请求地址在config/index.js里修改baseUrl为线上后端地址有一个常见问题前端打包后访问页面后刷新出现404。这是因为Vue Router的history模式导致的需要配置Tomcat的URL重写规则将所有非静态文件的请求重定向到index.html。解决方案是在web.xml里加一个Filter或者直接改用hash模式url带#。4.2 性能优化点从数据库到前端项目上线后我做了几个优化数据库优化给goods表的category_id字段加索引分类查询从0.2秒降到0.02秒给order表的user_id加索引用户查询订单列表提速明显用EXPLAIN分析慢查询发现有个SQL没用到索引是因为用了LIKE %keyword%这种无法命中索引只能改成用全文索引或搜索引擎前端优化商品图片用懒加载Element UI的el-image组件自带lazy属性性能提升明显列表页用虚拟滚动如果数据量大用v-for渲染所有DOM会导致页面卡顿我用了vue-virtual-scroller插件只渲染可视区域内的元素路由懒加载用Vue Router的异步组件加载首页只加载必要的组件其他页面按需加载const GoodsDetail () import(/views/GoodsDetail.vue) const Cart () import(/views/Cart.vue)4.3 安全防护基础的防SQL注入与XSS作为毕设项目安全问题不需要做到企业级但基本的防护还是要有的。SQL注入防护MyBatis的#{}占位符会自动处理参数防止SQL注入。但要注意如果用了${}直接拼接字符串就有注入风险。我写代码时严格规定所有参数都用#{}只有表名、列名这种动态字段才用${}且必须做白名单校验。XSS防护用户在商品评价里输入恶意脚本其他用户查看时就会执行。我的做法是在前端展示时对内容做HTML转义后端用过滤器过滤请求参数。Element UI的el-input组件默认会转义但如果用v-html渲染就可能出问题所以尽量用插值表达式{{ }}。5. 项目扩展与写论文的实用建议5.1 从毕设到实际项目的扩展思路这个项目做完后如果想进一步提升可以从几个方向扩展接入微信支付在订单支付页面调起微信支付需要申请商户号对接微信支付SDK。这个功能写在简历上面试官会很感兴趣引入消息队列用户下单后发短信通知团长、减库存这些操作如果同步执行接口响应会很慢。用RabbitMQ做异步处理解耦业务逻辑增加数据分析模块统计每日销量、热门商品、用户增长用ECharts画图表展示5.2 论文写作的实用技巧写论文时不要只写“我做了什么功能”要写“我为什么这么做”。比如为什么用SSM而不是Spring Boot因为我想展示对Spring底层原理的理解购物车为什么用Vuex管理因为前端状态管理更高效减少后端压力订单模块为什么用乐观锁因为并发量不大用乐观锁比悲观锁性能更好论文结构参考绪论项目背景、研究意义系统分析需求分析、用例图、功能模块划分系统设计架构设计、数据库设计、接口设计系统实现核心功能模块的代码实现与截图系统测试功能测试、性能测试总结与展望5.3 面试时如何讲清楚这个项目面试官问到这个项目时别急着说功能先说“这个项目解决了什么问题”然后从技术选型开始讲。重点突出几个点你搞清楚了前后端分离的跨域问题怎么解决你处理了购物车的数据一致性用VuexlocalStorage防止数据丢失你用了数据库乐观锁防止超卖说明你懂并发控制面试官最讨厌的是说“这个功能我用XXX框架实现了”然后问为什么用这个框架答不上来。所以每个技术选型都要有自己的思考过程哪怕这个选择不是最优的只要你能自圆其说面试官也会认可。最后分享一个小技巧项目做完了把代码上传到GitHub把服务部署到云服务器上面试时直接打开手机让对方看效果比说一万句都有说服力。