ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot校园二手市场毕设全攻略:从建表到订单状态流转避坑

Spring Boot校园二手市场毕设全攻略:从建表到订单状态流转避坑 简介面向计算机相关专业学生与毕业设计开发者的基于SpringBoot的校园二手市场平台完整提供项目源码与数据库文件。该设计经导师指导并获评审99分代码完整、结构清晰可直接运行既适合毕业设计、课程设计与期末大作业场景也可作为Java全栈实战练习项目。压缩包共289个文件整体约6.9MB以Java源码、XML配置、Vue与JS前端脚本、SQL数据库脚本为主要内容兼顾前后端核心代码与项目配置说明目录层次分明便于查阅。目前已有94人学习/下载。配套数据库脚本与前后端分离骨架可快速还原运行的校园二手交易网站适合需要快速搭建毕设项目或进行SpringBootVue实践训练的同学。1. 校园二手市场平台Java 毕业设计里那个“看着简单、跑起来事多”的项目每年毕业季基于 Spring Boot 的校园二手市场平台都会成批出现在 Java 毕业设计源码包里跟着源码一起给你的还有一份初始化数据库脚本。功能看着不复杂注册登录、发布商品、搜索下单、管理后台可真跑起来却常翻车——端口被占、MySQL 时区报错、图片 404、订单状态越改越乱。下面按二次开发的顺序把建表到跑通再到答辩演示的链路讲透。适合两类人拿这套源码数据库做毕业设计的 Java 学生和借源码练课程的初学者。价值不在 CRUD而在一条完整交易链发布、搜索、下单、状态流转、确认收货。跑明白它Spring Boot MyBatis-Plus MySQL 这套常用栈就吃透了大半。最容易翻车的不是登录注册而是订单状态流转和文件上传映射——前者写错出脏数据后者漏配置全是 404。每步给可直接抄的代码和参数。2. 先把数据库拆明白这个平台管哪些数据四张表怎么设计2.1 模块划分前台交易与后台管理的数据落点拿到源码先别急着启动先看数据库脚本。很多同学栽在第一印象上打开脚本看到七八张表感觉复杂其实按模块一拆就很清楚。校园二手市场平台的典型模块和落点表大概是这么一张对照模块核心操作落点表用户注册、登录、个人信息、我的发布/我的订单tb_user、tb_goods、tb_order商品发布、列表、搜索、详情、下架tb_goods交易下单、取消、确认收货tb_order收藏收藏/取消、我的收藏tb_favorite后台管理用户禁用、商品下架、订单查看上面三张表的管理接口把模块和表对上之后你会发现整个项目就是围绕这三四张表在做数据库增删改查后台管理只是给同几张表加了权限控制的入口。这个认知很重要答辩时老师问“你项目里有哪些表”你要能一句话说清每张表服务哪个模块而不是对着脚本念字段。一个容易被忽略的设计点商品和订单是一对多的关系一张商品只能有一笔有效订单但可以被多个用户收藏订单表里同时冗余了 buyer_id 和 seller_id而不是下单时再去 join 商品表拿卖家。这是二手交易场景常见的省事写法也是答辩时可以说上两句的“为什么这么设计”。2.2 建表语句用户、商品、订单、收藏的字段和索引怎么定核心表通常长这样我按常见的毕业设计写法给一份可直接用的建表语句字段注释都写全方便你对照源码理解。-- 用户表 CREATE TABLE tb_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, username VARCHAR(32) NOT NULL COMMENT 登录账号, password VARCHAR(64) NOT NULL COMMENT 加密后的密码, nickname VARCHAR(32) DEFAULT NULL COMMENT 昵称, phone VARCHAR(16) DEFAULT NULL COMMENT 联系电话, avatar VARCHAR(255) DEFAULT NULL COMMENT 头像URL, role TINYINT NOT NULL DEFAULT 1 COMMENT 角色1普通用户 2管理员, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1正常 0禁用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;三个细节值得注意。password 字段是加密后的密文而不是明文很多源码包用 MD5 加盐答辩时老师如果问密码安全你至少能说清楚“为什么不能存明文”。role 单独放一个字段而不是建角色表毕业设计级别这么做足够管理后台登录也走同一张表只是校验 role2。字符集用 utf8mb4 而不是 utf8因为商品描述里经常有 emojiutf8 存 emoji 会直接报 “Incorrect string value”。-- 商品表 CREATE TABLE tb_goods ( id BIGINT NOT NULL AUTO_INCREMENT, seller_id BIGINT NOT NULL COMMENT 发布者ID, title VARCHAR(64) NOT NULL COMMENT 商品标题, description TEXT COMMENT 详细描述, category VARCHAR(16) NOT NULL COMMENT 分类教材/数码/生活/其他, price DECIMAL(10,2) NOT NULL COMMENT 出售价, original_price DECIMAL(10,2) DEFAULT NULL COMMENT 原价用于展示折扣, cover_image VARCHAR(255) DEFAULT NULL COMMENT 封面图URL, images VARCHAR(1024) DEFAULT NULL COMMENT 多图URL逗号分隔, status TINYINT NOT NULL DEFAULT 0 COMMENT 0在售 1下架 2已售出, view_count INT NOT NULL DEFAULT 0 COMMENT 浏览次数, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_seller (seller_id), KEY idx_category_create (category, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表;价格用 DECIMAL(10,2) 而不是 FLOAT这是经典考点浮点做金额计算会有精度问题0.10.2 不等于 0.3。status 用 TINYINT 加注释而不是字符串程序里用常量比对避免“在售”和“onSale”这种前后端对不上的情况。索引方面列表页最常见的查询是“按分类时间排序”所以我建了 (category, create_time) 的联合索引如果你后续加了价格区间搜索再加 (category, price) 也不迟。-- 订单表 CREATE TABLE tb_order ( id BIGINT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单号, goods_id BIGINT NOT NULL COMMENT 商品ID, buyer_id BIGINT NOT NULL COMMENT 买家ID, seller_id BIGINT NOT NULL COMMENT 卖家ID, amount DECIMAL(10,2) NOT NULL COMMENT 成交金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待付款 1待发货 2待收货 3已完成 4已取消, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME DEFAULT NULL COMMENT 付款时间, finish_time DATETIME DEFAULT NULL COMMENT 完成时间, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_buyer (buyer_id), KEY idx_seller (seller_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;订单号 order_no 必须唯一程序里生成常见做法是时间戳加随机数别用自增 id 当订单号给用户看会暴露业务量。收藏表 tb_favorite 就简单得多id、user_id、goods_id、create_time唯一索引建在 (user_id, goods_id) 上防止重复收藏。2.3 初始化脚本一条命令建库建表顺带把演示数据灌进去源码包里的数据库脚本一般是一个完整的 init.sql开头是建库语句中间建表最后插演示数据。我习惯在命令行里直接导入比打开 Navicat 手工执行更不容易漏步骤mysql -uroot -p init.sql提示执行前确认 init.sql 第一行是不是 CREATE DATABASE IF NOT EXISTS campus_market没有这句的话表会建到默认库里去程序连 campus_market 就会报 “Table doesnt exist”。脚本末尾的演示数据不要删。常见的量级是十来个用户、三五十条商品、几条订单这个量刚好能让分页效果展示出来又不会让列表页加载变慢。还有一个关键检查项init.sql 里有没有内置管理员账号比如 usernameadmin、role2。很多源码包都带但也有人导入后发现后台登录不进去十有八九是演示数据里漏了管理员。另外如果手里没有现成的建表脚本MyBatis-Plus 可以根据实体类反推生成建表语句但毕业设计不建议这么干——手工建表能让你把每个字段的意义讲清楚答辩时这分丢不起。3. 用 Spring Boot 把骨架立起来版本搭配、配置与登录链路3.1 版本搭配JDK、Spring Boot、MyBatis-Plus 别打架“springboot版本太高”是这个项目最常见的第一道坎。源码里写着 Spring Boot 2.7你电脑装了 JDK 17或者反过来源码是 3.x、你还在用 JDK 8编译直接报错。先记住这张搭配表Spring Boot 版本JDKServlet APIMyBatis-Plus 依赖2.7.x8 / 11javax.*mybatis-plus-boot-starter3.x17jakarta.*mybatis-plus-spring-boot3-starter区分点就一个代码里 import 的是 javax.servlet 还是 jakarta.servlet。Spring Boot 3.x 把包名整个换成了 jakarta如果你看到源码里写的是 javax 但 pom 里是 3.x那就是别人改了一半的工程跑起来会报“程序包javax.servlet不存在”。反过来源码清爽地写着 javax就老老实实配 JDK 8 或 11别为了尝鲜升到 17。我的建议很直接毕业设计项目以“能跑、能讲、能改”为第一目标。拿到 2.x 的源码就用 2.7 JDK 8/11 MyBatis-Plus 3.5.x拿到 3.x 的就保持 3.x JDK 17。迁移版本不是不能做但那是答辩通过之后的加分项不是现在的优先级。查看当前 Java 版本用 java -version确认 Maven 版本用 mvn -v先让这两条命令的输出和 pom.xml 对得上再谈启动。3.2 项目结构与 application.yml先认清包再改配置一个规范的 Spring Boot 项目结构长这样路径按 com.example.campusmarket 这类包名组织src/main/java ├── controller/ 接口层只做参数接收和结果返回 ├── service/ 业务层订单、商品状态都在这 │ └── impl/ ├── mapper/ MyBatis-Plus 的 Mapper 接口 ├── entity/ 表实体和数据库字段一一对应 ├── config/ 配置类跨域、静态资源、分页插件 ├── common/ 统一返回结果、异常、工具类 └── CampusMarketApplication.java 启动类 src/main/resources ├── application.yml └── static/ 前端打包后的文件放这里如果有有的源码包把实体写成 pojo有的叫 domain不影响理解。真正要警惕的是没有 controller/service 分层、全挤在一个类里的写法那种工程答辩很难讲清楚。确认结构没问题后打开 application.yml重点改三个位置server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_market?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root # 改成你本机 MySQL 的密码 servlet: multipart: max-file-size: 5MB max-request-size: 20MB jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted file: upload-dir: ./upload逐个说参数。url 里的 serverTimezoneAsia/Shanghai 是必须的不加它 MySQL 8 的驱动会直接报时区错误后面避坑章节单独讲。max-file-size 设 5MB手机拍的商品图通常一两兆够用设太小演示时传图失败很尴尬。map-underscore-to-camel-case 让 user_name 自动映射成 userName这个开关不开查询结果里下划线字段全是 null。log-impl 是控制台打印 SQL调试阶段务必开着能让你看清 MyBatis-Plus 到底执行了什么语句答辩演示前记得去掉或者把日志级别调到 warn不然控制台滚屏会显得很不专业。file.upload-dir 是我额外加的配置很多源码包会把上传目录写死在代码里改成配置项后换机器部署不用改代码后面静态资源映射也要用到这个值。3.3 登录注册链路从实体类到接口MyBatis-Plus 替你做掉一半登录是整条链路的入口也是 MyBatis-Plus 最典型的使用场景。实体类标注表名和主键策略Mapper 继承 BaseMapper增删改查的基础方法就全有了这就是为什么说 MyBatis-Plus 能替你做掉一半 CRUDData TableName(tb_user) public class User { TableId(type IdType.AUTO) private Long id; private String username; private String password; private String nickname; private String phone; private String avatar; private Integer role; private Integer status; private LocalDateTime createTime; }注意两个注解缺一不可TableName 指明表名否则 MyBatis-Plus 会把 User 映射到 user 表你如果建表用了 tb_ 前缀直接报表不存在TableId(type IdType.AUTO) 告诉它主键是数据库自增不加这个insert 时它会尝试自己生成主键。实体字段是驼峰表字段是下划线正好被 3.2 里的 map-underscore-to-camel-case 处理掉。登录逻辑写一个 LoginDTO 接收用户名密码Service 层用 lambdaQuery 按用户名查再比对密码Service public class UserServiceImpl extends ServiceImplUserMapper, User implements UserService { Override public String login(String username, String password) { User user lambdaQuery() .eq(User::getUsername, username) .eq(User::getStatus, 1) .one(); if (user null || !Md5Util.verify(password, user.getPassword())) { throw new BusinessException(用户名或密码错误); } // token 有效期 2 小时过期重新登录 return JwtUtil.createToken(user.getId(), user.getRole(), 2 * 60 * 60); } }lambdaQuery 是 MyBatis-Plus 提供的类型安全查询方式写 eq(User::getUsername, username) 而不是手拼 SQL编译期就能发现字段名写错。查询条件为什么要带 status1因为后台可能禁用了某个用户禁用之后他不能再登录这个细节是答辩时“你的系统怎么处理用户封禁”的答案。密码比对用 Md5Util.verify如果源码包是直接存明文建议改成 MD5 加盐或 BCrypt这个改造很小但很加印象分。Controller 层只做参数接收真正的逻辑全在 Service 里RestController RequestMapping(/api/user) public class UserController { PostMapping(/login) public ResultString login(RequestBody LoginDTO dto) { return Result.success(userService.login(dto.getUsername(), dto.getPassword())); } }提示登录返回的 token 后续要靠拦截器校验否则任何接口都能直接访问。很多源码包懒到连拦截器都不写你答辩前补一个简单的 HandlerInterceptor只放行 /api/user/login 和注册接口其他都要带 token——这是高频考点。4. 商品发布、搜索与下单把二手交易主链路串起来4.1 商品发布与图片上传文件存哪、URL 怎么回给前端二手交易的核心资产就是商品图。图片上传的常见做法是存本地磁盘数据库只存相对 URL而不是把图片转成 base64 塞进数据库——后者会让表变得巨大查询变慢。上传接口长这样PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) { if (file null || file.isEmpty()) { throw new BusinessException(上传文件不能为空); } String original file.getOriginalFilename(); String ext original null ? .jpg : original.substring(original.lastIndexOf(.)); if (!Arrays.asList(.jpg, .jpeg, .png, .gif).contains(ext.toLowerCase())) { throw new BusinessException(仅支持 jpg/jpeg/png/gif 格式); } // 按日期分目录一个目录下文件太多会拖慢文件系统访问 String datePath LocalDate.now().toString(); String filename UUID.randomUUID().toString().replace(-, ) ext; File dir new File(uploadDir, datePath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir, filename)); return Result.success(/upload/ datePath / filename); }三件事是踩坑高发区。第一扩展名白名单必须做不校验的话用户传个 .jsp 上来配合某些容器配置就是安全漏洞毕业设计也要有安全意识。第二文件名用 UUID 重命名不用原始文件名否则两个用户都传“商品图.jpg”会互相覆盖中文文件名有时还会在 Linux 上乱码。第三按日期分目录一年后你的 upload 目录里不会躺着几千个扁平文件。文件存到磁盘后Spring Boot 默认不会把磁盘目录暴露成可访问 URL这就是“图片上传成功但访问 404”的根源。需要手动配静态资源映射Configuration public class WebMvcConfig implements WebMvcConfigurer { Value(${file.upload-dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 注意 addResourceLocations 结尾的 / 不能少 registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadDir /); } }addResourceHandler 定义的是浏览器访问路径addResourceLocations 定义的是磁盘真实路径。Windows 下要写成 file:./upload/ 或者 file:D:/xxx/upload/Linux 下是 file:/home/xxx/upload/相对路径 ./upload 会解析到当前工作目录打包后就在 jar 的同级目录。4.2 商品列表与分页搜索条件参数和分页插件的设置列表页是商品表被查询最频繁的场景通常要支持分类筛选、关键词模糊搜索、按时间排序。先定义一个查询参数对象再在 Service 里条件拼接public PageGoods search(GoodsQuery query) { // current 从第 1 页开始size 默认每页 12 条 PageGoods page new Page(query.getCurrent(), query.getSize()); LambdaQueryWrapperGoods wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.hasText(query.getCategory()) !全部.equals(query.getCategory()), Goods::getCategory, query.getCategory()) .like(StringUtils.hasText(query.getKeyword()), Goods::getTitle, query.getKeyword()) .eq(Goods::getStatus, 0) .orderByDesc(Goods::getCreateTime); return goodsMapper.selectPage(page, wrapper); }这里有个容易写错的点eq 的第一个布尔参数是“是否拼接这个条件”。如果不用这个重载直接 wrapper.eq(Goods::getCategory, query.getCategory())当 category 为 null 时MyBatis-Plus 会生成 WHERE category null查出来永远是空列表而不是“不过滤”。所以前端的分类下拉框传“全部”或者不传时必须让这个条件失效。status0 是硬性条件已下架和已售出的商品不能出现在列表里这行不能省。selectPage 依赖分页插件忘了配插件的话接口会返回全量数据列表页直接卡死Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }这个配置类在大多数源码包里都有没有的话你自己补上。分页参数 current 和 size 通常直接在 Controller 用 RequestParam 接收前端传 current1size12后端返回的 Page 对象里自带 total、pages 这些字段前端用来渲染分页条。4.3 下单与状态流转事务、并发和脏数据问题整个项目最值钱的一段代码是下单。二手商品只有一件两个买家同时下单必须只有一个能成功。状态机先定清楚常见的是0 待付款 → 1 待发货 → 2 待收货 → 3 已完成加一个 4 已取消作为分支。源码包里通常把“待付款”简化掉下单即进入待发货因为校园二手大多是线下见面交易。Transactional(rollbackFor Exception.class) public Long createOrder(Long goodsId, Long buyerId) { // 1. 查商品确认存在且在售 Goods goods goodsMapper.selectById(goodsId); if (goods null || goods.getStatus() ! 0) { throw new BusinessException(商品不存在或已下架); } if (goods.getSellerId().equals(buyerId)) { throw new BusinessException(不能购买自己发布的商品); } // 2. 生成订单号时间戳 4 位随机数演示够用 Order order new Order(); order.setOrderNo(DateUtil.format(new Date(), yyyyMMddHHmmss) RandomUtil.randomNumbers(4)); order.setGoodsId(goodsId); order.setBuyerId(buyerId); order.setSellerId(goods.getSellerId()); order.setAmount(goods.getPrice()); order.setStatus(1); orderMapper.insert(order); // 3. 商品置为已售出用 status 条件做乐观兜底防止重复下单 int rows goodsMapper.update(null, new LambdaUpdateWrapperGoods() .eq(Goods::getId, goodsId) .eq(Goods::getStatus, 0) .set(Goods::getStatus, 2)); if (rows 0) { throw new BusinessException(手慢了商品已被下单); } return order.getId(); }这段代码有三个值得在答辩时展开的点。第一是 Transactional(rollbackFor Exception.class)默认情况下只有运行时异常才触发回滚写 rollbackFor Exception.class 表示任何异常都回滚订单插入和商品状态更新必须在一个事务里否则会出现“订单建了但商品还在卖”的脏数据。第二是那个 update 的 where 条件UPDATE tb_goods SET status2 WHERE id? AND status0并发时两个人同时进来只有一个人 update 影响行数是 1另一个是 0从数据库层面挡住了重复下单这叫乐观锁思路比 synchronized 靠谱得多。第三是“不能购买自己发布的商品”的校验很多源码包漏了这个演示时一旦点到自己的商品就会暴露。确认收货的逻辑类似校验订单存在、状态是待收货、操作人是买家然后状态置为已完成写 finish_time。取消订单则要把商品状态从已售出回滚到在售注意回滚时也要带条件防止取消的同时商品被重新下单。5. 避坑清单这个 Spring Boot 项目最容易翻车的五个问题这一章是我帮人排过最多的问题汇总按出现频率排的序。每一条都是“现象 → 原因 → 解决”三段式遇到直接对号入座。5.1 启动失败端口被占和数据库连不上先看这两类日志现象启动日志一闪而过报 “Port 8080 was already in use”或者报 “Communications link failure” / “Access denied for user”。原因端口被占多半是上一个 java 进程没停干净或者装了其他服务占用 8080数据库连不上要么是 MySQL 服务没启动要么是账号密码、库名写错。解决先查端口再决定是杀进程还是换端口。# Windows 查看 8080 被谁占用 netstat -ano | findstr 8080 # Linux / macOS lsof -i:8080最省事的是直接换端口在 application.yml 里把 server.port 改成 8081连冲突进程都不用找。数据库这边先用命令行验证mysql -uroot -p能进去再核对 application.yml 里的 url 库名是不是 campus_market、密码是不是本机的。注意报 “Access denied” 就是密码错了报 “Unknown database” 才是库名错了两个不要搞混。5.2 MySQL 时区报错一看就是 serverTimezone 没配现象启动时抛 “The server time zone value Öйú±ê׼ʱ¼ä is unrecognized or represents more than one time zone”整段都是乱码。原因MySQL 8 的 JDBC 驱动强制要求 url 里带上 serverTimezone不然它不知道用哪个时区和你数据库会话。这个报错被传得很玄学其实原因非常明确。解决url 改一行就够url: jdbc:mysql://localhost:3306/campus_market?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai顺带检查 driver-class-name 是不是 com.mysql.cj.jdbc.Driver。如果你连的是 MySQL 5.x旧驱动是 com.mysql.jdbc.Driver这两个也会对不上。改完重启即可不用动数据库任何配置。5.3 MyBatis-Plus 表名对不上tb_user 还是 user现象接口报 “Table campus_market.user doesnt exist”或者 SQL 日志里查的表名没有 tb_ 前缀。原因MyBatis-Plus 默认把实体类名转下划线当表名User 对应 user。你的表是 tb_user实体类上没写 TableName就对不上了。解决两种方式任选其一但别混用。实体类上逐个加 TableName(tb_user)最直观、改动最小或者在 application.yml 里配全局表前缀mybatis-plus: global-config: db-config: table-prefix: tb_配了全局前缀之后所有实体会统一映射到 tb_ 开头的表如果源码里有一两张表没带 tb_ 前缀就会反过来报表不存在。我一般建议逐个实体加注解因为排查的时候一眼能看出来哪张表映射到哪不用猜。5.4 图片上传成功但访问 404静态资源配置漏了现象上传接口返回了 /upload/2025-06-01/xxxx.jpg浏览器打开直接 404开发环境正常打包成 jar 后图片全挂。原因Spring Boot 默认只映射 classpath:/static/ 下的静态资源磁盘目录不在这个范围必须自己加 ResourceHandler。打包后挂掉还有一个隐藏原因相对路径 ./upload 解析到的是“当前工作目录”你用 java -jar 启动时工作目录是 jar 所在目录但如果用 IDE 启动工作目录是项目根目录两边不一致。解决遵照 4.1 的 WebMvcConfig 配置把 addResourceLocations 指向 file: 开头的磁盘路径。然后统一启动方式开发用 IDE部署用 java -jar不要来回混图片目录有问题时先看日志里 uploadDir 实际解析到了哪个路径。顺手把上传目录加到 .gitignore 里别让一堆演示图混进提交。5.5 跨域和前端打包Vue 项目怎么接到 Spring Boot 上现象前端单独跑在 5173 端口调后端 8080 的接口浏览器报 “blocked by CORS policy” 或者 “Access-Control-Allow-Origin”。原因前后端端口不同浏览器同源策略拦截了请求。毕业设计最常见的前端是 Vue开发时前端起一个 dev server后端一个 8080必须放开跨域。解决后端加一个全局跨域配置最省事Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }allowedOriginPatterns 用 * 是因为 allowCredentials(true) 时 allowedOrigins 不能用 *这是 Spring 5.3 之后的规定直接抄这份配置就行。如果你的作品要演示“前后端一体”更稳的做法是 vue 项目 npm run build 之后把 dist 目录的内容拷到 Spring Boot 的 resources/static 下只启一个 8080 端口彻底没有跨域问题。注意 Vue Router 如果是 history 模式刷新非首页路径会 404需要加一个把找不到的路径转发回 index.html 的配置registry.addViewController(/{path:[^\\.]*}).setViewName(forward:/index.html);这条规则的含义是带点的路径如 .js、.css正常走静态资源不带点的路径全部转发到 index.html交给前端路由处理。6. 答辩前走一遍打包、部署和演示数据的事前准备6.1 一条命令打出可运行的 jar答辩前一周把“用命令行启动项目”练熟。在项目根目录执行mvn clean package -DskipTests-DskipTests 是跳过单元测试很多源码包自带的测试类在无数据库环境下会失败跳过比排查更快。打包完成后target 目录下会多出一个 xxx-0.0.1-SNAPSHOT.jar启动命令java -jar target/xxx-0.0.1-SNAPSHOT.jar演示机器上的数据库密码和你的开发机不一样怎么办不用重新打包加一个外部配置参数java -jar target/xxx-0.0.1-SNAPSHOT.jar --spring.config.additional-locationfile:./config/application.ymlconfig 目录下的 application.yml 会覆盖 jar 内的同名配置改完直接重启这就是你临时改密码的后悔药。别忘了先把 config/application.yml 里的数据库连接和上传目录都改成演示环境的实际值别等到现场手忙脚乱。6.2 演示数据与演示顺序答辩演示最怕的是临场找不到一个在售商品、图片点开全是裂图。提前准备一份演示专用的数据两个账号一个买家一个卖家加上内置管理员卖家账号下挂 35 条带图的商品覆盖两三个分类再准备一条“卖家已发货买家还没确认”的订单用来演示状态流转。演示顺序建议固定成一条业务闭环登录 → 搜索商品 → 看详情 → 下单 → 卖家端看订单 → 确认收货 → 后台看订单列表。每个环节控制在半分钟以内全程不切屏找数据。两个加分小细节。第一用 Spring Boot banner 生成器做个带自己姓名和“校园二手市场”字样的启动横幅替换 resources/banner.txt答辩老师在台下看你启动时一眼就能看到这是你定制过的项目。第二演示前把 MyBatis-Plus 的 SQL 日志关掉或把 logging.level 调到 warn控制台干净观感专业很多。老师追问时十有八九会问订单状态怎么流转、密码怎么存、图片存哪、事务怎么保证这些问题对应的其实就是 Java 基础和高频面试题里的事务、索引、安全那几个点把 4.3 和 5.4 这两节弄懂基本都能答上来。我第一次跑这类项目时就是在订单状态上栽的跟头演示时手快把前端状态改了后端状态没跟上订单卡在“待收货”动不了被老师追问状态机实现当场答得磕巴。之后养成的习惯是每做一个模块先把状态流转画在纸上再写代码就四五个值别嫌简单。到你这里答辩前把从登录到收货的完整流程手动过两遍每个状态下该谁操作、会触发什么变化做到心里有数这比背脚本有用得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表