做计算机毕业设计选题目,往往是整个项目的第一道坎。见过太多同学要么选了个“学生管理系统”“图书馆管理系统”这种烂大街的题,做完自己都没底气;要么选了个太前沿的题目,比如“基于深度学习的某某识别系统”,结果连环境都跑不起来,论文写得云里雾里。而我今天要聊的这类题目——“双鲤”国画作品交易平台、“墨韵”中国传统书画在线竞拍与商城系统、基于SpringBoot+Vue的东方艺术品交易与鉴赏社区平台——看起来像三个不同的毕设,实际上是一脉相承的同一类项目:用SpringBoot做后端、Vue做前端,围绕传统书画艺术品搭建一个集商城、竞拍、社区于一体的交易交流平台。这篇文章就从我实际做这类项目(以及帮学生改这类项目)的经验出发,把选题思路、技术选型、数据库设计、核心模块实现、联调部署和论文答辩一次讲透,希望能帮你少走几个月的弯路。
1. 为什么毕业设计选“艺术品交易平台”这个方向
1.1 一个题目覆盖三条业务线:商城、竞拍、社区
最初的“双鲤”国画作品交易平台,核心是作品展示和线上交易,业务比较单一;到了“墨韵”这个版本,加上了在线竞拍和商城系统,业务丰富度上了一个台阶;而东方艺术品交易与鉴赏社区平台,则在交易之外又融入了内容社区,用户可以发作品、交流鉴赏心得。你仔细看就会发现,这三个题目刚好是一个递进关系,也对应着这类项目的三条核心业务线:
- 商城线:商品展示、购物车、下单、支付、订单管理,这是最基础也最稳妥的部分,几乎所有电商系统的逻辑都能复用到这上面;
- 竞拍线:拍卖场次、出价记录、保证金、自动落槌、竞拍订单转化,这是整个系统最有“亮点”的部分,也是答辩时最能拿出来讲的东西;
- 社区线:作品发布、图文鉴赏、评论、点赞、关注,这部分能把系统的“文化属性”做出来,让题目不只是冷冰冰的交易工具,而是有“交流和鉴赏”的温度。
我当时选这类题目的核心原因很简单:一个题目能同时覆盖交易系统、实时交互、内容社区三个典型场景,对SpringBoot、Vue、MySQL、Redis这些主流技术的考察非常全面,拿出去面试也有的聊。而且“传统书画”这个题材在艺术类院校、综合性大学的毕设评审里,比“某某管理系统”要有辨识度得多——评委一看就知道你是认真想过业务场景的,而不是随便拿个模板改改。
1.2 这类题目在评审老师眼中的加分点与风险点
先泼一盆冷水:这个题目也有它的问题,最大的风险在于业务范围偏大。商城加竞拍加社区,如果全都做成完整版,一个人做下来可能要写几千行代码。所以我的建议是做一个“业务完整、深度优先”的版本,而不是“功能堆砌、每个都半吊子”的版本。
评审老师看这类项目的眼光,我自己总结过,核心就三条:
- 有没有真实的业务闭环。书画从“上架/上拍”到“竞拍出价/直接购买”到“下单支付”到“订单完成”,这条链路必须是通的。很多毕设功能页面一堆,但点到最后总是“演示数据”或者“接口未实现”,这在答辩时是致命的。
- 有没有能体现技术难度的点。对于SpringBoot+Vue项目,老师普遍关心的技术点包括:并发出价怎么处理、订单超时未支付怎么关单、图片上传怎么解决、权限控制怎么做的。这几点我会在后面第4章、第6章详细展开。
- 界面和交互是否完整。前端Vue做出来的页面,至少要能自圆其说。评审老师不会按设计规范去检查你的UI,但如果路由跳转报错、控制台一片红、图片加载不出来,那印象分会掉得很快。
也就是说,这个题目做得好,是一篇优秀的“基于SpringBoot的电子商务类”毕业设计;做得糙,就是一堆页面拼凑的“花架子”。下文的每一个章节,都是围绕“怎么把它做得完整、扎实”来的。
2. 技术栈选型的几条硬道理:SpringBoot + Vue为什么是黄金组合
2.1 SpringBoot做后端的不可替代性
有些同学会问,为什么一定要SpringBoot,用Servlet/JSP行不行?当然行,但那是十年前的玩法了。SpringBoot在校园项目里的地位,可以类比成“默认推荐”,理由其实非常现实:
第一,配置简化得太明显了。不需要手动配置一堆XML,一个@SpringBootApplication就能跑起来,内嵌Tomcat让部署不需要额外装服务器。对于毕设周期几个月、中途还有论文和实习压力的你来说,省下的时间非常可观。
第二,Spring全家桶的衔接太方便了。Spring Security做权限、Spring Data Redis做缓存、Spring Validation做参数校验,全是同一套依赖体系和管理模式。你想往项目里加东西(比如发邮件、定时任务、消息队列),基本都是在Maven里加一个依赖的事。
第三,简历上写出来有说服力。SpringBoot是Java后端岗位的高频关键词,哪怕毕业设计做得简单,写上“SpringBoot + Vue前后端分离项目”,面试官至少愿意多问两句。
但这里我要特别提醒一个版本选择的点:SpringBoot别追新,2.7.x是最稳的选择。SpringBoot 3.x要求JDK17起步,并且把很多javax包改成了jakarta,MyBatis-Plus等框架老版本直接不兼容。我自己就见过学生全项目配完SpringBoot 3.2.0,结果启动直接报“ClassNotFound: javax.servlet”,然后开始怀疑人生的场景。这个坑我放到第6章的避坑合集里再细说。
2.2 Vue 3 + Element Plus 前端方案的具体取舍
前端选型上,“Vue 3 + Element Plus + Axios + Vue Router + Pinia”是当前这类毕设的事实标准,没有之一。原因很清楚:
- Vue 3的Composition API写起来结构更清晰,
ref、reactive、onMounted这些语法对新手也友好,而且Vue 2官方已经停止维护了,新项目没理由再开倒车; - Element Plus是为Vue 3打造的组件库,表格、表单、弹窗、分页、上传组件都有现成的,做管理系统类的界面效率极高;
- 状态管理用Pinia,比Vuex更好用,TypeScript支持也好一些,不过如果项目简单,不引入状态管理也行,各组件之间通过props和事件传递完全够用。
有一类题目里会标注“东方艺术品交易与鉴赏社区平台”,这种带社区属性的前端页面需要一些自定义布局,比如瀑布流式的作品展示、文章详情页、评论区,这类Element Plus没提供现成组件,需要自己用CSS或Flex/Grid自己排版。这块不要怕,反而是展示前端能力的地方——社区页做得好看,答辩演示时评委的第一印象就是加分项。
2.3 配套选型:MySQL、Redis、MyBatis-Plus、JWT
下面用一个表格把配套技术组件和各自承担的角色理清楚,方便你对照着做技术方案和论文的“可行性分析”一节:
| 组件 | 用途 | 必选/可选 | 补充说明 |
|---|---|---|---|
| MySQL 8.x | 业务数据持久化 | 必选 | 商品、订单、用户、出价记录都放这里,字符集用utf8mb4,不然中文表情没法存 |
| Redis | 缓存、竞拍出价、分布式锁 | 强烈推荐 | 不加Redis系统也能跑,但加了之后竞拍并发这块才有东西可写 |
| MyBatis-Plus | ORM、单表CRUD、分页 | 必选 | 和SpringBoot集成度极高,能省大量重复的Mapper XML |
| JWT(如jjwt或java-jwt) | 登录鉴权、接口令牌 | 必选 | 前后端分离项目标准的登录方案,无状态、好扩展 |
| Spring Security | 权限控制、角色管理 | 可选 | 多数毕设可以直接用拦截器+注解实现权限,省得被Security的过滤器链折磨 |
| 七牛云/阿里云OSS | 图片存储 | 可选 | 本地存储+虚拟路径也可用,但社区类项目的图片会很多,推荐至少封装一个上传接口方便以后换OSS |
| Lombok | 简化实体类代码 | 必选 | @Data、@Builder实在太香了 |
这里我想重点泼一盆“技术选型要克制”的冷水。很多同学为了显得项目高级,会强行引入RabbitMQ、Elasticsearch、XXL-Job这类中间件。但在毕设项目里,中间件越少越稳,因为每个中间件都意味着额外的运维成本、版本兼容成本和踩坑成本。一个SpringBoot项目如果在答辩现场启动不起来,那比“没有用消息队列”严重一万倍。所以上面这个表里,我的真实推荐是:MySQL + Redis + MyBatis-Plus + JWT + 本地文件存储,已经是这类项目的最优性价比配置。至于消息队列,等你工作了再玩也不迟。
3. 从“双鲤”到“墨韵”:核心业务模型与数据库设计
3.1 用户、角色、权限的数据模型
“双鲤”国画作品交易平台这类题目,最容易被忽略的是“用户角色不是只有学生/管理员两种”,它跟真实交易平台的用户体系非常接近。我梳理了一下,这类艺术品平台至少需要四类角色:
- 普通用户(买家):浏览作品、参与竞拍、下单购物、发布评论;
- 艺术家/卖家:发布作品上架、管理自己的商品和拍卖场次、处理订单;
- 鉴定师(可简化为虚拟角色):给书画作品出具“鉴定说明”,这是艺术品平台特有的业务,做出来会很增值;
- 管理员:用户管理、审核上架内容、处理违规举报、查看统计数据。
权限模型建议用最简单的RBAC(用户-角色-权限),三张表就够:user(用户)、role(角色)、menu(菜单/权限),外加用户-角色关联表,后端用拦截器校验登录状态,再配合注解@RequireRole("ADMIN")做角色控制。菜单权限用在前端动态路由上,第5章会细说。
3.2 商品信息与拍卖场次设计
“商城+竞拍”双模式是这个项目的核心。我在数据库设计时,把商品和拍卖设计成“两个实体、一个关联”:
artwork(艺术品表):id、title(作品名)、author(作者)、category(分类:山水/花鸟/书法/工笔等)、description、cover_img(封面图)、detail_imgs(详情图,可用逗号分隔或JSON)、estimated_price(估价)、status(待上架/上架中/已下架/已售出);auction(拍卖场次表):id、artwork_id(关联艺术品)、starting_price(起拍价)、current_price(当前价)、min_increment(加价幅度)、deposit(保证金)、start_time、end_time、status(未开始/进行中/已结束/已成交)、winner_id(最终买受人);bid_record(出价记录表):id、auction_id、user_id、bid_price、create_time,每次出价都会插入一条流水,这也是竞拍业务分析和并发控制的基础。
这里有个设计重点是:拍卖的当前价和出价记录之间要保证一致性。如果直接改auction.current_price字段,高并发下很容易出现两个用户同时出价都成功的情况。正确的做法是把current_price看作一个“缓存值”,真正的权威数据是bid_record表里的最高出价记录,每次出价都要校验“新价格 > 当前最高价 + 最低加价幅度”,然后在一个事务里插入出价记录并更新current_price。具体实现我放到第4章竞拍模块里。
3.3 订单与资金流的设计关键
交易平台的核心闭环一定落在订单上。商城订单和竞拍订单可以共用一套订单表,但需要加一个order_type字段区分:
order_type=1:商城普通购买;order_type=2:竞拍成功自动生成的订单。
orders表建议字段:id、order_no(订单号,用时间戳+随机数生成)、user_id、artwork_id、auction_id(可空)、amount(成交金额)、status(待支付/已支付/已发货/已完成/已取消/退款中)、pay_time、create_time、update_time。
为什么“待支付”状态很重要?因为竞拍落槌之后买家可能不付钱,商城下单也可能反悔。一般的做法是:下单后给15-30分钟的支付窗口,超时自动取消订单并释放库存/重新上架作品。这个“超时关单”是个经典问题,后面我会用单独的篇幅讲三种实现方案的取舍。
关于支付本身,毕设里不建议去对接支付宝/微信支付的真实SDK,因为申请商户号、回调配置那一套流程周期太长,而且容易出幺蛾子。最稳妥的方式是做一个“模拟支付”:订单确认页放一个“余额支付”按钮,点击后调一个模拟支付接口,后端校验用户钱包余额够不够,够了就扣钱、改订单状态,然后写一条支付流水记录。这个设计完全不影响业务闭环的完成度,还能在论文里写一笔“考虑到毕设环境和安全要求,采用模拟支付,接口预留真实支付对接”,反而显得你考虑周全。
4. 三大核心模块的实现细节:竞拍、商城、社区
4.1 竞拍模块:出价并发与自动落槌
竞拍是“墨韵”中国传统书画在线竞拍系统的灵魂,也是这类项目里技术含量最高的部分。我把实现分成三步来拆解。
第一步:出价接口的并发控制
前端用户点“出价”按钮,后端POST /api/auction/bid收到请求后,不能简单做一个“价格大于当前价就更新”的操作。原因很简单:两个用户同时出价,数据库层面很可能都读到了同一个current_price,然后都判断“我的价格更高”,最后都写成功——这在真实拍卖里是不允许的。
我在实际项目里推荐的方案是Redis + 事务 + 乐观锁的组合:
- 用Redis缓存每个场次的当前最高价,出价前先
INCR一个自增序号或者用SETNX做个简单的加锁,防止同一时刻并发穿透; - 具体实现上,我直接在出价接口的Service方法上加了
@Transactional,然后执行一条带条件的UPDATE语句:UPDATE auction SET current_price = #{bidPrice} WHERE id = #{auctionId} AND current_price < #{bidPrice},如果更新的影响行数不是1,说明价格已经被别人抢先了,抛出自定义异常“出价低于或等于当前价格,请重新出价”; - 出价记录
bid_record在同一事务里插入,保证一致性。
这条带条件的UPDATE本质上是乐观锁的思路——不加锁也能防并发,靠的是数据库行锁和影响行数判断。实测下来,这种方案在毕设的并发量级下完全够用,而且代码非常好理解,论文里也容易讲清楚。
第二步:竞拍倒计时与自动落槌
场次有end_time,问题来了:用户出价时发现已经过了结束时间怎么办?系统怎么在结束时间到达时自动把“进行中”改成“已结束”并生成订单?
我对比过几种方案,很多教程推荐“在用户出价时顺便检查时间”,但这有漏洞:如果场次结束后没人出价,谁来修改状态?所以必须有一个“主动触发”的机制。三种常见做法:
| 方案 | 实现思路 | 优点 | 缺点 |
|---|---|---|---|
Spring@Scheduled定时任务 | 每30秒扫描所有end_time < 现在且status=进行中的场次,批量关拍 | 实现简单,代码一看就懂 | 有延迟(最多30秒),且服务器重启时可能错过任务 |
| Redis过期事件 + 监听 | 开拍时向Redis写入一个过期时间为“结束时间”的key,过期后通过监听触发关拍 | 相对实时 | 依赖Redis键空间通知,生产环境不一定默认开启,配置有坑 |
| 延迟队列(如Redisson) | 把关拍任务扔进延迟队列 | 实时性好 | 会引入额外框架,超出毕设复杂度 |
我的建议是直接用@Scheduled定时扫描。把“关拍”设计成一个独立的方法,每30秒跑一次,扫描条件加上end_time BETWEEN now AND now+30s或者简单一点end_time < now AND status = 1,把符合条件的场次挑出来,逐个在事务里改状态,并且对“当前最高出价非空”的场次自动生成待支付订单。这个方案不需要引入任何额外中间件,逻辑就放在Service层,80行代码搞定。
第三步:保证金与参拍限制
要不要做保证金是个权衡。真实拍卖平台都收保证金,防止恶意出价。但毕设如果做保证金,需要处理“缴纳-退还/扣款”的流程,会比较复杂。我的建议是做一个“简化版保证金”:用户参与竞拍前,必须对该场次缴纳固定金额的保证金(可以用钱包余额冻结),竞拍未成交自动解冻;竞拍成交后买家逾期不付,保证金扣除。这个功能点不大,但写在论文“系统特色”里非常有分量,能体现你考虑了交易安全,而不是只写CRUD。
4.2 商城模块:购物车、下单与订单状态机
商城部分是“双鲤”国画作品交易平台的基础功能,相对标准,但有几个细节值得重点处理:
- 购物车记录:
cart表,字段为id、user_id、artwork_id、quantity(数量,书画作品一般quantity=1)、create_time。加购接口要注意幂等——同一用户同一作品已经在购物车里,再次加购时只更新数量或提示“已添加”,不要重复插记录。 - 下单事务性:从购物车生成订单时,要在一个
@Transactional方法里完成“扣减库存/标记商品已售 → 生成订单 → 清空购物车对应记录”。如果商品是唯一孤品,库存字段就是0/1的逻辑,下单前要先SELECT ... FOR UPDATE锁住商品行,防止两个人同时买到同一件作品。 - 订单状态机:我维护的订单状态包括
待支付PAY_WAIT、已支付PAID、已发货SHIPPED、已完成DONE、已取消CANCELED。这里最重要的设计是:所有状态变更都要走同一个Service方法,比如updateOrderStatus(orderId, fromStatus, toStatus),在方法里用条件更新UPDATE orders SET status=#{toStatus} WHERE id=#{orderId} AND status=#{fromStatus},避免页面重复点击导致状态跳跃。很多同学直接在Controller里写一堆order.setStatus(2); orderService.updateById(order);,一旦并发请求打到接口,状态机就乱了,这是个很隐蔽的坑。
4.3 鉴赏社区模块:作品发布与互动
“东方艺术品交易与鉴赏社区平台”这个题目里的“鉴赏社区”是区别于普通电商的亮点功能。我做这部分时,设计了两个维度:
作品展示区(B2C):艺术家/卖家可以发布作品,填写作者、年代、尺寸、材质、创作故事,上传图片。这部分内容会同时展示在商城列表和社区信息流里——也就是说“一件作品,两种视图”。实现上只需要在查询接口里做一个参数区分,商城列表返回价格和购买入口,社区列表返回描述和喜欢数。所谓的设计技巧,并没有那么高深,想清楚“信息从哪里来、展示给谁看”就行。
用户互动区(UGC):包括帖子发布、评论、点赞、关注。这部分可以独立成两张表:post(帖子:标题、内容、图片、发布人、创建时间)、comment(评论:帖子ID、评论人、内容、父评论ID,支持楼中楼)。点赞、关注用一张关系表即可,比如user_follow(user_id, follow_id)、post_like(user_id, post_id)。
这块的代码没什么难度,但在前端展示时建议做一个瀑布流或卡片式的信息流,让书画作品的图片作为视觉主体。Vue里实现瀑布流可以用CSS columns属性,或者用Element Plus的“卡片+栅格布局”。实测下来,用CSS的columns: 2做两列瀑布流最简单,图片高度不一时效果自然。
4.4 登录鉴权与接口权限的完整套路
前后端分离项目,登录鉴权是绕不开的。我用的标准方案是JWT,流程如下:
- 前端登录页提交用户名/密码,后端校验通过后生成JWT返回给前端,同时把用户基本信息返回;
- 前端拿到Token后存到
localStorage或Pinia里,之后每次请求在Axios拦截器里加上Authorization: Bearer ${token}请求头; - 后端写一个拦截器(HandlerInterceptor),专门负责解析请求头里的Token,校验签名和过期时间,解析出用户ID后放进
ThreadLocal(或者用一个UserContext工具类),后续所有接口都能直接拿到“当前登录用户是谁”; - 需要角色限制的接口,在方法上打自定义注解比如
@RequireRole("ADMIN"),拦截器里判断当前用户角色是否匹配,不匹配直接返回403。
这里有一个常见的反面教材:很多人会把JWT的解析逻辑写在一个AuthInterceptor里,但又忘了把拦截器排除/api/user/login等白名单路径,结果前端还没登录,请求登录接口自己先被拦截了。排错时会看到控制台反复报“401”,但浏览器Network里其实是连登录接口都进不去。解决方法是注册拦截器时明确excludePathPatterns("/api/user/login", "/api/user/register", "/api/auction/public/**"),把公开接口单独列出来。
5. 前端工程化与前后端联调的硬骨头
5.1 Vue Router动态路由与菜单权限
很多毕设的前端菜单是写死的,默认显示所有菜单项。但既然后端设计了RBAC角色,前端最好也做成“动态菜单”——用户登录后,后端根据角色返回他有权访问的菜单列表,前端用vue-router的addRoute方法动态添加路由。
具体套路:用户信息里带上menus数组,前端登录后遍历数组,router.addRoute('Layout', { path: item.path, component: () => import(/* @vite-ignore */ item.component), meta: {...} }),然后再router.replace(router.currentRoute.value.fullPath)刷新一次。组件的路径可以用() => import('@/views/' + componentPath + '.vue')这种方式动态引入,但要注意Vite打包时对动态导入的处理,建议组件路径用一个白名单Map提前声明,而不是直接拼字符串。踩过一次Vite无法静态分析动态import的坑之后,我就老实了。
vue-router的路由守卫(beforeEach)也很关键,我习惯在守卫里做三件事:判断是否有Token、判断用户信息是否已加载(没有则先调/api/user/info)、判断目标路由的meta.roles是否包含当前用户角色。这三步写清楚,前端的权限控制才算完整闭环。
5.2 前后端联调中最常见的跨域问题
前后端分离开发时,前端跑在http://localhost:5173(Vite默认端口),后端跑在http://localhost:8080,端口不同必然产生跨域。
正常解法是三层配合:
- 开发环境:Vite配置
server.proxy,把所有/api请求代理到http://localhost:8080。这样浏览器里请求的是同源localhost:5173/api/xxx,Vite帮你转发,不需要后端做任何跨域配置; - 后端兜底:写一个全局CORS配置类,
addCorsMappings允许http://localhost:5173的跨域请求。这个配置在生产环境里一般用不上,但开发时能避免直接请求后端接口时报CORS错误; - 生产环境:把前端打包成静态文件后放进SpringBoot的
static目录(或者用Nginx部署),前后端同源,跨域问题自然消失。
我见过很多人的项目,前端已经打包进SpringBoot了,后端还开着CORS配置,两种方案叠加反而出问题。建议做个判断:如果前端最终要扔进SpringBoot,开发时用Vite代理就够了,别在后端开CORS,保持线上配置干净。
5.3 Vue打包后放进SpringBoot的细节
这里直接回答热词里那个高频问题:“Vue打包怎么放进SpringBoot?”答案其实很简单:执行npm run build,Vite会在项目根目录生成dist文件夹,把dist里的index.html和静态资源(assets)整个拷到SpringBoot的src/main/resources/static目录下,然后重新打SpringBoot的jar包。启动后访问http://localhost:8080,直接就能看到前端页面。
但这里有两个坑必须注意,不然打包进了也白搭:
- 路由History模式404问题:Vue Router如果用
createWebHistory,前端路由比如/artwork/1是前端的虚拟路径,后端没有对应的Controller。你直接访问http://localhost:8080/artwork/1会返回404。解法有两个:一是把前端路由改为createWebHashHistory,URL变成/#/artwork/1,后端不用处理;二是在后端写一个转发Controller,把非/api开头的路径全部转发到index.html。我推荐毕设用hash模式,省事不会错。 - 静态资源路径问题:Vite打包时默认资源路径是
/assets/xxx,如果你的SpringBoot部署在根路径下没问题;如果部署在子路径(比如http://ip:8080/art/),需要在vite.config.js里设置base: '/art/',否则资源全部404。毕设一般用根路径,但这个概念要懂。
6. 实测中踩过的坑:90%的毕设都会遇到
6.1 SpringBoot版本过高引发的“连锁反应”
我在第2章说过别追新,这里展开讲讲具体会碰到什么。有个学生使用了SpringBoot 3.2.1 + JDK 21 + MyBatis-Plus 3.5.3,启动时直接报ClassNotFoundException: javax.xml.bind.JAXBException,后面换成SpringBoot 2.7.18 + JDK8(或11)后一切正常。
更深层的原因是:SpringBoot 3.x基于Jakarta EE 9+,把大量javax.*包替换成jakarta.*,而一些框架的老版本还在引用javax。我给你的硬性建议是:
- JDK用8或11,SpringBoot用2.7.x(目前2.7.18是最终版)。这套组合和MyBatis-Plus 3.5.x、SpringDoc、jjwt 0.11.x全部兼容,网上教程也最多;
- 如果你的学校环境统一用JDK17,那选SpringBoot 2.7.x也一样跑,2.7.x兼容JDK8到JDK21;
- 万不得已非要用SpringBoot 3,所有依赖选型都要查一下“是否支持jakarta命名空间”,比如MyBatis-Plus要用3.5.4+,SpringDoc要用2.x。
一句话总结:毕设求稳不求新,这是无数人用熬到凌晨三点的教训换来的结论。
6.2 竞拍超时关单:为什么定时任务才是最优选
上面第4.1节我给了方案对比表格,这里补充一个我在实际测试时发现的细节。用Spring定时任务做超时关单,存在一个“服务重启后任务丢失”的问题——如果服务器在你定好的扫描周期内重启,期间到期的场次可能没人处理。
我的解决方案是“启动时补扫”:在应用启动完成事件(ApplicationRunner)里执行一次同样的“关拍+关单”扫描方法。这样重启之后,那些已经过期但状态未更新的场次会被立即纠正。这个思路同样适用于订单超时取消、保证金解冻等场景。代码很简短,比如:
@Component public class AuctionStartupRunner implements ApplicationRunner { @Resource private AuctionService auctionService; @Override public void run(ApplicationArguments args) { auctionService.closeExpiredAuctions(); log.info("启动时补扫过期拍卖场次完成"); } }加上这个,整个竞拍关拍逻辑就稳了。
6.3 图片上传与展示的静态资源映射
书画作品平台遍地都是图片,上传和回显示意的难点。如果不用云存储,最直接方案是本地存储:
- 新建一个
upload目录(比如项目根目录下的uploads/或者系统临时目录); - 后端接收MultipartFile后,用它原始文件名+UUID拼接成新文件名,
Files.copy(inputStream, targetPath)写入磁盘,然后把相对路径(比如/files/20250101/uuid.jpg)存到数据库; - 前端上传后用这个相对路径请求图片。
关键在后端要配置静态资源映射,让外部能访问到uploads目录下的文件:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String uploadPath = System.getProperty("user.dir") + "/uploads/"; registry.addResourceHandler("/files/**") .addResourceLocations("file:" + uploadPath); } }这里有个容易被忽略的注意点:不能用项目根目录的相对路径在打包成jar后直接访问。打成的jar包运行时,项目根目录是jar所在目录,不是源码路径。所以上传目录建议使用配置项指定,比如在application.yml里写upload.dir=/var/art-platform/uploads,或者用System.getProperty("user.dir")当前工作目录作为基准。另外,上传接口还要限制文件类型和后缀,不然别人传个.jsp或.html进uploads目录,可能被当成可执行脚本,有安全风险——限制扩展名jpg/jpeg/png/webp并校验MIME类型是最基本的。
6.4 服务器部署与环境配置
毕设最后要部署,很多人在本地跑得飞起,一到服务器就懵。我建议按这个清单来,每项都对:
- 把SpringBoot打成jar包:
mvn clean package -DskipTests,注意检查application.yml里数据库地址、用户名密码是服务器环境的,而不是本地localhost; - 服务器装环境:MySQL 8.x(记得
CREATE DATABASE xxx DEFAULT CHARACTER SET utf8mb4)、Redis(如果用了Redis)、JDK(版本匹配SpringBoot)、Nginx不是必须的; - 启动命令用
nohup java -jar xxx.jar --server.port=8080 > app.log 2>&1 &,日志重定向到文件,方便排查; - 防火墙/安全组放行端口:不同云厂商的安全组规则别忘了配,不然8080端口外部访问不了;
- 前端要不要单独部署:如果想让前后端彻底分离,就把
dist目录丢给Nginx,配置location反代后端/api。如果图省事,就按5.3节说的把dist放进jar包静态目录,一个jar包搞定一切。毕设答辩演示,我个人推荐后者,越简单越不容易出事故。
7. 论文写作与答辩准备的实用建议
7.1 论文结构怎么组织才不空洞
“注重过程、轻模板”是写好毕设论文的关键。很多同学的论文是“需求分析罗列功能,设计与实现用代码截图糊弄”,这在评阅阶段很容易被看出来。我说一下我当时(以及辅导学生时)采用的论文结构,你可以参考:
第一章 绪论:写传统书画交易线下转线上的背景,引入“双鲤/墨韵/东方艺术品”这个项目的立意。
第二章 需求分析:功能性需求用用例图(用户、艺术家、管理员各一张)、非功能性需求(性能、安全、易用性)简单描述。
第三章 系统设计:总体架构图(图里画出前端Vue、后端SpringBoot、MySQL、Redis之间的交互)、功能模块划分、数据库ER图和各表结构。
第四章 系统实现:重点写关键功能的实现思路和核心代码片段——竞拍出价的并发控制、超时关单、JWT鉴权、上传模块。不要贴大段完整Controller,贴核心Service方法,然后配文字讲解。
第五章 系统测试:单元测试和功能测试(列测试用例表),有性能测试更好——比如用JMeter模拟50个并发用户同时出价,看是否出现超卖。这个测试数据一旦放上去,论文的“工作量”立刻就显出来了。
7.2 答辩演示的节奏和话术
答辩现场翻车最多的情况是“演示中途报错”。我的建议是提前准备好一套演示脚本,按这个顺序来走:登录(演示JWT鉴权生效)→ 首页浏览作品(介绍平台的视觉风格)→ 进入商城详情页(讲商品状态)→ 模拟竞拍出价(开两个浏览器窗口用两个账号同时出价,展示并发处理的效果)→ 竞拍成功后生成订单 → 模拟支付 → 社区发布一条书画赏析帖(顺便展示图片上传)→ 管理员后台审核内容。
每一步结束后,用一两句话总结这一步背后的技术点,比如“您能看到,我这边两个窗口同时出价,系统不会出现价格覆盖的情况,因为出价接口采用乐观锁机制,同一时刻只有一个出价能更新成功”。这样演示和讲解是同步的,评委不需要追问太多,主动权在你手里。
7.3 展示亮点:把“普普通通”讲成“有思考”
如果你问评委之间对毕设的私底下评价,他们最看重的其实不是功能多,而是“这个学生有没有思考”。同样一个竞拍模块,A学生只会说“我用了定时任务关拍”,B学生能说“对比了Redis过期事件和定时任务两种方案后,考虑到服务重启会丢任务,我增加了一个启动时补扫逻辑”——高下立判。
所以,做完项目之后,我强烈建议你复盘一遍,总结至少三个“你比别人多想了一步”的点:
- 竞拍出价防并发的乐观锁设计;
- 超时关单的启动时补扫机制;
- 图片上传的安全校验和路径管理;
- JWT无状态认证比Session更适合前后端分离,为什么。
这三点分别对应“并发”“事务”“安全”,是评委最爱听得三块内容,也是你从“会写增删改查”到“会设计系统”的分水岭。
我做这种类型的毕业设计项目带过不少人,也帮人改过不少烂尾代码。这类“交易平台”项目的真实情况是:它不追求你发明什么新技术,而是追求你能不能把一个有真实业务场景的需求完整、稳定地实现出来。你愿意在并发那块多想一层,愿意在关单补偿逻辑上多贴一段代码,愿意把社区页面的瀑布流做得比别的组好看一点,你就已经超过九成的同届选手了。如果时间紧任务重,我建议按“商城链路 → 竞拍核心 → 社区展示 → 后台管理”的顺序推进,优先保证交易闭环能跑通,再把亮点功能做深。这条路走完,论文有内容、答辩有底气、简历有项目,一举三得。