简介:这套源码是一套基于Spring Boot开发的二手交易系统前后端实现,适合计算机相关专业学生和Java Web初学者,可作课程设计或毕业设计参考。系统覆盖商品发布与分页查询、用户注册登录(JWT令牌认证)、订单创建与状态管理、商品留言等核心模块,从前端Vue页面到后端接口形成完整链路。整包共138个文件,压缩包大小约4.05MB,以31个Java源码、18个Vue组件、36个编译class、11个XML配置以及12份Word文档、图片等辅助资料为主,便于导入开发工具查看与二次开发。目前已有65人浏览学习。借助这份资料,可一边对照Vue界面一边阅读后端代码,重点学习JWT权限控制、分页查询、订单状态流转等典型业务实现,快速理解Spring Boot + Vue前后端分离项目的协作方式,对了解二手交易平台开发流程也具有不错的参考价值,整体可读性和完整性较好。 手头正好有一套基于 Spring Boot 的二手交易系统源码,打包成了 zip 分发给朋友学习。这里不聊那些虚的架构图,直接把这套系统的设计思路、核心代码结构、踩过的坑,以及怎么把它跑起来、改成自己能用的项目,从头到尾捋一遍。无论你是刚学 Spring Boot 想找项目练手,还是准备做毕业设计/课程设计,或者想快速搭一个校园二手交易平台,这篇内容应该都能给你省下不少时间。
先说明一下,这套系统不是玩具级别的那种 CRUD Demo,它涵盖了用户注册登录、商品发布与管理、搜索筛选、下单购买、订单管理、个人中心等功能,虽然比不上电商大厂的生产系统,但作为学习和二次开发的基底,结构上已经足够完整。源码打包成了 zip,解压后导入 IDE 就能看到完整工程,关键依赖和配置都在里面,照着做基本能跑通。
1. 项目整体设计与思路拆解
1.1 为什么选 Spring Boot 而不是 SSM 或者别的框架
先说结论:Spring Boot 把 Spring 生态里那些繁琐的 XML 配置全部干掉了,尤其是内嵌 Tomcat、自动配置、起步依赖这三大特性,让开发体验直接拉满。对于二手交易这种典型的 Web 业务系统,用 Spring Boot 去写,团队协作时的成本最低,新手也更容易看懂代码脉络。
这套源码里,我用了 Spring Boot 2.x 版本,配合 MyBatis-Plus 做持久层。很多人可能会问,为什么不直接用 JPA?我的习惯是:MyBatis-Plus 对于复杂 SQL 的控制粒度更细,尤其在商品多条件筛选、订单状态流转这种场景下,写 XML 里的动态 SQL 比 JPA 的派生查询直观得多,而且 MyBatis-Plus 自带的BaseMapper提供了大量的单表 CRUD 方法,简单操作用内置方法,复杂操作用自定义 SQL,两者结合非常舒服。
还有一个点,Spring Boot 的分层结构非常规整。这套源码也是标准的四层架构:Controller 层(接口入口)、Service 层(业务逻辑)、Mapper 层(数据访问)、Entity 层(数据库实体对象)。有人可能觉得“四层架构”是老生常谈,但真正写项目的时候,很多新手把业务逻辑堆在 Controller 里,一个方法几百行,后面想维护都无从下手。这套源码里,每个类都严格分层,Service 接口和实现分开写,也是为了让代码结构更清晰。
1.2 二手交易系统的核心业务流程与功能模块
二手交易和普通电商最大的不同点在于:它的买家卖家都是个人用户,商品的发布者同时也是平台使用者,所以必须有完整的用户体系、商品生命周期管理、订单状态流转。这套系统的核心业务流是这样的:
用户注册/登录 -> 发布商品 -> 商品上架(默认待审核/直接上架,看配置) -> 买家浏览/搜索 -> 发起购买/联系卖家 -> 生成订单 -> 卖家确认交易 -> 订单完成 -> 双方评价(可选)功能模块上,我拆成了六大块:
- 用户模块:注册、登录、个人信息管理、密码加密存储、头像上传。
- 商品模块:发布商品(标题、描述、价格、图片、分类)、商品列表分页、商品详情、编辑/下架/删除商品、搜索与条件筛选。
- 订单模块:买家下单、卖家查看订单、确认交易、取消订单、删除订单。
- 收藏模块:用户的收藏商品列表、添加/取消收藏。
- 评论模块:对商品或交易的评论,简单版实现。
- 管理后台:用户管理、商品管理、订单管理,简单统计功能。
模块之间不是孤立的,比如商品发布时要校验用户是否登录,下单时要校验商品状态,订单生成后商品状态要改成“已被购买”,这类状态同步逻辑是源码里最值得看的部分。
2. 核心细节解析与实操要点
2.1 源码目录结构与包命名规范
拿到 zip 解压后,先看整体目录结构。这套源码用的是 Maven 标准布局,主包名是com.example.secondhand。下面把关键 package 和它的职责说下:
com.example.secondhand ├── common // 通用类:统一返回结果、异常处理、常量定义 ├── config // 配置类:拦截器、跨域、文件上传配置等 ├── controller // 控制层:接收前端请求,返回 JSON ├── entity // 实体类:对应数据库表 ├── mapper // 数据访问接口:继承 BaseMapper ├── service // 业务接口定义 │ └── impl // 业务实现类 ├── utils // 工具类:JWT 工具、文件工具、字符串工具等 └── SecondhandApplication.java // 启动类很多人拿到别人的源码,第一步不是导入 IDE,而是先打开pom.xml看依赖。这套源码的依赖包括:Spring Web、MyBatis-Plus、MySQL 驱动、Lombok、JWT(用于登录令牌)、Hutool(工具库)、Spring Validation 等。这些依赖基本覆盖了做小型业务系统会用到的全部组件。
还有一个注意点:application.yml里的配置。源码里默认配置了本地的 MySQL 连接,库名是secondhand_db,用户名root,密码是123456。你自己跑的时候,一定要根据本地数据库密码去改,否则启动直接报连接异常。配置里还设置了 MyBatis-Plus 的逻辑删除、驼峰命名映射、分页插件等,这些配置项是业务正常运行的关键。
2.2 数据库设计的几个关键表与关联关系
一套系统能不能扩展、维护是否方便,看数据库设计就能知道个大概。二手交易系统的核心表我设计了七张,分别是:用户表、商品表、订单表、收藏表、评论表、商品图片表、分类表。这里挑三张最核心的表来分析。
用户表(t_user):
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| username | varchar | 用户名,唯一 |
| password | varchar | 密码,BCrypt 加密后存储 |
| nickname | varchar | 昵称 |
| avatar | varchar | 头像地址 |
| phone | varchar | 手机号 |
| create_time | datetime | 注册时间 |
| deleted | tinyint | 逻辑删除标记 |
商品表(t_goods):
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| user_id | bigint | 发布者 ID |
| category_id | bigint | 分类 ID |
| title | varchar | 商品标题 |
| description | text | 商品描述 |
| price | decimal | 价格(单位:元) |
| status | int | 状态:0 在售,1 已售出,2 下架 |
| view_count | int | 浏览次数 |
| create_time | datetime | 发布时间 |
| deleted | tinyint | 逻辑删除 |
订单表(t_order):
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| order_no | varchar | 订单编号,唯一 |
| goods_id | bigint | 商品 ID |
| buyer_id | bigint | 买家 ID |
| seller_id | bigint | 卖家 ID |
| price | decimal | 成交价格 |
| status | int | 状态:0 待确认,1 已完成,2 已取消 |
| create_time | datetime | 下单时间 |
这三张表之间的关联逻辑是:商品表通过user_id关联用户表,订单表通过goods_id关联商品表,同时冗余了buyer_id和seller_id,这样在查询订单列表时不需要反复 join 用户表去拿买卖双方的信息。这是典型的三层数据模型,业务上很清晰。
2.3 登录认证:JWT 的前后端交互设计
二手交易系统里,用户登录后的会话保持不能依赖传统 Session,因为现在前后端分离项目越来越普遍,移动端也可能要接入。这套源码用了 JWT(JSON Web Token)作为登录令牌。
具体过程是这样的:
- 用户提交用户名和密码,
UserService校验密码正确后,用 jwt 工具类生成一个包含用户 ID 和过期时间的 token。 - 后端把 token 返回给前端,前端存到 localStorage 或请求头里。
- 后续每次请求,前端在请求头加上
Authorization: Bearer <token>。 - 后端通过拦截器解析 token,如果能解析成功并且没过期,就把用户信息放进
ThreadLocal或 request 属性里,供后续业务代码使用;如果解析失败,直接返回 401 提示未登录。
这套设计里有个容易犯错的地方:JWT 只做身份认证,不做会话管理,所以后端无法主动让某个 token 失效。如果要做“强制下线”功能,就得引入 Redis 做 token 黑名单,这套源码里没有加,但对学习来说,先把 JWT 基本的生成和解析逻辑弄明白是最重要的。
使用拦截器时一定要在WebMvcConfig里配置好放行路径,比如登录接口、注册接口、商品列表、商品详情这些不需要登录也能访问的接口,都要加到excludePathPatterns里,否则前端一进来就被拦截,什么都看不了。这个坑我第二次写项目时还踩过。
3. 实操过程与核心环节实现
3.1 从 zip 解压到项目启动全流程
我按自己实际操作的过程,写一份完整的运行指南。
第一步:环境准备。JDK 1.8 或 11(源码里 pom.xml 指定了 Java 1.8,用高版本也能跑,但要注意兼容性);Maven 3.6+;MySQL 5.7 或 8.0;IDEA 或 Eclipse。
第二步:解压源码压缩包。zip 包解压后,你会看到完整的 Maven 工程目录。不要直接双击文件一个个打开,应该用 IDEA 的File -> Open,选择解压后的目录(里面有pom.xml)。IDEA 会自动识别为 Maven 项目,并开始下载依赖。
第三步:创建数据库。在 MySQL 里执行源码附带sql目录下的secondhand_db.sql脚本,这个脚本建好了所有表和几条测试数据。注意,如果本地 MySQL 字符集不是 utf8mb4,建议建库时手动指定:
CREATE DATABASE secondhand_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;第四步:修改数据库配置。打开src/main/resources/application.yml,改成你本地 MySQL 的用户名和密码。如果 MySQL 版本是 8.0,记得在 JDBC URL 后面加上?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true,否则会出现时区报错。
第五步:启动项目。运行SecondhandApplication.java的 main 方法。看到类似以下日志就说明启动成功:
Tomcat started on port(s): 8080 (http) Started SecondhandApplication in x.xxx seconds启动成功后,浏览器访问http://localhost:8080/api/goods/list,如果可以返回 JSON 数据,说明你的环境没问题。
3.2 商品发布功能的核心实现代码解析
商品发布是整个二手交易系统里最重要、也最能体现细节的功能。前端提交表单,后端需要接收商品基本信息以及图片上传。由于商品有多张图片,我在前端用了一个MultipartFile[]数组来接收,后端逐个保存文件并存入t_goods_image表,再关联到商品 ID。
先看 Controller 层的核心方法:
@PostMapping("/add") public Result add(@RequestBody GoodsAddDTO dto, HttpServletRequest request) { Long userId = JwtUtil.getUserIdFromRequest(request); goodsService.addGoods(dto, userId); return Result.success(); }这里没有直接用Goods实体接收参数,而是用了GoodsAddDTO。为什么?因为前端传的参数包含图片列表,而Goods实体类没有图片这个字段,直接用实体接收反而麻烦。DTO 和实体分离在业务开发中是个好习惯,尤其在接口参数复杂的时候。
再看 Service 实现的关键部分:
@Override @Transactional(rollbackFor = Exception.class) public void addGoods(GoodsAddDTO dto, Long userId) { Goods goods = new Goods(); goods.setUserId(userId); goods.setTitle(dto.getTitle()); goods.setDescription(dto.getDescription()); goods.setPrice(dto.getPrice()); goods.setCategoryId(dto.getCategoryId()); goods.setStatus(0); goods.setViewCount(0); goodsMapper.insert(goods); // 保存图片列表 if (dto.getImages() != null && dto.getImages().length > 0) { for (MultipartFile file : dto.getImages()) { String url = fileService.uploadFile(file); GoodsImage image = new GoodsImage(); image.setGoodsId(goods.getId()); image.setImageUrl(url); goodsImageMapper.insert(image); } } }注意到@Transactional注解,这是必须的。如果文件上传成功了,但商品图片关联数据插入失败,整个事务回滚,不会残留脏数据。很多新手写到这里会漏掉事务控制,结果数据库里商品的图片信息对不上,后面排查半天。
商品发布还有一个隐藏问题:文件名重复。我用的是UUID.randomUUID()加原始文件后缀的方式生成新的文件名,避免多人上传同名文件时互相覆盖。这个细节特别值得学,随便用原始文件名保存,用不了多久就会出问题。
3.3 搜索与筛选功能:MyBatis-Plus 动态 SQL 实战
二手交易系统的商品列表既要支持分页,又要支持按分类、价格区间、关键词搜索,还要支持排序(按时间、按价格)。如果全用 Java 代码去判断拼接 SQL,会非常啰嗦。我这里用了 MyBatis-Plus 的QueryWrapper配合Page来做。
Service 里的查询逻辑大概这样:
@Override public Page<GoodsVO> queryGoodsList(GoodsQueryDTO queryDTO) { Page<Goods> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize()); LambdaQueryWrapper<Goods> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Goods::getStatus, 0) .eq(queryDTO.getCategoryId() != null, Goods::getCategoryId, queryDTO.getCategoryId()) .like(StrUtil.isNotBlank(queryDTO.getKeyword()), Goods::getTitle, queryDTO.getKeyword()) .between(queryDTO.getMinPrice() != null && queryDTO.getMaxPrice() != null, Goods::getPrice, queryDTO.getMinPrice(), queryDTO.getMaxPrice()) .orderByDesc(Goods::getCreateTime); Page<Goods> result = goodsMapper.selectPage(page, wrapper); // 转为 VO,补充图片、卖家昵称等信息 return convertToVO(result); }这段代码最关键的是wrapper.eq(条件, 字段, 值)这种写法,MyBatis-Plus 会在条件成立时才拼接相应 SQL。这样就不用自己写<if>标签,代码简洁很多。如果条件较多、组合复杂,也可以选择在 Mapper XML 里写<select>,两种方式各有适用场景。个人建议:简单条件用 QueryWrapper,复杂多表查询写 XML 里的动态 SQL。
商品列表返回给前端时,我额外做了数据补全,把每件商品的封面图(第一张图片)、卖家昵称、卖家头像查出来拼成GoodsVO,避免前端为了展示这些信息反复发请求。这也算是性能优化的小经验:尽量减少前端循环里的请求次数。
3.4 订单流程与状态流转设计
二手交易的下单流程跟标准电商不同,它不涉及在线支付,核心是“买卖双方线下交易、平台确认状态”。所以我把订单状态设计得非常简单:0待确认、1已完成、2已取消。
买家下单时的核心逻辑:
@Transactional public void createOrder(Long goodsId, Long buyerId) { Goods goods = goodsMapper.selectById(goodsId); if (goods == null || goods.getStatus() != 0) { throw new BusinessException("商品不存在或已下架"); } if (goods.getUserId().equals(buyerId)) { throw new BusinessException("不能购买自己发布的商品"); } Order order = new Order(); order.setOrderNo(generateOrderNo()); order.setGoodsId(goodsId); order.setBuyerId(buyerId); order.setSellerId(goods.getUserId()); order.setPrice(goods.getPrice()); order.setStatus(0); orderMapper.insert(order); // 商品状态更新为已售出 goods.setStatus(1); goodsMapper.updateById(goods); }这里最关键的两个业务规则:一是商品必须处于“在售”状态才能被购买,二是不能买自己发布的商品。这两条规则在业务层做校验,而不是靠前端隐藏按钮,因为接口可以被直接调用,后端校验才安全。
生成订单编号我用的是时间戳加随机数:
private String generateOrderNo() { return "SO" + System.currentTimeMillis() + RandomUtil.randomNumbers(4); }虽然这个编号方案在极端并发下理论上可能重复,但作为小型系统足够用了。如果要更可靠,可以引入 Redis 自增或者雪花算法。
卖家确认交易时的状态流转:卖家看到订单后点击“确认交易”,订单状态从0变成1,同时商品保持已售出状态,整个流程闭环。如果没有确认交易,买家也可以取消订单,取消后商品的状态要恢复为0在售。这个状态回滚逻辑一定不能漏,不然就会出现“商品被买了但实际没卖出去”的 bug。
4. 常见问题与排查技巧实录
4.1 启动时报数据库连接错误
这个问题在我让身边朋友跑源码时出现频率很高,基本都是application.yml里的 MySQL 账号密码没改,或者 MySQL 服务没启动。错误日志通常是:
Cannot create PoolableConnectionFactory (Access denied for user 'root'@'localhost' (using password: YES))解决办法:确认 MySQL 能本地登录,然后修改配置文件里的username和password。还有一种情况是 MySQL 8.0 的驱动版本和连接 URL 不兼容,源码里默认用的 MySQL 驱动是mysql-connector-java 8.0.x,如果连接 URL 少了serverTimezone参数,会报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。把 URL 改成我之前写的那串带时区参数的即可。
4.2 代码有红色报错,找不到类或符号
这类问题绝大多数是 Maven 依赖没下载完整。导入项目后,等 IDEA 右下角显示依赖索引完成,如果还报错,执行mvn clean install -DskipTests强制重新拉取依赖。还有一个常见原因:本机 JDK 版本与 pom 里java.version不一致。用 IDEA 的Project Structure -> SDKs把项目 SDK 切换成 1.8 或 11,再在Settings -> Maven -> Runner里把 JRE 也换成对应版本。
如果使用 Lombok,记得 IDEA 要安装 Lombok 插件,并在Settings -> Build, Execution, Deployment -> Compiler -> Annotation Processors勾选Enable annotation processing。很多新手卡在这一步,明明代码没问题,但@Data注解生成的 getter/setter 方法找不到,就是因为注解处理器没开启。
4.3 登录拦截器导致所有接口 404 或 401
如果你在地址栏直接访问http://localhost:8080/api/goods/list,返回 401 未认证,多半是拦截器放行路径没配好。检查WebMvcConfig里addInterceptors方法,把商品查询、首页推荐等不需要登录的接口加入排除列表。这里有个小技巧:可以先把excludePathPatterns("/**")临时放行所有请求,把页面调通后再逐步放开需要登录的接口,这样能快速判断是接口逻辑的问题还是拦截器拦截的问题。
4.4 上传图片后访问不到图片文件
源码里的文件上传默认保存在项目根目录的upload/文件夹下,同时通过一个虚拟路径映射/upload/**到本地磁盘目录。如果你改了保存路径,却没有同步修改application.yml中的自定义配置,前端展示图片时就会 404。
我建议本地开发时用fileService.uploadFile方法返回相对路径,前端拼接服务器地址访问;部署到生产环境时,用 Nginx 映射upload目录,这样更灵活。源码里关于文件上传配置的部分在application.yml中已经给出示例,改起来很方便。
5. 二次开发与扩展方向建议
5.1 如何把项目改成自己的课程设计或毕设
这套系统拿来作为课程设计或毕业设计完全够用,但建议做以下几点改造,让它更像一个“完整作品”:
- 增加管理员模块:目前源码里后台管理非常轻量,可以扩展管理员角色、权限控制,用 Spring Security 或 Sa-Token 做更完善的权限体系。如果不想引入重框架,也可以自己写一个简单的拦截器做管理员判断。
- 增加在线聊天功能:二手交易里买家和卖家需要聊价格、约时间,可以集成 WebSocket 做一个简单的私信聊天模块。这一步做好了,项目的亮点会非常突出。
- 增加数据统计:在管理后台用 ECharts 展示每日新增用户、商品发布量、成交订单数等趋势图。这块可以用 SQL 的
GROUP BY加日期函数实现,难度不大但视觉效果很好。 - 商品推荐:基于分类和浏览量做简单的推荐列表,比如“看过此商品的人还看过”。
如果你要写在论文或说明文档里,记得强调项目的业务完整性和技术选型原因,尤其是为什么选 Spring Boot、MyBatis-Plus、JWT 这些技术栈,都能讲出合理的取舍逻辑。
5.2 代码规范化与注释补充的建议
这套源码的命名风格比较规范,但注释量不算多。既然是学习用,建议在核心业务方法上手动补充注释,比如GoodsServiceImpl里发布商品那段,把事务控制的必要性写清楚;JwtInterceptor里把 token 校验流程写清楚。注释不是为了给老师看,而是为了三个月后的你还能快速想起来代码逻辑。
我在编写源码时,刻意保持了 Controller 层代码的薄,Service 层代码的厚。你可以观察一下,Controller 里几乎没有业务判断,全部调 Service 方法,Service 方法之间也是单一职责。这种习惯在简历上写“熟悉企业级项目分层开发”时,是有底气说的。
5.3 部署上线时需要注意的几个坑
本地跑通之后,如果想把项目部署到云服务器,有几个坑提前告诉你们:
- 打包命令:执行
mvn clean package -DskipTests,生成的 jar 在target/目录下。不要用 IDE 自带的 Artifacts 打包,容易把依赖漏掉。 - 上传到服务器后,运行
java -jar secondhand.jar,记得在启动命令里指定环境配置,比如--spring.profiles.active=prod。 - 数据库迁移:用
mysqldump导出本地库,再到服务器导入。字符集注意保持 utf8mb4,不然中文乱码。 - 图片上传路径:本地绝对路径与服务器路径不同,需要修改文件上传配置,建议把图片存到固定的
/data/upload目录,并通过 Nginx 做静态资源访问。 - 端口:如果服务器上还有 Nginx,让 Nginx 监听 80 端口,转发到项目的 8080 端口。
6. 这套源码的边界与总结性心得
说了这么多,最后聊点实在的。
这套基于 Spring Boot 的二手交易系统,本质上是一个面向学习和二次开发的完整工程。它不像网上的碎片化 Demo 一样只演示某个功能点,而是把用户、商品、订单这三条核心业务线全部贯通,让初学者真正体会到“做一个完整系统”是什么感觉。我在整理源码时,也刻意加入了一些实际开发中才会遇到的细节,比如逻辑删除、事务回滚、文件上传重命名、DTO 与实体分离、拦截器放行配置等。这些都是真实工作经验里沉淀下来的习惯,不是教科书上能直接抄到的。
你真的把它跑起来,再对着代码逐行去看,一定会有收获。我猜很多人下载了源码,第一步就卡在环境配置上,这里再强调一句:遇到问题先看控制台日志,百度错误信息时去掉自己项目特有的路径,只看核心异常关键字,大多数问题都能解决。
最后再分享一个小技巧:拿到任何 Spring Boot 开源项目,别急着看业务代码,先看pom.xml和application.yml,这两个文件决定了项目的技术栈和运行环境。理解它们之后,再顺着启动类去追踪一次完整的请求流程,比漫无目的地翻代码高效得多。
这套源码作为起点,你可以继续在上面加功能、优化性能、甚至重构代码,把它慢慢变成你自己的项目。动手去改、去踩坑,才是提升最快的方式。
本文还有配套的精品资源,点击获取