1. 先说清楚:校园闲置物品交易到底要解决什么问题
每逢毕业季,宿舍楼道里堆满了考研资料、小台灯、收纳箱,甚至九成新的自行车。这些东西不是没人要,是没人知道该去哪要——校园集市群的聊天记录翻几百条才能找到一个卖家,二手平台发货运费比商品还贵,线下摆摊又受时间和场地限制。做这个校园闲置物品交易系统,本质上是把“闲鱼”的逻辑缩小到校园半径内:同校学生之间完成发布、浏览、留言、交易,把离校时恨不得扔掉的东西变成下届学弟学妹手里的宝贝。
从项目层面看,这是一个非常典型的Java全栈毕业设计/课程设计题目,业务模型清晰但足够完整。它不要求你实现支付平台级的资金安全,也不要求消息推送达到微信的并发量,但它覆盖了从一个互联网产品想法到可运行系统所需的全部环节:前后端分离架构、数据库设计、接口开发、权限验证、文件上传、部署运行。换句话说,做完这一个项目,你对“一个Web系统是怎么跑起来的”会有一个全景式的认知,这比死记八股文有用得多。
系统核心的角色只有三类:普通用户(买家和卖家的身份可以叠加)、管理员、游客。游客可以浏览商品列表,但要发布商品、留言、下单必须先注册登录;管理员负责用户管理和商品审核。业务链条也是标准电商的简化版:用户注册登录 → 发布闲置商品 → 其他用户浏览检索 → 感兴趣的话在线留言或直接下单 → 双方线下交易 → 商品标记下架。没有复杂的购物车和在线支付,让整个系统的开发量控制在两三个星期内可完成,同时又不失一个完整项目该有的技术覆盖度。
我的建议是,拿到这类题目不要直接开写代码,先把业务边界画清楚。很多同学把系统设计得无比庞大,又是积分、又是优惠券、又是多级分类,最后光表就建了三十多张,代码写了一万多行,实际可用性反而不如一个功能收敛、逻辑自洽的小系统。校园闲置交易的核心只有商品、用户、订单三个实体,其余都是围绕这三者转的辅助功能。
2. 技术选型的理由:为什么是SpringBoot + Vue + MyBatis + MySQL 而不是别的
这个标题里的技术栈几乎是目前Java Web开发的“标准套餐”:SpringBoot做后端框架,Vue做前端界面,MyBatis做持久层,MySQL存数据。有人会问,为什么不直接用Spring Data JPA?为什么不用Spring Cloud?为什么前端不用React?这些问题在答辩时老师大概率会问,提前想清楚答案,比背概念要好得多。
2.1 后端为什么用SpringBoot而不是传统SSH/SSM
早期做Java Web用的是SSH(Struts + Spring + Hibernate)或者SSM(Spring + SpringMVC + MyBatis),痛点在于配置地狱——XML配置文件动辄上百行,环境搭建就能熬掉你两三天。SpringBoot的核心价值是“约定优于配置”,内嵌了Tomcat,一个main方法就能启动整个Web服务,这对学生做项目来说是巨大的效率解放。
具体到本项目,SpringBoot带来的直接好处有三个。第一,依赖管理简化,引入spring-boot-starter-web就自动带上了SpringMVC和默认的Jackson序列化;引入mybatis-spring-boot-starter就自动配好了SqlSessionFactory,不用自己写一堆Bean。第二,内置Tomcat,本地调试不用再去下载安装一个独立Tomcat再配置server.xml。第三,配置文件可以用application.yml统一管理,数据库连接、端口、文件上传大小限制一目了然,改配置不用重新编译。
版本选择上需要注意,这也是很多初学者踩坑的重灾区。如果用的是SpringBoot 2.7.x,对应JDK 8或JDK 11都没问题;如果没注意直接拉了SpringBoot 3.x,就需要JDK 17以上,且MyBatis对应的starter也要换版本,很多旧教程的写法直接不能用。所以我的建议是不要盲目追求最新版本,选SpringBoot 2.7.18这个终极2.x版本,生态资料最丰富,遇到问题搜解决方案最容易。
2.2 前端为什么是Vue而不是原生JSP或者Thymeleaf
传统方案里,JSP和Thymeleaf都是服务端渲染,页面逻辑和Java代码绑在一起,前后端分不开。Vue的核心价值是实现了前后端分离:前端工程独立开发调试,通过Ajax请求调用后端的RESTful接口。这种模式更接近真实企业开发环境,也是技术面试时被问得最多的能力。
Vue在校园交易这个场景里最合适的点在于:页面上有大量“状态变化”的需求。比如商品列表的排序筛选、搜索框的实时联想、收藏按钮的无刷新切换、下单弹窗的开关,这些如果用JSP来做,每次交互都要刷新整个页面或写大量原生JS操作DOM,开发效率和体验都很差。Vue的响应式数据绑定让这些事情变得极其自然——数据变了,页面自动变。
前端构建工具建议使用Vue CLI(对应Vue 2)或者Vite(对应Vue 3)。考虑到网上教程的丰富度和毕设答辩文档的成熟度,Vue 2 + Element UI仍然是一个稳妥的选择,写起来直观,组件库颜值足够,示例代码一搜一大把。如果你更想贴近当前企业用的主流技术,Vue 3 + Element Plus + Vite也完全可行,只是要注意和Node版本之间的兼容性。
2.3 MyBatis和MySQL的组合逻辑
MyBatis是半自动ORM框架,SQL由你自己写,但参数映射、结果集映射、动态SQL这些机械工作由框架代劳。选它不选Hibernate/JPA的核心原因是:它的学习曲线平缓,执行过程透明,SQL调优方便。校园交易系统里有一个非常典型的业务——“根据多个可选条件筛选商品”——比如按价格区间、按商品分类、按成色新旧来组合查询。这种多条件动态查询用MyBatis的<where>和<if>标签做动态SQL,每一个分支都直观可控。用JPA也不是不能做,但自己拼Specification时对初学者来说抽象程度过高,一旦排查问题就是一团乱麻。
MySQL在这个项目里是最没有争议的选择。轻量、开源、安装方便、图形化管理工具成熟(Navicat、SQLyog、DataGrip都行),本地球的5万数据毫无压力。需要注意的只是安装时的字符集设置和时区配置,这两个问题后面我单独说。
3. 从0到1建库:核心表结构设计与业务边界
很多初学者建表喜欢把所有业务塞进一两张“万能表”里,或者反过来把每个属性拆成单独的表。这两种都是极端错误。正确做法是先梳理业务活动中的名词,再确定它们和动词之间的关系。
3.1 数据表的总体设计
本系统我建议设计六张核心表,再加两张辅助表,一共八张,足够支撑整个业务而不显得臃肿:
| 表名 | 核心字段 | 用途说明 |
|---|---|---|
| t_user | id, username, password, nickname, avatar, phone, role, create_time | 用户表,role区分普通用户和管理员 |
| t_category | id, name, sort | 商品分类表,如书籍教材、电子产品、生活用品 |
| t_goods | id, user_id, category_id, title, description, price, original_price, degree, images, status, view_count, create_time | 商品表,status表示在售/已下架/已售出 |
| t_order | id, order_no, goods_id, buyer_id, seller_id, price, status, create_time, finish_time | 订单表,记录买家卖家关系和交易状态 |
| t_comment | id, goods_id, user_id, content, create_time | 留言表,潜在买家在商品下的留言 |
| t_favorite | id, user_id, goods_id, create_time | 收藏表 |
| t_notice | id, title, content, create_time | 公告表(辅助表,管理员发通知用) |
| t_admin_log | id, admin_id, action, target, create_time | 操作日志表(辅助表,记录后台审核操作) |
订单表为什么要同时存buyer_id和seller_id?因为一个用户既可以是买家也可以是卖家,订单本质上连接了“商品归属人(seller)”和“下单人(buyer)”。如果你只存一个user_id而试图在查询时反推卖家,会给自己挖一个大坑,尤其是在做“我买到的”和“我卖出的”这两个列表页面时,你会发现查询逻辑绕得想哭。
3.2 状态流设计与字典值约定
业务里最核心的状态字段是goods.status和order.status。我强烈建议在编码前先把这两个字段的所有取值和流转关系用表格固定下来,不要边写边想。
商品状态我用的是三个值:
- 0 = 在售(发布后默认状态)
- 1 = 已下架(卖家主动下架或管理员审核下架)
- 2 = 已售出(订单完成时自动置为该状态)
订单状态我用的是四个值:
- 0 = 待确认(买家下单,卖家未处理)
- 1 = 已确认(卖家同意交易,等待线下见面)
- 2 = 已完成(交易完成,商品状态同步改为已售出)
- 3 = 已取消(买家或卖家取消订单)
这个设计是照抄成熟电商平台的状态机但做了大幅精简。为什么“待确认”和“已确认”要分开?因为校园闲置交易场景下,下单不代表成交,卖家和买家往往需要沟通交易地点和时间。订单被确认后才意味着这笔交易正式进入线下履约阶段。状态机在代码里怎么落地?最简单可靠的方案不是用状态模式,而是把状态流转的控制逻辑写在一个Service方法里,比如sellerConfirm(orderId, userId)方法内部先校验订单状态是否为0,再校验当前操作者是否为seller,都通过才把状态改为1。每个状态变化都做成一个独立方法,比在Controller里随意set状态的写法安全得多。
3.3 数据库初始化要注意的细节
SQL脚本不要等到写代码的时候再临时建表,我建议拿到题目后第一天就把建表SQL写好并执行到位。几个容易忽略的细节:
- 存储引擎统一InnoDB,字符集统一utf8mb4(注意不是utf8,utf8mb4才能完整显示emoji和生僻字)。
- 主键一律用
BIGINT AUTO_INCREMENT,不要用UUID做主键。UUID作为主键在数据量小的时候没感觉,但插入时索引页会产生随机IO,后面数据量上来性能下降明显。 - 统一给
create_time字段加DEFAULT CURRENT_TIMESTAMP默认值,这样插入数据时不用手动维护创建时间。 - 价格字段用
DECIMAL(10,2),不要用DOUBLE。DOUBLE存小数有精度丢失风险,虽然学生项目里不会出大事故,但这个习惯要从一开始就养成。 - 索引方面,除了主键索引,必须给
goods.user_id、goods.category_id、order.buyer_id、order.seller_id建立普通索引,这几张表的核心查询条件都是这些字段。不加索引的表在数据量超过几千条后,联表查询速度会肉眼可见地变慢。
我还建议在初始化SQL里插入几条测试数据——几个不同分类的商品、两三个用户、一条留言。不要小看这件事,后端接口写完后一启动,前端页面立刻有数据可展示,联调效率会提高很多,而不是对着空白页面反复检查是不是接口写错了。
4. 关键功能实现拆解:登录、商品发布、订单流转中最容易翻车的几个点
功能模块划分上,我建议按用户端、商品端、订单端、管理端四个维度来组织代码。这一节不展开每一行代码,但会把最容易翻车、最难排查的几个技术点讲透。
4.1 登录鉴权:JWT还是Session,推荐选JWT
前后端分离的架构下,Session方案天然有跨域和浏览器Cookie策略的麻烦,所以绝大多数现代项目直接用JWT(JSON Web Token)。流程是这样的:用户登录成功后,后端用密钥生成一个包含用户ID和过期时间的Token返回给前端;前端把Token存在localStorage里,之后每次请求在请求头里带上Authorization: Bearer <token>;后端通过一个拦截器解析Token,解析成功就放行并从Token里取出用户信息。
具体实现上,不推荐自己手写完整的JWT工具类,直接用jjwt这个库,引入依赖后大约二十行代码就能完成生成和解析。关键点在于:
- 密钥要写在配置文件里,不要写死在代码中。
- Token过期时间建议设为24小时,太短会导致用户频繁重新登录,太长则有安全隐患。毕设项目24小时是一个平衡点。
- 拦截器只做“登录校验”,不写业务逻辑。放行白名单要配置好:注册、登录、首页商品列表、商品详情这些接口游客也是可以访问的。
我在实际写这类项目时还发现一个细节:拦截器解析完Token后,要把用户ID放到Request的attribute里,Controller里再从request中拿,而不是每个Controller都重新解析Token。这样统一处理,后面写任何接口都可以直接用“当前登录用户”,代码会整洁很多。
4.2 商品发布与图片上传:本地存储这么搞最省心
学生项目不建议接入阿里云OSS,理由很简单:需要实名认证、需要配置Bucket权限、涉及跨域设置,还要考虑流量费用,成本虽低但流程繁琐,容易在中途放弃。最本地的方案是“本地磁盘存储 + 后端映射为虚拟路径”。
具体做法是:在项目配置里定义一个上传根目录,比如E:/project/upload/,用户上传的图片按日期分子目录存放,数据库里只保存相对路径,比如/upload/2025/05/01/xxx.jpg。前端请求图片时,URL写法是http://localhost:8080/upload/2025/05/01/xxx.jpg。为了让这个URL能访问到本地磁盘文件,需要做一个静态资源映射配置,本质上就是把/upload/**这个URL路径映射到本地磁盘目录上。
这里有几个我踩过的坑必须强调:
- 配置文件里的上传目录如果写成相对路径,在不同的启动方式下(IDE启动、命令行启动、打包后启动)指向的目录可能不一样。强烈建议写绝对路径,或者用
user.dir这个系统属性拼接相对路径,但要在代码里做好目录不存在时自动创建的兜底逻辑。 - SpringBoot默认的上传文件大小限制是1MB,但手机拍的照片动辄3-5MB,所以必须在配置文件里把
spring.servlet.multipart.max-file-size调大,否则学生会反馈“图片传不上来”,排查半天结果是框架限制。 - 图片格式校验不能只依赖前端。后端拿到文件后要判断ContentType和扩展名是否匹配,至少排除掉伪装成图片的可执行文件。
4.3 订单流转的并发问题:防止超卖是一种什么样的体验
校园闲置交易里的商品只有一件,所以不存在电商秒杀那种高并发超卖问题,但依然有一个逻辑漏洞值得注意:同一件商品被两个用户同时下单。这么说似乎很抽象,举个例子你就明白了。用户A看中了那辆二手自行车,点了“立即下单”,还没付款前(实际上我们的流程里没有在线支付,下单即锁定意向),其实商品应该是“被占用”状态,不应再被其他用户下单。但如果代码没做状态校验,用户B也能成功下单,那卖家就要面对两个买家,尴尬到脚趾抠地。
解决这个问题的标准做法是:在创建订单的Service方法里,先执行一条UPDATE goods SET status = 2 WHERE id = ? AND user_id != ? AND status = 0形式的乐观更新(这里的status=2可以理解为“有单处理中”,或者单独设计一个状态),再通过受影响行数判断是否抢单成功。受影响行数为1表示更新成功,商品被当前用户锁定;为0则表示商品状态已变化(被下架或已被别人锁定),直接抛业务异常提示“手慢了,宝贝已被他人锁定”。这种做法利用了数据库行锁的原子性,代码也不复杂,比先查询再判断再更新的方案安全得多。
订单和商品的状态联动我也建议用同步事务实现,即在同一个事务里同时更新订单状态和商品状态,避免出现“订单已完成但商品还在售”的数据不一致情况。
4.4 多条件筛选商品:MyBatis动态SQL的典型应用
商品列表页最常见的需求是:按分类、价格区间、成色、关键词下拉筛选。用MyBatis写动态SQL是最舒服的场景,核心逻辑是使用<where>标签配合<if>标签进行条件拼接。这玩意儿写起来不难,但有个经典的坑——条件不生效。造成这个问题的原因通常有三个:
- 参数名没对上。Mapper接口方法里的参数如果没有加
@Param注解,XML里用#{xxx}取值就取不到。 - 判断条件写错了,比如判断Integer类型的status时写成
status != '',空字符串和Integer比较永远返回true,SQL就永远带上这个条件。 - XML文件没有刷新,改了SQL但没重新编译,运行的是旧class文件,看起来“条件不生效”。
这个功能还有一个体验上的加分项:写SQL时使用ORDER BY和LIMIT配合实现分页,但不要自己手动计算页码偏移。我建议直接引入PageHelper插件,三行代码完成分页,而且它和MyBatis的整合很成熟,不用自己拼LIMIT,减少出错面。
5. 完整部署过程:从MySQL安装到前端打包放进SpringBoot
这一节是很多从来没独立部署过项目的同学的噩梦。IDE里能跑,一部署就废,问题是大概率出在环境一致性上。我把从零开始部署的整个过程完整梳理一遍,照着做基本能一次跑通。
5.1 MySQL安装的版本选择与安装陷阱
先说版本。MySQL 5.7和8.0都可以用,但我更推荐MySQL 5.7.44,原因不是它先进,而是教材和网上教程的大多数截图、配置项都基于5.7,遇到问题最容易搜到答案。如果你一定要用8.0,那么必须留意三个差异点,不然会相当痛苦。
8.0的默认认证插件是caching_sha2_password,而很多老的数据库连接驱动(包括一些老版本JDBC驱动)不支持这个插件,会导致连接报错Unable to load authentication plugin。解决办法是安装时选择Legacy Authentication(如果用安装程序引导),或者初始化后执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'。在SpringBoot的application.yml里连接数据库时,5.7对应的驱动类是com.mysql.jdbc.Driver(老版本驱动)或com.mysql.cj.jdbc.Driver(新版本驱动都兼容,推荐后者),8.0则必须用com.mysql.cj.jdbc.Driver,且URL里必须要加serverTimezone=Asia/Shanghai,否则会报时区错误。8.0连接串里还有一个容易忽略的项是useSSL=false,不关闭SSL的话控制台会打出一堆SSL警告,不影响运行但看起来非常糟心。
Windows安装时还有一个挂科级细节:安装目录和数据目录不要有中文和空格。很多同学把MySQL装到C:\Program Files下,后续读写数据文件时权限问题频出。建议装到D:/mysql这样的纯英文路径下。
安装完成后,用Root账号登录,创建本项目专属的数据库和账号。千万不要直接用root账号连项目数据库,虽然毕设项目没有安全审计要求,但这个工作习惯值得养成:
CREATE DATABASE campus_trade DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER 'campus'@'localhost' IDENTIFIED BY 'your_password'; GRANT ALL PRIVILEGES ON campus_trade.* TO 'campus'@'localhost'; FLUSH PRIVILEGES;5.2 SpringBoot后端的配置要点
启动后端前,把application.yml配置认真核对一遍,这是我总结的常用配置模板:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_trade?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: campus password: your_password servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.campustrade.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl jwt: secret: your-secret-key-change-me expire: 86400我单独解释一下map-underscore-to-camel-case这个配置。数据库字段风格是create_time、goods_id这种下划线命名,Java实体类风格是createTime、goodsId这种驼峰命名。开启这个配置后,MyBatis在把结果集映射为实体对象时,会自动把create_time转成createTime,不需要你给每个字段写resultMap。很多新手没开这个配置,发现查询出来所有字段都是null,排查几个小时,其实一条配置就能解决。
另外log-impl: StdOutImpl在开发阶段建议开启,这样控制台会把每条SQL和执行参数打印出来,排查MyBatis条件不生效的问题非常有帮助。上线前再把它注释掉即可。
5.3 Vue前端构建与后端合并部署
前端开发阶段用npm run serve跑在8081端口,通过Vue CLI的代理配置解决跨域问题。在vue.config.js里配置:
module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }这里建议前端所有请求都以/api开头,后端Controller的RequestMapping统一加/api前缀。这样开发环境通过Vue代理转发到8080,避免了开发时的跨域问题。
但要注意,代理只对开发环境有效。项目打包后,前端文件是静态资源,读取的是它所在服务器的接口地址,此时根本没有代理服务器了。这时候有两个做法。第一个是部署时把前端打包产出的dist目录复制到SpringBoot项目的static目录下,然后重新打包后端的jar包,这样前端和后端同源,都是8080端口,不存在跨域。这个方案最适合毕设演示,因为只需要在服务器上启动一个Java进程。第二个做法是用Nginx把前后端分开部署,通过Nginx配置反向代理,这种方案更接近企业级部署,但需要额外安装Nginx、配置代理规则,复杂度上去一个量级。
我建议的项目演示方案是第一种:前端打包后放进SpringBoot的static目录。具体操作是:前端执行npm run build生成dist目录,把dist目录下的所有文件复制到后端项目的src/main/resources/static下,然后mvn clean package打出jar包,java -jar启动。访问http://localhost:8080时,默认首页就是Vue应用。
这个方案有一个坑:Vue Router在history模式下,刷新页面时后端会去static目录里找路径对应的静态文件,找不到就404。解决办法有两个:要么把路由模式改成hash模式(URL里会出现#号,但简单可靠);要么在后端加一个forward转发配置,把所有非/api开头的请求转发到index.html。考虑到毕设项目对外观没有强烈要求,我推荐直接用hash模式,省心省力。
5.4 打包部署时的Maven配置
后端打包时有一个坑是SpringBoot的repackage插件。如果你用mvn package打包后,发现生成的jar包只有几十KB,而java -jar启动报“没有主清单属性”,说明你用的是Maven默认的打包方式,没有把SpringBoot的依赖打进去。必须在pom.xml的build节点里配置spring-boot-maven-plugin,它会让最终jar包变成可执行的fat jar。
如果你在IDEA里打包,注意先执行mvn clean再mvn package,避免旧编译产物残留导致新代码没生效,出现的症状就是你改了代码但运行时行为没变化。
6. 试跑过程中必须注意的坑:跨域、MyBatis条件不生效、版本兼容这些
这个项目我前前后后帮人调试过不下二十遍,下面这些问题出现的频率,可以说几乎每跑一遍都能踩中其中一两个。
6.1 跨域问题:开发环境和部署环境是两种解法
开发模式下,前端在8081,后端在8080,如果不做任何处理,浏览器的同源策略会让所有Ajax请求失败,控制台报错提示“CORS policy”。网上搜到的解决方案大多是后端加一个@CrossOrigin注解或者写一个全局CorsFilter配置类。确实可行,但我要提醒你这种方法只解决开发环境的问题,而且会带来一个安全隐患:它允许了所有来源的跨域请求(allowedOrigins("*")),对生产环境是不负责任的。
更优雅的方案是前面说的Vue开发代理。它的原理是:前端请求的是8081端口自己的地址,Vue的devServer接收到请求后转发给8080,对于浏览器来说,请求来源和目标始终是同源的,所以根本不会触发跨域拦截,连后端都不需要写任何CORS配置。这个方案在开发和部署时都更干净。
如果前端请求报404而不是跨域错误,那大概率是你的代理路径拼错了。检查一下前端请求的URL是/api/goods/list还是http://localhost:8080/api/goods/list——用了代理后绝对不要写完整地址,写了直接绕过代理,必然跨域。
6.2 MyBatis条件不生效的排查链路
这个坑太经典了,几乎每个用过MyBatis的人都遇到过。我给你一个完整的排查链路,照这个顺序排查,五分钟内能定位问题。
首先打开控制台SQL日志(配置里启用StdOutImpl)。看打印出来的SQL语句里,动态条件是否拼上了。如果SQL里压根没有WHERE条件,说明XML里的<if>判断为false。此时分两步检查:test表达式里的参数名是否正确(XML里取的是#{title},Java方法参数就要有对应的@Param("title"));判断条件本身是否正确(String类型判断!= null and != '',Integer类型只判断!= null就足够了)。
如果SQL语句里条件拼上了,但查询结果还是不符合预期,那问题可能出在参数传递上。比如前端传的参数名是categoryId,后端实体里的属性名却是category_id风格的,映射不上,导致参数是null。这种问题控制台SQL都能看出来——SQL里条件有了,但绑定参数显示为null。
还有一种情况是结果集映射问题。查询条件生效了,返回的结果里某些字段是null。大概率就是前文说的没有开启map-underscore-to-camel-case,或者实体类属性名和数据库字段名对不上。
6.3 “SpringBoot版本太高”的连锁反应
这个问题真的是2025年高频词。新手从网上下载一个开源项目模板,SpringBoot是2.1.5这种老版本,然后自己新建项目时IDEA默认拉取了3.3.x,结果一大堆问题集中爆发:JDK版本不对(3.x要求17+)、javax包名全换成了jakarta、MyBatis的starter版本不兼容、老教程里的配置项全失效。这个局怎么破?
我给你的建议非常简单粗暴:做毕设级项目,别追求新版本,锁定2.7.x版本线。它是2023年前最成熟的SpringBoot版本,资料最多,坑最少,所有老教程的代码和配置基本通用。如果公司环境要求你用3.x,那你得接受一套新规则,但对于学习和毕业设计来说,完全没有必要在版本兼容性上消耗宝贵精力。
如果你非要挑战3.x,记住关键的对应关系:
- JDK使用17或21,不能用8
javax.servlet换成jakarta.servlet- MyBatis starter用
mybatis-spring-boot-starter的3.0+版本 - SpringBoot的配置项大部分没变,但依赖传递的兼容性问题可能需要逐个排查
6.4 文件上传后图片无法访问
这个问题的表象是:上传接口返回成功,数据库里存了路径,但前端图片URL打开是404。排查思路也很单一。先确认磁盘上文件是否真的存在——如果文件压根没上传成功,检查配置里的上传大小限制是否被触发;如果文件在,但URL访问不到,那就是静态资源映射没生效。检查你的映射配置路径和URL的前缀是否完全一致,包括斜杠。比如你映射的是/upload/**,URL写成/upload/xxx.jpg是对的,但如果你映射的是/files/**,URL却写成/upload/xxx.jpg,那必然是404。
另外一个隐蔽的问题是:数据库里如果存的是完整地址http://localhost:8080/upload/xxx.jpg,部署后域名或端口变了,图片全挂。所以数据库里只存相对路径,前端展示时动态拼接当前请求的域名和端口,这是更健壮的做法。
最后再分享两个实用的小技巧
第一个是启动项目前,先启动后端验证接口通不通,再启动前端联调,不要两边同时Sprint。怎么快速验证后端?接口文档用Swagger集成(SpringBoot 2.7对应springfox或springdoc)或者简单的思路——后端启动后在浏览器直接访问http://localhost:8080/api/goods/list,如果返回JSON数据,说明数据库连接、Mapper、Controller都没有问题。再做UI是纯前端的事情,错误面一下子缩小了一半。
第二个是写代码时顺便把测试数据想清楚。我在建库时就在SQL脚本里塞好了5个用户、20件商品、若干留言和订单,覆盖了“在售”“已下架”“已售出”三种状态、不同分类、不同价格区间。联调时无论是列表页的数据丰富度、筛选功能的测试,还是订单流程的演示,都能直接操作,节省大量临时造数据的时间。另外把一套完整的账号信息写进README,比如admin/123456、seller/123456、buyer/123456,演示时用现成的账号,不用现场注册,答辩也会从容很多。
做完这个项目你会发现,校园闲置交易系统表面上是个两个星期就能写完的课程设计,但认认真真走一遍从技术选型、建库、编码、联调到部署的完整流程之后,你对Java Web开发的理解会有一个很大的提升。很多问题不亲手踩一遍,看十篇文章都记不住。这个项目值得你亲手做一次。