恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Spring Boot+Vue在线拍卖系统设计与实现:并发控制、状态机与前后端分离实践

  • 首页
  • 资讯中心
  • /
  • Spring Boot+Vue在线拍卖系统设计与实现:并发控制、状态机与前后端分离实践

相关资讯

本地RAG+Agent实战:LangGraph编排与Streamlit调试 2026/10/7 10:14:42
SpringBoot+MySQL食物营养推荐系统:从数据表到推荐算法全解析 2026/10/7 10:14:42
Unity3D欢乐麻将源码解析:从环境搭建到牌型判定与网络同步 2026/10/7 10:14:42

最新资讯

第095篇 Flow 操作符进阶:debounce 与 combine
HTML表格从入门到不翻车:标签结构、单元格合并与样式全解析
HTML表格全链路实战:从标签结构到样式优化与数据导出
微软蓝牙鼠标3600拆解维修指南:微动更换与滚轮清洁
iOS代码保护与IPA加固实战:从源码混淆到防重签名全方案
SQL Server 行转列实战:从 CASE WHEN 到 PIVOT 的动态处理与性能优化

今日推荐

SSD不认盘怎么修?金士顿SV300板级排查与短接ROM进工厂模式
Unity 3D RPG开发:C#状态机与物理更新时机实战指南
AIoT开发工程师岗位全景:从嵌入式Linux到边缘计算与端侧AI部署

本周热门

MR25H40CDF + PIC18F65K40:工业记录仪高可靠存储实战
基于STM32的数控恒压恒流电源设计:从硬件到PID调参全解析
LT9211 MIPI重定时器原理与双路扇出实战指南

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

Spring Boot+Vue在线拍卖系统设计与实现:并发控制、状态机与前后端分离实践

发布时间:2026/10/7 10:19:42
Spring Boot+Vue在线拍卖系统设计与实现:并发控制、状态机与前后端分离实践 简介一套基于Java、SpringBoot、Vue与MySQL构建的校园在线拍卖系统面向高校毕业设计、课程设计与期末大作业场景解决了从零搭建前后端分离项目、实现商品展示、拍卖竞价、用户管理及支付结算等核心功能的需求。项目经过导师指导并获高分评价下载即可运行无需额外修改包含完整项目源码、SQL数据库脚本、开发工具与运行说明前后端代码均在其中。压缩包共837个文件、约17.26MB以Java后端业务、Vue前端页面、HTML/JS/CSS静态资源及SQL脚本为主附带Maven构建配置和Navicat数据库脚本结构清晰便于导入IDE快速启动或按模块扩展。系统界面美观、操作流程顺畅在CSDN已有69人学习适合作为毕业设计直接提交也可作为课设练手项目具有较高的参考与应用价值。1. 校园在线拍卖系统毕业设计里那个让你从零到答辩都能交差的组合校园在线拍卖系统说白了就是把线下跳蚤市场搬到网页上学生注册登录后把自己不用的教材、数码、生活用品挂上去其他人限时出价价高者得。这个题目在毕设里出现频率极高因为它覆盖了 Java、Spring Boot、Vue、MySQL 这四样东西的全部典型用法——后端有状态机、定时任务和并发控制前端有列表、倒计时和路由跳转数据库有竞价记录、订单关联和索引设计。无论你是想找个能跑通的项目交差还是想从里面抽出几个能讲清楚的模块写进答辩稿这套系统的性价比都相当可观。我见过太多人拿到类似的源码包导入 IDE 跑起来却全是报错最后连演示都要靠录像来凑。问题不在于代码本身而在于没人告诉你这套系统哪些地方容易踩坑以及每个模块为什么要这样写。这篇文章就按我做这类全栈项目时习惯的落地方案从架构、数据库、后端、前端到排错把整套逻辑拆开讲清楚。2. 设计与选型为什么是 Spring Boot Vue MySQL前后端分离的边界在哪2.1 系统用例与角色学生、卖家、管理员三条主线的交互校园在线拍卖系统的用户模型严格来说是三类普通学生、管理员以及任何一个学生只要发布商品就自动成为卖家。大部分源码包会把角色设计成两个表或者一个字段搞定——user表里加一个role字段值0表示学生1表示管理员。有些做得细的系统会把学生和卖家拆成两个 profile 表但对于毕设来说没有必要反而会让联调时多绕一层。核心用例其实只有五条注册登录、发布商品、浏览商品并出价、拍卖结束后生成订单、管理员审核和下架违规商品。你拿到任何一套源码先看这五条链路是否完整基本就能判断这个项目的完成度。有些源码只做了前端页面后端出价逻辑完全没有并发保护这种项目演示时看不出问题答辩时一问并发就露馅。我一般会建议把“发布商品”和“出价”这两条链路作为讲解重点因为它们恰好对应 Vue 表单提交 Spring Boot 接口接收 MySQL 事务写入的完整闭环。管理员那条线相对独立用一个/admin前缀的接口模块区分开就行不要混在普通用户接口里。2.2 前后端分离架构接口、鉴权、跨域的分工这套系统的技术栈决定了它天然是前后端分离的结构但实际上很多毕设源码的“分离”程度并不彻底。前端用 Vue 开发环境跑在localhost:8080后端 Spring Boot 跑在localhost:8081两者通过 HTTP 接口通信。开发阶段最大的问题就是跨域——浏览器的同源策略会拦截来自8080页面到8081接口的请求。常见的解决方案有两种源码包里绝大多数用的是后端 CORS 配置也就是加一个WebMvcConfigurer允许指定来源访问。另一部分是在前端vue.config.js里配 devServer 代理把/api开头的请求转发到后端地址。两种方式各有坑CORS 配置如果写得过于宽松比如allowedOriginPatterns(*)联调方便但答辩时容易被问安全策略代理方式开发时体验更接近线上但打包后放到 Spring Boot 里就要额外处理请求转发。我的习惯是后端写一个规范的 CORS 配置允许本地开发地址同时注意allowCredentials要跟allowedOrigins配合使用不能用了*又允许携带认证信息。线上部署时让前端打包后的静态文件直接由 Spring Boot 托管同一个端口下不存在跨域问题这是后话。2.3 项目目录结构一个可交付的 split 工程怎么摆拿到源码第一件事不是急着启动而是先看目录。一套结构清晰的源码通常长这样auction-system/ ├── backend/ # Spring Boot 后端 │ ├── src/main/java/... │ ├── src/main/resources/ │ └── pom.xml ├── frontend/ # Vue 前端 │ ├── src/ │ ├── package.json │ └── vue.config.js └── sql/ └── auction.sql # 数据库脚本后端代码里至少应该有controller、service、mapper、entity、config、common这几个包。common里放统一返回结果类ResultT、全局异常处理器、分页参数封装这些是答辩时能讲“规范”的加分细节。前端至少应该有views、router、api、components、store如果用 Vuex这几个目录。有人会问为什么不用controller里直接写 SQL或者用 JPA 省掉 mapper你可以这么做但效果完全不同。controller写 SQL 的项目代码量少但极难维护稍微改一个字段就得在所有调用点同步修改JPA 虽然省事但复杂查询和批量更新时调试困难而且答辩老师大概率会追问 SQL 优化。用 MyBatis 配合 XML 或注解 SQL是这套技术栈里最稳的选型。提示如果源码里把前后端混在同一个工程里甚至用 JSP 写页面那它用的就不是真正的 Vue而是挂了个名字。这种项目跑起来容易但你学不到前后端分离的完整流程答辩时也很难讲清楚。3. 数据库建模拍卖系统的五张核心表与字段陷阱3.1 用户表与商品表的字段设计学号、状态、封面存储数据库是整个拍卖系统最容易翻车的部分因为拍卖的核心逻辑不是计算而是状态。先看用户表除了常规的id、username、password、real_name、student_no、phone之外通常还有一个credit_score整数字段默认 100。这个字段是拍卖系统特有的——出价后不履约信用分就往下扣分数低于某个阈值就不能参与拍卖。这个设计在答辩时特别好讲因为它引出了业务规则的取舍。password字段必须用 BCrypt 加密存储源码包里如果明文存密码属于减分项。Spring Security 自带BCryptPasswordEncoder哪怕你没用 Spring Security 的过滤链单独调这个工具类加密也能做到。MySQL 字段类型用varchar(60)BCrypt 密文是定长的。商品表item是第二张核心表字段包括title、description、category_id、cover_image、starting_price、current_price、step_price、seller_id、status、start_time、end_time、version、create_time。这里有两个坑值得讲。第一个坑是价格类型。拍卖系统的价格必须用decimal(10,2)不能用float或double。浮点数在二进制里没法精确表示两个学生连续出价几次比较大小可能没问题但生成订单做金额累加时就会出现0.30000000000000004这种结果。第二个坑是current_price和starting_price的关系。出价记录表里能反推当前价但商品表里也要冗余存一个current_price否则每次显示商品列表都要子查询取最高出价列表页性能会很差。这是典型的空间换时间做法答辩时可以主动提。3.2 竞价记录表出价、版本号、外键的唯一约束竞价记录表bid_record是这套系统的灵魂它记录每一次出价行为。字段设计上要有id、item_id、user_id、bid_price、create_time再加一个is_auto_bid——不少源码实现了自动出价代理出价功能这个布尔字段就是给扩展功能留的口子。表的核心约束有两个。第一(item_id, bid_price)不应该有唯一约束因为两个学生可能同时出同一个价虽然真正落库时只有一个能成功但数据库层面不能把这个写死。第二item_id user_id上要建普通索引因为按商品查历史出价的频率很高。索引用idx_item_user(item_id, user_id)这种复合索引即可查询WHERE item_id ? ORDER BY bid_price DESC时能直接命中。另一个细节是出价金额的“步进约束”。大多数拍卖系统要求新出价必须比当前价高至少一个最小加价幅度比如step_price是 5 元那新价格必须是current_price step_price的整数倍。这个约束写在应用层Service里而不是数据库的 check 约束因为不同拍卖对步进规则可以不一样改业务时不想动表结构。3.3 订单表与状态流转从拍卖成交到交易完成的幂等设计拍卖结束后系统要为最高出价者生成订单。订单表orders的字段有id、item_id、buyer_id、seller_id、amount、status、pay_deadline、create_time、update_time。关键约束是item_id加唯一索引因为一件商品最终只能生成一个有效订单。有些源码没有这个唯一索引定时任务重复执行时就会生成两条订单这就是典型的代码缺陷。状态字段建议用tinyint不要用字符串。0 - 待支付、1 - 已支付待发货、2 - 已发货、3 - 已完成、4 - 已取消。用字符串的好处是可读性强但坏处是 Java 里要写很多equals判断MyBatis 返回结果也要做映射tinyint配枚举类是最常见的选择。订单生成的时机也很讲究不是拍卖时间一到就立刻生成而是定时任务扫描到end_time已过、且status 进行中的商品时生成。如果这个商品最终没有任何出价记录状态直接置为已流拍不生成订单。这个时序逻辑在代码里要处理干净否则会出现订单买家为空的情况。4. 后端实现Spring Boot 的出价接口、状态机与定时任务4.1 出价接口的最小实现数据库行锁与乐观更新出价接口是整套系统的核心也是最容易被问并发的地方。先看一个最小可实现的接口代码RestController RequestMapping(/api/auction) public class BidController { Autowired private BidService bidService; PostMapping(/bid) public ResultBidVO bid(RequestBody BidRequest req) { Long userId StpUtil.getLoginIdAsLong(); // 当前登录用户 return Result.ok(bidService.bid(req.getItemId(), userId, req.getBidPrice())); } }接口只做参数接收和身份获取真正的逻辑在 Service 层。Service 里最核心的方法是bid它需要保证同一个商品在同一时间只能有一次成功出价Transactional(rollbackFor Exception.class) public BidVO bid(Long itemId, Long userId, BigDecimal price) { // 1. 查询商品并加行锁防止并发读到同一份数据 Item item itemMapper.selectForUpdate(itemId); if (item null) { throw new BizException(商品不存在); } if (item.getStatus() ! 1) { // 1 表示拍卖中 throw new BizException(该商品当前不在拍卖中); } if (item.getEndTime().isBefore(LocalDateTime.now())) { throw new BizException(拍卖已结束); } if (price.compareTo(item.getCurrentPrice().add(item.getStepPrice())) 0) { throw new BizException(出价不能低于当前价 加价幅度); } // 2. 插入竞价记录 BidRecord record new BidRecord(); record.setItemId(itemId); record.setUserId(userId); record.setBidPrice(price); bidRecordMapper.insert(record); // 3. 更新商品当前价 int rows itemMapper.updateCurrentPrice(itemId, price, item.getVersion()); if (rows 0) { throw new BizException(出价失败请重试); } return new BidVO(record.getId(), price); }selectForUpdate是 MySQL 的行锁SELECT * FROM item WHERE id ? FOR UPDATE在事务提交前其他事务对这个商品的更新都会阻塞。这保证了第 2 步和第 3 步之间不会被插入其他出价记录。updateCurrentPrice里带了version条件是乐观锁兜底——万一行锁没生效版本号不一致也能让更新失败从而避免把旧价钱写回去。这段代码有两点值得注意。第一Transactional必须加在bid方法上因为行锁要等整个事务提交才释放如果方法没有事务包裹锁会在 SQL 执行完就释放逻辑全乱。第二异常要抛出并回滚BizException是自定义运行时异常不能抓了吞掉。有些源码在 Controller 里直接处理业务异常这会让事务回滚失效属于典型错误。4.2 拍卖状态机与定时任务EnableScheduling 下的倒计时处理拍卖结束后需要自动生成订单这里用 Spring Boot 的定时任务最合适。在启动类加EnableScheduling然后写一个扫单任务Component public class AuctionScheduler { Autowired private OrderService orderService; Scheduled(cron 0 */1 * * * *) public void closeExpiredAuctions() { // 找出所有已过期、状态仍为“拍卖中”的商品 ListItem expiredItems itemMapper.selectExpired(1, LocalDateTime.now()); for (Item item : expiredItems) { try { orderService.createOrder(item); } catch (Exception e) { log.error(商品关闭失败, itemId: {}, item.getId(), e); } } } }selectExpired的 SQL 大概是SELECT * FROM item WHERE status #{status} AND end_time #{now}。有人会问为什么不在启动时一次性把所有到期的商品都处理掉因为运行时随时可能会有新的拍卖到期定时任务每隔一分钟扫一次把已经到期的商品状态流转掉是成本最低也最可靠的做法。定时任务的边界要注意不要在一个事务里把所有过期商品全部处理而是一批一批来每个商品单独一个事务。如果某个商品生成订单时出异常不能影响其他商品的状态流转所以循环内部要try-catch只记日志不中断任务。状态流转用枚举维护避免散落的魔法数字。比如public enum ItemStatus { AUCTIONING(1), SOLD(2), FLOW(3), OFF_SHELF(4), TIME_OUT(5); private final int code; ItemStatus(int code) { this.code code; } public int getCode() { return code; } }2 是已成交3 是流拍4 是管理员下架5 是超时未支付。这套枚举就是拍卖生命周期最直观的体现答辩时把这张状态表列出来比任何代码都更有说服力。4.3 接口鉴权JWT 或 token 拦截器的落地方式拍卖系统的接口不能裸奔至少需要登录后才能调用出价、发布商品等接口。源码里常见的做法是 JWT流程是登录时生成 token 返回给前端前端存在 localStorage 里之后每次请求带上Authorization: Bearer token后端通过拦截器校验。Spring Boot 里实现一个TokenInterceptor是常规做法Component public class TokenInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { String jwt token.substring(7); // 解析 JWT成功则放行 if (JwtUtil.parseToken(jwt)) { return true; } } response.setStatus(401); return false; } }注册拦截器时要注意放行路径登录、注册、首页商品列表、商品详情这些接口必须白名单否则前端一打开首页就被 401 拦了。常见的做法是/api/auth/**、/api/item/list、/api/item/detail放行/api/bid/**、/api/order/**需要登录。提示如果使用 Sa-Token 或 Spring Security拦截逻辑框架已经封装好但你依然需要理解 token 校验的原理。答辩时被问到“怎么防止别人伪造 token”回答思路是“JWT 用密钥签名篡改后验签失败”这句话比背一堆框架 API 有用得多。5. 前端实现Vue 商品列表、倒计时与打包进 Spring Boot5.1 Vue 页面结构与路由list、detail、publish 三个关键视图如何串起来前端页面最少需要四个视图首页商品列表ListView、商品详情与出价区DetailView、发布商品PublishView、个人中心ProfileView。路由配置用 Vue Router把三个关键业务串起来import ListView from ../views/ListView.vue; import DetailView from ../views/DetailView.vue; import PublishView from ../views/PublishView.vue; const routes [ { path: /, name: list, component: ListView }, { path: /item/:id, name: detail, component: DetailView }, { path: /publish, name: publish, component: PublishView, meta: { requiresAuth: true } }, { path: /profile, name: profile, component: ProfileView, meta: { requiresAuth: true } } ];meta.requiresAuth配合 Vue Router 的全局前置守卫做登录跳转router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }); } else { next(); } });PublishView页面提交的数据结构要和后端Item实体的字段一一对应尤其是start_time和end_time前端提交的是带时区的 ISO 字符串还是yyyy-MM-dd HH:mm:ss必须在接口文档里定死。很多源码前后端跑不通就是时间格式没对齐。5.2 倒计时组件用 setInterval 计算剩余时间别用本地时间拍卖页的倒计时是最容易出问题的前端组件。很多同学直接用浏览器本地时间减end_time一旦用户电脑时间不对倒计时就乱了。正确做法是进入详情页时请求后端拿到服务器当前时间serverTime和拍卖截止时间endTime算出剩余毫秒数再用setInterval每秒减 1000 刷新显示。template div classcountdown :class{ danger: remaining 60000 } 剩余 {{ timeText }} /div /template script export default { props: { endTime: { type: String, required: true }, serverTime: { type: String, required: true } }, data() { return { remaining: 0, timer: null }; }, mounted() { const end new Date(this.endTime.replace(/-/g, /)).getTime(); const now new Date(this.serverTime.replace(/-/g, /)).getTime(); this.remaining end - now; this.timer setInterval(() { this.remaining - 1000; if (this.remaining 0) { clearInterval(this.timer); this.$emit(timeup); } }, 1000); }, computed: { timeText() { const h Math.floor(this.remaining / 3600000); const m Math.floor((this.remaining % 3600000) / 60000); const s Math.floor((this.remaining % 60000) / 1000); return ${this.pad(h)}:${this.pad(m)}:${this.pad(s)}; } }, methods: { pad(n) { return n 10 ? 0 n : String(n); } }, beforeUnmount() { if (this.timer) clearInterval(this.timer); } }; /script这里有个隐含的细节new Date()解析字符串时2025-05-20 12:00:00这种格式在部分浏览器会被当成 UTC 时间处理导致偏移 8 小时。把字符串里的-替换成/可以规避这个坑这就是代码里replace(/-/g, /)的原因。倒计时归零后不能依赖页面自己去改状态要向后端查询一次商品最新状态因为服务端可能已经把它标记为“已成交”或“流拍”。5.3 前端打包与部署build 产物如何放进 Spring Boot 的 static 目录开发时前后端分两个端口跑部署时最省事的方案是把前端打包成静态文件扔进 Spring Boot 的src/main/resources/static目录这样整个系统只有一个端口。前端的 API 请求路径要从/api/...出发因为前后端端口相同同源不存在跨域。打包命令是npm run build产物在frontend/dist目录。把dist下的index.html和assets文件夹复制到后端的static目录即可。Spring Boot 会自动把static作为静态资源根目录访问http://localhost:8080直接打开首页。但这条路有个经典坑前端用了 history 模式路由时直接访问http://localhost:8080/item/3会 404因为 Tomcat 没有这个物理路径。解决方案有两种。第一路由改用 hash 模式URL 变成http://localhost:8080/#/item/3刷新时不会请求后端第二后端加一个 fallback 控制器把所有非/api开头的路径都转发到index.htmlController public class SpaController { RequestMapping(value {/, /item/**, /publish, /profile, /login}) public String forward() { return forward:/index.html; } }我推荐开发环境用 history 模式、部署时用 hash 模式的做法虽然 URL 不太美观但少一层配置就少一个坑。源码包里如果已经用了 history那你必须同时确认后端 fallback 是否存在否则演示时刷新页面直接变白屏这种翻车现场在答辩时最容易出现。6. 避坑排查在线拍卖系统跑起来后最容易翻车的五个问题6.1 并发重复出价超卖与重复扣款的根因和修复现象两个学生同时盯着同一件商品都点了出价数据库里出现两条相同价位的出价记录商品当前价只更新到其中一个价位另一个也提示“出价成功”。原因Service 层没有加锁或者加了锁但事务范围不对。先查商品、再插入记录、再更新价格这三步之间没有隔离两个线程各自读到同一个current_price各自以为自己出价成功。解决用SELECT ... FOR UPDATE对商品记录加行锁保证同一时间只有一个线程能对这单商品做完整出价流程。另外更新价格时带version条件做乐观锁兜底UPDATE item SET current_price #{price}, version version 1 WHERE id #{id} AND version #{oldVersion}如果更新影响行数为 0说明数据被别人改过了直接报“出价失败请重试”。6.2 时间差 8 小时时区导致的拍卖提前结束现象本地测试一切正常部署到服务器之后商品页显示的截止时间总比预期早 8 小时有时刚发布就显示“已结束”。原因MySQL 连接串没有指定serverTimezone驱动用的是服务器默认时区而 Java 的LocalDateTime用的是系统时区。当两者不一致时间就会在写入或读取时发生偏移。很多服务器默认时区是 UTC中国时间是 UTC8刚好差 8 小时。解决JDBC 连接串里明确写serverTimezoneAsia/Shanghai例如jdbc:mysql://localhost:3306/auction?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai同时检查 MySQL 服务端时区SHOW VARIABLES LIKE %time_zone%如果不是08:00可以在连接串里追加useSSLfalse并把serverTimezone写死。前端那边不要把字符串当本地时间再转一次统一按后端返回的字符串显示。6.3 前端刷新后 404路由模式的取舍现象首页能打开点进详情页也正常按 F5 刷新那一瞬间页面白屏浏览器控制台报 404。原因Vue Router 用的是 history 模式刷新http://localhost:8080/item/3时浏览器向 Tomcat 发了一个 GET 请求但 Tomcat 只在/路径下找到了 index.html/item/3没有对应的物理文件或路由规则于是 404。解决优先选 hash 模式改动最小如果坚持 history 模式必须在后端配置 URL 重写把所有非接口路径都指向 index.html。Spring Boot 里通过实现WebMvcConfigurer的addViewControllers方法或者上面提到的SpaController把/**的非/api请求统一转发。注意要把静态资源路径排除掉否则assets下的 JS、CSS 也会被误伤。6.4 版本不兼容Spring Boot 2.x 与 3.x 的迁移之痛现象源码按 Spring Boot 2.7 写的你本地用 3.x 启动大量报错。最常见的是javax.servlet这个包直接找不到或启动时报ClassNotFoundException: javax.xml.bind.JAXBException。原因Spring Boot 3.x 把javax命名空间整体迁移到了jakarta所有import javax.servlet.*、javax.annotation.*都要改另外 3.x 要求 Java 17 及以上如果你本地 JDK 是 8连编译都过不了。解决最省事的办法是把 pom 里的spring-boot-starter-parent改成 2.7.x 系列同时 JDK 保持在 8 或 11。如果非要用 3.x就要全局替换 import——IDEA 里CtrlShiftR全项目搜索javax.servlet替换成jakarta.servletjavax.annotation替换成jakarta.annotation。还要检查数据库驱动版本MySQL Connector/J 8.x 才支持 Spring Boot 3。6.5 CORS 跨域本地联调时前端访问不到接口现象前端页面跑在localhost:8080后端跑在localhost:8081Network 面板里请求/api/auction/bid直接红色报错Response 里能看到CORS policy字样。有时候 GET 请求正常POST 请求报错或者带 token 的请求报错。原因浏览器同源策略拦截了跨源请求。如果后端的 CORS 配置里allowedOrigins写死了某个地址而前端实际用的是另一个端口请求就被拦。携带Authorization请求头时必须把allowedHeaders加上否则预检请求直接失败。解决在后端写一个全局 CORS 配置类允许本地开发时前端的来源Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(http://localhost:8080) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }提示allowedOrigins指定具体来源不要用*。allowCredentials(true)加上*会直接报错因为浏览器不允许在有凭证请求中使用通配来源。部署后前后端同端口就不再涉及 CORS这个问题基本只出现在开发阶段。7. 部署验证与进阶扩展跑通流程后再做的三件事系统跑起来之后我习惯按流量顺序做一次完整验收注册一个新学生账号发布一件商品用另一个账号出价等到截止时间验证自动生成订单再模拟支付和发货流程。一套走完基本能确认主链路没有缺口。具体到验证我会在容器或本机做一份检查清单逐项打勾MySQL 服务端口能连、Spring Boot 启动无红字日志、前端页面能打开、登录接口返回正常、发布商品后列表能刷新、出价后当前价实时变化、倒计时归零后商品状态变更、订单能查到买家和卖家信息。做完主链路我建议你再做三件事。第一给商品列表加上分页和搜索——pageNum、pageSize、keyword三个参数后端用 MyBatis 的PageHelper或手写LIMIT这段代码能讲清楚 MySQL 的分页原理答辩时几乎是送分题。第二给“我的拍卖”页面增加一个交易状态的筛选 tab本质上是对订单状态字段的查询组合工作量不大但能体现你对业务状态的理解。第三把数据导出一份 Excel 或 PDF 的成交统计报表用EasyExcel或者Apache POI实现这是大部分毕设都没做的亮点功能做出来印象分直接拉满。我自己的习惯是每次接手这类系统第一件事重建数据库而不是直接用原库的数据因为源码包里带的演示数据往往和你的本地环境不匹配。跑通之后重新灌一条学生、一件商品的干净数据这样演示时界面清晰答辩时也方便现场演示出价流程。你不要只满足于“能启动”要把出价并发、拍卖倒计时、订单生成这三个关键节点的代码位置记在心里被问到的时候能直接说出在哪个包哪个类。这份系统的价值不在代码本身在于你能清楚地讲出每个模块为什么这样设计。希望这篇文章里的这些判断和踩坑记录能帮你在启动和答辩的路上都省下一点时间。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号