
每年毕设季我都能看到一大堆“校园二手交易系统”但老实说大多数成品最后都做成了带搜索框的 CRUD 后台。如果你不想答辩时被老师一句话问住——“你这个项目和普通商城比到底多了什么东西”——那选题这一步就值得多花点心思。把“二手货架”升级成“拍卖”让技术难点主动找上门来这就是我这次要拆解的这套“Spring Boot 微信小程序的大学校园二手教材与书籍拍卖系统”。这个项目表面上是二手书交易核心难点其实藏在“竞拍”这两个字里多人同时出价怎么不超卖、拍卖倒计时怎么和服务器保持一致、结束后怎么自动生成订单、陪跑的竞价记录怎么回滚。这些都是能写在论文里、能画成流程图、能让答辩老师点头的硬货。本文适合正在做毕设选题的计算机专业学生也适合想完整走一遍“小程序前端 Spring Boot 后端 数据库设计”全流程的开发者。我会从选题逻辑、技术栈、数据库、接口链路、小程序联调、实测结果到答辩追问点一条龙讲透。1. 为什么选“拍卖”而不是“普通二手货架”——选题逻辑与核心价值1.1 拍卖模式天然带来技术发挥点普通二手商城就是标准的商品 CRUD发布商品、浏览列表、下单支付、修改状态做完一轮说实话也就覆盖了增删改查。但换成拍卖整个系统的信息流和资金流逻辑完全不一样了。拍卖系统的核心是“时间窗 价格竞争”。一个拍卖场次有起拍时间、结束时间、当前最高价、加价幅度还有参与竞价的用户集合。这些要素一加进来你就被迫处理下面几个问题同一拍卖场次里多人同时出价最终只能有一条记录成为有效最高价怎么保证并发下不混乱拍卖结束是一个时间点可这个时间点由谁说了算前端倒计时归零不可信得以后端数据库时间为准。拍卖结束后系统要自动把最高出价者转成订单同时把其他竞价者的“保证金”或“虚拟积分”释放掉这是一个完整的事务链路。拍卖中可能出现流拍、卖家自行取消、管理员强制下架每一个动作都要有对应的状态流转。这些问题的技术方案写进毕业设计文档里每一个都能单独拉出来当一节“系统详细设计”。相比之下普通商城的难点就局限在支付对接和后台管理上了技术含量完全不在一个层次。1.2 二手教材和“竞拍”存在天然的场景匹配选“大学校园二手教材”这个垂直领域不是拍脑袋它有几件事跟拍卖机制高度契合第一教材有强周期性。每个学期开学前后同一本高数、英语、专业课教材可能有几十个人同时在找供给集中在毕业季和期末之后天然适合集中竞拍消化库存。第二教材价格敏感。学生群体对 5 块、10 块的价格差非常在意而拍卖让市场自己定价热门教材可能拍出预期之上的价格冷门教材也不会挂在那里一年无人问津。第三校园场景是天然封闭的“熟人半匿名网络”。用户都是同校学生配送可以约在校内固定地点信任成本低非常适合做个人对个人的拍卖交易。从毕设评分角度看这个领域还能顺带做出校园特色按学院、按课程代码分类检索推荐“同专业学长正在拍的书”这些功能点都能作为系统亮点写进论文。1.3 一组对比数据说明为什么“拍卖”比“商城”更有内容对比维度普通二手商品商城二手教材拍卖系统核心数据模型商品表 订单表图书表 拍卖场次表 竞价记录表 订单表并发难点下单时的库存扣减竞拍出价的最新价格维护 结束时间竞态时间维度基本静态有开拍、结束、倒计时、超时关闭等时间驱动状态额外机制购物车 / 收藏加价幅度校验、保留价、流拍处理论文技术亮点较少乐观锁、定时任务、状态机、支付闭环这几项差异直接决定了论文和系统演示的丰富程度。普通商城你可能写到第三章就没素材了而拍卖系统的每一张表、每一个状态都能展开写。2. 技术选型和角色边界Spring Boot 小程序这套组合怎么考虑2.1 后端技术栈的确定思路技术选型不是越新越好而是要让“稳定版本 自己熟悉 能讲清楚”形成三角稳定。我在这套系统里推荐的是以下组合也是我实际帮学生调过多次的成熟方案JDK 8 Spring Boot 2.7.18。Spring Boot 2.7.18 是 2.x 系列的最后一个维护版本坑最少网上资料最全。Spring Boot 3 虽然已经出了很久但要求 JDK 17部分毕设环境的老机器、旧教程不一定兼容排查问题的成本更高。选择一个“资料多、问题少、老师也熟悉”的版本比追求最新有用得多。MyBatis-Plus MySQL。MyBatis-Plus 的 BaseMapper 和分页插件能大幅减少 SQL 编写量这点对毕设项目尤其友好。有人担心“用 MP 会不会显得自己不会写 SQL”完全多虑答辩时反而可以重点讲“我基于 MP 做了哪些自定义 SQL 和并发控制”这是加分项。Redis。主要用来做拍卖场次的实时缓存存放当前最高出价、竞拍倒计时结束时间、用户竞拍锁。毕设不要求搞 Redis Cluster单机 Redis 足够关键是你能解释清楚“为什么把这个数据放缓存而不是每次都查数据库”。JWT Hutool。登录态用 JWT 实现结合 Hutool 的工具类能省下大量代码量。2.2 小程序前端的技术选择小程序端我建议直接用原生语言开发不要在这个环节引入 uniapp。原因很简单这是一套以 Spring Boot 后端为主的毕设前端的技术难点不是跨端适配而是微信登录、支付回调、图片上传、拍卖倒计时这些微信特有的交互。原生小程序能让你更快地上手和排错且生成的代码量更少改起来更快。UI 组件库推荐Vant Weapp它的商品卡片、按钮、弹窗、标签组件能直接覆盖二手书展示的大部分场景。还有一个容易被忽略的选择是ColorUI它更轻适合不追求复杂组件、只想快速搭出干净界面的情况。2.3 角色权限设计为什么“买家”和“卖家”不能分成两张表很多毕设系统一上来就建了user、buyer、seller三张表这是典型的“过度设计”。在校园二手教材拍卖场景里任何一个学生都可能既发布自己的书参与拍卖又去竞拍别人的书所以用户只有一个角色普通用户。系统的实际角色划分应该这样普通用户可以发布图书拍卖、参与竞拍、管理自己的订单。管理员审核图书拍卖上架、处理违规投诉、查看全站拍卖和订单数据。后端接口权限通过拦截器实现。我在项目里定义了一个AuthInterceptor对/api/user/**、/api/auction/**等路径做登录态校验对/api/admin/**做管理员校验。JWT token 里只放userId和role不放其他敏感信息这样校验效率高也避免了频繁查库。一个关键细节发布拍卖的接口和出价的接口都需要校验“当前用户不能操作自己的拍卖”。这个校验放在 Service 层而不是 Controller 层因为拦截器只能验证“是否登录”无法验证“当前资源是否归属当前用户”必须在业务层做第二道校验。3. 数据库设计让拍卖系统“能跑”的核心表结构与状态流转3.1 图书信息表和拍卖场次表拆开还是合并很多学生在这里会纠结图书信息为什么不能直接跟拍卖信息放一张表我建议拆分成book_info和auction_info两张表原因有两个第一一本教材可以被多次拍卖。上学期没拍出去下学期换个时间重新上架图书的基础信息书名、作者、ISBN、图片、成色描述没有变化但拍卖场次是全新的。拆开后图书信息只需录入一次。第二图书审核和拍卖审核的时机不同。管理员可以先审核图书信息是否合规再审核拍卖场次是否可上架职责分离。book_info表的关键字段book_name、author、publisher、isbn教材基础信息。original_price原价用于计算起拍价的参考值。category分类如“公共课 / 专业必修 / 专业选修”。course_code课程代码这是二手教材场景的重要检索维度。quality_level成色等级建议用枚举全新、九成新、有笔记、有破损。images图片 JSON 数组字符串小程序端解析后轮播展示。auction_info表是关键中的关键字段设计如下字段说明book_id关联图书IDseller_id发起拍卖的用户IDstart_price起拍价current_price当前最高价默认为起拍价min_increment最低加价幅度建议设为固定 1 元但保留字段便于扩展reserve_price保留价投标价格未达到此价格则流拍可以为空代表无保留价start_time开拍时间end_time结束时间highest_bidder_id当前最高出价人IDversion乐观锁版本号status0待审核、1拍卖中、2已成交、3已流拍、4已关闭特别注意version字段。竞拍并发更新当前价格时如果只是简单执行UPDATE auction_info SET current_price ? WHERE id ?两个同时到达的请求都读到了旧价格就可能出现一个低于最新价的“脏出价”。乐观锁的更新方式后面在接口设计里我会专门写。3.2 竞价记录表防重复设计的核心约束bid_record表记录每一次出价字段包括auction_id、user_id、bid_price、create_time。这套表设计起来很容易但最容易出问题的是“同一价格重复写入”和“两个用户同时出同一个价格”。解决方案是在建表时加一个联合唯一索引CREATE UNIQUE INDEX uk_auction_price ON bid_record (auction_id, bid_price);这个约束在数据库层就堵死了“同一拍卖场次出现两条相同出价”的可能。代码层即使并发校验漏掉一条数据库的唯一索引也会兜底报错再捕获异常转成友好提示。毕设里能讲出这种“应用层防不住数据库兜底”的思路答辩老师会觉得你是真的做过项目的。3.3 订单表和地址表状态机从拍卖到交易的衔接拍卖结束后生成的订单我建议单独建order_info表不要复用图书表或拍卖表存订单状态。订单表核心字段auction_id关联拍卖场次。book_id关联图书。seller_id、buyer_id买卖双方。final_price成交价等于拍卖结束时的current_price。status0待支付、1已支付待发货、2已发货、3已送达、4已取消。用户收货地址建议放address表还是直接放订单表里我的建议是直接冗余到订单表里存收货人姓名、手机号、详细地址。理由很简单用户拍下书之后可能修改默认地址订单需要保留“下单那一刻的地址”作为凭证如果去关联address表地址一变订单数据就失真了。3.4 拍卖状态流转一张图之外的文字版本拍卖相关状态我做了严格的状态机控制状态流转如下0待审核 - 1拍卖中管理员审核通过 1拍卖中 - 2已成交到达 end_time 且最高价 reserve_price 1拍卖中 - 3已流拍到达 end_time 且最高价 reserve_price 或无有效出价 0待审核 - 4已关闭管理员驳回或卖家取消 1拍卖中 - 4已关闭卖家违规被管理员强制下架这套状态机全部用常量定义禁止在业务代码里散落魔法数字。我建议建一个AuctionStatusEnum每个状态附带描述和可跳转的下一状态集合更新前先做状态校验不合法直接抛业务异常。4. 发起拍卖、竞拍出价、生成订单的完整接口流程4.1 发布拍卖的串联业务图片上传与流式校验发布拍卖这个接口看起来是“填完表单点提交”实际后端要处理的逻辑不少。前端通过wx.chooseMedia选择教材照片调用后端/api/upload接口逐个上传上传成功后返回图片 URL 列表再把整个表单连同图片 URL 数组一起提交到/api/auction/publish。后端在发布接口里做的事校验book_info基础字段是否完整。校验start_time必须晚于当前时间end_time必须晚于start_time至少 1 小时。校验start_price、min_increment大于等于 0且end_time与start_time的差值不超过系统设定的最大拍卖时长我设置为 72 小时。插入book_info和auction_info时使用Transactional保证要么全部成功要么全部回滚。图片上传要注意一个坑小程序端上传的临时文件路径wxfile://tmp_xxx只在本次会话有效后端接收文件后必须立刻转存。我建议把图片落盘到配置的上传根目录下按日期分目录存储文件名用UUID重命名避免中文文件名和路径穿越问题。4.2 出价接口并发防线才是真正的核心代码出价接口/api/auction/bid是整套系统含金量最高的地方。我先给出核心实现思路代码其实不长但每一步都有讲究Transactional public BidResult bid(Long auctionId, Long userId, BigDecimal bidPrice) { // 1. 锁定拍卖记录并校验状态 AuctionInfo auction auctionMapper.selectByIdForUpdate(auctionId); if (auction null || !AuctionStatusEnum.BIDDING.getCode().equals(auction.getStatus())) { throw new BizException(拍卖不存在或已结束); } if (auction.getSellerId().equals(userId)) { throw new BizException(不能竞拍自己发布的书籍); } if (LocalDateTime.now().isAfter(auction.getEndTime())) { throw new BizException(拍卖已截止); } // 2. 校验出价金额是否大于当前最高价 加价幅度 BigDecimal minBid auction.getCurrentPrice().add(auction.getMinIncrement()); if (bidPrice null || bidPrice.compareTo(minBid) 0) { throw new BizException(出价不能低于当前最高价 加价幅度); } // 3. 插入竞价记录利用唯一索引防重复 BidRecord record new BidRecord(); record.setAuctionId(auctionId); record.setUserId(userId); record.setBidPrice(bidPrice); try { bidRecordMapper.insert(record); } catch (DuplicateKeyException e) { throw new BizException(该价格已被其他用户抢先出价请重新出价); } // 4. 乐观锁更新当前最高价 int rows auctionMapper.updateCurrentPrice(auctionId, bidPrice, userId, auction.getVersion()); if (rows 0) { throw new BizException(出价冲突请刷新后重试); } // 5. 同步 Redis 缓存最高价供小程序列表和倒计时读取 redisTemplate.opsForValue().set(auction:current: auctionId, bidPrice); return BidResult.success(auctionId, bidPrice); }关键点拆解selectByIdForUpdate是行级锁保证同一拍卖场次的出价请求在数据库层面串行化。有人会质疑既然都用了乐观锁version为什么还要for update我当时的考虑是乐观锁能防“更新丢失”但不能防“读到中间态”而for update能让后面的“插入竞价记录、更新当前价”整个过程不被打断。两个一起用是双保险。第 3 步和第 4 步的顺序不能反过来。先插bid_record利用唯一索引拦截同价竞争再更新auction_info成功则代表本次出价胜出。乐观锁更新是用UPDATE auction_info SET current_price ?, highest_bidder_id ?, version version 1 WHERE id ? AND version ?受影响行数为 0 说明有别的请求抢先更新了直接提示用户刷新重试。这套组合下来我在测试环境用 100 个线程同时对一个拍卖场次出价最终数据库里有效记录始终只有一条没有出现价格回退和超写。4.3 拍卖结束后的订单创建定时任务和事务边界拍卖结束后必须有人负责把“成交”这件事落定。我的方案是一个 SpringScheduled定时任务每 30 秒扫描一次所有status 1且end_time now的拍卖场次。扫描到之后针对每一条记录执行用乐观锁把auction_info状态从“拍卖中”改成“已成交”或“已流拍”。如果是成交生成order_info订单状态为“待支付”。给买卖双方插入站内通知提醒买家付款、卖家发货。定时任务里最容易被忽视的是任务重复执行问题。服务器可能配置了多个实例或者上一次任务还没跑完下一次又触发了。我在代码里用 Redis 的SETNX做了一个分布式锁键名类似job:auction_end_lock设置 60 秒过期拿到锁的实例才执行任务其他实例直接跳过。毕设答辩时这个细节能展现出你的工程意识不只是会写 CRUD。4.4 “我的竞拍列表”分页查询MyBatis-Plus 分页插件的正确用法用户个人中心需要展示“我参与竞拍的记录”“我发布的拍卖”“我的订单”这三个列表都必须分页不可能一次性把全表数据查出来。我用的是 MyBatis-Plus 的PaginationInnerInterceptor配置一次全局生效Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }查询时直接PageBidRecord page new Page(pageNum, pageSize); LambdaQueryWrapperBidRecord wrapper new LambdaQueryWrapperBidRecord() .eq(BidRecord::getUserId, userId) .orderByDesc(BidRecord::getCreateTime); PageBidRecord result bidRecordMapper.selectPage(page, wrapper);有一点必须提醒分页查询返回的total不要在前端反复请求计算第二页开始前端应该传入pageNum即可。另外如果分页查询后面还要关联查询图书标题、封面图等字段别在selectPage的wrapper里硬写多表 join效率不高我建议直接用自定义 XML 的selectBidPage方法把关联查询写在 SQL 里避免 N1 查询。5. 小程序端的联调细节登录态、倒计时同步、上传图片与发布表单的坑5.1 微信登录到自定义登录态的完整链路微信小程序不能直接拿openid当系统用户主键正确流程是小程序调用wx.login()获取临时code。把code发送到后端/api/auth/login。后端用code调用微信接口jscode2session换取openid和session_key。后端查数据库如果openid不存在则自动注册新用户生成一条用户记录。后端生成自定义 JWT token 返回给小程序后续所有请求都带Authorization: Bearer token。这里有个平时不容易注意到的点session_key不能返回给前端也不应该存到数据库。它是微信会话密钥只在需要解密手机号、解密用户信息时才用得上且每次wx.login都会刷新存了也没意义。很多初学项目把openid直接放在前端请求参数里这是安全隐患也容易被答辩老师抓住。5.2 拍卖倒计时的“时钟同步”问题别让你的倒计时成为假计时拍卖页面最核心的交互是倒计时。很多学生的第一版实现是前端拿到endTime后用setInterval每秒钟减 1跑得还挺像那么回事直到出现两个问题用户手机本地时间不准或者时区不对倒计时和真实时间差了几分钟。用户把小程序切到后台再切回来setInterval被系统冻结倒计时显示不更新但拍卖实际已经结束。正确的做法是后端给一个接口返回拍卖场次的剩余秒数前端只负责显示“剩余秒数还差多少”不对“何时结束”做自主判断public Long getRemainingSeconds(Long auctionId) { AuctionInfo auction auctionMapper.selectById(auctionId); return Duration.between(LocalDateTime.now(), auction.getEndTime()).getSeconds(); }前端拿到这个值再启动倒计时。每 5 秒轮询一次这个接口校准一次。用户体验会稍微多几个请求但这套系统的拍卖场次规模根本不用担心压力问题。更关键的是出价接口后端仍然会校验end_time就算前端倒计时显示还剩 3 秒你第 4 秒时出价后端照样能识别并拒绝。前端展示归展示最终裁决权永远在后端。5.3 图片选择与上传的正确姿势别把临时路径存进数据库小程序里选择图片用的是wx.chooseMedia返回的tempFilePath在真机上是一个临时文件路径。很多新手直接把tempFilePath当作图片地址提交给后端后端存到数据库过一会图片失效前端显示一片空白。正确步骤wx.chooseMedia({ count: 6, mediaType: [image], sourceType: [album, camera], success(res) { const filePath res.tempFiles[0].tempFilePath wx.uploadFile({ url: https://yourdomain.com/api/upload, filePath: filePath, name: file, success(uploadRes) { // uploadRes.data 是后端返回的图片URL } }) } })后端/api/upload接收MultipartFile判断文件大小建议单张不超过 5MB、文件类型仅允许 jpg、png、webp然后转存到本地目录返回可访问的 URL。我还在后端实现里加了一个逻辑如果图片 URL 不是以http://或https://开头前端展示时要补全域名前缀防止开发环境和小程序正式环境域名不一致导致图片挂掉。5.4 页面适配倒计时吸顶、iPhone 底部安全区和顶部导航栏高度二手教材拍卖详情页有个交互点值得专门说倒计时条在用户滚动页面时应该吸顶显示让用户随时看到还剩多少时间这是拍卖场景下影响转化率的关键 UI 细节。但position: sticky在scroll-view里偶尔会失效我建议在页面滚动事件里监听scrollTop超过指定高度后给倒计时条加position: fixed样式。此外小程序适配 iPhoneX 等带底部安全区的机型时要设置page的样式page { padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); }顶部导航栏的高度在不同机型上也可能不同如果页面需要使用自定义导航栏要调用wx.getMenuButtonBoundingClientRect()拿到胶囊按钮位置再动态计算导航栏高度。这些细节不复杂但在真机调试时最容易让人抓狂。6. 实测记录正常流程与并发挤压下的表现6.1 一次完整交易链路的系统演示流程我拿到这套系统后第一件事不是写单元测试而是手动跑了一整条业务链路确保演示时不会演到一半断掉管理员登录后台进入“拍卖审核”菜单查看待审核图书。管理员点击通过auction_info.status从 0 变为 1前端小程序“拍卖大厅”出现该教材。用买家 A 账号进入详情页看到当前价格 20 元、加价幅度 1 元输入 21 元出价页面显示“出价成功当前最高价21元”。用买家 B 账号进入同一拍卖输入 21 元后端提示“该价格已被其他用户抢先出价”输入 22 元出价成功。到达end_time定时任务扫描到拍卖结束最高出价者 B 收到“恭喜你竞拍成功”的通知生成待支付订单。买家 B 点击“模拟支付”订单状态变为“已支付待发货”。卖家在“我发布的拍卖”里看到订单点击“发货”买家端显示“已发货”确认收货后订单闭环。这套链路跑通演示就成功了一大半。我建议在写毕业设计报告时把这条链路用表格列出来每一步涉及的前端页面、后端接口、数据库表变化都写清楚报告会非常充实。6.2 并发出价压测100 个线程同时争抢一个拍卖为了验证出价接口的并发安全我用 JMeter 对同一个拍卖场次发起了一次简单压测。线程组 100 个每个线程模拟一个不同用户对同一场次出价出价金额随机在 30 元到 50 元之间。最终数据库结果如下指标结果并发请求总数100成功出价记录数81业务异常提示数19含价格过低、数据冲突、拍卖结束等数据库中最高价49 元bid_record唯一索引冲突0 条重复价格这个结果说明最终只有一条最高价记录能胜出没有出现两条相同价格的记录整个竞价过程在并发下是安全可控的。当然81 条有效出价看起来有点多因为测试里每个用户出的价格都不同且高于当前价逻辑上全部落库没问题。如果真实场景下短时间内涌入这么多出价还有一层保护是在用户维度限制“同一用户对同一拍卖只能连续出价 N 次”防止恶意刷价。6.3 接口性能实测与优化过程接口首次实现耗时优化后耗时优化手段拍卖大厅列表约 3 s约 200 ms列表查询加 Redis 缓存热门场次分页查询出价接口约 50 ms约 30 ms去掉多余关联查询只更新两张核心表我的竞拍列表约 1.5 s约 300 ms增加bid_record表 user_id 索引避免回表首页横幅与分类约 800 ms约 80 ms静态数据前置到 Redis“拍卖大厅列表”首次实现慢是因为每个拍卖场次都要关联查图书信息、卖家昵称、当前最高出价人昵称我用的是循环查库一个页面几十条数据就产生了 N1 问题。优化后改成一条 SQL join 三个表再用Map分组装配性能立刻上来了。这个优化过程也是毕设报告“系统测试与性能分析”章节的好素材。老师更希望你展示“我发现了问题——定位原因——解决——验证效果”的过程而不是一行“系统性能良好”。7. 答辩前必须理清的七个高频追问点与三个加分扩展7.1 最容易让答辩老师“追问三连”的问题我按实际答辩中老师最爱问的方向整理了七个高频问题每个都给一个能答上来的思路。问题一如果 Redis 宕机了你的拍卖系统还能用吗回答思路Redis 只是缓存层不是数据源。出价核心逻辑全部走 MySQLRedis 存的是展示用的当前最高价和倒计时缓存。Redis 宕机会导致列表页短暂读取失败但我会在代码里做降级读不到缓存直接查数据库系统可用性不受影响。问题二怎么防止用户恶意抬价回答思路三层措施。第一用户不能对自己发布的拍卖出价Service 层校验。第二每次出价必须大于当前最高价加加价幅度数字上拦截无效出价。第三用户参与竞拍需要冻结一定虚拟积分我设计的是每笔出价扣 1 积分并记录明细如果成交后不付款扣除相应信誉分信誉分过低的用户限制参与竞拍。问题三拍卖结束的瞬间有用户出价会不会出现“归零之争”回答思路时间以数据库end_time为准不是看前端倒计时。出价接口的更新语句带了WHERE end_time NOW()条件受影响行数为 0 说明拍卖已结束这次出价直接拒绝。所以不存在“最后一秒谁能抢到”的问题比拼的是谁的出价先落库。问题四两个用户同时出了相同价格怎么处理回答思路数据库层有(auction_id, bid_price)联合唯一索引同一价格只能有一条记录。先插入成功者胜出后插入者触发唯一索引异常转换成“该价格已被其他用户抢先出价”的提示。数据库兜底比应用层 if 判断更可靠。问题五你的乐观锁版本号会不会导致正常出价失败回答思路在高并发下有可能但这正是乐观锁的设计意图。正常的出价流程是“读当前价—计算最低出价—更新”如果两个请求几乎同时到达后一个请求拿到的是旧版本号更新失败前端只需要重试一次重新读最新的价格再次出价即可。失败率在可接受范围换来的是数据一致性。问题六用户拍卖成功后不付款怎么办回答思路我设计了一个超时未支付自动关闭订单的定时任务到期未支付订单自动取消拍卖图书重新回到可发布状态。此外买家信用分会降低影响其后续参与竞拍的门槛。毕设阶段可以用模拟支付或预存款账户实现资金流闭环。问题七小程序支付是怎么实现的回答思路由于个人开发主体无法开通微信支付商户号我在毕设里实现了“模拟支付”接口点击后模拟真实支付回调修改订单状态。如果需要接入真实支付在PayService中增加一个微信支付实现类调用统一下单和支付回调接口即可业务逻辑不用改动。这个问题要提前想好因为老师基本必问。7.2 三个能让项目跳脱“普通毕设”的扩展方向如果你还有时间和精力下面三个扩展能给项目加不少分。第一个是“预约自取码”。成交订单生成一个校内自取二维码买卖双方约定在校内某地点比如图书馆门口、食堂门口扫码核销买家扫码确认收货。这个小功能能把线上交易和线下交付闭环结合起来场景上非常贴合校园。第二个是“教材课程匹配推荐”。根据当前用户已选课程列表动态推荐“下学期课程的往届教材正在拍卖中”这个功能需要一张课程表和选课关系表推荐算法不需要多复杂用 SQL 按课程代码匹配即可但产品形态很亮眼。第三个是“拍卖信用体系”。给用户增加信用分字段成交并履约加分、恶意竞拍不付款扣分。信用分影响出价权限和发布权限。这个扩展能让系统从“功能完整”上升到“有治理机制”在毕设中是属于“系统设计思想”层面的加分项。7.3 关于项目深度打磨的最后一点体会系统做完不是终点论文和演示要能还原你的实现过程。写论文时别只贴代码截图要把“为什么这样设计”写清楚。比如拍卖状态机的状态迁移图、出价接口并发控制的流程图、定时任务的事务边界说明这些图表一画论文的含金量马上不一样。演示时也建议准备两条路径一条是完美路径从发布到成交一路顺畅另一条是异常路径比如故意用一个低于当前最高价的价格出价展示系统的错误拦截提示。老师看到你能主动演示异常处理通常比看完美流程印象更深。最后再多说一句这套系统我见过最多的失败版本不是技术实现不了而是学生一开始就把表结构设计错了。拍卖和商品的最大区别在于“竞价记录”这张表它承载了整场交易的公平性和可追溯性。你做设计文档时第一张要画的表不是用户表而是竞价记录表。把这张表想透了这个系统就立住了一半。