1. 二次元商品项目不是“再抄一个商城”:动手前先把需求边界划清楚
做毕设或者练手项目,最怕拿到一个“springboot二次元商品销售”的题目就急着去抄淘宝的模块清单:用户、商品、购物车、订单、支付、优惠券、秒杀、后台管理……一上来铺得比天猫还大。结果代码写了一万多行,功能全是半吊子,答辩的时候老师问任何一个模块深一点就卡壳。这种项目我见过太多,实际上根本没必要。
二次元和普通电商的区别在于:二次元商品的核心不是“全”,而是“圈层”. 商品大多是手办、景品、cos服、同人制品、谷子(吧唧、立牌、色纸)、毛绒玩偶,用户关心的是角色的IP归属、系列、发行时间、是否绝版、是否现货,而不是超市里那种按日用百分类目就能解决的逻辑。所以这个系统真正该用心的地方是“围绕角色的商品信息结构”,而不是在支付和优惠券上自找麻烦。
我的建议是:先明确这道题目里的“销售”做到什么程度。毕设题目没给你线下营业执照,也不用真的接第三方支付平台,那业务边界就可以划成“模拟交易闭环”——从前端浏览、加购、下单、生成订单,到后台发货更新状态,再留一个“模拟支付成功回调”的入口,把整条链路跑通即可。真实支付、真实物流对接是加分项,但不是决定项目能不能通过的关键。
接下来动手前,把角色和用例理出来,这是后面所有表和接口设计的起点:
- C端用户:浏览商品、按IP/分类筛选、搜索、查看详情、加入购物车、提交订单、模拟支付、查看订单、确认收货、管理收货地址。
- 管理员:登录后台、商品上架下架、商品分类管理、轮播图管理、商品图片上传、订单列表与发货操作、用户管理、简单的销量统计。
这个范围覆盖了电商系统最标准的骨架,又不会因为盲目叠加模块把自己拖垮。之后无论是画ER图、写数据库设计文档,还是答辩时讲架构,都能讲得很扎实。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈与工程初始化:Spring Boot版本和ORM选型是第一个分水岭
很多同学在项目还没开始写业务代码前,就卡在“Spring Boot版本太高,启动报错”上。这个搜索热词能排得那么靠前,确实是新手重灾区。我的习惯是:如果没有强烈的学习目的,就不要追新,直接选Spring Boot 2.7.x + JDK 1.8。原因很现实:
- 2.7是2.x系列最后维护的大版本,资料最多,遇到问题能搜到大量现成答案;
- JDK 1.8是大多数学校机房和旧服务器的标配,答辩现场演示环境不容易出问题;
- 涉及后续整合Redis、MyBatis-Plus、JWT等生态组件时,2.x和这些库的兼容坐标基本都是验证过的。
有人会问,现在都Spring Boot 3了,还用2.7是不是老套?如果是个人学习,想了解Jakarta EE迁移、GraalVM这类新特性,那用3.x没问题。但作为一个要兼顾开发效率、稳定性和演示效果的毕设项目,不冒兼容性风险是更聪明的选择。
依赖方面,我推荐这一组“黄金搭档”,全部经过大量电商项目验证:
| 组件 | 用途 | 备注 |
|---|---|---|
| Spring Boot 2.7.x | 项目基础框架 | starter-web、starter-validation、starter-data-redis按需引入 |
| MyBatis-Plus | ORM与通用CRUD | 分页插件、代码生成器能极大提升编码速度 |
| MySQL 5.7/8.0 | 持久化存储 | 8.0注意驱动坐标和时区配置 |
| Redis | 缓存、购物车临时存储 | 配合Spring Cache使用,别重复造轮子 |
| JWT + Spring Security或Interceptor | C端登录鉴权 | 学生项目用拦截器就够,Security配置门槛略高 |
| MinIO或本地磁盘 | 商品图片存储 | 毕设优先本地目录,别一上来就搞OSS |
| Lombok | 消除冗余代码 | IDEA记得装插件 |
| Vue 3 + Element Plus(可选) | 管理端前端 | 也可以用Thymeleaf,取决于你前端熟练度 |
我的建议是:如果前后端你都能Hold住,管理端用Vue3 + Element Plus做成前后端分离效果会更亮眼;如果前端比较吃力,或者项目时间紧,那就老老实实用Thymeleaf + Bootstrap把页面渲染出来。答辩评委看的是业务链路是否完整,不会因为你用了前后端分离就多给分,但会因为“你连页面都跑不起来”而扣分。
工程结构我一般按这样分包,简单直接,也符合大多数毕设模板的口味:
text复制src/main/java/com/example/secondarymarket/
├── config/ // 跨域、拦截器、MyBatis-Plus分页等配置
├── controller/ // C端和B端接口
├── service/ // 业务接口与实现
├── mapper/ // MyBatis-Plus Mapper
├── entity/ // 数据库实体
├── dto/ // 前端入参对象
├── vo/ // 返回给前端的视图对象
├── common/ // 统一返回体、异常处理、常量
├── utils/ // JWT、文件上传等工具
└── SecondMarketApplication.java
有些同学不喜欢分dto和vo,User对象从Controller一路传到Mapper,实体字段里面带着password一起返回给前端。这在个人项目里看着能跑,但你在简历和论文里写“系统设计合理”的时候自己都会心虚。分离dto/vo不会多花多少时间,却能挡住“密码明文返回前端”这种致命低级错误,属于花了小成本换大收益的事。
3. 数据库设计:商品SKU、预售状态、订单状态机一次建模到位
数据库是这种项目的重中之重。二次元商品销售系统的表和通用商城表差别不大,但有几张表必须单独用心设计,否则业务根本撑不住。
我建议核心表这样划分:
- user(用户):用户ID、昵称、头像、手机号、密码、状态、注册时间。
- category(商品分类):分类ID、父分类ID、分类名称、图标、排序——用来支撑“手办/景品/谷子/cos服/同人周边”这种顶层分类。
- product(商品SPU):商品ID、分类ID、商品名称、副标题、主图、详情描述、关联IP/角色标签、售价、原价、总库存、销量、状态(上架/下架)、是否预售、预售发货时间、创建时间。
- product_sku(商品SKU):对于手办或服装这种有规格的商品非常关键。SKU ID、商品ID、规格名称(如“高16cm/普通版”“M码/蓝色”)、库存、价格。二次元商品常常一个IP出好几种“版本”,SPU/SKU分开是正确姿势。
- product_image(商品图集):商品ID、图片URL、排序。很多毕设把图片塞在product表的一个字段里,一张主图两张详情图,用逗号割开,后期改起来非常痛苦。
- cart_item(购物车):购物车ID、用户ID、SKU ID、数量、勾选状态、加入时间。
- order(订单):订单ID、订单编号、用户ID、收货人、手机号、省市区、详细地址、总金额、支付方式、状态、支付时间、发货时间、完成时间、备注。
- order_item(订单明细):明细ID、订单ID、SKU ID、商品名称、商品图片快照、规格快照、单价、数量、小计。
- address(收货地址):地址ID、用户ID、收货人、手机号、省市区、详细地址、是否默认。
- banner(轮播图):用于首页运营位展示。
- admin(管理员):后台登录账号。
这里有个新手特别容易忽略的设计点:订单表和订单明细表里,必须有冗余的商品名称、图片、规格快照。为什么?因为商品以后会改价、改描述甚至删除,而订单是历史数据,它代表的应该是用户下单那一刻的商品信息。如果不冗余快照,商品改个名字,你所有历史订单都跟着变,这在电商设计里是严重失误。答辩时把这个想清楚,老师会觉得你是真做过业务的。
订单状态字段,我强烈建议用整数状态码,而不是字符串。状态机可以这样设计:
| 状态码 | 含义 | 说明 |
|---|---|---|
| 0 | 待付款 | 下单成功但未支付 |
| 1 | 待发货 | 已支付,模拟支付回调处理成功 |
| 2 | 待收货 | 管理员发货完成 |
| 3 | 已完成 | 用户确认收货 |
| 4 | 已取消 | 用户主动取消或超时未支付取消 |
状态转换必须是有向的:0 → 1 → 2 → 3,0 可以直接到 4。绝不能出现从“已完成”又跳回“待付款”这种状态回滚。写接口时候,我习惯加一个if判断当前状态是否允许流转,防止别人通过手动调接口绕过流程。
库存这快,二次元商品有个很常见的业务:手办限量发售、预售后过几个月才补款发货。所以product表里要加is_pre_sale(是否预售)和presale_delivery_time(预计发货时间)字段,前端商品列表根据is_pre_sale打上“预售”角标,详情页展示发货时间,下单时也能在订单备注里提示“预售商品支付后xx天内发货”。这种设计比单纯卖现货更像二次元垂直电商,是很好的差异化亮点。
4. 核心业务实现:从JWT登录到下单扣库存的完整链路
业务代码的编写顺序我推荐“从基础到流程”:先做统一返回体和全局异常,再做登录鉴权,然后是商品展示,最后做购物车和下单。别一上来就写订单接口,那样调试起来你会被各种前置bug烦死。
4.1 JWT登录怎么做才省心
我这个项目里登录用户用的是JWT,后台管理员我也建议用同一套机制但区分角色。核心思路是用户登录成功后,生成一个包含用户ID和用户名的token,后续请求在Header里带上Authorization: Bearer <token>,拦截器解析token并把用户信息塞进ThreadLocal或Request attribute里。
拦截器里要注意两个坑。一个是放行白名单:登录、注册、首页商品列表、商品详情这些接口必须允许匿名访问,否则前端还没登录连首页都刷不出来。另一个是跨域配置:前后端分离时必须配置CORS,否则浏览器直接给你拦了,接口在Postman里通、在页面上死活不通,多半就是这个原因。统一返回体我一般设计成code/message/data三段,code为200表示成功,401表示未登录,500表示业务异常。这样前端axios拦截器只需要判断code,不需要每个接口单独处理错误。
4.2 商品列表与搜索的缓存策略
商品列表是访问量最大的接口,也是最容易被查爆的。最简单的做法是MyBatis-Plus的LambdaQueryWrapper按分类、关键词、上下架状态、排序条件去查,然后套一层缓存:
java复制@Cacheable(cacheNames = "product:list", key = "#categoryId + ':' + #keyword + ':' + #page + ':' + #size")
public Page<ProductVO> listProducts(Long categoryId, String keyword, Integer page, Integer size) {
// 拼接查询条件,分页返回
}
这里有个细节:搜索引擎是like %keyword%,数据量小看不出来问题,数据量一大就有性能隐患。毕设阶段用MyBatis-Plus的like就够了,但如果想给自己加亮点,可以通过热点IP标签做筛选,再配合Redis缓存首页热门分类,把查询压力降下来。
缓存更新要注意时效性。管理员后台改了商品价格或库存,必须清除对应缓存。最省事的方案是在商品更新的service方法上用@CacheEvict指定清掉相关key,如果key规则复杂,可以封装一个缓存工具类手动删除。毕设答辩时能讲出“我做了缓存穿透/缓存过期策略”就已经比大多数同学强了。
4.3 购物车放Redis还是放数据库
购物车有两条路线。路线一:直接存MySQL的cart_item表,优点是实现简单,和管理端共享同一套ORM;缺点是每次加购都要查库,用户没登录就没法加购。路线二:存Redis的Hash结构,以cart:userId为key,skuid为field,数量为value,优点是轻快,缺点是还得处理登录后合并、过期清理等逻辑。
我的建议是:毕设项目直接存数据库。原因是购物车算是订单的前置环节,最终下单要以数据库中的购物车数据为准;而且B端+C端提交的表,数据库购物车更容易讲清楚流程。Redis可以放在登录token缓存、验证码、首页热点商品这些更适合它的地方,各司其职。
加购接口有一个业务规则必须处理:库存校验。用户加购的数量不能超过当前SKU的剩余库存,下单时也一样。数据库表里可以加一个逻辑,把加购数量和库存做比较,超出就在service里抛业务异常,前端收到统一的错误提示。
4.4 下单与库存扣减:并发不会考你Nginx+多线程压测,但会问超卖怎么防
下单是技术上最核心的接口,至少值得写几百行思考。这个系统里订单创建大致流程是:
text复制接收订单参数 -> 校验收货地址 -> 锁定购物车/直购商品 -> 计算金额
-> 扣减库存 -> 创建订单主记录 -> 创建订单明细 -> 删除购物车已购项 -> 返回订单ID
库存扣减的代码是超卖问题的重灾区。简单形态,不锁的话长这样:
java复制ProductSku sku = skuMapper.selectById(skuId);
if (sku.getStock() < quantity) {
throw new BizException("库存不足");
}
sku.setStock(sku.getStock() - quantity);
skuMapper.updateById(sku);
这段代码在低并发下没问题,但如果两个请求同时都查到库存还有1件,然后都执行减1,就可能出现stock变成-1的情况。防超卖有两个常用方案,毕设选数据库乐观锁就够了:
xml复制<update id="deductStock">
UPDATE product_sku
SET stock = stock - #{quantity}
WHERE id = #{skuId}
AND stock >= #{quantity}
</update>
这条SQL本身是原子操作,stock >= #{quantity}条件保证了不会扣成负数。如果更新影响行数为0,说明库存被抢光了,直接抛异常即可。这个方法实现成本低,又能自圆其说,面试官和答辩老师都会认。
支付回调也可以做一个模拟版本,不需要真接支付宝。下单后订单状态是0(待付款),前端点“模拟支付成功”,后端只做一件事:校验订单属于当前用户、状态是0,然后把状态改为1(待发货),记录支付时间。只要你把接口文档里写清楚“真实环境中这里应该替换为第三方支付异步通知验签”就非常体面。
5. 管理端的关键差异点:图片上传、订单发货与销量统计
B端管理端的核心不是界面多漂亮,而是数据操作对C端的影响是否即时生效。我的习惯是给管理员设计独立的Controller体系,与C端接口分开,比如:
- C端接口:
/api/user/**、/api/product/**、/api/cart/**、/api/order/** - B端接口:
/api/admin/**
拦截器里对/api/admin/**额外校验管理员身份token,防止普通用户拿到管理员的接口地址越权调用。
5.1 图片上传:本地存比云存储更适合
商品要卖得好,在二次元电商里,图片就是“门面”,手办的一张大图比一百个字都管用。所以我建议produc表只存一个URL,上传的图片文件(jpg/png/webp)保存到服务器磁盘的/upload/年/月/日/随机文件名.jpg下,再用一个虚拟路径映射出去:
yaml复制spring:
resources:
static-locations: file:D:/upload/,classpath:/static/
上传接口里必须做两件事情。第一是限制文件类型,只允许图片后缀,不要信任前端传来的文件名的“图片后缀”,用工具的getContentType()和文件头做双重校验;第二是控制图片大小,手办详情图的单张建议不超2MB,必要的话用thumbnailator压缩一下再存。不然图是传上去了,页面一整个卡成PPT,答辩演示时特别丢人。
5.2 发货与订单状态闭环
订单管理列表按状态筛选:待付款、待发货、待收货、已完成、已取消。管理员的“发货”操作要注意:并不是只有点击发货完事,而是要校验订单确实处于待发货状态,然后更新物流公司、物流单号、发货时间,再把订单状态从1变为2。
这里我踩过一个坑:如果只更新“发货时间”,不更新“状态”,前端订单页会一直显示“待发货”,用户看不到物流信息就会认为商家没发货。所以状态字段和物流字段必须在一个事务里同时更新,并且前端订单详情的展示逻辑要按状态码渲染,不能只靠几个时间字段是否存在来判断。在管理端和C端联调的时候一定要把这条链路完整跑一遍:下单→支付→发货→收货。
5.3 简单的销量统计
二次元商品比普通商品更看重“热销榜”和“即将售罄”。后台统计可以很简单,直接根据order_item表分组统计销量:
sql复制SELECT product_id, SUM(quantity) AS total_sales
FROM order_item
WHERE create_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY product_id
ORDER BY total_sales DESC
LIMIT 10
这个统计在秒级、千级订单量下没有任何性能问题,不需要引入定时任务去洗数。饼图折线图就用ECharts在管理端展示,一组接口返回最近7天订单数、销售额、热门分类销量,拼一拼就是一个很能打的Dashboard页面。
6. 实战踩坑记录:从环境配置到答辩演示的六个“保命细节”
这部分是正文风格的博主最想写的部分,踩坑经验往往比功能清单更值钱。我把这个项目里最容易让新手崩溃的问题整理出来,按“开发期→部署期→答辩期”排列。
6.1 版本兼容性是最耗时间的隐形杀手
Spring Boot 2.7 + MyBatis-Plus要特别注意,MyBatis-Plus不要用最新3.5.9这种太新的社区版,因为部分版本和Spring Boot 2.7的自动配置有兼容差异,启动时会报奇怪的bean冲突。安全组合是Boot 2.7.x搭配MyBatis-Plus 3.5.3.x附近的小版本,或者干脆用官方mybatis-plus-boot-starter里的默认版本。Redis方面,如果本机没装Redis,简单做法是用Docker跑一个单机实例;如果连Docker都没有,可以先用本地缓存代替,把Redis部分作为“扩展改进点”写进论文里。
6.2 时间字段的时区问题
数据库连接串没加serverTimezone=Asia/Shanghai的情况下,本地MySQL保存的时间比实际时间早8小时。新手容易以为是代码问题,在实体上手工加8小时,然后在另一台电脑上又不一致了。正确做法是在连接串里显式加:
yaml复制jdbc:mysql://localhost:3306/second_project?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
另外,在实体类的时间字段上统一用LocalDateTime,不要用java.util.Date。否则JSON序列化格式和数据库存储类型会出各种稀奇古怪的错。
6.3 密码安全和越权问题
用MD5直接存密码属于答辩硬伤。Spring Boot里给你自带了DigestUtils,但MD5可爆破性很强。简单但规范的做法是用BCryptPasswordEncoder,这个编码器每次加密结果都不同,自带随机盐,是Spring Security生态里现成的方案。如果你不想引入整个Spring Security,只引入spring-security-crypto这个依赖也能用。
再提一个越权问题:修改地址、取消订单、查看订单详情的时候,接口必须拿当前登录用户ID和资源归属用户ID做匹配。很多毕设代码只写了orderService.getById(id)就返回数据,结果任意登录用户只要改一下订单id就能看别人的订单,这在论文里被老师抓住就是一个严重安全缺陷。
6.4 演示数据要提前准备好
演示的时候千万别临时往库里敲数据。二次元商品项目要提前把几十条商品数据维护好,包含手办、景品、谷子、毛绒玩具各若干条,商品名称里带上角色和IP特征,例如“初音未来 景品 手办 16cm ”之类的关键词。图片找正规无版权风险的素材,不要拿大厂官网的原图商用。首页轮播图弄三四张,分类图标齐全,搜索测试也用真实会命中的关键词——用“手办”能搜出一页数据,比空搜索页面专业得多。
6.5 远程演示环境备一套
很多同学答辩时用的是自己笔记本,现场WiFi质量无法保证,MySQL连不上、图片本地路径加载不出来都是常见事故。建议答辩前准备两套环境:一套是本机终端+服务端;另一套是打包好的jar包,里面带有sql/init.sql,在演示电脑的本地装好MySQL导入数据,用nohup java -jar xxx.jar方式启动。同一台电脑上既跑前端又跑后端,把地址都配为本机localhost,演示时打死也不依赖外网。
6.6 回答问题前想清楚这几个点
老师针对这种毕设提问,最常问的问题其实就那么几个:
- 为什么用MyBatis-Plus而不用JPA?答:需要灵活控制SQL用于扣库存、统计,MyBatis-Plus保留手写SQL能力。
- 订单的支付流程是真实的吗?答:模拟支付,真实环境预留回调验签接口。
- 商品多规格怎么设计的?答:SPU+SKU,SKU粒度控制库存和价格。
- 如果秒杀/限量发售时高并发怎么办?答:库存扣减用原子update防止超卖,缓存热点商品数据。
- 用户密码存的是什么形式?答:BCrypt加密,数据库不存明文。
- 图片上传存哪?答:本地磁盘+虚拟映射,生产可替换OSS/MinIO。
把这些问题事先在项目里跑一遍,心里有底,答辩的时候就不会被问住。不要刻意背答案,你要做的就是真正把项目代码敲出来、数据库建对、接口调通、关键截图截好。源码能跑、逻辑能讲、亮点能演示,这套“springboot二次元商品销售”就能成为一份拿得出手的计算机毕业设计作品。
我在带类似项目时最深的一个体会是:毕设的价值不在于功能有多全,而在于你是否能把每一条业务规则的“为什么”讲清楚。商品表为什么要有SKU、订单明细为什么要存快照、扣库存为什么要用条件更新,这些问题想明白,代码写出来就是有灵魂的。哪怕你只实现了最核心的浏览、加购、下单、发货闭环,只要每个环节都经得起追问,整场答辩都会非常顺畅。照着这些细节把项目从零搭一遍,你收获的不只是一份源码,而是一套以后工作中遇到交易系统时能直接迁移的底子。
