简介:这是一套面向高校计算机相关专业学生与Java进阶学习者的二手交易平台项目源码,聚焦校园二手书籍与社区闲置物品交易场景,可作为毕业设计、课程设计或工程实训的完整参考方案。项目采用SpringBoot构建后端,前端以JSP页面配合CSS、JavaScript实现交互,涵盖用户管理、订单处理、收货地址、图片上传、商品分类等核心业务模块,目录结构清晰,便于按功能模块拆解学习。压缩包共119个文件,以42个Java源文件、36个JSP页面、22个XML配置为主,另含少量样式脚本与图片资源,整体约1.69MB,轻量易部署。目前已有295人学习下载,适合希望理解电商类系统分层设计、掌握SpringBoot与JSP整合开发的读者参考借鉴,也可在此基础上修改扩展,实现个性化功能。
1. 校园二手书交易系统:一份能跑通的 SpringBoot 毕设源码到底长什么样
每年毕业季,计算机专业的选题里总有一类经久不衰——二手交易平台。尤其是校园二手书籍交易这个方向,需求真实、场景清晰、功能边界好界定,答辩时老师也容易理解。但真正动手做的时候,很多人卡在第一步:从 GitHub 抄来的项目跑不起来,或者跑起来了但代码结构一团乱,文档和源码对不上,最后改都不知道从哪改。
这份基于 SpringBoot 的二手商城系统源码,定位就是校园二手书籍交易和社区二手交易两个场景。技术栈是 Java + SpringBoot + Vue 的前后端分离架构,附带笔记和文档。它解决的核心问题是:给你一套结构完整、能本地跑通、方便二次修改的毕设级工程,而不是一个只能看不能动的压缩包。适合正在做毕业设计、需要快速搭建可演示系统的同学,也适合想拿一个完整项目练手 SpringBoot 全栈开发的人。
我拿到这类资源的第一反应从来不是直接打开代码,而是先确认三件事:能不能跑、结构清不清楚、文档和代码是否对得上。下面按这个顺序拆。
2. 环境搭建与项目启动:从 JDK 版本到前后端联调的完整链路
2.1 技术栈确认与版本选型
在动手之前,先把这套系统的技术栈拆清楚。根据项目定位和常见实现方式,这类校园二手交易平台通常包含以下层次:
| 层次 | 技术选型 | 说明 |
|---|---|---|
| 后端框架 | SpringBoot 2.x | 主流稳定版本,生态成熟 |
| 持久层 | MyBatis / MyBatis-Plus | 配合 MySQL 使用 |
| 数据库 | MySQL 5.7 / 8.0 | 存储用户、商品、订单数据 |
| 前端框架 | Vue 2.x / 3.x | 配合 Element UI 或 Ant Design Vue |
| 构建工具 | Maven(后端)/ npm(前端) | 标准构建流程 |
| JDK 版本 | JDK 8 或 JDK 11 | 与 SpringBoot 版本匹配 |
这里有一个血泪经验:SpringBoot 版本和 JDK 版本的匹配关系不能拍脑袋。SpringBoot 2.x 系列建议搭配 JDK 8 或 11,如果你本地装的是 JDK 17 甚至更高,启动时大概率会遇到反射相关的报错。常见做法是先用java -version确认本地 JDK,再去看pom.xml里<parent>标签声明的 SpringBoot 版本。
提示:如果本地 JDK 版本过高,不要急着降级系统 JDK,可以在 IDE 里为这个项目单独指定 JDK 版本,IDEA 在 Project Structure 里就能配。
2.2 数据库初始化与配置修改
拿到源码后,第一步不是启动项目,而是先把数据库建好。通常项目里会有一个sql目录或者根目录下有一个.sql文件,里面包含建表语句和初始数据。
操作步骤:
- 打开 MySQL 客户端,创建一个新数据库,字符集用
utf8mb4:
CREATE DATABASE second_hand_market DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;- 导入项目自带的 SQL 文件:
mysql -u root -p second_hand_market < /path/to/project.sql- 修改 SpringBoot 配置文件
application.yml或application.properties中的数据库连接信息:
spring: datasource: url: jdbc:mysql://localhost:3306/second_hand_market?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver这里的参数需要逐个确认:serverTimezone必须设置,否则 MySQL 8.0 会报时区错误;characterEncoding=utf8保证中文不乱码;useUnicode=true配合前者使用。如果用的是 MySQL 5.7,驱动类可以写成com.mysql.jdbc.Driver,但推荐统一用com.mysql.cj.jdbc.Driver。
2.3 后端启动与常见报错处理
数据库配好之后,用 Maven 拉依赖并启动:
cd backend mvn clean install -DskipTests mvn spring-boot:run或者直接在 IDEA 里找到主启动类(通常叫Application.java或XxxApplication.java),右键运行。
启动过程中最常见的几个问题:
- 端口被占用:默认 8080 端口如果被其他程序占了,改
application.yml里的server.port即可。 - 依赖下载失败:Maven 中央仓库网络不稳定时,配置阿里云镜像源,在
settings.xml里加<mirror>配置。 - 数据库连接拒绝:检查 MySQL 服务是否启动、用户名密码是否正确、数据库名是否和配置文件一致。
2.4 前端启动与接口联调
前端通常是独立的 Vue 工程,目录名可能是frontend、vue或web。
cd frontend npm install npm run serve启动后前端默认跑在 8081 或 3000 端口。关键点是接口代理配置——前端需要把 API 请求转发到后端。在vue.config.js或vite.config.js里找到proxy配置:
// vue.config.js module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', // 后端地址 changeOrigin: true, pathRewrite: { '^/api': '' } } } } }target指向后端实际地址,pathRewrite决定是否去掉/api前缀。如果前端请求 404,先看这里的路径重写规则和后端 Controller 的@RequestMapping是否对得上。联调阶段建议打开浏览器开发者工具的 Network 面板,逐个接口确认请求 URL、请求方式和返回状态码。
3. 核心功能模块拆解:用户、商品、订单三条主线的实现逻辑
3.1 用户模块:注册登录与权限区分
校园二手交易平台的用户体系一般分两种角色:普通用户(买家/卖家)和管理员。核心功能包括注册、登录、个人信息管理、权限拦截。
登录态的实现方式常见有两种:Session 和 JWT。这套项目大概率用的是 JWT(JSON Web Token),因为前后端分离架构下 JWT 更自然。登录成功后后端返回一个 token,前端存到 localStorage,后续请求在 Header 里带上Authorization: Bearer xxx。
后端拦截器的典型写法:
// JwtInterceptor.java public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 从请求头获取 token String token = request.getHeader("Authorization"); if (token == null || !token.startsWith("Bearer ")) { response.setStatus(401); return false; } // 校验 token 有效性 try { JwtUtil.verify(token.substring(7)); return true; } catch (Exception e) { response.setStatus(401); return false; } } }这段代码的逻辑是:每次请求进来先检查 Header 里有没有合法 token,没有就直接返回 401。token.substring(7)是因为Bearer前缀占了 7 个字符。参数方面,JwtUtil.verify里通常包含签名密钥和过期时间校验,密钥一般配在application.yml里,不要硬编码在 Java 文件中。
注册环节需要注意密码加密。常见做法是用 BCrypt 或 MD5 加盐。如果项目里用的是明文存储,建议自己改成 BCrypt,答辩时也是一个加分项。
3.2 商品模块:书籍发布、分类检索与图片上传
商品模块是整个系统的核心。校园二手书籍交易场景下,商品就是二手书,字段通常包括:书名、作者、出版社、ISBN、成色、售价、原价、描述、图片、分类、发布者。
发布商品的流程:
- 前端表单收集书籍信息,包括图片文件
- 图片先上传到服务器(或对象存储),返回图片 URL
- 表单提交时把图片 URL 和其他字段一起发给后端
- 后端写入
product表,状态设为「待审核」或「已上架」
图片上传的后端接口示例:
@PostMapping("/upload") public Result upload(@RequestParam("file") MultipartFile file) { // 生成唯一文件名,避免冲突 String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename(); // 按日期分目录存储 String datePath = new SimpleDateFormat("yyyy/MM/dd").format(new Date()); File dest = new File(uploadDir + datePath + "/" + fileName); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } try { file.transferTo(dest); // 返回可访问的 URL return Result.success("/upload/" + datePath + "/" + fileName); } catch (IOException e) { return Result.error("上传失败"); } }关键参数说明:uploadDir是文件存储根路径,建议配在配置文件里而不是写死;UUID.randomUUID()保证文件名唯一;按日期分目录是为了避免单目录文件过多导致性能下降。如果部署到服务器,还需要配置静态资源映射,让上传的图片能通过 URL 访问。
分类检索方面,校园二手书通常按「教材/考研/考证/课外读物」等维度分类。后端用 MyBatis 的动态 SQL 实现多条件查询:
<select id="searchProducts" resultType="Product"> SELECT * FROM product <where> <if test="categoryId != null"> AND category_id = #{categoryId} </if> <if test="keyword != null and keyword != ''"> AND (title LIKE CONCAT('%', #{keyword}, '%') OR author LIKE CONCAT('%', #{keyword}, '%')) </if> <if test="minPrice != null"> AND price >= #{minPrice} </if> <if test="maxPrice != null"> AND price <= #{maxPrice} </if> AND status = 1 </where> ORDER BY create_time DESC </select><where>标签会自动处理第一个条件的 AND 前缀问题,<if>实现条件拼接。注意>和<是 XML 转义写法,不能直接写>和<。
3.3 订单模块:下单、状态流转与并发处理
订单模块涉及的状态比较多:待付款、已付款、已发货、已完成、已取消。校园二手书交易通常是线下自提或校内配送,所以状态流转可以简化。
下单的核心逻辑:
@Transactional public Result createOrder(Long productId, Long buyerId) { // 1. 查询商品并加锁,防止并发下单 Product product = productMapper.selectForUpdate(productId); if (product == null || product.getStatus() != 1) { return Result.error("商品已下架或不存在"); } if (product.getSellerId().equals(buyerId)) { return Result.error("不能购买自己发布的商品"); } // 2. 创建订单 Order order = new Order(); order.setOrderNo(generateOrderNo()); order.setProductId(productId); order.setBuyerId(buyerId); order.setSellerId(product.getSellerId()); order.setAmount(product.getPrice()); order.setStatus(0); // 待付款 orderMapper.insert(order); // 3. 更新商品状态为已售 product.setStatus(2); productMapper.updateById(product); return Result.success(order); }这里有几个关键点:@Transactional保证订单创建和商品状态更新在同一个事务里;selectForUpdate是悲观锁,防止两个人同时下单同一本书;订单号生成通常用时间戳加随机数。如果项目里没有做并发控制,答辩时被问到「两个人同时买同一本书怎么办」就会很被动。
注意:
selectForUpdate必须在事务中才生效,如果方法上没有@Transactional,加锁语句不会起作用。
4. 避坑与排查:源码跑不起来时先查这五个地方
4.1 启动报错「Table doesn't exist」
现象:SpringBoot 启动成功但访问接口时报错,日志显示某张表不存在。
原因:SQL 文件没有完整导入,或者导入到了错误的数据库。
解决:重新确认application.yml里的数据库名,然后重新执行 SQL 文件。导入前先USE 数据库名,避免导错库。
4.2 前端请求跨域被拦截
现象:浏览器控制台报 CORS 错误,请求被 blocked。
原因:前后端端口不同,浏览器同源策略拦截。
解决:后端加跨域配置类,或者在前端vue.config.js里配代理。后端配置示例:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns和allowedOrigins的区别:当allowCredentials(true)时,不能用allowedOrigins("*"),必须用allowedOriginPatterns。
4.3 图片上传后访问 404
现象:上传接口返回成功,但前端展示图片时 404。
原因:上传目录没有配置静态资源映射,或者路径不对。
解决:在配置类里加静态资源映射:
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadDir); }addResourceLocations里的路径必须以file:开头,且末尾要带/。
4.4 MyBatis 查询返回字段为 null
现象:数据库里有数据,但查询结果中某些字段是 null。
原因:数据库字段名和 Java 实体类属性名不一致,且没有配置驼峰映射。
解决:在application.yml里开启驼峰映射:
mybatis: configuration: map-underscore-to-camel-case: true这样create_time会自动映射到createTime。
4.5 项目能启动但页面空白
现象:后端正常,前端 npm run serve 也正常,但打开页面一片空白。
原因:可能是路由模式问题(history 模式需要后端配合),或者 API 请求全部失败导致页面无数据渲染。
解决:先打开浏览器控制台看有没有 JS 报错,再看 Network 面板确认接口请求是否返回了数据。如果是路由问题,把 Vue Router 的 mode 从history改成hash试试。
5. 二次开发与答辩加分:三个能让你脱颖而出的改造方向
5.1 加一个简单的推荐逻辑
原始项目大概率是「最新发布排前面」的排序方式。你可以加一个基于分类偏好的简单推荐:用户浏览某分类的商品时,记录浏览次数,首页优先展示浏览次数多的分类下的商品。不需要上协同过滤,一个ORDER BY view_count DESC就能让答辩老师觉得你有思考。
-- 在 product 表加一个浏览次数字段 ALTER TABLE product ADD COLUMN view_count INT DEFAULT 0; -- 每次详情页请求时更新 UPDATE product SET view_count = view_count + 1 WHERE id = #{id};5.2 订单超时自动取消
校园二手书交易里,买家下单后迟迟不付款会占着商品。加一个定时任务,超过 30 分钟未付款的订单自动取消,商品重新上架:
@Scheduled(fixedRate = 60000) // 每分钟执行一次 public void cancelTimeoutOrders() { List<Order> orders = orderMapper.selectTimeoutOrders(30); for (Order order : orders) { order.setStatus(4); // 已取消 orderMapper.updateById(order); // 商品重新上架 Product product = productMapper.selectById(order.getProductId()); product.setStatus(1); productMapper.updateById(product); } }别忘了在启动类上加@EnableScheduling。
5.3 文档和代码对齐检查
很多毕设项目的文档是最后补的,导致文档里写的接口和实际代码对不上。答辩前花半天时间,把文档里的功能模块和代码里的 Controller 逐个对照,确保每个功能都能在代码里找到对应实现。这一步看起来笨,但能避免答辩时被问到「你说的这个功能在哪个文件里」却翻不到的尴尬。
从那以后我每次拿到这类毕设源码,都强制自己先跑通再改代码,绝不在一堆报错的基础上动手。希望帮到你。
本文还有配套的精品资源,点击获取