每年到这个季节,后台总会收到同一类私信:毕业设计到底选什么题,既能稳稳过审,用到的技术又主流,演示效果还能撑得起答辩?我的回答一直比较固定——商城类项目可以做,但要选垂直场景,别上来就是一套泛泛的“图书商城”或“电商平台”。户外用品、登山装备这类带明确用户画像的垂直商城,在业务完整度、数据表设计和展示效果上都更容易做出层次感。
前阵子整理源码库,翻出一套 Spring Boot 登山用品商城(编号 27394),从选题、建库、写接口到部署上线,整个链路都在。这篇文章就顺着这套源码实际跑过的路线,把“登山用品商城”这个计算机毕业设计题目的完整开发思路捋一遍。不管你是打算拿现成源码做二次开发,还是想从零手写一版,耐心看完都能少踩几个坑。
1. 为什么毕设选“登山用品商城”:一个不算惊艳却很稳的选题
1.1 一眼看穿选题的门道
毕设选题最怕什么?怕题目太大,写不完;也怕题目太小,没东西可写。商城系统恰好卡在中间:既有用户注册登录、首页展示、商品详情、购物车、订单这些经典业务,又有后台管理、数据统计、权限控制这些能体现工程能力的模块。而“登山用品商城”在商城的通用骨架上,又加了一层垂直商品的特性。
拿实物商品举例。登山用品商城里的商品天然带有分类结构:鞋靴类有登山鞋、溯溪鞋、攀岩鞋;服装类有冲锋衣、抓绒衣、羽绒服;装备类有登山杖、头灯、帐篷、睡袋、绳索。这种多级分类天然适合用parent_id做树形结构,也能把“分类导航 + 筛选 + 搜索”做扎实。相比那些只有十几条测试数据的图书商城,这类项目的商品数据也更贴近真实运营场景,答辩演示时视觉上就不空洞。
1.2 你需要交付哪些东西才算“完整”
很多同学以为“源码能跑”就完事了,这是对毕设最大的误解。一套能拿得出手的 Spring Boot 登山用品商城,交付物应该是“代码 + 数据库 + 文档 + 演示”四件套。
功能层面,至少要覆盖两条业务线:
- 用户端:首页轮播与精选推荐、商品分类浏览、关键词搜索、商品详情、加入购物车、结算下单、模拟支付、订单状态跟踪、个人资料与收货地址维护。
- 管理端:管理员登录、商品增删改查、分类管理、库存调整、订单发货、用户禁用、销售数据可视化。
数据库层面,核心表至少得有用户表、分类表、商品表、购物车表、订单表、订单明细表。如果再想加亮点,可以补一张收货地址表、一张操作日志表。这里要特别注意:order和user在 MySQL 里虽然不是保留字,但为了避免不必要的麻烦,建表时我习惯把订单表命名成t_order,用户表命名成t_user。
文档层面,开题报告、任务书、中期检查、论文正文加附录缺一不可。论文里的 E-R 图、用例图、时序图和数据库设计说明,务必与源码里的实际字段保持一致,这是答辩老师最喜欢核对的地方。
1.3 功能范围如何做减法
毕设不欢迎“过度设计”。我看到太多人在订单支付环节硬接支付宝沙箱或微信支付,结果被商户号、回调验签、证书配置折磨了两周,最后答辩还只字不提。登山用品商城这个题目,完全可以用“模拟支付”来解决:用户下单后生成待支付订单,点击“模拟支付”按钮,前端直接调用一个pay(orderNo)接口,后端把订单状态置为“已支付”并记录支付时间。
权限控制也一样。用 Spring Boot + JWT 做 Token 鉴权,一套拦截器解决登录态校验,既符合当前前后端分离的主流写法,又能把你对认证鉴权的理解讲清楚。没必要为了“技术看起来高级”硬上 Spring Security OAuth2,那套东西在单体毕设里只会带来巨大配置量,还容易把 HashMap 安全漏洞写出来被老师追问。
2. 技术选型:用一套“主流 + 够用”的组合避免给自己挖坑
2.1 选型原则:答辩时能说清楚的就是好技术
技术栈选型最怕“从众”。看到网上的博客全员 Spring Cloud Alibaba,你也跟着上 Nacos、Gateway、OpenFeign,结果本地启动三四个服务内存就爆了。登山用品商城这种单体项目,最好的选择是:Spring Boot 2.7 + MyBatis-Plus 3.5 + MySQL 8.0 + Redis + JWT + Vue 2/3。
为什么不用 Spring Boot 3?因为 Boot 3 强制要求 JDK 17,而多数学校机房、老师演示环境用的还是 JDK 8。Spring Boot 2.7 既兼容 JDK 8,又能满足大多数新特性要求,属于“稳中带新”的选择。如果你手上源码本来就是 3.x 版本,也没关系,但部署时务必确认 JDK 版本与你 pom.xml 里java.version一致。
MyBatis-Plus 是这类毕设项目里的“救命稻草”。它提供内置的通用 CRUD、分页插件、条件构造器,能省掉大量繁琐的 XML Mapper。更关键的是,答辩老师对它的接受度很高,毕竟它本质是 MyBatis 的增强工具,面试时还能顺带聊 Lambda 表达式和插件机制。
2.2 Spring Boot 项目结构规划
源码拿到手后,先不要急着启动。花十分钟把项目结构看清楚,后面排错会省非常多的时间。一套比较清晰的结构是这样的:
tshop ├── src/main/java/com/example/tshop │ ├── common # Result 统一返回、自定义异常、常量 │ ├── config # 跨域配置、拦截器注册、静态资源映射 │ ├── controller # 用户端和管理端接口 │ ├── entity # 数据库实体 │ ├── mapper # MyBatis-Plus 的 Mapper 接口 │ ├── service # 业务逻辑层 │ ├── utils # JWT 工具类、MD5 工具类 │ └── TshopApplication.java ├── src/main/resources │ ├── mapper # 自定义 SQL 的 XML 文件 │ ├── sql # 建表脚本和初始化数据 │ ├── static # 上传图片的默认目录 │ └── application.yml └── frontend # Vue 前端工程这种按common / config / controller / service / mapper分层的结构,不仅代码好找,写论文画架构图的时候也直接能照搬。很多同学把 controller 写得巨厚无比,业务逻辑全堆在接口里,看起来跑通了,实际上论文里“业务逻辑层设计”那一章根本没法写。
2.3 为什么不用更“炫”的微服务和高并发方案
我知道有同学想给自己的项目加“分布式锁”“消息队列”“Redis 缓存秒杀”这些关键词。这里说句掏心窝的话:如果这套系统确实是你一行行写出来的,加这些无可厚非;如果你连源码都没完全跑通,就不要在文档里写“基于 Redis 实现高并发库存扣减”。老师只需要随便问一句“你 Redis 的序列化方式是什么”“缓存和数据库一致性怎么保证”,现场就很容易穿帮。
单体架构不是缺点,恰恰是毕设最合适的规模。一次请求链路短、事务控制简单、部署运维方便,你完全可以把精力集中在订单状态流转、权限拦截、数据统计这些能讲出深度的点位上。真正加分的是“你能把自己的项目在单体架构下做到逻辑严密”,而不是“你用了多少中间件”。
3. 核心模块实现:从建表到接口,把商城业务走通
3.1 数据库设计:先理清几条核心链路
数据库是商城项目的底盘。我习惯先画一条“用户 → 商品 → 购物车 → 订单 → 订单明细”的主链路,再围绕这条链路补表。下面这套表结构是按源码 27394 实际使用的设计整理出来的,可以直接作为参考:
| 表名 | 关键字段 | 说明 |
|---|---|---|
| t_user | id, username, password, nickname, avatar, phone, role | role 区分管理员和普通用户 |
| t_category | id, parent_id, name, sort, icon | parent_id 实现多级分类 |
| t_product | id, category_id, name, subtitle, main_image, price, stock, sales, status, detail | price 用 DECIMAL, stock 用 INT |
| t_cart | id, user_id, product_id, quantity, checked | 购物车项 |
| t_order | id, order_no, user_id, total_amount, pay_amount, status, receiver_name, receiver_phone, receiver_address, remark, created_at | status 作为订单状态机 |
| t_order_item | id, order_id, product_id, product_name, product_image, price, quantity, total_price | 下单时冗余商品快照 |
| t_address | id, user_id, name, phone, province, city, district, detail | 收货地址 |
这里有三条经验:
第一,“商品快照”。订单明细里的product_name、product_image、price必须是下单那一刻的商品信息拷贝,而不能通过product_id去关联查询。否则商品改价或删除之后,历史订单就全乱套了。这是商城项目里一个非常容易踩的设计坑,也是答辩时一个很好的加分点。
第二,金额字段一律用DECIMAL(10,2),代码里用BigDecimal。double 在二进制浮点运算里会产生精度误差,订单金额一旦出现1.9999999,演示效果就直接崩了。
第三,订单号不要用数据库自增 id,应该用时间戳 + 随机数生成唯一字符串,比如202504071530122345678。这样既方便查询,也能在演示时显得专业。
3.2 用户登录与 JWT 鉴权
用户模块是商城系统的入口,核心就两件事:注册时密码不能明文存,登录后要有无状态凭证。
密码处理最简单的方案是 MD5 + 固定盐。虽然不如 BCrypt 高级,但对于一个不涉及真实支付的毕设项目,把“不能明文存储”这个意识体现出来就够了。注册接口大致长这样:
@PostMapping("/register") public Result register(@RequestBody RegisterDto dto) { if (userMapper.selectCount( new LambdaQueryWrapper<User>() .eq(User::getUsername, dto.getUsername())) > 0) { return Result.error("用户名已存在"); } User user = new User(); user.setUsername(dto.getUsername()); user.setPassword(Md5Util.encode(dto.getPassword())); user.setNickname(dto.getNickname()); user.setRole("USER"); userMapper.insert(user); return Result.success(); }登录成功后签发 JWT:
public static String createToken(Integer userId, String username) { return Jwts.builder() .setSubject(username) .claim("userId", userId) .setExpiration(new Date(System.currentTimeMillis() + 1000L * 60 * 60 * 24)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); }紧接着在 WebMvcConfig 里注册一个拦截器,拦截所有/api/**的请求,只放行登录、注册、商品浏览这些公开接口:
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new JwtInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/api/user/login", "/api/user/register", "/api/product/list", "/api/product/detail/**"); }JWT 的优势是服务端不用存 Session,天然适合前后端分离。前端把 token 放到 localStorage,请求时塞进Authorization头,后端从请求头解析用户信息。这里有个小坑:解析 token 失败时不要直接返回空,要区分“未登录”和“token 过期”,否则前端没法决定是跳登录页还是刷新 token。
3.3 商品列表与分类检索
商品列表是整个商城承载力最强的一个接口。它既要支持分页,又要支持分类过滤、关键词搜索、按价格或销量排序。用 MyBatis-Plus 的条件构造器可以写得很干净:
public PageResult<ProductVO> pageProducts(int page, int size, Integer categoryId, String keyword) { Page<Product> p = new Page<>(page, size); LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Product::getStatus, 1) .eq(categoryId != null, Product::getCategoryId, categoryId) .like(StringUtils.hasText(keyword), Product::getName, keyword) .orderByDesc(Product::getSales); productMapper.selectPage(p, wrapper); return PageResult.of(p); }如果你把分类做成了树形结构,这里还要考虑一个问题:选中父分类时,要不要把子分类下的商品也查出来?我的建议是要。实现方式有两种:一是递归查询子分类 id 列表,二是直接用IN子查询。前一种代码好理解,适合在论文里画流程图;后一种效率高,但可读性稍差。毕设项目不缺性能,优先选择能讲清楚的方式。
3.4 购物车与下单事务控制
购物车本质上就是一张关联表,用户点击“加入购物车”时插入或更新t_cart记录。真正考验逻辑的是“结算下单”这一步,它涉及库存扣减、订单头写入、订单明细写入、购物车清理,任何一个环节失败都得回滚。
我之前看到有同学在 Service 里这样写下单逻辑:
productMapper.updateStock(productId, product.getStock() - quantity); // 先扣库存 orderMapper.insert(order); // 再插订单 cartMapper.deleteById(cartId); // 最后清购物车表面看是对的,但没加锁。当两个用户同时买最后一个库存时,两个请求都读到库存为 1,都去扣减,最后库存变成 -1。这就是经典的“超卖”。
正确做法是,在下单事务里先用行锁锁住商品记录,再判断库存:
@Transactional(rollbackFor = Exception.class) public OrderVO createOrder(OrderCreateDto dto) { // 1. 锁行查询商品,防止超卖 Product product = productMapper.selectByIdForUpdate(dto.getProductId()); if (product.getStock() < dto.getQuantity()) { throw new BizException("库存不足"); } // 2. 扣减库存并增加销量 productMapper.updateStockAndSales(dto.getProductId(), dto.getQuantity()); // 3. 生成订单头 Order order = buildOrder(dto, product); orderMapper.insert(order); // 4. 生成订单明细(商品快照) orderItemMapper.insert(new OrderItem(order.getId(), product, dto.getQuantity())); // 5. 删除购物车记录 cartMapper.deleteById(dto.getCartId()); return OrderVO.from(order); }对应的 Mapper XML 自定义 SQL:
<select id="selectByIdForUpdate" resultType="com.example.tshop.entity.Product"> SELECT * FROM t_product WHERE id = #{id} FOR UPDATE </select>关键在于FOR UPDATE。它会在事务期间锁住这一行,其他事务必须等当前事务提交或回滚后才能操作同一行,从源头上避免了超卖。
这地方还有第二个坑:事务内部 try-catch。如果你在createOrder方法内部把异常捕获了,Spring 就感知不到异常,事务自然不会回滚。所以异常一定要往外抛,在 Controller 层统一处理。
3.5 订单状态机与模拟支付回跳
订单状态我建议用一个常量类或枚举维护,不要直接在代码里散落一堆魔法数字:
public class OrderStatus { public static final int UNPAID = 0; // 待支付 public static final int PAID = 1; // 已支付 public static final int SHIPPED = 2; // 已发货 public static final int FINISHED = 3; // 已完成 public static final int CANCELED = 4; // 已取消 }状态流转定义为:待支付 → 已支付 → 已发货 → 已完成;待支付也可以直接流转到已取消。这样的状态机在论文里画成图非常清晰。模拟支付接口也很简单:
@PostMapping("/api/order/pay/{orderNo}") public Result pay(@PathVariable String orderNo) { Order order = orderMapper.selectOne( new LambdaQueryWrapper<Order>() .eq(Order::getOrderNo, orderNo)); if (order == null || order.getStatus() != OrderStatus.UNPAID) { return Result.error("订单不存在或状态异常"); } order.setStatus(OrderStatus.PAID); order.setPaidAt(new Date()); orderMapper.updateById(order); return Result.success("支付成功"); }如果有人问你“用户下单后一直不支付怎么办”,可以补一个定时任务:每五分钟扫描超过 30 分钟未支付的订单,将订单置为取消状态,并把冻结的库存回补。这个功能见得很多,但真去实现的人不多,它属于“答辩时随口一提就很加分”的小亮点。
3.6 管理端数据看板(朴素版统计)
后台管理端最有演示效果的不是商品 CRUD,而是数据看板。用两个 SQL 统计订单数和销售额:
// 今日订单数 select count(*) from t_order where date(created_at) = curdate(); // 近 30 天每日销售额 select date(created_at) as day, sum(pay_amount) as amount from t_order where status in (1, 2, 3) and created_at >= date_sub(curdate(), interval 29 day) group by date(created_at) order by day;把查出来的数据返回给前端,用 ECharts 画一张折线图,整个项目的完成度瞬间就上一个档次。这个模块代码量不大,但视觉冲击力很强,可以在最终演示时放在靠后的位置,作为收尾亮点。
4. 把源码跑起来:环境配置和最容易卡住的三个环节
4.1 环境清单与版本匹配
导入源码之后第一件事不是双击运行,而是检查环境。“版本不匹配”造成的报错,在毕设群里的出现频率高得离谱。我的建议配置是:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | 看 pom.xml 中 java.version |
| Maven | 3.6+ | IDEA 自带或独立安装均可 |
| MySQL | 8.0 | 兼容 5.7,但推荐 8.0 |
| Redis | 5.x 及以上 | 若源码用到缓存/验证码 |
| Node.js | 16 或 18 | 启动 Vue 前端用 |
| IDEA | 2021+ | 社区版也能跑,不强制旗舰版 |
4.2 配置文件里的“坑”:端口、库名、时区、上传路径
application.yml里最容易出问题的不是技术,而是细节:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/tshop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: root redis: host: localhost port: 6379 servlet: multipart: max-file-size: 10MB max-request-size: 10MB mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true第一个坑是serverTimezone。MySQL 8 默认时区和中国差 8 小时,如果不写serverTimezone=Asia/Shanghai,插入数据的时间字段会整体偏早 8 小时,答辩演示订单时间对不上很尴尬。
第二个坑是数据库名。源码里配置的是tshop,你本地建的库如果是demo,启动时一直报Table 'demo.t_user' doesn't exist。所以要么改配置,要么按 SQL 脚本建库,不要自创名。
第三个坑是文件上传路径。商品图片上传后默认写到src/main/resources/static/upload,但正常打包部署后,这个目录可能在 jar 包内部,无法真实落地。建议在配置里单独指定一个外部路径:
upload: dir: D:/tshop-upload/然后写一个静态资源映射类:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Value("${upload.dir}") private String uploadDir; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadDir); } }这样图片文件独立于 jar 包存在,重启不会丢。
4.3 数据库脚本与初始化数据
一套规范的毕设源码应该自带sql/tshop.sql,里面既有建表语句又有初始化数据。运行顺序是:先建库,再导入脚本。导入时注意看脚本里是否包含DROP TABLE IF EXISTS,如果答案是否,而你的库里又已经有同名表,会导入失败。
初始化数据至少要有:
- 管理员账号一个,比如
admin / 123456,密码用 MD5 加密后的值。 - 分类数据至少两级,比如“装备”下面挂“登山杖”“头灯”。
- 商品数据每条配好主图和详情,不要用外链图片,本地图片更好,避免答辩时没网。
4.4 前端启动:跨域与静态资源
前端工程启动时会遇到一个经典问题:接口请求跨域。开发环境最简单的方案是配置 Vue 的 devServer 代理,让前端请求转发到后端,从浏览器视角看是同源的。
// vue.config.js module.exports = { devServer: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } };后端同时也要做跨域兜底,因为生产环境很可能是前后端分开部署。这里记住一个要点:allowedOriginPatterns("*")可以配合allowCredentials(true)使用,但allowedOrigins("*")不行,后者在允许携带 Cookie 时会报错。
4.5 源码导入后常见报错与排查链路
把这几个报错放在前面,基本能覆盖大多数启动问题:
错误一:Invalid bound statement (not found)这是 Mapper XML 没有被扫描到。检查mapper-locations: classpath:mapper/*.xml是否与 XML 实际位置一致,再检查 Mapper 接口上有没有加@Mapper或启动类上有没有加@MapperScan。
错误二:Failed to configure a DataSource: 'url' attribute is not specified大概率是application.yml没被加载,或者pom.xml里把配置文件名改了。检查 resources 目录下是否有配置文件,IDEA 中右键配置文件选择Run 'Project'时,是否把它标记为资源目录。
错误三:Redis connection refused源码用了 Redis,但本地 Redis 服务没启动。毕设如果不需要 Redis 缓存,也可以把相关配置和依赖去掉,减少学生机部署负担。但如果源码里 Redis 承载了验证码存储,就不要强行移除。
错误四:图片上传后刷新页面 404按照 4.2 的静态资源映射配置检查addResourceHandlers,并确认upload.dir指向真实存在的目录。
5. 实测过程中的故障排查:三个让我印象深刻的 Bug
5.1 下单扣了库存,订单却消失了:事务边界和异常被吞
有位同学拿着这套源码来问我,问题描述得很经典:“我明明下单成功了,购物车也清了,但订单列表里什么都没有。再试一次,库存又少了一点。”
听到这里我基本猜到了原因。他为了代码好看,把下单流程拆成了createOrder、deductStock、clearCart三个私有方法,三个方法都在同一个类里。然后在createOrder这个公有方法上加了@Transactional,又在clearCart里try-catch了异常。
问题就出在“同类内私有方法调用 + 吞掉异常”的组合上。Spring 的事务本质上是 AOP 代理,同类内部调用不会经过代理对象,所以事务注解实际没生效。加上cartMapper.deleteById抛出的异常被 catch 住,事务不会感知,最终部分操作写入、部分操作没有回滚。
正确的修法是:把事务方法放到一个独立的 Service 里,让外部通过 Spring Bean 调用;同时保证事务方法内不吞异常,让RuntimeException一路抛到全局异常处理器。全局异常处理器也得写好,避免用户端看到一坨堆栈信息。
5.2 前端跨域还是后端跨域:CORS 配置为什么会重复
另一个高频问题:前端配了 proxy,后端也配了 CORS,结果刷新页面时浏览器提示Response to preflight request doesn't pass access control check。
这个问题的本质是“预检请求被处理了两次”。开发环境下,前端请求先经过 devServer 代理转发到后端,代理会添加一些头信息,到达后端时又触发一次 CORS 过滤器,两端配置不一致就导致预检出问题。
排查时先确认请求到底走没走代理。浏览器 Network 面板看到请求地址是http://localhost:3000/api/...,说明走了前端代理;看到http://localhost:8080/api/...,说明是直连后端。开发阶段我建议二选一,只保留前端 proxy 就够了,后端 CORS 配置留给生产环境用。如果非要同时保留,后端的allowedOriginPatterns要写*,而不要写死成http://localhost:3000。
5.3 金额用 double 存储出现的“魔法数字”
有位同学在测试下单时发现订单金额变成了299.99999999999994。他查遍代码没找到问题,最后发现金额字段从数据库到 Java 都用的 double。
double 是二进制浮点数,0.1 + 0.2在二进制里根本不能精确表示,这是计算机底层的经典问题。解决方式其实就一句话:数据库用DECIMAL(10,2),Java 用BigDecimal,前端传值时用字符串而不是数字。
BigDecimal price = new BigDecimal("299.00"); BigDecimal quantity = new BigDecimal("2"); BigDecimal total = price.multiply(quantity); // 598.00注意new BigDecimal(299.00)和new BigDecimal("299.00")结果不同,前者在构造时已经引入浮点误差,所以一定要用字符串构造。这个细节写在论文里也很有说服力,属于“看起来基础但很多人都不知道”的点。
6. 答辩现场:这些准备能让你的项目“听起来”和“看起来”都很完整
6.1 演示脚本怎么设计最加分
答辩演示有一条黄金时间线:第一分钟展示系统整体界面,第二分钟走通一条完整业务流,第三分钟留给自己讲设计亮点。最怕的是从头到尾点菜单,老师看五分钟就困了。
我的演示顺序是这样设计的:
- 先用管理员账号登录后台,展示“商品管理 + 数据看板”,让老师第一眼看到系统的管理能力。
- 切换到前台用户端,演示注册、登录、搜索“冲锋衣”、加入购物车、下单、模拟支付、查看订单状态。
- 回到数据库,现场查一下
t_order_item里的商品快照,说明“订单不会因为商品改价而受影响”。 - 打开日志或控制台,展示一次下单过程中事务的执行链路,顺带提一句“库存查询用了 FOR UPDATE 行锁”。
这套流程 4 到 5 分钟讲完,节奏紧凑,每个环节都能引出问题。一定要注意:演示前先把浏览器缓存清掉,数据恢复干净,不要出现“上一个人下单的残留记录”。
6.2 高频提问与参考答案
答辩前可以拿下面这些问题做一次自查,能答上来大半,现场基本就稳了。
| 常见问题 | 建议回答思路 |
|---|---|
| 为什么选择 Spring Boot? | 简化配置、内嵌容器、生态成熟、自动配置机制,适合快速构建独立应用 |
| MyBatis-Plus 和 MyBatis 有什么区别? | MyBatis-Plus 增强 MyBatis,提供通用 CRUD、分页插件、条件构造器,不改变 MyBatis 原有能力 |
| 订单状态如何流转? | 五状态有限状态机,待支付 → 已支付 → 已发货 → 已完成,待支付可取消 |
| 如何防止库存超卖? | 事务内使用 SELECT ... FOR UPDATE 行锁,保证扣减库存和生成订单的原子性 |
| JWT 和 Session 有什么区别? | Session 存在服务端,有状态;JWT 存在客户端,无状态,适合前后端分离,但无法主动失效 |
| 用户下单后一直不支付怎么办? | 定时任务扫描超时未支付订单,修改状态并回补库存 |
| 项目有哪些亮点? | 统一返回结果、全局异常处理、商品快照、模拟支付、数据看板统计 |
| 数据库为什么这么设计? | 订单和商品解耦,减少商品信息变更对历史订单的影响,金额使用 DECIMAL 保证精度 |
回答问题时不要背课文,最好直接在自己代码里指出对应位置。比如老师问超卖,你就说“在createOrder方法里,第 34 行是selectByIdForUpdate,先锁行再判断”。这个细节比任何口头描述都更能证明项目是你自己做的。
6.3 论文与文档写作的小提醒
论文不要直接照搬网上模板。至少要检查三件事:
第一,E-R 图和数据库表的字段必须完全一致。很多同学图里画的是product,代码里用的是t_product,老师对照出来会很尴尬。
第二,流程图和时序图要与你实际代码路径一致。比如你在论文里画了“支付回调”,但源码里是模拟支付,就要把图的名称改成“模拟支付流程”,不要自己给自己埋雷。
第三,参考文献要真实。至少找到 5 篇关于 Spring Boot、MySQL、电商系统的期刊或硕博论文,认真读过摘要和结论,再写进参考文献。不要编一个不存在的作者。
最后再分享一点个人体会
这套源码 27394 我前后带着人跑过很多遍,最大的感触是:登山用品商城这种题目,难度不高,但非常考验你把“业务诉求”翻译成“代码结构”的能力。很多同学拿到源码第一反应是赶紧跑起来,跑起来就以为万事大吉。但真正到了答辩,老师问的第一个问题往往不是“你这个页面怎么做的”,而是“你这个表为什么这么设计”。
所以拿到任何一套毕业设计源码,我都建议至少手动重写三个地方:下单事务、JWT 拦截器、数据看板的统计 SQL。这三块覆盖了“事务 + 鉴权 + 聚合查询”三个核心能力,是整篇论文里最有技术含量的部分。你亲手敲一遍,远比抄十遍印象更深。
如果你正在准备这个题目,希望这篇文章能帮你少走弯路。项目不怕简单,怕的是你讲不清楚;代码不怕有坑,怕的是你从来没排查过。把一条业务链路完整走通,把每个状态流转的原因都说明白,你的 Spring Boot 登山用品商城毕业设计就成功了一大半。