
我做校园类项目前后带过不少学弟学妹二手交易这个题目几乎每年都有人选但真正能把源码跑起来、开题写明白、答辩讲清楚的并不多。大部分人的困境不是不会写代码而是不知道这个项目的“边界”在哪——功能堆太多做不完堆太少又显得没内容最后交上去的东西既不像个能用的系统也不像一份合格的毕业设计。这篇就围绕校园二手交易网站这个题目把源码结构、技术选型、数据库设计、核心功能实现、开题报告写法、部署演示这几块掰开揉碎讲一遍从零基础到已经动手写了一半的人都能拿去对照。开题部分我按实际评审关注的逻辑来拆不写那种满篇“具有重要意义”的空话。1. 先把这个项目的真实边界划清楚1.1 校园场景和闲鱼这类平台到底差在哪很多人一上手就说“我要做一个校园版闲鱼”这个定位一出来后面所有的设计都会跑偏。社会化的二手平台解决的是陌生人之间的低频大额交易而校园二手交易的核心特征是高频、小额、强信任、短周期。一本书、一个台灯、一辆毕业甩卖的自行车价格几十块买卖双方大概率住同一栋楼或者同一个校区交易方式是当面交付付款方式很多时候是当面转账甚至现金。这四条特征决定了整个系统的重心根本不在“交易担保”而在“信息撮合效率”和“身份可信”。我见过太多同学照着电商系统的套路去设计做了一整套购物车、优惠券、多规格SKU、物流跟踪、退款仲裁流程结果一个学期过去只完成了商品发布剩下的全是半成品。这就是没把场景吃透的后果。校园场景里购物车基本是伪需求——没人会把三本书加进购物车一起结算因为每件商品的卖家都不一样。物流模块更是完全不需要同城当面交易根本不存在快递环节。把这些砍掉你省下的时间足够把搜索、筛选、消息通知、信用评价这几块做到能拿得出手的程度。另一个经常被忽略的点是周期性。校园二手交易有明显的时间波峰开学季、毕业季、考研季。毕设答辩如果安排在春季正好可以拿毕业季的真实需求当业务背景讲这个细节写进开题报告的“研究背景”里比泛泛而谈“二手交易市场规模逐年增长”要扎实得多。1.2 该做什么、不该做什么列个清单我给几个学弟整理过一份功能取舍清单原则很简单核心链路必须跑通边缘功能可以做但不能影响主流程。必做部分围绕“发布—浏览—联系—成交—评价”这条主线展开。用户注册登录、商品发布含多图上传、分类浏览、关键词搜索、商品详情页、站内留言或联系方式展示、下单/预定、订单状态流转、完成后互评这是最小闭环。少了其中任何一环演示时就会卡壳答辩老师一问“买家怎么联系卖家”你答不上来就很尴尬。加分项是可以做但要控制工作量的部分收藏、浏览历史、举报违规、求购专区、按距离排序需要位置信息成本较高建议只做校区筛选、简易后台管理用户管理、商品审核、举报处理。这些功能的共同特点是独立性强做不完直接关掉入口不影响主线。明确不要做的部分在线支付涉及资质校园项目直接做会被质疑合规性、物流对接、复杂促销、多语言。尤其是支付我的建议是做成“线下当面交易状态标记”系统只记录订单达成不碰资金流。这个取舍一定要在开题报告的“研究内容”里写清楚反而是加分项——说明你懂业务边界。2. 技术选型别把简单项目做复杂2.1 三套主流方案的实际对比学生项目最常见的坑就是技术栈选择被“流行度”绑架。我见过一个二手交易网站用户量预期不超过两百硬是拆成了六个微服务最后部署的时候光内存就不够答辩前一天还在调服务注册中心。这种失败完全是自己给自己找的。方案技术组合开发周期单人适合人群主要风险方案ASpring Boot MyBatis-Plus MySQL Vue34~6周有 Java 基础想做主流后端前后端分离调试耗时跨域配置容易卡方案BSSMSpringSpringMVCMyBatis JSP/Thymeleaf5~7周课程学的是老技术栈模板渲染调试繁琐页面交互偏弱方案CNode.jsExpress/Koa MySQL 原生前端3~5周熟悉 JS想快点出成果后端工程化能力体现不足答辩时容易被追问我个人的建议是方案A。原因不在于它“先进”而在于它的可解释性最强。答辩时老师问“你的分层是怎么设计的”你能清晰地说出 Controller 负责参数校验和路由、Service 承载业务逻辑和事务、Mapper 负责数据访问这套分层逻辑是教科书级的讲起来毫不费力。方案C虽然开发快但很多评审会认为后端深度不够尤其是当你的题目被归到“计算机科学与技术”而不是“软件工程”的时候。方案B如果是课程强制要求那就没办法但要注意一个问题JSP 在现代浏览器和容器版本上兼容性坑不少用 Thymeleaf 会稳一些而且它和 Spring Boot 集成更顺。2.2 为什么单体架构才是这个项目的正确答案单体架构不是“落后”而是匹配当前规模的最优解。你的并发量预估是几十到几百一台 2 核 4G 的云服务器足够跑起来数据库和应用部署在同一台机器上出了问题看日志一目了然。微服务带来的收益——独立部署、技术异构、弹性伸缩——在这个规模下一条都用不上但成本是实打实的服务间调用要处理超时和重试、链路追踪要额外搭、本地开发要起一堆进程。我在实际项目里的做法是逻辑分层、物理单体代码层面严格按模块划分用户模块、商品模块、订单模块、消息模块每个模块内部再分层但打包出来就是一个 jar。这样既保证了代码可读性又避免了运维复杂度。将来真要拆分因为模块边界清晰改造成本也不高。这个思路可以原封不动写进开题报告的“技术路线”部分是很标准的表述。2.3 中间件的取舍哪些能省哪些不能省Redis 要不要上我的答案是如果要展示登录态和热点商品缓存可以上如果只是为了“显得技术栈丰富”别上。校园项目的会话量很小用 Spring Session 存数据库或者干脆用 JWT 无状态令牌都能解决多引入一个中间件就多一个部署和排查的成本。文件存储反而是一个必须认真对待的点。商品图片是这个系统的核心内容如果直接把图片以二进制存进数据库表会迅速膨胀查询性能肉眼可见地下降。正确做法是图片存磁盘或对象存储数据库只存访问路径。本地开发时存到项目外的目录比如/data/upload/通过 Nginx 或 Spring 的静态资源映射对外提供访问。这个细节在很多学生的代码里是错的答辩时被问到“图片怎么存的”答“存数据库里了”基本就露馅了。还有一个常被忽略的是全局异常处理和参数校验。用RestControllerAdvice统一捕获异常、用Valid做参数校验这两个东西加起来不到一百行代码但能让你的接口在异常输入下不返回一大坨堆栈信息。答辩演示的时候如果因为输入了一个空标题就白屏观感非常差。3. 数据库设计这个项目的真正骨架3.1 核心表结构和字段取舍数据库设计得好后面写代码是顺水推舟设计得烂每加一个功能都要改表。我按实际项目经验给一套可以直接用的结构。用户表要区分普通用户和管理员用角色字段控制权限。手机号做唯一索引因为校园场景里手机号是最主要的联系方式。还要留一个campus字段记录所属校区这个字段在筛选时非常有用。CREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT 加密后密码, nickname VARCHAR(32) NOT NULL, phone VARCHAR(20) UNIQUE COMMENT 手机号用于联系, campus VARCHAR(50) COMMENT 所属校区, avatar VARCHAR(255), credit_score INT DEFAULT 100 COMMENT 信用分初始100, role TINYINT DEFAULT 0 COMMENT 0普通 1管理员, status TINYINT DEFAULT 1 COMMENT 1正常 0封禁, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;商品表是整个系统的核心字段设计上要有几个前瞻性考虑。status不能只做“上架/下架”两态实际业务里至少需要待审核、在售、已预定、已售出、已下架。view_count记录浏览量用于详情页展示和热度排序。off_shelf_time记录下架时间方便做超期自动下架。CREATE TABLE t_goods ( id BIGINT PRIMARY KEY AUTO_INCREMENT, seller_id BIGINT NOT NULL, category_id INT NOT NULL, title VARCHAR(100) NOT NULL, description TEXT, price DECIMAL(10,2) NOT NULL, original_price DECIMAL(10,2) COMMENT 原价用于展示折扣, cover_image VARCHAR(255), images VARCHAR(1000) COMMENT 多图逗号分隔或JSON, campus VARCHAR(50), condition_level TINYINT COMMENT 成色 1全新 2九成新 3有使用痕迹, status TINYINT DEFAULT 1 COMMENT 0待审核 1在售 2已预定 3已售出 4已下架, view_count INT DEFAULT 0, off_shelf_time DATETIME, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_category_status (category_id, status), KEY idx_seller (seller_id), KEY idx_title (title) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;订单表要特别注意一点同一件商品在“已预定”状态下不能被第二个人下单。这个约束靠代码判断会有并发问题正确做法是在数据库层加唯一索引做兜底。CREATE TABLE t_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, goods_id BIGINT NOT NULL, buyer_id BIGINT NOT NULL, seller_id BIGINT NOT NULL, amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 1 COMMENT 1待确认 2已确认 3已完成 4已取消, remark VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, finish_time DATETIME, KEY idx_buyer (buyer_id), KEY idx_seller (seller_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;3.2 几个当时踩过的设计问题多图存储用逗号分隔还是 JSON我一开始用逗号拼接存字符串后来要做“删除其中一张图”的时候发现得写一堆字符串切割逻辑还容易出错。改用 JSON 字段后MySQL 5.7 支持读写都清爽很多。如果你的 MySQL 版本较低那就单独建一张t_goods_image表一图一行是最规范的做法。分类要不要做成两级单级分类足够。校园二手商品的品类本来就不多教材、数码、生活用品、运动器材、代步工具、其他六个大类能覆盖九成场景。做两级分类用户发布时要在两个下拉框里点来点去体验反而变差。真要细分用标签更适合“考研资料”“四六级”“宿舍神器”这种标签比分类灵活得多。信用分怎么算我见过一些毕设里信用分是随机生成的一看就假。比较合理的做法是初始 100 分每完成一笔订单加 2 分上限 150被举报核实扣 20 分低于 80 分限制发布。这套规则简单但在开题报告的“研究内容”里写出来显得你确实想过业务闭环。3.3 索引和数据量预估的细节索引不是越多越好。t_goods表上我加了三个索引分类状态联合索引用于列表页筛选卖家 ID 索引用于“我的发布”标题索引用于搜索。注意标题索引用的是普通 B 树索引LIKE %关键词%这种前后模糊匹配是走不上的这也是为什么很多人说“我加了索引怎么还慢”。数据量不大的情况下几千条商品根本感觉不到差异但如果开题报告里被问到“如何优化搜索性能”你可以回答两条一是数据量小的时候直接全表扫描加LIKE足够二是数据量上来后引入全文索引MySQL 的FULLTEXT或独立的搜索引擎。这个回答说明你知道当前的方案和它的边界在哪比硬套一个 Elasticsearch 强得多。4. 核心功能实现的实操要点4.1 登录与密码处理别在这种地方犯低级错误密码必须加盐哈希存储用 BCrypt 是最省事的选择Spring Security 自带BCryptPasswordEncoder一行代码搞定。绝对不要用 MD5更不要明文。我在帮人看代码时真的见过明文存密码的这个如果被评审发现基本就是一票否决。登录态的选择上前后端分离项目用 JWT 比较顺手。核心逻辑是登录成功返回一个 token前端存到 localStorage之后每个请求在请求头里带上Authorization。后端写一个拦截器统一校验把用户 ID 解析出来放进 ThreadLocal业务代码直接取不用每个方法都传一遍。public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) { String token req.getHeader(Authorization); if (StringUtils.isBlank(token)) { throw new BizException(401, 未登录); } Claims claims JwtUtil.parse(token); UserContext.set(Long.valueOf(claims.get(uid).toString())); return true; } Override public void afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception e) { UserContext.clear(); } }注意afterCompletion里必须清理 ThreadLocal否则线程池复用会导致用户信息串号。这个问题在本地测试时几乎发现不了上线后偶尔出现“看到别人订单”的诡异现象排查起来极其痛苦。我自己就栽过一次后来养成习惯凡是 ThreadLocal 一律配套写remove。4.2 商品发布图片上传是坑最多的地方图片上传的完整链路是前端选文件 → 后端接收 → 校验类型和大小 → 重命名防止重名覆盖和路径穿越→ 存盘 → 返回访问 URL。这五步里每一步都有坑。类型校验不能只看扩展名要读文件头。一个改了后缀的脚本文件如果只按扩展名校验就会被放行。简单做法是用ImageIO.read()尝试读取读不出来就不是图片。大小限制在配置文件里设置Spring Boot 默认单文件 1MB、单请求 10MB商品图一般 2MB 上限比较合适超过的话在application.yml里改spring: servlet: multipart: max-file-size: 5MB max-request-size: 20MB文件名一定要重命名用 UUID 加原扩展名。如果直接用用户上传的文件名一方面会重名覆盖另一方面如果有人上传一个叫../../config.yml的文件可能写到意料之外的目录。多图上传时前端用 FormData 一次带多个文件后端用MultipartFile[]接收循环处理后把路径拼成 JSON 数组存进images字段。这里有个细节如果其中某一张失败应该整体回滚不要让商品创建成功但图片缺一张。4.3 搜索与筛选把用户最常用的路径做顺搜索框的默认行为要设计好。我建议是多字段联合匹配标题或描述里包含关键词就命中。SQL 层面就是WHERE (title LIKE ? OR description LIKE ?) AND status 1。性能不用担心几千条数据下毫秒级返回。筛选条件的组合要用动态 SQL。MyBatis 里用if标签或者用 MyBatis-Plus 的LambdaQueryWrapper链式拼接后者代码更清爽LambdaQueryWrapperGoods wrapper new LambdaQueryWrapper(); wrapper.eq(Goods::getStatus, 1); if (StringUtils.isNotBlank(keyword)) { wrapper.and(w - w.like(Goods::getTitle, keyword).or().like(Goods::getDescription, keyword)); } if (categoryId ! null) wrapper.eq(Goods::getCategoryId, categoryId); if (StringUtils.isNotBlank(campus)) wrapper.eq(Goods::getCampus, campus); if (minPrice ! null) wrapper.ge(Goods::getPrice, minPrice); if (maxPrice ! null) wrapper.le(Goods::getPrice, maxPrice); wrapper.orderByDesc(Goods::getCreateTime);注意排序规则要给用户可选项最新发布、价格从低到高、价格从高到低。默认按发布时间倒序这是二手交易里最符合直觉的排序方式因为新发布的商品往往性价比更高、卖家也更活跃。分页要记得做别一次性把几百条数据全返回。用 MyBatis-Plus 的Page对象配合分页插件两行代码搞定。前端做滚动加载或者页码跳转都行页码更简单也更好演示。4.4 订单状态机这块逻辑乱整个系统就乱订单状态流转必须定义清楚而且要集中管理不能每个接口里各写各的判断。我建议定义一个枚举加一张流转规则表代码里用一个方法统一做状态校验和推进。当前状态允许的操作目标状态触发方待确认卖家确认已确认卖家待确认取消已取消买家/卖家已确认完成交易已完成买卖双方任一确认后由卖家确认已确认取消已取消买家/卖家已完成互相评价已完成双方已取消无--关键点是状态推进要加乐观锁。两个客户端同时点“确认”如果不加控制可能会重复触发。做法是在t_order表加一个version字段更新时带上版本号条件boolean ok orderMapper.update(null, new LambdaUpdateWrapperOrder() .eq(Order::getId, orderId) .eq(Order::getStatus, OrderStatus.WAIT_CONFIRM.getCode()) .set(Order::getStatus, OrderStatus.CONFIRMED.getCode()) .set(Order::getVersion, version 1)) 0; if (!ok) throw new BizException(订单状态已变更请刷新);商品状态要跟订单状态联动。买家下单成功商品立刻从“在售”改成“已预定”避免被别人重复下单订单取消则退回“在售”订单完成则商品改为“已售出”。这个联动逻辑放在 Service 层的方法里加上Transactional保证一致性。我特意提这一点是因为见过太多项目里商品状态和订单状态各走各的最后出现“已售出的商品还能下单”这种明显 bug。4.5 站内消息做轻不做重完整的即时通讯系统对这个项目来说太重了需要长连接、消息存储、已读未读、离线推送。我的建议是做一个简化版的站内信买家在商品详情页点“联系卖家”跳转到一个对话页消息按会话分组存储双方进入页面时轮询拉取新消息三秒一次就够用或者干脆做成留言板形式不需要实时。数据库设计上一张t_message表记录发送方、接收方、商品 ID、内容、是否已读、时间。查询会话列表就是按(from_id, to_id, goods_id)分组取最新一条稍微复杂一点但 SQL 写得出来。如果觉得麻烦直接用“留言”模式——留言展示在商品详情页下方所有人可见这样连会话管理都省了实现成本极低演示效果也不差。我个人的取舍是做留言不做私聊。理由很实在校园二手交易的沟通量本身就不大很多交易在详情页留言两三条就敲定了。省下来的时间投到搜索和筛选上收益更高。5. 开题报告怎么写才不会被退回5.1 开题的核心逻辑证明这件事值得做、你能做开题报告不是走流程的表格它的本质是回答两个问题这个题目有研究价值吗你有能力在给定时间内完成吗。所有章节都是围绕这两点展开的。理解了这个你就知道为什么有些开题会被退回——要么选题太泛“做一个电商平台”要么技术路线含糊“采用先进技术实现”要么进度安排明显做不完。选题背景部分别去堆宏观数据。写校园场景的具体痛点毕业生离校时大量物品无法带走、低年级学生置办生活用品成本高、现有社交群组交易信息刷屏且无法检索、交易双方身份不明缺乏信任基础。这四条每一条都能对应到你系统里的一个功能模块逻辑是闭合的。这种写法比“随着互联网的快速发展”强一百倍。5.2 国内外研究现状怎么写才不像凑字数这一节最容易写成产品介绍大全 —— 罗列一堆平台名称然后说“功能强大”。这种写法评审一眼就能看出你没读过文献。比较靠谱的结构是先讲二手交易模式的研究脉络从分类信息到社区化交易再讲技术层面的研究推荐算法在二手交易中的应用、信用评价机制最后落到“校园垂直场景的研究相对较少”从而引出你的选题。文献要真读几篇。不需要多八到十二篇就够其中至少三到五篇是近三年的期刊论文。知网上搜“二手交易”“校园电商”“信用评价”这些关键词挑几篇核心期刊的读摘要和结论知道人家解决了什么问题、用了什么方法。写作的时候用自己的话概括别整段翻译。这一节能写到八百到一千字整份开题的厚度就有了。5.3 研究内容和技术路线写细一点反而好交差研究内容要按模块列每个模块写清楚做什么、用什么方法、达到什么效果。比如“商品检索模块支持关键词模糊匹配与多条件组合筛选采用 MyBatis 动态 SQL 实现条件拼接通过分类与状态联合索引优化查询效率”。这么一句话信息量比“实现商品搜索功能”大得多而且每一句都是你能兑现的。技术路线用文字加流程图描述注意开题文档里的流程图用 Visio 或 draw.io 画成图片插入不要用代码生成。写清楚开发模型是瀑布还是迭代、每个阶段产出什么。我建议写“迭代式开发”因为实际情况就是你做一块测一块写瀑布反而和事实不符答辩时被追问细节容易露馅。进度安排必须是可执行的。给一份参考阶段时间跨度主要任务交付物需求分析与设计第1-2周调研、用例梳理、库表设计需求文档、ER图环境搭建与框架集成第3周项目骨架、依赖配置、基础工具类可运行的空白工程用户与商品模块第4-5周注册登录、发布、列表、详情主线功能可演示订单与消息模块第6-7周下单流转、留言、评价完整业务闭环后台与优化第8周管理端、异常处理、性能调优可交付版本测试与论文撰写第9-12周功能测试、文档整理、答辩准备论文、演示材料这里有个经验每个阶段的交付物都要是可验证的。“完成用户模块”这种描述没法验收“注册登录流程可用密码加密存储登录态可保持”就是可以当场演示的。答辩老师如果翻到这一页看到的是具体成果而不是模糊承诺印象分完全不同。6. 部署上线与答辩演示的实战细节6.1 服务器准备和基础环境一台 2 核 4G 的云服务器足够。系统装 Ubuntu 或者 CentOS 都行我更习惯 Ubuntu软件源更新方便。需要装的东西JDK 17或者 8看你的 Spring Boot 版本、MySQL 8、Nginx。Node 只在本地打包前端时需要服务器上不用装。MySQL 装完后要做三件事改字符集为 utf8mb4、建独立的业务账号别用 root 连应用、配置允许本地连接。字符集这个问题会在存中文和 emoji 时暴露商品描述里带个表情符号就报错很影响演示。安全组要放开 80 端口Web 访问和 22 端口SSH数据库的 3306 千万别对外开放只允许本机访问。这一点写进论文的“系统部署”章节是加分项说明你有基本的安全意识。6.2 Nginx 配置和前后端分离的部署方式前端打包成静态文件后端打成 jar 包用nohup或者systemd后台运行。Nginx 负责静态资源和反向代理。server { listen 80; server_name _; client_max_body_size 20m; location / { root /var/www/school-market; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /upload/ { alias /data/upload/; expires 7d; } }try_files ... /index.html这行是关键。前端用 history 模式路由时直接访问/goods/123这种路径Nginx 找不到对应文件就返回 404。加上这行回退到index.html让前端路由接管。这个坑我在第一次部署时踩过本地开发一切正常上线后刷新页面就白屏找了两个小时。后端的启动脚本写成systemd服务比nohup稳宕机能自动拉起[Unit] Descriptionschool-market Afternetwork.target [Service] Userwww WorkingDirectory/opt/app ExecStart/usr/bin/java -jar -Xms256m -Xmx512m school-market.jar --spring.profiles.activeprod Restarton-failure [Install] WantedBymulti-user.target-Xmx512m这个参数别设太大4G 的机器还要跑 MySQL 和 Nginx堆内存给到 512M 到 1G 之间比较合适。设成 2G 反而容易触发系统 OOM。6.3 答辩演示清单提前一天过一遍演示环节翻车的方式就那么几种提前规避掉。准备一份数据至少 20 条商品、5 个用户、3 个不同状态的订单。空系统演示是最忌讳的点进去一个商品都没有效果直接减半。数据要真实一点商品标题写“高数教材第七版”“宿舍小台灯”别写“测试1”“aaa”。演示路径提前走三遍注册登录 → 发布商品 → 换账号搜索到该商品 → 下单 → 卖家确认 → 完成 → 互评。这条链路七步控制在三分钟内讲完。每一步说什么话提前写好别临场组织语言。准备两个浏览器或者一个正常窗口一个无痕窗口用来演示买卖双方的不同视角。这个技巧很多人都没想到但效果很好能直观展示订单状态的双向变化。7. 常见问题排查速查表7.1 环境和依赖类问题这类问题占了新手卡壳原因的七成以上而且往往一卡就是一整天。现象大概率原因处理方式启动报Table xxx doesnt exist数据库没建表或连错库检查application.yml的库名执行建表脚本中文乱码数据库或连接串字符集不是 utf8mb4连接串加?useUnicodetruecharacterEncodingutf8mb4前端请求 404代理路径不匹配检查前端 baseURL 和 Nginx 的location /api/是否一致请求跨域被拦截本地开发时前后端端口不同后端配置CorsFilter或在WebMvcConfigurer里加映射打包后页面空白静态资源路径配置错误Vue 项目publicPath设为./或与部署路径一致上传报FileSizeLimitExceeded超过默认大小限制调spring.servlet.multipart的配置时间差 8 小时数据库时区或 JDBC 时区参数问题连接串加serverTimezoneAsia/Shanghai端口被占用上次进程没停干净lsof -i:8080找到 PID 后 kill7.2 业务逻辑类的隐蔽 bug有几类问题不报错但现象很怪排查起来最费劲。商品列表少了几条数据。检查筛选条件里是不是把status写成了等于 1而实际上待审核0的商品也应该在某些场景下可见。还有一种是LEFT JOIN写成了INNER JOIN导致没有分类的商品被过滤掉。并发下单同一件商品都能成功。这就是前面提到的靠代码先查后改是有时间窗口的。要么给商品表加乐观锁版本号要么在下单时用UPDATE t_goods SET status 2 WHERE id ? AND status 1判断影响行数影响行数为 0 就说明被人抢先了。分页总数不对。八成是在分页插件之外自己又写了一层 count 查询两个条件不一致。用 MyBatis-Plus 的分页插件就不用管这个它自动生成 count 语句。修改商品后列表页没变化。浏览器缓存或者前端没重新拉数据。开发阶段可以在接口上加时间戳参数或者直接开 DevTools 的禁用缓存。提醒一句所有涉及金额、库存、状态的写操作都要想清楚并发场景。校园项目的并发量虽然小但答辩时被问“两个人同时下单怎么办”你能说出乐观锁或者条件更新这个回答的分量很重。7.3 一个反面案例的处理过程有位同学的项目症状是“发布商品成功后我的发布列表里有但首页搜不到”。他先怀疑是搜索的 SQL 写错了改了半天没效果又怀疑是缓存清了缓存还是不行。最后定位到问题是商品发布时status默认值取了 0待审核而首页查询条件是status 1。这个案例的教训是排查问题要从数据入手不要从代码入手。正确顺序是先查数据库看那条记录的每个字段值是什么和查询条件对比差异在哪里一眼就能看出来。上来就翻代码等于在几百行里找一个可能不存在的错误。我一般的排查顺序是确认数据对不对 → 确认 SQL 单独执行返回什么 → 确认接口实际返回什么 → 最后看代码逻辑。这四步走完九成的 bug 都能定位。这个方法论写进论文的“系统测试”章节也比单纯罗列测试用例更有说服力。最后分享一个小技巧。开题和论文里凡是提到“创新点”或者“特色”的地方不要写“界面美观”“操作简便”这种谁都能说的话写具体一点比如“针对校园场景的强信任特征设计了基于手机号与校区绑定的身份校验机制”“考虑到毕业季的商品集中发布潮实现了按校区的分区展示与就近排序”。这些描述有场景、有方法、有针对性评审一看就知道你确实在思考业务而不是在套模板。