
简介本资源是一套高分Java毕业设计实战项目面向计算机专业本科生及Java初学者聚焦校园闲置物品交易场景提供从开发到答辩的全流程交付物。项目基于SSMSpringSpringMVCMyBatis主流框架构建完整实现用户管理、商品发布/浏览/搜索、订单处理等核心功能并集成数据库初始化脚本idleitemtradingdb.sql、论文、PPT答辩稿、详细使用说明文档及Windows 10/11环境下的部署教程开箱即用。压缩包共1489个文件含138个Java业务逻辑文件、149个JSP页面、165个CSS与398个JS前端资源、216个GIF/183个PNG等静态资源以及SQL、XML、HTML、字体文件等总大小39.56MB结构清晰、模块分明便于学习MVC分层设计与Web全栈开发实践。目前已有42人下载学习配套微信技术支持与调试记录可直接用于毕业设计选题、课程设计或期末作业参考具备强实用性与教学示范价值。1. 这不是又一个“能跑就行”的SSM毕设它把校园闲置交易的边界问题全摊开了讲你见过多少个标着“SSM校园二手平台”的毕业设计压缩包点开一看登录页能进商品列表空着后台管理打不开数据库脚本执行报错三连——最后发现是 MyBatis 的resultMap拼错了字段名或者 SpringMVC 的RequestMapping路径漏了/admin前缀。这个项目不一样它不是“能跑”而是在 Windows 10/11 环境下实测可部署、可注册、可发闲置、可下单、可查订单全流程闭环答辩拿了 98 分导师手写批注“逻辑完整、安全考虑到位、文档颗粒度细”。它解决的不是“怎么搭个 SSM 框架”而是“学生用手机拍张旧书照片上传后如何让同校同学真能搜到、联系上、约在快递柜交接、不暴露手机号、不被黄牛爬取邮箱”这一整条链路上的真实约束。适合两类人一是正卡在毕设部署环节、反复改web.xml和spring-mvc.xml却始终 404 的大四同学二是想拿它当教学案例、给学生拆解“从数据库建模到 XSS 防御落地”的某高校实训导师。它不炫技但每处代码都带着血泪经验比如为什么用户表里phone字段加了Pattern却还在 Controller 层做二次校验为什么搜索功能没用 Elasticsearch 而坚持用 MySQLLIKE 全文索引往下看我们一节一节把它拆透。2. 从 idleitemtradingdb.sql 到 Tomcat 启动成功五步部署链与每个环节的“必须检查项”这个项目最硬的底气是它把“下载即用”四个字落到了文件级。不是靠一句“配置好 JDK 和 Tomcat 就行”而是把每个环境变量、每个路径依赖、每个 XML 配置的坑位都标清楚了。下面这五步是我自己在三台不同配置的 Win11 笔记本上反复验证过的最小可行路径跳过任意一步后续都会卡在某个玄学报错里。2.1 数据库初始化别急着 run sql先看这三处字段定义idleitemtradingdb.sql不是纯 DDL 脚本它混入了INSERT INTO user的测试数据且关键字段做了业务级约束。直接双击运行会失败必须用命令行或客户端分步执行。重点检查以下三处用 MySQL 8.0 测试通过-- 【必须检查】1. user 表的 phone 字段类型是 VARCHAR(11)但插入值带空格或横线会触发 MyBatis 插入失败 -- 正确示例脚本中已修正 INSERT INTO user VALUES (1,张三,13812345678,zhangsanschool.edu.cn,123456,1,2023-01-01 10:00:00); -- 【必须检查】2. item 表的 status 字段是 TINYINT(1)取值范围 0/1/2对应“待审核/已上架/已下架” -- 若手动 INSERT 时写了 pending 字符串MySQL 会静默转成 0但前端 JS 判断会失效 -- 【必须检查】3. order 表的 create_time 是 DATETIME 类型但脚本里用了 NOW() 函数 -- 如果你的 MySQL 时区不是 SYSTEM会导致订单时间比系统时间快/慢 8 小时影响“24 小时内未付款自动取消”逻辑提示执行前务必确认 MySQL 服务已启动且 root 用户密码与src/main/resources/jdbc.properties中一致。若修改过密码必须同步改jdbc.properties里的jdbc.password否则 Tomcat 启动时抛SQLException: Access denied。2.2 JDK 与 Tomcat 版本锁死为什么必须用 JDK 1.8.0_202 Tomcat 8.5.99项目.classpath文件里明确写了classpathentry kindcon pathorg.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/而pom.xml中 MyBatis 版本为3.4.6Spring 为4.3.29.RELEASE—— 这两个版本组合在 JDK 11 下会出现java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException。Tomcat 8.5.99 是官方最后一个支持 JDK 1.8 的 8.5.x 小版本且内置了tomcat-jdbc.jar与项目druid-1.1.10.jar兼容性最佳。实测对比环境组合启动结果关键报错JDK 11 Tomcat 9❌ 启动失败Failed to instantiate [org.springframework.web.servlet.HandlerMapping]JDK 1.8.0_181 Tomcat 8.5.30⚠️ 登录页 CSS 错乱bootstrap.min.css加载 404因 Tomcat 8.5.30 的 default servlet 静态资源映射有 bugJDK 1.8.0_202 Tomcat 8.5.99✅ 全流程通过—2.3 web.xml 与 spring-mvc.xml 的“双配置锚点”路径映射失效的根源在这里很多同学部署后首页能打开但点击“发布闲置”就 404问题一定出在这两个文件的联动上。web.xml是 Servlet 容器入口spring-mvc.xml是 SpringMVC 的控制器路由中心二者必须严格对齐!-- src/main/webapp/WEB-INF/web.xml -- servlet servlet-namespringmvc/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name !-- 【关键】这里指向的是 classpath 下的配置文件不是绝对路径 -- param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namespringmvc/servlet-name !-- 【关键】url-pattern 必须是 /不能是 *.do 或 /dispatcher -- url-pattern//url-pattern /servlet-mapping!-- src/main/resources/spring-mvc.xml -- !-- 【关键】HandlerMapping 的 order 值必须小于 10否则静态资源拦截器会抢在 Controller 前处理请求 -- bean classorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping property nameorder value1/ /bean !-- 【关键】静态资源放行规则必须显式声明否则 bootstrap.css 等全 404 -- mvc:resources mapping/static/** location/static/ / mvc:resources mapping/upload/** location/upload/ /注意/static/目录对应src/main/webapp/static//upload/对应src/main/webapp/upload/。项目默认上传路径是D:/upload/若你的 D 盘不存在需在src/main/java/com/xxx/controller/ItemController.java第 87 行手动改为C:/upload/否则发布商品时图片上传会抛java.io.FileNotFoundException。2.4 Maven 依赖树修剪删掉这三个 jar否则登录页白屏pom.xml中存在三个冗余依赖它们与 SSM 主版本冲突导致 Thymeleaf 模板引擎无法解析th:if${session.user ! null}这类表达式!-- 【必须删除】以下三项在本项目中完全无用且引发 EL 表达式解析失败 -- dependency groupIdorg.thymeleaf/groupId artifactIdthymeleaf-spring5/artifactId version3.0.12.RELEASE/version /dependency dependency groupIdjavax.el/groupId artifactIdjavax.el-api/artifactId version3.0.1-b06/version /dependency dependency groupIdorg.glassfish.web/groupId artifactIdel-impl/artifactId version2.2/version /dependency删掉后mvn clean compile再刷新项目登录页的欢迎语你好${session.user.username}才会正常渲染。这是我在某次调试中抓包发现的浏览器 Network 面板里login.html返回状态 200但 Response Body 里所有th:*标签原样输出说明模板引擎根本没工作。2.5 Tomcat 启动日志的“三秒黄金判断法”看懂这三行你就知道部署成了没不要等整个日志刷完盯住控制台最后 20 行只看这三行是否连续出现INFO : org.springframework.web.servlet.DispatcherServlet - FrameworkServlet springmvc: initialization completed in 1245 ms INFO : com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited INFO : org.apache.catalina.startup.Catalina - Server startup in 8765 ms第一行DispatcherServlet initialization completed出现代表 SpringMVC 控制器加载成功Controller类已注册第二行DruidDataSource inited出现代表数据库连接池创建成功jdbc.properties配置无误第三行Server startup in XXX ms出现且耗时 12000ms代表 Tomcat 容器启动完成无阻塞线程。如果第一行没出现检查web.xml的contextConfigLocation路径如果第二行没出现检查jdbc.properties的jdbc.url是否拼错端口号默认3306如果第三行耗时 15000ms大概率是D:/upload/目录权限不足Windows 下需右键目录 → 属性 → 安全 → 编辑 → 添加Users组并勾选“完全控制”。3. 用户注册登录模块的三层防御从表单校验到 Session 管理的落地细节这个模块看着简单但它是整个平台信任链的起点。项目没用 Shiro 或 Spring Security而是用“前端 JS 后端 Controller Session 拦截器”三层手工织网每一层都针对校园场景做了妥协与加固。我们来拆解它怎么防住常见攻击又不让同学填个注册表单就放弃。3.1 前端校验不是为了省服务器压力而是防“手抖党”register.jsp里嵌了三段原生 JS目的很务实避免学生因为邮箱少打一个字母、手机号多输一位点提交后等 3 秒才弹出红色提示。这种体验在校园网环境下尤其致命。!-- src/main/webapp/register.jsp -- input typetext idphone namephone onblurcheckPhone() / script function checkPhone() { const phone document.getElementById(phone).value; // 【校园特供】只允许 11 位纯数字且必须以 13/15/17/18 开头 const reg /^1[3-9]\d{9}$/; if (!reg.test(phone)) { alert(手机号格式错误请输入11位校园常用号码如13812345678); document.getElementById(phone).focus(); return false; } return true; } /script注意这个正则没写^1[3-9]\d{9}$的g标志因为test()方法不需要全局匹配onblur而非onchange确保鼠标离开输入框就校验不依赖回车键。3.2 后端 Controller 校验为什么Valid注解和手动if并存UserController.java的register方法同时用了两种校验方式RequestMapping(value /register, method RequestMethod.POST) public String register(Valid ModelAttribute User user, BindingResult result, Model model) { // 【第一层】Valid 触发实体类上的 NotBlank/Email 注解 if (result.hasErrors()) { model.addAttribute(error, 请检查邮箱或密码格式); return register; } // 【第二层】手动查重同一校园邮箱只能注册一个账号 User existingUser userService.findByEmail(user.getEmail()); if (existingUser ! null) { model.addAttribute(error, 该邮箱已被注册请使用校园邮箱如 zhangsanschool.edu.cn); return register; } // 【第三层】密码加密用 MD5 加盐盐值固定为 campus2023 user.setPassword(DigestUtils.md5Hex(user.getPassword() campus2023)); userService.save(user); return redirect:/login.jsp; }为什么不用Size(min6)限制密码长度因为测试发现太多同学会设123456这种弱口令Size只能防长度防不住强度。所以项目在UserService.java的save方法里加了硬规则// src/main/java/com/xxx/service/UserService.java public void save(User user) { // 【血泪经验】MD5 加盐后密码长度固定为 32但原始密码必须含数字字母 if (!user.getPassword().matches(^(?.*[0-9])(?.*[a-zA-Z]).{6,16}$)) { throw new RuntimeException(密码必须包含数字和字母长度6-16位); } userMapper.insert(user); }3.3 Session 拦截器不是所有页面都需要登录但“发布闲置”必须卡死项目没用web.xml的security-constraint而是自定义了LoginInterceptor精准控制哪些 URL 必须登录// src/main/java/com/xxx/interceptor/LoginInterceptor.java public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); User user (User) session.getAttribute(user); String uri request.getRequestURI(); // 【精准放行】首页、登录页、注册页、商品列表页允许游客访问 if (uri.contains(/index) || uri.contains(/login) || uri.contains(/register) || uri.contains(/item/list)) { return true; } // 【精准拦截】所有 /user/ /item/publish /order/ 开头的 URL 必须登录 if (uri.startsWith(/user/) || uri.startsWith(/item/publish) || uri.startsWith(/order/)) { if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return false; } } return true; } }提示拦截器在spring-mvc.xml中注册时mvc:interceptors的order值必须设为1否则会被静态资源拦截器覆盖导致/static/js/common.js也被重定向到登录页。3.4 登录态维持为什么 Session 失效时间设为 30 分钟而不是 2 小时web.xml中配置了session-config session-timeout30/session-timeout /session-config这不是拍脑袋定的。我们模拟了真实场景学生在宿舍用笔记本登录去教室上课关盖休眠2 小时后回来发现要重新登录——这没问题但如果他在图书馆用公共电脑登录30 分钟不操作就自动登出能防止下一个人直接看到他的“我的订单”。更关键的是LoginInterceptor在每次请求时会刷新 Session 最后访问时间Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HttpSession session request.getSession(false); if (session ! null) { // 【隐式续期】只要用户在 30 分钟内有任意请求Session 就自动延长 session.setMaxInactiveInterval(30 * 60); } }所以实际效果是“30 分钟无操作”才会失效而不是“登录后 30 分钟必登出”。4. 商品发布与搜索模块的性能取舍为什么不用 Redis 缓存却坚持用 MySQL 全文索引校园闲置平台的流量特征很清晰峰值在开学季和毕业季平时日活 500但搜索行为高度集中——“教材”“考研资料”“自行车”“台灯”这几个词占了 70% 的查询量。项目没上 Redis而是把 MySQL 的FULLTEXT索引用到了极致。这不是技术保守而是算过账的务实选择。4.1 商品表item的全文索引设计三个字段的权重分配item表结构中title、description、category三个字段被联合建了全文索引ALTER TABLE item ADD FULLTEXT(title, description, category);但搜索时不是简单MATCH() AGAINST()而是用布尔模式 权重系数让标题匹配优先级最高// src/main/java/com/xxx/dao/ItemDao.java Select(SELECT * FROM item WHERE MATCH(title, description, category) AGAINST(#{keyword} IN BOOLEAN MODE) ORDER BY MATCH(title, description, category) AGAINST(#{keyword} IN BOOLEAN MODE) * 3 IF(category LIKE CONCAT(%, #{keyword}, %), 2, 0) DESC LIMIT #{offset}, #{limit}) ListItem searchItems(Param(keyword) String keyword, Param(offset) int offset, Param(limit) int limit);MATCH(...) AGAINST(...)默认返回相关度分数乘以 3 是为了让标题匹配主导排序IF(category LIKE ...)是兜底策略如果搜索词恰好是分类名如搜“教材”而category教材额外加 2 分确保分类页不被淹没LIMIT #{offset}, #{limit}支持分页offset由前端传入默认 0。注意MySQL 全文索引对中文支持有限所以项目要求用户发布时title必须含至少两个汉字如“二手《高等数学》教材”description里要重复关键词三次如“出售《高等数学》教材《高等数学》教材九成新《高等数学》教材附赠笔记”。这是在没上 ES 前用人力换性能的无奈但有效方案。4.2 图片上传的“本地化妥协”为什么不用七牛云或阿里 OSSItemController.java的publish方法里图片保存路径硬编码为D:/upload/// src/main/java/com/xxx/controller/ItemController.java String uploadPath D:/upload/; File uploadDir new File(uploadPath); if (!uploadDir.exists()) uploadDir.mkdirs(); String fileName UUID.randomUUID().toString() _ file.getOriginalFilename(); file.transferTo(new File(uploadPath fileName)); item.setImgUrl(/upload/ fileName); // 前端 img src 用此路径为什么不走云存储因为毕设评审老师最反感“调第三方 API 却没提供 accessKey”。本地路径虽然糙但满足两个硬需求一是答辩时能现场演示“我上传一张图你们立刻在商品页看到”二是idleitemtradingdb.sql里item.img_url字段是VARCHAR(255)存的就是/upload/xxx.jpg这种相对路径和前端img标签的src完全一致零适配成本。4.3 搜索结果页的“防抖优化”不是前端加 debounce而是后端限流ItemController.java的search方法加了简单的请求频率控制private static final MapString, Long SEARCH_IP_LIMIT new ConcurrentHashMap(); RequestMapping(/item/search) public String search(RequestParam String keyword, RequestParam(defaultValue 0) int page, Model model, HttpServletRequest request) { String ip getClientIp(request); long lastTime SEARCH_IP_LIMIT.getOrDefault(ip, 0L); if (System.currentTimeMillis() - lastTime 2000) { // 2秒内只允许一次搜索 model.addAttribute(error, 搜索太频繁请稍后再试); return search_result; } SEARCH_IP_LIMIT.put(ip, System.currentTimeMillis()); // ... 执行搜索逻辑 }getClientIp方法能正确识别 Nginx 反向代理后的 IP避免局域网内所有学生共享同一个限流 key。这个 2 秒阈值是实测定的学生用手机搜“耳机”点一下没结果本能会连点两下2 秒足够覆盖这种手抖又不会影响正常浏览。4.4 商品状态机从“待审核”到“已成交”的五种状态与数据库约束item.status字段是TINYINT(1)但实际用了 5 个值每个值对应明确的业务动作和数据库约束status 值状态名触发条件数据库约束前端按钮0待审核用户发布后status0 AND audit_time IS NULL无按钮仅管理员可见1已上架管理员审核通过status1 AND audit_time IS NOT NULL“立即购买”“联系卖家”2已下架卖家主动下架或超期未售status2 AND offline_time IS NOT NULL“重新上架”仅卖家3已售出买家下单支付成功status3 AND sold_time IS NOT NULL“查看订单”“评价”4已关闭卖家拒绝订单或超时未发货status4 AND close_reason IS NOT NULL“申诉”仅买家关键约束在itemMapper.xml的updateStatus方法里!-- 只允许从 0→1审核通过、1→2下架、1→3售出、3→4关闭 -- update idupdateStatus UPDATE item SET status #{status}, audit_time CASE WHEN #{status} 1 THEN NOW() ELSE audit_time END, offline_time CASE WHEN #{status} 2 THEN NOW() ELSE offline_time END, sold_time CASE WHEN #{status} 3 THEN NOW() ELSE sold_time END, close_reason CASE WHEN #{status} 4 THEN #{closeReason} ELSE close_reason END WHERE id #{id} AND status #{oldStatus} /updateAND status #{oldStatus}这句是灵魂防止并发时 A 线程把状态从 1 改成 3B 线程又把 1 改成 2造成状态错乱。5. 订单与交易安全模块的“非银联方案”如何用纯 Java 实现资金暂管与隐私保护校园闲置交易最敏感的不是钱而是人。学生不愿暴露手机号怕被骚扰老师不愿用私人微信收款怕财务审计风险。这个项目用“虚拟账户短信验证码脱敏展示”三招在不接入任何支付接口的前提下实现了可信交易闭环。5.1 虚拟账户体系为什么订单表order里没有 real_amount 字段order表结构精简到只有 8 个字段其中amount存的是“协商金额”不是支付金额CREATE TABLE order ( id bigint(20) NOT NULL AUTO_INCREMENT, item_id bigint(20) NOT NULL, buyer_id bigint(20) NOT NULL, seller_id bigint(20) NOT NULL, amount decimal(10,2) NOT NULL COMMENT 双方协商金额, status tinyint(1) NOT NULL DEFAULT 0 COMMENT 0待确认 1已确认 2已发货 3已完成 4已关闭, create_time datetime NOT NULL, update_time datetime NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;真正的资金流动发生在线下买家把钱给卖家卖家点击“确认收款”系统只记录status3。amount字段的作用是生成订单号如ORD20230915123456789和计算信用分不参与任何资金结算。这是对校园场景的精准妥协——没人真会为 30 块钱的旧书走支付宝担保交易。5.2 隐私保护的三重脱敏手机号、邮箱、地址的显示逻辑OrderController.java的detail方法返回订单详情时对敏感字段做了分级脱敏// src/main/java/com/xxx/controller/OrderController.java RequestMapping(/order/detail/{id}) public String orderDetail(PathVariable Long id, Model model, HttpSession session) { Order order orderService.findById(id); User buyer userService.findById(order.getBuyerId()); User seller userService.findById(order.getSellerId()); // 【买家视角】只显示卖家手机号前3后4中间用*代替 seller.setPhone(seller.getPhone().substring(0, 3) **** seller.getPhone().substring(7)); // 【卖家视角】只显示买家邮箱用户名部分域名用 * 隐藏 buyer.setEmail(buyer.getEmail().split()[0] ***); // 【双方视角】收货地址只显示“XX校区快递柜”不显示具体柜号 order.setAddress(XX校区快递柜); model.addAttribute(order, order); model.addAttribute(buyer, buyer); model.addAttribute(seller, seller); return order_detail; }注意脱敏逻辑写在 Controller 层而非 Service 层是因为User对象在其他模块如个人中心需要完整信息不能污染领域模型。5.3 短信验证码的“伪发送”机制为什么不用真实短信平台SmsService.java是个空实现类sendCode方法只往控制台打印日志// src/main/java/com/xxx/service/SmsService.java public class SmsService { public void sendCode(String phone, String code) { System.out.println([SMS MOCK] To phone : Your verification code is code); // 【答辩演示专用】真实环境可替换为阿里云 SMS SDK } }但OrderController.java的confirmReceive方法里验证码校验是真实走的RequestMapping(/order/confirm/{id}) public String confirmReceive(PathVariable Long id, RequestParam String code, Model model, HttpSession session) { Order order orderService.findById(id); // 从 Session 里取上次生成的验证码实际项目应存 Redis String sessionCode (String) session.getAttribute(sms_code_ order.getId()); if (!code.equals(sessionCode)) { model.addAttribute(error, 验证码错误); return order_detail; } order.setStatus(3); // 已完成 order.setUpdateTime(new Date()); orderService.update(order); return redirect:/order/list; }session.setAttribute(sms_code_ order.getId(), code)这行代码保证了每个订单的验证码独立不会被其他订单覆盖。答辩时你只要在控制台复制Your verification code is 123456填进去就能完成流程。5.4 交易纠纷的“举证存档”为什么每张商品图都带水印和时间戳ItemController.java的publish方法在保存图片前会用 Java2D 加水印// src/main/java/com/xxx/controller/ItemController.java private void addWatermark(File originalFile, File watermarkedFile) throws IOException { BufferedImage image ImageIO.read(originalFile); Graphics2D g2d image.createGraphics(); g2d.setColor(Color.GRAY); g2d.setFont(new Font(微软雅黑, Font.BOLD, 24)); // 水印文字校园平台 当前时间 商品ID String watermark 校园闲置平台 new SimpleDateFormat(yyyy-MM-dd HH:mm).format(new Date()) ID: item.getId(); g2d.drawString(watermark, 20, 30); g2d.dispose(); ImageIO.write(image, jpg, watermarkedFile); }这个水印不是为了防盗而是为纠纷举证如果买家说“你发的图是假的”卖家可以出示这张带时间戳的图证明“我在 X 月 X 日 X 时发布了此商品”。水印位置固定在左上角字体大小 24灰度色不影响主体图片辨识度但足以在法庭上作为电子证据被采信。6. 从 98 分答辩到可复用工程我把这六个习惯刻进了每次毕设交付这个项目能拿 98 分不是因为代码多炫而是因为从第一天写pom.xml开始我就在对抗学生最常见的六种“毕设失焦症”环境配置失焦、文档描述失焦、安全逻辑失焦、性能预期失焦、答辩演示失焦、未来扩展失焦。现在我把这些对抗过程固化成了每次交付必做的六个动作哪怕只是帮同学改个 Bug我也强制自己走一遍。6.1 动作一部署前必做“三路径校验表”我永远在README.md顶部放一张表里面只有三行但覆盖了 90% 的部署失败原因校验项检查方式合格标准不合格后果JDK 路径echo %JAVA_HOME%输出C:\Program Files\Java\jdk1.8.0_202ClassNotFoundExceptionTomcat 端口netstat -ano | findstr :8080无输出端口空闲启动失败报Address already in useupload 目录dir D:\upload显示0 个文件或1 个文件发布商品时FileNotFoundException这张表不是给人看的是给我自己看的。每次帮别人远程调试我第一句一定是“请按顺序执行这三行命令把结果截图发我”。从那以后我不再听“我配置好了”只认这三行命令的输出。6.2 动作二所有 SQL 脚本加-- [CHECKED]标记idleitemtradingdb.sql里每一句CREATE TABLE后我都加了-- [CHECKED]注释并在旁边写上校验依据CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL, phone varchar(11) NOT NULL, -- [CHECKED] 长度11匹配前端JS正则 email varchar(100) NOT NULL, -- [CHECKED] 长度100兼容 school.edu.cn 邮箱 password varchar(32) NOT NULL, -- [CHECKED] MD5加盐后固定32位 status tinyint(1) NOT NULL DEFAULT 1, -- [CHECKED] 1启用0禁用 create_time datetime NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这个习惯源于一次翻车某次我改了phone字段为VARCHAR(15)忘了改前端 JS 正则结果测试同学用138-1234-5678格式注册成功但登录时后端校验失败折腾了两小时才发现是字段长度和正则不一致。现在只要脚本里没[CHECKED]我就绝不提交。6.3 动作三每个 Controller 方法加ApiOperation与真实请求示例UserController.java的login方法上我写了完整的 Swagger 注释/** * ApiOperation(value 用户登录, notes POST /user/loginbr/ * 请求体br/ * {br/ * \email\: \zhangsanschool.edu.cn\,br/ * \password\: \123456\br/ * }br/ * 成功响应HTTP 200br/ * {br/ * \code\: 200,br/ * \msg\: \登录成功\,br/ * \data\: {br/ * \userId\: 1,br/ * \username\: \张三\br/ * }br/ * }) */ RequestMapping(value /login, method RequestMethod.POST) public Result login(RequestBody User user, HttpSession session) { // ... }这不是为了生成 Swagger UI而是为了把“这个接口到底怎么调”钉死在代码里。答辩时老师问“登录接口参数是什么”我不用翻文档直接打开这个方法CtrlC 就是本文还有配套的精品资源点击获取