
如果你正在找一个既能完整跑通前后端、又能讲清楚设计思路、还能拿来做毕设和面试项目的SSM练手场景校园代送服务平台是我见过最接地气的选择之一。它不像电商系统那样庞大吓人也不像图书管理系统那样千篇一律业务逻辑恰好卡在“够用但又不简单”的位置上。不管你是准备毕业设计还是想巩固SpringSpringMVCMyBatis这套经典组合这个项目都值得认真做一遍。我最近刚把一套基于SSM的校园代送服务平台从零到一完整实现出来从需求梳理、数据库设计、接口划分到前后端联调、部署上线整个过程踩了不少坑。这篇就把当时的设计决策、开发步骤、排错经验全部写出来重点讲清楚为什么这样做、哪里容易出问题、出了问题怎么查。想把这个项目做扎实的同学按这条路线走能省掉大量瞎折腾的时间。1. 为什么选校园代送服务平台作为SSM练手项目很多同学做SSM项目时习惯去找商城、博客、学生管理系统这类模板。这些项目不能说没价值但业务逻辑太简单最后写进简历或者答辩时很难体现你真正理解了三层架构和数据库建模。校园代送平台表面上是“跑腿订单”实际上涵盖用户权限、订单状态流转、抢单派单、支付模拟、信息发布等多个完整模块很适合用来展示工程能力。1.1 业务场景的典型性校园代送的实质是连接“发单用户”和“接单用户”的撮合平台。一个学生没时间去快递点取件就在平台下单写明取件地点、送达地址、期望时间、跑腿费另一个学生有空闲时间看到订单后接单并完成配送最后双方确认完成。这个场景天然包含几组核心关系用户与订单、发单人与其代送需求、接单人与执行过程、管理员与服务监督。这样一来CRUD不再只是抽象增删改查而是被业务串起来用户下单要校验地址信息接单要操作订单状态配送完成要触发结算逻辑。你在做这个项目时每张表每个字段都能找到真实业务语义这种“有目的感的编码”和“为了做功能而做功能”是完全不同的。从技术学习角度看它又不会像大型电商那样引入高并发、分布式事务等超纲概念。SSM这套框架组合承载这种量级的业务刚刚好Spring负责对象管理和事务声明SpringMVC负责请求路由和参数绑定MyBatis负责SQL映射与数据库访问。三者分工清晰初学者容易理解边界又不至于觉得“太简单没东西可写”。1.2 SSM三件套在这个项目里的实际分工很多人学SSM时总喜欢一个问题一个框架孤立地看等到真做项目就懵了。实际在一个业务里三个框架是流水线配合浏览器发来请求Web容器把请求交给SpringMVC的DispatcherServletHandlerMapping找到对应的Controller方法Controller里调用Service层接口Service实现类在Spring容器中是单例Bean需要操作数据库时就注入Mapper接口Mapper接口对应MyBatis的XML文件里面写了具体SQL。放到校园代送项目里一条典型链路长这样用户在小程序或网页上点击“发布代送订单”请求到OrderController的createOrder方法参数被SpringMVC自动绑定到OrderDTO。Controller调用OrderService的createOrder方法这个方法上加了Transactional内部先检查用户信用状态和订单参数再插入订单主表数据同时往订单日志表写入一条操作记录。MyBatis通过动态SQL完成两处数据插入整个事务由Spring统一管理任何一步失败都全部回滚。这套流程跑通后你就真正理解了为什么SSM不是“三个框架的简单相加”。Spring是黏合剂SpringMVC解决Web层MyBatis解决持久层各干各的活通过约定和数据模型连接起来。1.3 功能模块全景我当时梳理功能时没有一上来就画表格写字段而是先把角色列出来再给每个角色配操作权限最后把操作落到页面上。这样不容易漏功能也方便后续写文档和接口清单。系统一共拆成四大模块用户模块、订单模块、管理模块、辅助模块。用户模块注册登录、身份切换、个人主页、信用分展示。学生有两种身份一种是发起代送的发单人一种是接单跑腿的接单人。账号体系早期可以都用同一个用户表加一个user_type字段区分登录后根据类型加载不同操作菜单。订单模块发布订单、订单列表、订单详情、抢单/取消/确认送达。这是整个项目的心脏。订单状态维护我是用状态字段来控制的初始状态是待接单有人接单后变配送中发单人确认后变已完成取消订单要区分是发单人取消还是接单后取消不同情况的退费逻辑不一样。管理模块管理员登录、用户管理、订单审查、数据概览。管理员不参与具体配送主要看平台数据、处理异常订单、封禁违规用户。辅助模块公告管理、地址簿、消息通知。这些功能可以撑起列表查询和富文本框操作也给项目增加丰富度。这样一套模块下来既有SpringMVC的Controller和过滤器使用场景也有MyBatis复杂的多表关联查询和动态SQL场景还有Spring声明式事务的典型应用场景。无论做课题答辩还是面试讲项目都有足够的“料”可以讲。2. 系统设计从需求到数据模型业务梳理完之后下一步是设计。在这个阶段偷懒后面写代码一定返工。我一般先做角色权限矩阵再定核心流程最后才落数据库表。顺序反了容易出现“表建好了但业务跑不通”的尴尬局面。2.1 角色与核心业务流程先看角色。校园代送平台有发单人、接单人、管理员三种角色。发单人可以发布代送需求、查看自己订单、确认收货、评价接单人。接单人最核心的权限是浏览待接单的订单池、抢单、执行配送。管理员负责平台侧的审核与监管。再看流程。整个项目的业务主流程是一条状态机驱动的单链发单人填写代送信息保存后生成待接单订单。待接单订单进入公共订单池任何接单人都能看到并抢单。接单人抢单成功订单变成配送中同时锁定接单人防止被重复抢。接单人完成配送后可以标记为待确认也可以等待系统超时自动确认。发单人确认收货后订单变成已完成平台记录完成时间接单人获得跑腿费收入。状态机这一点我强烈建议你在设计数据库之前就画清楚。因为订单状态字段到底设几种枚举哪些操作合法、哪些非法全部由状态机决定。我当时就吃过亏一开始给订单只设计了待接单、进行中、已完成三个状态后来发现无法区分“发单人下单后正在选择接单人”和“接单人配送中”导致查询列表时逻辑混乱不得不回头加字段改代码。2.2 数据库设计的核心表与要点数据库设计是这个项目最值得讲的部分。我最终用了六张核心表每张表都有明确的职责边界。因为采用的是MySQL字符集我统一用utf8mb4排序规则用utf8mb4_general_ci这样能正确存储中文和表情符号避免乱码问题。用户表(user)id, username, password, nickname, phone, user_type, credit_score, status, create_time。密码必须加密存储一般是MD5加盐或者用Spring自带的加密工具。如果你不想在答辩时被问住建议不要自己造加密算法直接讲清楚加盐原理就行。user_type字段用tinyint0表示发单人1表示接单人2表示管理员。credit_score信用分初始设为100每次违规扣分扣到低于60分禁止发单。订单表(orders)id, order_no, publisher_id, receiver_id, pickup_location, deliver_location, expected_time, fee, status, remark, create_time, finish_time。order_no我用时间戳加随机数生成保证唯一性。status字段对应状态机中的不同状态0待接单1配送中2待确认3已完成4已取消。receiver_id在订单刚创建时为空抢单成功后更新。fee字段表示跑腿费属于额度较小但逻辑敏感的字段要防止被恶意篡改实际操作中最好只在后端计算和更新。地址表(address)id, user_id, contact_name, contact_phone, location, is_default。这是为了提升发单体验设计的。用户可以直接从常用地址里选不用每次手输。很多同学可能觉得这个表可有可无但我建议保留因为这正好演示了MyBatis里的嵌套查询场景。订单日志表(order_log)id, order_id, operator_id, action_type, action_desc, create_time。这张表非常有用记录每一次状态变更。后面排查问题、写展示用例、回答面试官“如何追踪订单状态”这个问题时全靠它。我当时该表被问到的次数远超订单主表。公告表(notice)、管理员表(admin)这两张表逻辑比较简单主要给管理端做列表展示。其中公告表可以直接用MyBatis的PageHelper分页插件做分页查询顺便演示一下分页通用插件的用法。设计这些表时我会强调一个原则能冗余就冗余能标识就标识。很多学生做设计时舍不得冗余喜欢把所有字段都拆成关联表最后每次查询都要join四五个表性能差不说代码也不好写。比如订单表里我特意存了publisher_id和receiver_id两个用户外键而不是通过关联用户表去取这样列表展示时一次单表查询就能拿到发单人和接单人的ID再配合用户表的二次查询或者联查速度和代码可读性都更好。2.3 接口设计原则接口设计直接决定前后端联调是否顺畅。如果前后端都是你自己写更要提前约定好统一返回格式不然前端拿到的数据结构一会儿是Map一会儿是List调试会抓狂。我用的统一返回对象是一个Result类包含三个字段code、message、data。code200表示成功code500表示业务异常code401表示未登录或权限不足。所有Controller方法返回的都是Result对象SpringMVC通过ResponseBody把对象转成JSON前端再统一解析。这个模式现在很常见学过SpringBoot的人可能习以为常但在SSM项目里自己动手封装一遍能加深对JSON序列化和HTTP状态码的理解。接口路径按模块划分比如/api/user/register、/api/user/login、/api/user/info/api/order/create、/api/order/list、/api/order/detail、/api/order/accept、/api/order/finish、/api/order/cancel/api/admin/userlist、/api/admin/orderlist接口之间不要交叉保持单一职责。比如取消订单不要在删除接口里顺带把订单状态改了而是单独走cancel接口业务逻辑彼此独立。3. 手把手实现从搭建骨架到跑通流程设计阶段完成后立刻进入编码。这里我按实际开发的顺序来讲不是按照代码层级从Controller写到Mapper而是按“能跑起来”的优先级组织先搭工程再做认证再做核心业务最后补前端和联调。这样整个过程始终能看到阶段性成果不至于写了三天还看不到界面。3.1 环境与工程结构工具版本建议直接用经典稳定组合JDK1.8、Tomcat8.5、Maven3.6、MySQL5.7或8.0。太高版本反而容易遇到兼容问题。IDE用IDEA社区版也能跑但付费版对Tomcat和数据库插件的支持更顺手。创建一个Maven工程packaging是war。如果你的IDE没有自带Tomcat运行插件也可以直接把工程打包war丢进Tomcat的webapps目录启动bin目录下的startup.bat访问。我建议开发阶段直接用IDE内置Tomcat方便热部署和断点调试。工程目录沿用标准结构com.example.campusdelivery.controller控制层com.example.campusdelivery.service以及impl业务层com.example.campusdelivery.mapperMyBatis的Mapper层com.example.campusdelivery.entity实体类com.example.campusdelivery.interceptor拦截器com.example.campusdelivery.common通用类resources目录下放mybatis-config.xml、spring-mvc.xml、spring-mybatis.xml以及mapperXMLwebapp目录下放静态页面这个结构不是随便分的记住一条原则调用方向必须是路由从上到下Controller不允许直接操作SqlSessionService不允许直接操作HttpServletRequest。如果发现代码里出现跨层调用说明设计有问题赶紧拆开。这样做的好处是后续AOP切面、事务管理、单元测试都能顺利搞定。3.2 登录认证的实现登录认证我用的方案是拦截器加Session。用户登录成功后把User对象放进Session写一个LoginInterceptor在preHandle方法里检查当前请求路径是否在放行列表里。请求路径包含/api/user/register、/api/user/login的放行其他路径全部校验Session里是否存在user对象。如果不存在返回统一消息code401。很多学生用的方案是每个Controller方法里手动判断Session这样做第一个缺点是代码重复第二个缺点是很容易漏判。拦截器方案把登录控制收敛到一处新增接口只要没有意外暴露默认都要求登录。判断路径时可以简单判断是否包含“/admin/”管理端用另一个AdminInterceptor做权限校验。注意只校验请求头或路径不要试图在登录接口里用拦截器否则会出现“登录时还没登录”的逻辑死锁。密码部分我加密后存入数据库我用的加盐方式是salt字段加密码拼接后进行SHA-256散列。注册时生成随机盐登录时先根据用户名查盐和密文再把用户输入的密码加盐散列与库中密文对比。这样即便数据库泄露密码也不是明文。Session会话超时时间需要单独配置。Tomcat默认是30分钟如果用户操作到一半去吃饭回来发现会话失效被踢回登录页体验很差。我在web.xml里设置了会话超时时间为60分钟同时前端每次请求在Ajax的回调里对code401做统一跳转所以实际体验是当前接口返回未登录前端跳去登录页而不是整个页面白屏。3.3 订单状态机与业务闭环订单模块是整个项目的核心我单独说说实现思路。创建订单时Controller接收前端传来的pickupLocation、deliverLocation、expectedTime、fee等字段转成OrderDTO调用OrderService.createOrder。Service里做几件必做事情校验参数非空、校验用户状态正常、生成orderNo、插入订单、插入日志。整个过程一个事务任何一个环节失败都回滚。抢单接口是重点。当接单人点击抢单时需要做两件事先把订单状态从0改成1再把receiverId设置为当前用户。这里有一个并发安全性问题如果两个接单人同时抢同一单怎么办只用简单的updateById加状态判断可能会出错我用的是原子更新UPDATE orders SET status 1, receiver_id #{receiverId} WHERE id #{orderId} AND status 0然后检查受影响行数。如果受影响行数为1说明抢单成功如果为0说明订单已经被别人抢走返回“手慢了”。这一招在面试时真的很加分它会向面试官证明你考虑过并发问题。这在SSM项目里用MyBatis一个动态update就能实现不需要引入分布式锁也不需要悲观锁性价比很高。完成配送和确认收货也走同样的路径。接单人把订单状态从1改成2发单人把订单状态从2改成3。每次变更前先执行条件更新再插入日志保证日志记录与状态变更一致。日志内容除了actionType还可以把旧状态和新状态都存进去方便后面查历史轨迹。3.4 前端页面与交互细节说实话SSM项目如果强制要求纯JSP写起来确实头大。但很多课程设计和毕设的场景就是SSM项目我建议不要为了炫技强行换Vue前后端分离因为那样会偏离“学SSM”这个目标还要额外处理跨域问题。我当时用的办法是JSP作为视图层页面通过Ajax请求后端接口后端返回JSON前端用jQuery操作DOM渲染数据。页面整体包括登录页、注册页、用户主页、订单发布页、订单列表页、订单详情页、管理后台几个主要页面。不需要做得多花哨但交互细节要到位。我在发布订单页做了一个地址选择器用户可以先维护常用地址发布时直接从列表选取不用手动重复输入。这个功能就是调地址表的数据前端每次加载地址列表后端提供一个/list接口。交互设计不用复杂但能明显提升体验也让你在答辩时有东西可以演示。列表页我实现了按状态筛选和分页。筛选是前端用一个下拉框把状态条件拼到Ajax请求的query参数里。后端先用PageHelper插件做分页再按状态字段筛选。这里提醒一下前端分页和后台分页不要混在一起。前端拿到PageInfo之后渲染的是当前页的list总页数由total字段控制。如果筛选条件变动要重新加载列表而不是停留在原页上。3.5 本地部署与联调本地联调我有一个固定流程先开MySQL验证数据库连接再启动Tomcat看日志是否打印出Spring容器初始化的信息然后用浏览器访问登录页登录成功后打开控制台看网络请求。每一步都确认没问题再进入下一步不要一口气写五个接口然后一次性调试那样出错了根本不知道是哪个环节的问题。Tomcat端口我改成了8080。这里有个小细节如果8080被占用可以在conf/server.xml里改成8081或别的端口但记得前端Ajax请求里的baseURL也要同步改否则会出现页面能打开但数据加载不出来的情况。数据库连接配置我放在jdbc.properties里然后在spring-mybatis.xml里用context:property-placeholder引入。driver用com.mysql.jdbc.Driver如果MySQL8以上要用com.mysql.cj.jdbc.Driver连接URL里还要带serverTimezoneAsia/Shanghai不然日期字段会报时区错误。这些小坑看起来不大实际能卡一下午。4. 典型问题与排查手册这次开发中我记录了不少踩坑过程这里挑最具代表性的几个写成排查实录都是在SSM项目中高频出现、值得收藏的问题。4.1 中文乱码问题像这种平台上地址、备注全是中文一旦乱码基本没法用。乱码有三个层面的来源页面编码、Tomcat请求编码、数据库编码。页面方面我在JSP顶部设置contentType为text/html; charsetutf-8模板本身也用UTF-8保存这能解决大多数显示乱码。请求参数乱码则要在web.xml里配置CharacterEncodingFilter强制所有请求和响应使用UTF-8。数据库层面所有表字符集统一用utf8mb4连接URL加characterEncodingutf8参数。这里有个容易忽略的坑配置文件里数据库连接URL如果没有加characterEncoding即便页面和过滤器都正确存进去的中文入库后会变成问号。而且这个问题通常在我们往数据库插入数据时才暴露排查起来绕一圈。建议一步到位字符集相关的配置全都配齐。4.2 404与请求路径问题开发阶段404高频出现大部分不是代码逻辑问题而是路径写成相对地址或者少了前缀。在SpringMVC的配置里DispatcherServlet映射的是斜杠Controller方法上的RequestMapping和视图解析器前缀后缀都决定最终URL。改完Controller路径后记得观察Tomcat控制台日志里的Mapping信息看是否加载了你写的路径。还有一种情况是JSP页面里的Ajax请求地址写成了相对路径导致当前页面层级变化后请求路径不一致。我的习惯是全站Ajax请求路径都带上下文根比如前后端分离部署inline不行不用代码块包整体但代码片段是允许的。避免“通过本文”. 使用编号.另外不要出现“全文共计”等. 最后检查一遍安全合规. 没问题.现在书写正文。会自我检查每个段落字数。注意不要有 # 主标题。从 ## 开始。文章会比较长要控制质量。开始。 如果你正在找一个既能完整跑通前后端、又能讲清楚设计思路、还能拿来做毕设和面试项目的SSM练手场景校园代送服务平台是我见过最接地气的选择之一。它不像电商系统那样庞大吓人也不像图书管理系统那样千篇一律业务逻辑恰好卡在“够用但又不简单”的位置上。不管你是准备毕业设计还是想巩固SpringSpringMVCMyBatis这套经典组合这个项目都值得认真做一遍。我最近刚把一套基于SSM的校园代送服务平台从零到一完整实现出来从需求梳理、数据库设计、接口划分到前后端联调、部署运行整个过程踩了不少坑。这篇就把当时的设计决策、开发步骤、排错经验全部写出来重点讲清楚为什么这样做、哪里容易出问题、出了问题怎么查。想把这个项目做扎实的同学按这条路线走能省掉大量瞎折腾的时间。1. 为什么选校园代送服务平台作为SSM练手项目很多同学做SSM项目时习惯去找商城、博客、学生管理系统这类模板。这些项目不能说没价值但业务逻辑太简单最后写进简历或者答辩时很难体现你真正理解了三层架构和数据库建模。校园代送平台表面上是“跑腿订单”实际上涵盖用户权限、订单状态流转、抢单派单、支付模拟、信息发布等多个完整模块很适合用来展示工程能力。1.1 业务场景的典型性校园代送的实质是连接“发单用户”和“接单用户”的撮合平台。一个学生没时间去快递点取件就在平台下单写明取件地点、送达地址、期望时间、跑腿费另一个学生有空闲时间看到订单后接单并完成配送最后双方确认完成。这个场景天然包含几组核心关系用户与订单、发单人与其代送需求、接单人与执行过程、管理员与服务监督。这样一来CRUD不再只是抽象增删改查而是被业务串起来用户下单要校验地址信息接单要操作订单状态配送完成要触发结算逻辑。你在做这个项目时每张表每个字段都能找到真实业务语义这种“有目的感的编码”和“为了做功能而做功能”是完全不同的。从技术学习角度看它又不会像大型电商那样引入高并发、分布式事务等超纲概念。SSM这套框架组合承载这种量级的业务刚刚好Spring负责对象管理和事务声明SpringMVC负责请求路由和参数绑定MyBatis负责SQL映射与数据库访问。三者分工清晰初学者容易理解边界又不至于觉得“太简单没东西可写”。1.2 SSM三件套在这个项目里的实际分工很多人学SSM时总喜欢一个问题一个框架孤立地看等到真做项目就懵了。实际在一个业务里三个框架是流水线配合浏览器发来请求Web容器把请求交给SpringMVC的DispatcherServletHandlerMapping找到对应的Controller方法Controller里调用Service层接口Service实现类在Spring容器中是单例Bean需要操作数据库时就注入Mapper接口Mapper接口对应MyBatis的XML文件里面写了具体SQL。放到校园代送项目里一条典型链路长这样用户在小程序或网页上点击“发布代送订单”请求到OrderController的createOrder方法参数被SpringMVC自动绑定到OrderDTO。Controller调用OrderService的createOrder方法这个方法上加了Transactional内部先检查用户信用状态和订单参数再插入订单主表数据同时往订单日志表写入一条操作记录。MyBatis通过动态SQL完成两处数据插入整个事务由Spring统一管理任何一步失败都全部回滚。这套流程跑通后你就真正理解了为什么SSM不是“三个框架的简单相加”。Spring是黏合剂SpringMVC解决Web层MyBatis解决持久层各干各的活通过约定和数据模型连接起来。1.3 功能模块全景我当时梳理功能时没有一上来就画表格写字段而是先把角色列出来再给每个角色配操作权限最后把操作落到页面上。这样不容易漏功能也方便后续写文档和接口清单。系统一共拆成四大模块用户模块、订单模块、管理模块、辅助模块。用户模块注册登录、身份切换、个人主页、信用分展示。学生有两种身份一种是发起代送的发单人一种是接单跑腿的接单人。账号体系早期可以都用同一个用户表加一个user_type字段区分登录后根据类型加载不同操作菜单。订单模块发布订单、订单列表、订单详情、抢单/取消/确认送达。这是整个项目的心脏。订单状态维护我是用状态字段来控制的初始状态是待接单有人接单后变配送中发单人确认后变已完成取消订单要区分是发单人取消还是接单后取消不同情况的退费逻辑不一样。管理模块管理员登录、用户管理、订单审查、数据概览。管理员不参与具体配送主要看平台数据、处理异常订单、封禁违规用户。辅助模块公告管理、地址簿、消息通知。这些功能可以撑起列表查询和富文本框操作也给项目增加丰富度。这样一套模块下来既有SpringMVC的Controller和过滤器使用场景也有MyBatis复杂的多表关联查询和动态SQL场景还有Spring声明式事务的典型应用场景。无论做课题答辩还是面试讲项目都有足够的“料”可以讲。2. 系统设计从需求到数据模型业务梳理完之后下一步是设计。在这个阶段偷懒后面写代码一定返工。我一般先做角色权限矩阵再定核心流程最后才落数据库表。顺序反了容易出现“表建好了但业务跑不通”的尴尬局面。2.1 角色与核心业务流程先看角色。校园代送平台有发单人、接单人、管理员三种角色。发单人可以发布代送需求、查看自己订单、确认收货、评价接单人。接单人最核心的权限是浏览待接单的订单池、抢单、执行配送。管理员负责平台侧的审核与监管。再看流程。整个项目的业务主流程是一条状态机驱动的单链发单人填写代送信息保存后生成待接单订单。待接单订单进入公共订单池任何接单人都能看到并抢单。接单人抢单成功订单变成配送中同时锁定接单人防止被重复抢。接单人完成配送后可以标记为待确认也可以等待系统超时自动确认。发单人确认收货后订单变成已完成平台记录完成时间接单人获得跑腿费收入。状态机这一点我强烈建议你在设计数据库之前就画清楚。因为订单状态字段到底设几种枚举哪些操作合法、哪些非法全部由状态机决定。我当时就吃过亏一开始给订单只设计了待接单、进行中、已完成三个状态后来发现无法区分“发单人下单后正在选择接单人”和“接单人配送中”导致查询列表时逻辑混乱不得不回头加字段改代码。2.2 数据库设计的核心表与要点数据库设计是这个项目最值得讲的部分。我最终用了六张核心表每张表都有明确的职责边界。因为采用的是MySQL字符集我统一用utf8mb4排序规则用utf8mb4_general_ci这样能正确存储中文和特殊字符避免乱码问题。用户表(user)id, username, password, nickname, phone, user_type, credit_score, status, create_time。密码必须加密存储一般做法是加盐后做SHA-256散列。如果你不想在答辩时被问住建议不要自己造加密算法直接讲清楚加盐原理就行。user_type字段用tinyint0表示发单人1表示接单人2表示管理员。credit_score信用分初始设为100每次违规扣分扣到低于60分禁止发单。订单表(orders)id, order_no, publisher_id, receiver_id, pickup_location, deliver_location, expected_time, fee, status, remark, create_time, finish_time。order_no我用时间戳加随机数生成保证唯一性。status字段对应状态机中的不同状态0待接单1配送中2待确认3已完成4已取消。receiver_id在订单刚创建时为空抢单成功后更新。fee字段表示跑腿费属于逻辑敏感字段只能在后端计算和更新不能由前端任意传入。地址表(address)id, user_id, contact_name, contact_phone, location, is_default。这是为了提升发单体验设计的。用户可以直接从常用地址里选不用每次手输。很多同学可能觉得这个表可有可无但我建议保留因为这正好演示了MyBatis里的嵌套查询场景。订单日志表(order_log)id, order_id, operator_id, action_type, action_desc, create_time。这张表非常有用记录每一次状态变更。后面排查问题、写展示用例、回答面试官“如何追踪订单状态”这个问题时全靠它。我当时该表被问到的次数远超订单主表。公告表(notice)、管理员表(admin)这两张表逻辑比较简单主要给管理端做列表展示。其中公告表可以直接用MyBatis的PageHelper分页插件做分页查询顺便演示一下分页通用插件的用法。设计这些表时我会强调一个原则能冗余就冗余能标识就标识。很多学生做设计时舍不得冗余喜欢把所有字段都拆成关联表最后每次查询都要join四五个表性能差不说代码也不好写。比如订单表里我特意存了publisher_id和receiver_id两个用户外键列表展示时一次单表查询就能拿到两个ID再配合用户表的二次查询速度和代码可读性都更好。2.3 接口设计原则接口设计直接决定前后端联调是否顺畅。如果前后端都是你自己写更要提前约定好统一返回格式不然前端拿到的数据结构一会儿是Map一会儿是List调试会抓狂。我用的统一返回对象是一个Result类包含三个字段code、message、data。code200表示成功code500表示业务异常code401表示未登录或权限不足。所有Controller方法返回的都是Result对象SpringMVC通过ResponseBody把对象转成JSON前端再统一解析。这个模式现在很常见但在SSM项目里自己动手封装一遍能加深对JSON序列化和HTTP状态码的理解。接口路径按模块划分比如/api/user/register、/api/user/login、/api/user/info/api/order/create、/api/order/list、/api/order/detail、/api/order/accept、/api/order/finish、/api/order/cancel/api/admin/userlist、/api/admin/orderlist接口之间不要交叉保持单一职责。比如取消订单不要在删除接口里顺带把订单状态改了而是单独走cancel接口业务逻辑彼此独立。3. 手把手实现从搭建骨架到跑通流程设计阶段完成后立刻进入编码。这里我按实际开发的顺序来讲不是按照代码层级从Controller写到Mapper而是按“能跑起来”的优先级组织先搭工程再做认证再做核心业务最后补前端和联调。这样整个过程始终能看到阶段性成果不至于写了三天还看不到界面。3.1 环境与工程结构工具版本建议直接用经典稳定组合JDK1.8、Tomcat8.5、Maven3.6、MySQL5.7或8.0。太高版本反而容易遇到兼容问题。IDE用IDEA社区版也能跑但付费版对Tomcat和数据库插件的支持更顺手。创建一个Maven工程packaging是war。如果你的IDE没有自带Tomcat运行插件也可以直接把工程打包war丢进Tomcat的webapps目录启动bin目录下的startup.bat访问。我建议开发阶段直接用IDE内置Tomcat方便热部署和断点调试。工程目录沿用标准结构com.example.campusdelivery.controller控制层com.example.campusdelivery.service以及impl业务层com.example.campusdelivery.mapperMyBatis的Mapper层com.example.campusdelivery.entity实体类com.example.campusdelivery.interceptor拦截器com.example.campusdelivery.common通用类resources目录下放mybatis-config.xml、spring-mvc.xml、spring-mybatis.xml以及mapperXMLwebapp目录下放静态页面这个结构不是随便分的记住一条原则调用方向必须是自上而下Controller不允许直接操作SqlSessionService不允许直接操作HttpServletRequest。如果发现代码里出现跨层调用说明设计有问题赶紧拆开。这样做的好处是后续AOP切面、事务管理、单元测试都能顺利搞定。3.2 登录认证的实现登录认证我用的方案是拦截器加Session。用户登录成功后把User对象放进Session写一个LoginInterceptor在preHandle方法里检查当前请求路径是否在放行列表里。请求路径包含/api/user/register、/api/user/login的放行其他路径全部校验Session里是否存在user对象。如果不存在返回统一消息code401。很多学生用的方案是每个Controller方法里手动判断Session这样做第一个缺点是代码重复第二个缺点是很容易漏判。拦截器方案把登录控制收敛到一处新增接口只要没有意外暴露默认都要求登录。判断路径时可以简单判断是否包含“/admin/”管理端用另一个AdminInterceptor做权限校验。注意不要试图在登录接口里用拦截器否则会出现“登录时还没登录”的逻辑死锁。密码部分我加密后存入数据库我用的加盐方式是salt字段加密码拼接后进行SHA-256散列。注册时生成随机盐登录时先根据用户名查盐和密文再把用户输入的密码加盐散列与库中密文对比。这样即便数据库泄露密码也不是明文。Session会话超时时间需要单独配置。Tomcat默认是30分钟如果用户操作到一半去吃饭回来发现会话失效被踢回登录页体验很差。我在web.xml里设置了会话超时时间为60分钟同时前端每次请求在Ajax的回调里对code401做统一跳转所以实际体验是当前接口返回未登录前端跳去登录页而不是整个页面白屏。3.3 订单状态机与业务闭环订单模块是整个项目的核心我单独说说实现思路。创建订单时Controller接收前端传来的pickupLocation、deliverLocation、expectedTime、fee等字段转成OrderDTO调用OrderService.createOrder。Service里做几件必做事情校验参数非空、校验用户状态正常、生成orderNo、插入订单、插入日志。整个过程跑在一个Transactional事务里任何一个环节失败都回滚。抢单接口是重点。当接单人点击抢单时需要做两件事先把订单状态从0改成1再把receiverId设置为当前用户。这里有一个并发安全性问题如果两个接单人同时抢同一单怎么办只用简单的updateById加状态判断可能会出错我用的是原子更新UPDATE orders SET status 1, receiver_id #{receiverId} WHERE id #{orderId} AND status 0然后检查受影响行数。如果受影响行数为1说明抢单成功如果为0说明订单已经被别人抢走返回“手慢了”。这一招在面试时真的很加分因为它证明你考虑过并发问题。这在SSM项目里用MyBatis一个动态update就能实现不需要引入分布式锁也不阻塞其他订单的读取操作性价比很高。完成配送和确认收货也走同样的路径。接单人把订单状态从1改成2发单人把订单状态从2改成3。每次变更前先执行条件更新再插入日志保证日志记录与状态变更一致。日志内容除了actionType还可以把旧状态和新状态都存进去方便后面查历史轨迹。3.4 前端页面与交互细节说实话SSM项目如果强制要求纯JSP写起来确实头大。但很多课程设计和毕设的场景就是SSM项目我建议不要为了炫技强行换Vue前后端分离因为那样会偏离“学SSM”这个目标还要额外处理跨域问题。我当时用的办法是JSP作为视图层页面通过Ajax请求后端接口后端返回JSON前端用jQuery操作DOM渲染数据。页面整体包括登录页、注册页、用户主页、订单发布页、订单列表页、订单详情页、管理后台几个主要页面。不需要做得多花哨但交互细节要到位。我在发布订单页做了一个地址选择器用户可以先维护常用地址发布时直接从列表选取不用手动重复输入。这个功能就是调地址表的数据前端每次加载地址列表后端提供一个/list接口。交互设计不用复杂但能明显提升体验也让你在答辩时有东西可以演示。列表页我实现了按状态筛选和分页。筛选是前端用一个下拉框把状态条件拼到Ajax请求的query参数里。后端先用PageHelper插件做分页再按状态字段筛选。这里提醒一下前端分页和后台分页不要混在一起。前端拿到PageInfo之后渲染的是当前页的list总页数由total字段控制。如果筛选条件变动要重新加载列表而不是停留在原页上。3.5 本地运行与联调本地联调我有一个固定流程先开MySQL验证数据库连接再启动Tomcat看日志是否打印出Spring容器初始化的信息然后用浏览器访问登录页登录成功后打开控制台看网络请求。每一步都确认没问题再进入下一步不要一口气写五个接口然后一次性调试那样出错了根本不知道是哪个环节的问题。Tomcat端口我改成8080。这里有个小细节如果8080被占用可以在conf/server.xml里改成8081或别的端口但记得前端Ajax请求里的baseURL也要同步改否则会出现页面能打开但数据加载不出来的情况。数据库连接配置我放在jdbc.properties里然后在spring-mybatis.xml里用context:property-placeholder引入。driver用com.mysql.jdbc.Driver如果MySQL8以上要用com.mysql.cj.jdbc.Driver连接URL里还要带serverTimezoneAsia/Shanghai不然日期字段会报时区错误。这些小坑看起来不大实际能卡一下午。4. 典型问题与排查手册这次开发中我记录了不少踩坑过程这里挑最具代表性的几个写成排查实录都是在SSM项目中高频出现、收藏价值很高的经验。4.1 中文乱码问题像这种平台上地址、备注全是中文一旦乱码基本没法用。乱码有三个层面的来源页面编码、Tomcat请求编码、数据库编码。页面方面我在JSP顶部设置contentType为text/html; charsetutf-8模板本身也用UTF-8保存这能解决大多数显示乱码。请求参数乱码则要在web.xml里配置CharacterEncodingFilter强制所有请求和响应使用UTF-8。数据库层面所有表字符集统一用utf8mb4连接URL加characterEncodingutf8参数。这里有个容易忽略的坑配置文件里数据库连接URL如果没有加characterEncoding即便页面和过滤器都正确存进去的中文入库后会变成问号。而且这个问题通常在我们往数据库插入数据时才暴露排查起来绕一圈。建议一步到位字符集相关的配置全都配齐再开始开发。4.2 404与请求路径问题开发阶段404高频出现大部分不是代码逻辑问题而是路径写成相对地址或者少了前缀。在SpringMVC的配置里DispatcherServlet映射的是斜杠Controller方法上的RequestMapping和视图解析器前缀后缀都决定最终URL。改完Controller路径后记得观察Tomcat控制台日志里的Mapping信息看是否加载了你写的路径。还有一种情况是JSP页面里的Ajax请求地址写成了相对路径导致当前页面层级变化后请求路径不一致。我的习惯是全站Ajax请求路径都带上下文根比如${pageContext.request.contextPath}/api/order/list。上下文根就是工程名如果你把工程改名所有前端请求拼接的基础路径也要同步调整。否则前端访问其他页面没问题一刷新详情页就404排查很久才发现是路径少了一个斜杠。4.3 数据库连接与事务问题数据库连接池我用的Druid配了初始连接数5、最大连接数20连接URL里设置useUnicodetrue。这个配置要放在spring-mybatis.xml的dataSource里MyBatis的SqlSessionFactory会引用这个数据源。常见问题有两个一是连接池配置写错导致启动报错无法注入二是手抖把用户名密码写错报Access denied for user这时先单独用数据库客户端测一下凭证再回看配置文件。事务问题的经典场景是Service方法内部调用另一个Service方法。如果内部方法自己标注了Transactional而外部方法没有开事务内部方法的回滚只能作用在自己的调用范围内无法覆盖外部的其他数据库操作。更安全的做法是事务边界尽量放在对外的服务方法入口上。校园代送平台里抢单并写日志这段逻辑就应该整体在一个事务下如果拆到两个Service方法分别管理事务就可能出现“订单状态改了但日志没记”或者反过来数据一致性很难保证。4.4 MyBatis动态SQL的易错点MyBatis的XML文件里写动态SQL最常见的错误是if标签判断参数时用了错误的条件。比如状态筛选前端传了status0但如果你在if里判断status ! nullMyBatis会自动拆包装箱这里还好但如果判断的是status ! 遇到Integer类型0时会出问题因为MyBatis的OGNL表达式在比较时可能把空字符串和数字混淆导致条件不生效。解决方法是统一约定前端不传的状态字段要么不放在请求里要么传null后端在实体类里用Integer接收判断时只用status ! null和status 0这种明确写法。另一个常见问题是XML文件里的小于号需要转义。在写时间范围查询时如果直接写XML解析器会报错。必须写成lt;或者用CDATA标记。我第一次写超时订单查询时就栽在这上面日志里一直提示元素类型必须匹配后来才发现是这里少了个转义。5. 把项目做成能展示的作品开发完成只是第一步真正让这个项目“拿得出手”还需要在演示准备、资料整理、讲解思路上面下功夫。很多同学代码写得不错但一上场演示就手忙脚乱或者被面试官一问就说不清设计考虑非常可惜。5.1 演示数据与演示脚本在答辩或测试阶段最好准备一套完整的演示数据而不是临时随便插几条。我的做法是写一个data.sql文件里面包含三个演示用户一个发单人账号、一个接单人账号、一个管理员账号。订单数据也按照状态机不同阶段各准备几条让每个功能按钮都有对应可视化结果。演示脚本是更重要的东西。把操作路径固定下来按顺序点这样不会紧张忘词。我习惯的演示顺序是注册新用户、维护一个常用地址、以发单人身份发布代送订单、切到接单人身份抢单、标记完成配送、切回发单人确认收货、去订单列表看状态变化、最后登录管理员账号查看订单总览。每一步配合一句话讲解比如“现在可以看到订单状态从待接单变成配送中因为抢单时我们用了一条条件更新确保同一订单只能被抢一次”。这里需要提前准备一份简洁的数据库说明文档写清楚每张表的作用和关键字段。不用多长但要能让你在回答“为什么订单表里要冗余userId”这种问题时拿出依据。5.2 展示要点与答辩思路答辩或讲项目时常见误区是只讲我用了Spring、SpringMVC、MyBatis然后就开始读日志。实际上对方更想听的是你对业务的理解和关键设计取舍。我建议准备三个“小专题”第一个专题是业务状态机。画一个订单状态迁移说明讲清楚为什么订单会有5个状态而不是3个以及状态变化如何保证数据一致性。这个专题能同时展示你的业务分析能力和事务意识。第二个专题是登录认证链路。讲清楚拦截器如何工作、Session如何维护、密码如何加盐散列。这是面试官大概率追问的点提前准备好回答比临场发挥稳得多。第三个专题是并发抢单设计。重点讲那条条件更新SQL说明为什么它比“先查询再更新”更安全。这个问题一旦讲透面试官对你的好感度会明显上升因为它直接体现你对多线程和数据一致性的理解哪怕你没有实际接触过大型并发系统。5.3 项目复盘与后续扩展做得差不多后我强烈建议做一次代码和设计的复盘。不要急着开始下一个项目先把已经写过的代码通读一遍看看有没有可以重构成更清晰的接口有没有在Service里塞了太多Controller逻辑有没有重复代码可以抽取。这个过程本身提升非常大。如果想继续扩展可以往几个方向加内容。比如增加基于Excel的订单导出功能用SpringMVC的文件下载方式实现比如接入一个简单支付模拟把订单费用流转做成两条流水记录再比如把Web端的接口抽出来配一个移动端小程序。每个扩展方向都能带出新的技术点且仍然围绕原有业务不会破坏整体结构。我在实际做这个项目时最大的体会是不要总想着源码哪里看不懂、哪里背下来就好而是要把自己当成平台的开发者去思考每一个按钮背后需要哪些数据、每一次状态变化需要哪些约束。一旦你开始用这种思路去写代码SSM就不再是一堆配置文件的堆砌而成为一套顺手的工具。现在这套校园代送平台的完整过程我已经复盘到文档里配合设计说明、SQL脚本和演示数据就是希望后面做这个题目的同学能少走弯路把时间花在理解业务和优化设计上而不是浪费在配置报错和路径404上。最后再分享一个小技巧开发过程中把每次改动记录下来不必写多正式只要能回忆起“我为什么这样改”“当时报的是什么错”就行。这些小记录最后整理成一篇开发笔记比任何现成都保存的文档都有说服力因为那是你真实走过的路。