
1. 从毕业设计题目出发这个土特产电商平台到底要做什么最近不少同学私信问我同一个题目基于Spring Boot的雄宗土特产销售网站该怎么下手。说实话这个课题在计算机毕业设计里属于非常典型的电商平台类项目但它又比单纯的学生管理系统、图书管理系统多了一层业务复杂度——它涉及商品展示、购物车、订单流转、支付对接、后台管理等一系列真实电商场景。而土特产这个垂直领域又带来了一些特殊需求比如商品规格差异散装称重和礼盒包装价格不同、区域限售、库存单位换算等。我在帮某高校几位同学复盘这个课题时发现大多数人第一次看到题目就卡在从哪开始写代码上。其实正确的顺序是先把业务拆清楚再做技术选型最后才是写代码。这个题的底层诉求并非让你发明一个新电商模式而是考察你有没有能力用Spring Boot这个主流框架把一个完整的、可运行的、有业务深度的交易系统从零搭建出来。具体到雄宗土特产这五个字它隐含了两个设计前提一是商品品类偏向农副产品、地方特色食品这些商品天然适合用图片展示 详情描述 规格选择的方式来呈现二是销售模式往往是产地直发 区域性优惠所以后台需要支持运费模板、库存预警这类电商基础功能。从技术角度看这个项目最适合采用前后端分离的结构后端以Spring Boot为核心前端可以用Vue或Thymeleaf模板引擎实现。Spring Boot不只是一个框架它带来的是整个生态Spring MVC处理请求路由Spring Data JPA或MyBatis操作数据库Spring Security做登录鉴权Redis处理缓存和购物车会话。下面我会按照一个实际可运行的毕设项目标准把整个设计与实现过程完整过一遍。2. 技术选型与开发环境准备为什么是Spring Boot 这些周边组件2.1 核心框架选择的底层逻辑很多教程一上来就建议选Spring Boot MyBatis-Plus MySQL”但很少有人解释为什么这样组合。我根据实际开发经验帮大家理一下这个选型链条Spring Boot解决的是配置地狱问题。传统的SSH或SSM项目光XML配置就能写几百行。Spring Boot通过自动配置和起步依赖Starter机制让你用几行配置就能跑起一个Web应用。对于毕设答辩你还能专门讲出自动配置原理约定优于配置这些加分点。MyBatis-Plus则在Spring Boot的JDBC基础上又进一步——它把单表CRUD操作封装到了极致不需要手写SQL就能完成大部分数据访问。这一点非常重要因为毕设时间有限你不能把精力全耗在基础CRUD上必须留出时间做订单流程、支付逻辑这些真正的业务亮点。MyBatis-Plus的LambdaQueryWrapper写条件查询非常方便比如按价格区间筛选商品、按销量排序一行代码就能搞定。数据库选MySQL不用多说关键是要注意字符集必须设置为utf8mb4否则土特产商品名称里的生僻字比如某些地方特产专用字会出现乱码。存储引擎建议InnoDB支持事务——订单创建时的库存扣减和订单记录写入必须在一个事务里这是电商项目的基本素养。2.2 开发环境与版本搭配的踩坑提醒我这边结合多个学生项目的实测情况给出一套最少踩坑的版本搭配组件推荐版本备注JDK1.8 或 11Spring Boot 2.x系列用JDK 8最稳如果一定要用Spring Boot 3.xJDK 17起Spring Boot2.7.x成熟稳定网上资料最多遇到问题容易搜到答案MySQL5.7 或 8.0两者都可8.0注意驱动要用com.mysql.cj.jdbc.DriverMyBatis-Plus3.5.x注意与Spring Boot 2.x兼容别盲目升3.6Redis6.x用于购物车缓存和验证码存储Maven3.8管理依赖国内镜像建议配阿里云这里面有一个非常容易踩的坑Spring Boot 2.7.x默认的Spring Security版本是5.7.x它的WebSecurityConfigurerAdapter已经标记为过时。如果你照着网上2020年之前的教程写extends WebSecurityConfigurerAdapter代码能跑但会有一堆deprecated警告。更麻烦的是新版写法改成SecurityFilterChain的Bean定义方式很多同学不适应。我的建议是既然做毕设就按新写法来答辩时还能展示你关注技术演进。2.3 项目初始化与目录结构设计创建项目建议直接用Spring Initializr如果网络不方便也可以用阿里云的镜像加速地址。关键是要选对依赖Spring Web、Spring Data JPA或MyBatis、MySQL Driver、Lombok、Validation。项目包结构我推荐按业务模块分包而不是传统的controller/service/dao三层一刀切com.xiongzong ├── config配置类跨域、MyBatis-Plus分页、Redis序列化 ├── controller接口层AdminController、ProductController等 ├── service业务层接口实现 ├── mapper数据访问层 ├── entity实体类 ├── dto数据传输对象接收前端请求参数 ├── vo视图对象返回给前端的数据 ├── common公共类统一返回结果、异常处理、常量 ├── utils工具类JWT工具、文件上传工具这种分包方式的好处是每个功能模块比如订单的完整链路——从Controller到Mapper——都聚在一起答辩时讲解代码结构会非常清晰而不是在百来人的经典三层结构里来回跳转找代码。3. 数据库设计一张表一张表说清楚为什么这么建3.1 需求分析驱动的核心表划分我见过不少同学的毕设一号实体建得巨复杂二十几个字段恨不得把商品的所有属性都塞进去。这其实是设计失误。对土特产电商平台来说核心表应该围绕**人、货、单**三条线展开用户线用户表存储账号密码、昵称、手机号、地址表收货地址关联用户。货品线商品表基础信息名称、主图、详情、分类、商品规格表价格、库存、规格名、商品分类表。交易线购物车表临时选品、订单表订单主信息、订单明细表每个商品项的快照。此外还有两张辅助表轮播图表管理首页广告位、运费模板表不同地区不同运费。3.2 关键字段的设计细节与理由**商品表product**中最容易忽略的是content字段它存的是商品详情页的富文本内容。字段类型必须用text或longtext如果你用varchar(255)前端提交的图文介绍会被截断而且MySQL在严格模式下会直接报错。另外要预留sales_count字段——销量这个值不能每次通过统计订单明细来算那样数据量一大性能就崩了正确做法是每次支付成功后在事务里给sales_count 1。**商品规格表product_sku**是整个数据库设计的亮点。土特产很典型的一种情况是同一款茶叶250g装和500g装是两个价格而且库存独立。把价格和库存从商品表抽出来设计一张SKU表产品表只保留最小单价作为展示价。前端规格选择时通过SKU的ID关联对应价格与库存。**订单表orders**的status字段必须用tinyint而不是varchar字符串。用0、1、2、3表示不同状态0待付款、1待发货、2待收货、3已完成、4已取消。这个设计配合Java端的枚举类代码可读性和扩展性都高也方便数据库索引优化。三个订单状态的流转有几种特殊路径需要提前想清楚用户下单后超时未付款订单要自动取消。这个功能可以用定时任务实现也可以在有库存回补需求的场景用Redis延迟队列。毕设阶段用Spring的Scheduled定时扫描超时订单就够了但答辩时如果能说出实际项目中一般会考虑延迟消息或定时补偿这种话会很加分。用户在待发货状态下申请退款后台审核通过后订单直接变已取消同时库存要自动加回。发货后的退款只能走售后退款通道这里不做太复杂后台手动处理就行。**订单明细表order_item**里注意要冗余一份商品名称、商品主图、规格名称和单价快照。为什么因为商品信息后续会被管理员修改如果只存商品ID用户查历史订单时看到的可能是改过的价格和名称这在真实电商中是不能接受的。下单时商品信息是什么样订单里就必须永远展现什么样。3.3 索引设计毕设里也要有点性能意识每张核心表的主键一旦构建好有几个高频查询是你天天都在调的索引必须提前建好orders表的user_id——个人中心查询我的订单大概率按时间倒序。order_item表的order_id——订单详情要一次性查出所有明细。product_sku表的product_id——商品详情页需要加载规格列表。cart表的user_id——进购物车页面要查全量。唯一索引方面表与表之间若有全局唯一性质的数据比如users表的username字段建议建unique index。MyBatis-Plus的saveOrUpdate方法依赖这种约束来实现有则更新、无则插入。4. 后端核心业务模块实现登录鉴权、商品展示与购物车4.1 登录注册的完整链路设计用户登录这块我强烈推荐用**JWTJSON Web Token**而不是传统的Session。原因是前后端分离后前端可能部署在另一台服务器Session在各服务端之间同步很麻烦而JWT是无状态的后端只需要在拦截器里校验Token即可。毕设答辩还能顺势讲一句这更贴近微服务场景下的认证方式直接拉高评分。实现步骤上是这样的注册接口的逻辑流程是前端提交用户名、密码、手机号后端先检查用户名是否已存在然后密码用MD5加盐哈希注意不能明文存储再插入用户表。注册成功后自动返回一个Token实现注册即登录。登录接口接收用户名和密码校验通过后生成Token。Token里只放userId和userType区分普通用户和管理员过期时间设为24小时。然后设置响应头Authorization: Bearer token。拦截器在Spring MVC里拦截所有/api/**路径检查Token是否存在且有效。用户端和管理端的鉴权可以共用一套拦截器通过userType区分权限即可。这块有一个容易被扣分的点密码不能明文传参和存储。我之前帮某位同学检查代码发现用户表里密码字段直接存了明文这是答辩时的安全隐患也很容易被评委抓到。正确做法是前端把密码MD5再传参后端再用Salt 摘要算法比如SHA-256处理。还有一点用户请求中携带的Token过期后接口要返回统一的401未授权JSON而非白屏或乱码评审会看你的异常处理是否规范。4.2 商品列表与详情的分页查询实现商品模块最核心的两个接口是列表和详情。列表页需要支持分页、分类筛选、按价格排序、关键词搜索。MyBatis-Plus的Page对象配合LambdaQueryWrapper写起来非常简洁Override public PageProductVO getProductPage(ProductQuery query) { PageProduct page new Page(query.getPageNum(), query.getPageSize()); LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); // 关键词搜索: 匹配商品名称 if (StringUtils.hasText(query.getKeyword())) { wrapper.like(Product::getName, query.getKeyword()); } // 分类筛选 if (query.getCategoryId() ! null) { wrapper.eq(Product::getCategoryId, query.getCategoryId()); } // 上架状态避免把下架商品展示给用户 wrapper.eq(Product::getStatus, 1); // 排序: 默认按创建时间倒序可选按价格/销量排序 if (price_asc.equals(query.getSort())) { wrapper.orderByAsc(Product::getMinPrice); } else if (sales_desc.equals(query.getSort())) { wrapper.orderByDesc(Product::getSalesCount); } else { wrapper.orderByDesc(Product::getCreateTime); } PageProduct result this.page(page, wrapper); return convertToVO(result); }注意一个经常被忽略的性能细节商品列表不要查content富文本字段。你可以用MyBatis-Plus的select()指定只查id, name, main_image, min_price, sales_count这些列表页需要的字段富文本留在详情接口再查。这样列表页响应快很多。等数据量上了几十万list查询全量字段很容易变成慢查询。商品详情接口的返回结构应该嵌套SKU列表{ id: 1, name: 野生山茶油, mainImage: http://..., content: p产地直发.../p, skuList: [ {skuId: 10, specName: 500ml单瓶装, price: 68.00, stock: 520}, {skuId: 11, specName: 1L两瓶礼盒装, price: 128.00, stock: 300} ] }前端拿到这个结构就能渲染多规格选择器。价格展示规则可以统一一下如果SKU价格有高有低显示68~128元如果只有一个规格就直接显示单个价格。4.3 购物车缓存优先还是数据库优先购物车是土特产电商平台里一个有趣的设计点。技术上有两条路方案一是存数据库建一张cart表user_id product_id sku_id确定唯一记录改数量直接update。方案二是存Redis用Hash结构key为cart:{userId}field为skuIdvalue为选中数量。我推荐毕设阶段选方案一理由有三点第一数据持久化用户换设备登录购物车还在体验完整第二实现复杂度低不用操心Redis的序列化和缓存同步问题第三答辩时讲数据库设计的完整性更容易展开。购物车表结构很简单但加数量这个逻辑要写严谨public void addToCart(CartDTO dto, Long userId) { // 先查购物车表中是否存在相同SKU的记录 Cart cart this.getOne(new LambdaQueryWrapperCart() .eq(Cart::getUserId, userId) .eq(Cart::getSkuId, dto.getSkuId())); if (cart null) { // 不存在就新建注意把商品名称、主图、单价冗余进来 Cart newCart new Cart(); newCart.setUserId(userId); newCart.setSkuId(dto.getSkuId()); newCart.setProductId(dto.getProductId()); newCart.setProductName(dto.getProductName()); newCart.setProductImage(dto.getProductImage()); newCart.setSpecName(dto.getSpecName()); newCart.setPrice(dto.getPrice()); newCart.setQuantity(dto.getQuantity()); this.save(newCart); } else { // 已存在则累加数量但累加前检查库存上限 int newQuantity cart.getQuantity() dto.getQuantity(); if (newQuantity dto.getStock()) { throw new BusinessException(库存不足); } cart.setQuantity(newQuantity); this.updateById(cart); } }这里有个很容易出bug的地方加购时校验库存但真正防超卖要放到下单扣库存时做。因为用户加购后可能放一晚上再去结算这期间商品可能被其他用户买完。所以加购校验只能是友好提示不能当作并发安全屏障。5. 订单流程设计从结算到支付的完整状态机5.1 提交订单时的事务处理与库存扣减用户从购物车勾选商品点去结算前端提交的是一次结算的商品明细列表。后端createOrder接口的完整步骤如下校验用户登录状态。遍历提交的商品项逐个查询最新价格和库存。重新计算订单总金额——绝不相信前端传过来的总价后端必须用数据库里的最新价格重新算。这个是对电商安全性的基本认知。扣减SKU库存同时检查扣减后不能为负数。创建订单主记录状态为0待付款。创建订单明细记录。清空购物车中已结算的条目。这7个步骤必须放在同一个事务里。更精确地说第4步是并发控制的关键点Transactional public Long createOrder(OrderCreateDTO dto, Long userId) { // 先锁定库存: 乐观锁或悲观锁 int updated skuMapper.deductStock(dto.getSkuId(), dto.getQuantity()); if (updated 0) { throw new BusinessException(库存不足或竞争失败); } // 继续创建订单、明细... }deductStock的SQL类似UPDATE product_sku SET stock stock - #{quantity} WHERE id #{skuId} AND stock #{quantity}。这种条件更新是避免超卖的核心技巧——数据库行锁保证并发下只有一个事务能成功扣减库存。如果你用先查库存再更新的两步操作并发场景下必然超卖这个在技术评审时几乎必问。事务里还要注意分布式事务在单库单应用场景下不必要Transactional配合InnoDB的ACID已经够用。答辩被问到为什么不用分布式事务时可以这样回答当前系统是单体架构数据库事务已经能保证一致性将来拆微服务再引入分布式事务方案比如Seata。5.2 模拟支付接入支付宝沙箱还是自己写一个毕设这一块通常有两条路。接入支付宝沙箱环境是真实感最强的选择官方文档比较完善也有专门的alipay-sdk-java依赖。但它的代价是配置繁琐——需要去开放平台签约沙箱应用、配置公私钥、回调地址。很多同学在这一步卡了几天相当影响进度。我自己的建议是第一条路用模拟支付第二条路把支付宝沙箱作为加分项。模拟支付接口的逻辑很简单前端调/pay/mock接口传订单号后端直接判断订单状态是否为待付款如果是则把订单状态改为待发货写入支付时间和支付方式public void mockPay(Long orderId, Long userId) { Order order orderMapper.selectById(orderId); // 校验归属权 if (!order.getUserId().equals(userId)) { throw new BusinessException(无权操作该订单); } if (order.getStatus() ! 0) { throw new BusinessException(订单状态不允许支付); } order.setStatus(1); // 待发货 order.setPayTime(LocalDateTime.now()); orderMapper.updateById(order); // 同步更新商品销量这里注意销量更新必须与订单状态变更在同一事务 orderItem list... }答辩演示时点击立即支付就能看到状态流转用户体验非常完整。如果时间充裕再在订单详情显示模拟支付完成提示效果已经足够好。真正要用心思的是订单超时取消逻辑。用Scheduled(cron 0 */2 * * * ?)每两分钟扫一次orders表把status 0 AND create_time NOW() - INTERVAL 30 MINUTE的订单找出来逐条取消并恢复库存。注意定时任务执行时也要加事务因为取消订单与回补库存必须是原子的。为了防止任务重复执行比如集群部署环境下可以加一个Redis分布式锁毕设阶段说明这个思路即可不必复杂实现。5.3 购物车表与订单明细表的数据一致性涉及购物车和订单的数据同步一个常见的坑是用户结算后清空购物车此时删除条件必须与订单明细精确匹配。假设用户勾选了A和B两个商品去结算但购物车里还有C那么删除时只能删A和B对应的记录。用IN条件逐条删除即可记得加userId条件防止删错人。另一个值得提前处理的边界结算中商品被管理员下架或删除。后端校验时如果发现商品不存在或状态不为1应该返回明确提示商品已失效请刷新购物车而不是直接报500。这种提示信息虽然简单但能体现系统设计的细节水平也是很多答辩评审会点出来的用户体验问题。6. 后台管理端实现商品管理、订单处理与数据分析6.1 后台管理模块的权限控制后台和前端需要分开处理。简单做法是用户表加userType字段0普通用户、1管理员。后台所有接口统一加RequireAdmin的拦截器校验。在Spring Security的配置里按路径区分权限是最常规的Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(/admin/**).hasRole(ADMIN) .antMatchers(/api/**).permitAll() .and().csrf().disable(); }这里要注意的是JWT过滤器和Spring Security的过滤器链要正确协同。JWT过滤器从请求里解析Token并设置SecurityContextSpring Security后续的hasRole判断才能拿到用户权限信息。6.2 商品管理图片上传的本地存储与访问映射土特产商品管理最常出问题的是图片上传。本地存储方案要注意三点第一上传的物理路径不能直接写在代码里要配到application.ymlupload: local: path: E:/project/upload/ # 存储目录 prefix: /upload/ # 访问前缀第二启动类里加静态资源映射使http://localhost:8080/upload/**能指向本地目录。写一个WebMvcConfigurer的Bean重写addResourceHandlers方法。第三上传接口校验文件的扩展名.jpg .png .gif和大小限制单图不超过5MB。用UUID重新生成文件名避免中文文件名带来的编码问题String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFileName UUID.randomUUID().toString() ext; file.transferTo(new File(uploadPath, newFileName).getCanonicalFile());如果项目部署在云服务器更推荐接入对象存储OSS只需要引入SDK、配置Bucket和AccessKey代码量并不大且能展示你对生产级方案的理解。答辩时提一句本地存储适合演示环境生产环境建议用对象存储就很加分。6.3 订单管理状态筛选与发货操作后台订单管理页面有四个核心操作按状态筛选待付款、待发货、待收货、已完成、已取消。用枚举值和下拉框筛选。发货录入物流公司和运单号订单状态从待发货变为待收货。查看详情展示订单基本信息 明细列表 收货地址 操作日志。取消订单后台强制取消待付款或待发货状态订单同时回补库存。发货操作中订单表需要预留logistics_company和logistics_no两个字段。这些字段虽小但关系到待收货状态能否正常流转也是评委容易提问的点。发货后用户端确认收货操作主体是用户点击按钮后端校验订单归属和状态必须是待收货然后改为已完成同时记录receive_time。这种状态只能单向前进的设计不能从已完成回退到待收货要在Service层做状态机校验防止用户模拟请求篡改数据。6.4 简单的数据看板撑起数据分析这个加分项后台首页如果只有商品和订单表格会显得比较单薄。加一个数据看板Top区域销售总额、今日订单数、待发货数量、商品总库存。再整合两个图表近7日销售额折线图、商品分类销量饼图。数据看板中最核心的SQL是按日期统计销售金额。没有订单表时可以先这样说数据库结构SELECT DATE_FORMAT(create_time, %Y-%m-%d) AS date, SUM(total_amount) AS total_amount FROM orders WHERE status IN (1, 2, 3) -- 已支付状态 AND create_time DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE_FORMAT(create_time, %Y-%m-%d) ORDER BY date ASC;然后前端用ECharts折线图展示。这块不需要太复杂但能把数据库聚合查询 前端可视化两个能力展示出来。答辩时如果被问到性能问题可以说数据量大了以后这类统计可以迁移到离线计算或数仓目前这个规模用MySQL聚合足够。7. 前端页面与交互Vue页面怎么搭、请求封装怎么做7.1 页面结构与路由规划如果采用前后端分离方案前端推荐用Vue 2.7 Element UI更容易上手或Vue 3 Element Plus更现代。毕设的功能页面其实不多核心路由可以这样规划路由页面说明/Home.vue首页轮播图、推荐商品/product/:idProductDetail.vue商品详情SKU选择、加购按钮/cartCart.vue购物车改数量、删除、全选、结算/order/confirmOrderConfirm.vue订单确认地址选择、商品清单/order/listOrderList.vue我的订单状态Tab、各状态列表/order/detail/:idOrderDetail.vue订单详情/adminAdminLayout.vue后台布局侧边栏内容区/admin/productAdminProduct.vue商品管理列表/admin/product/editAdminProductEdit.vue商品新增/编辑/admin/orderAdminOrder.vue订单管理路由层级不用太深能通过功能导航到达所有页面即可。注意点只有一个后台路由要加全局守卫判断本地存储的userType是否为管理员否则跳回登录页。7.2 Axios请求封装与Token刷新前端请求封装直接决定你全站接口调试的效率。我建议按以下方式统一封装// request.js import axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器: 自动带Token service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) // 响应拦截器: 统一处理业务码和401 service.interceptors.response.use( response { const res response.data if (res.code ! 200) { this.$message.error(res.message) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { // Token失效: 清空并跳回登录页 localStorage.clear() location.href /login } return Promise.reject(error) } )如果时间够再加一层Token刷新机制也是加分项——后端额外提供一个/refresh接口用没过期的RefreshToken换新AccessToken。不过对毕设而言定时任务和消息处理已经很重了的话先做统一封装即可。7.3 商品详情页的SKU选择交互商品详情页是整个前端交互中最容易做砸的。核心逻辑是用户点击不同规格时展示对应SKU的价格、库存、图片。你需要维护一个currentSkuId变量规格选择后用它的属性重新渲染价格区域和库存不足提示。这里有个常见做法值得借鉴先把规格组数据做一个映射。接口返回的SKU列表往往是扁平数组前端根据specName拆成规格组。当所有规格都被选中时用组合键去匹配完整SKU。如果匹配不到说明该组合无货禁用加入购物车按钮。实现不算复杂但能让答辩评委看到前端逻辑的完整性。8. 毕设避坑实录我在帮同学们调试过程中遇到的高频问题这个部分我想专门写一下因为这几个月帮不同同学复现这个题目我整理出了几个反复出现的共性问题提前注意到能省非常多的时间。8.1 跨域请求配置为什么前端请求一直报403/404前后端分离开发时如果前端跑在localhost:8081后端在localhost:8080必然存在跨域问题。处理方案是后端写一个CORS配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }最容易踩的坑allowCredentials(true)时allowedOrigins不能设为*必须用allowedOriginPatterns。否则Spring Boot直接拒绝请求。另外如果用了Spring SecurityCORS配置写在这种地方可能不生效——因为Security的过滤器链先拦截了请求你得在SecurityConfig里同时配置.cors()并注入这个CorsConfigurationSource。这个双配置问题的排查过程我印象非常深当时某位同学前端一直报跨域错误我检查了一小时才发现是Security过滤链优先级导致的。8.2 MyBatis-Plus的自动填充与逻辑删除土特产商品有create_time和update_time两个字段每次手动setCreateTime很繁琐。用MyBatis-Plus的自动填充功能更优雅Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }实体类对应字段上加TableField(fill FieldFill.INSERT)和TableField(fill FieldFill.INSERT_UPDATE)。另一个必须处理的是逻辑删除。后台删除商品如果直接删数据库行用户历史订单里关联的数据就乱了。正确做法是在商品表加deleted字段0未删除、1已删除MyBatis-Plus实体类加TableLogic注解所有查询自动带上deleted 0条件。这样数据永远在库里只是不出现在商品列表。删除商品时还要做个级联处理把该商品关联的SKU也逻辑删除同时若有未完成订单引用该商品应当保留订单快照逻辑不能把订单明细也删掉。8.3 接口返回格式不统一导致的联调痛苦很多同学第一次做前后端分离时今天这个接口返回{code: 200, data: []}明天那个接口返回{status:0}前端封装很难统一处理。建议从第一天就定好统一返回对象Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }配合一个全局异常处理器RestControllerAdvice把BusinessException和兜底异常都转成这个格式。这样前端响应拦截器只要判断code是否等于200流程瞬间清爽。8.4 前端表格分页参数名对不上后端Page对象默认接收的参数名是current和size而前端表格组件默认传的是pageNum和pageSize。如果不对齐分页永远只有第一页数据。两种解决办法要么后端在Controller手动映射GetMapping(/list) public ResultPageProduct list( RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize) { PageProduct page new Page(pageNum, pageSize); // ... }要么前端axios拦截器统一改参数名。更建议在方法上使用RequestParam显式映射因为后端能同时兼容两种入参也方便前端对接。9. 部署发布、答辩要点与后续扩展建议9.1 打包与部署需要注意的事项Spring Boot打包直接用Maven的package生成jar包但毕业设计部署时有三件事很多人会栽跟头数据库初始化本地MySQL的表结构和初始数据要导出SQL脚本部署到服务器后先执行脚本。脚本里要包含一个管理员账号、几个分类、十几条演示商品和SKU数据。演示不能让人登录后看到空荡荡的界面。配置外部化数据库地址、端口、Redis地址不要硬编码打在application-prod.yml里通过--spring.profiles.activeprod激活。这个方法很简单却是生产环境必备。静态文件路径本地upload.path可能是E:/xxx部署到Linux服务器就要改成/usr/local/upload/。分享时提醒一下路径分隔符问题经常被忽略Windows用反斜杠Linux用正斜杠用File.separator或Path API拼接可以避免跨平台问题。9.2 答辩时的高频提问与应答思路根据我帮同学们模拟答辩的经验评委最喜欢围绕这几个点发问为什么选择Spring Boot而非Spring MVC回答要点Spring Boot在Spring MVC基础上提供了自动配置、起步依赖、内嵌Tomcat让开发者专注于业务代码显著减少XML配置同时它本身并没有抛弃MVC分层思想底层依然是Spring的IoC和AOP。库存超卖问题怎么解决的回答要点在数据库层面使用条件更新WHERE stock quantity配合事务保证原子性。还可以补充更高并发场景可以用Redis预扣库存 异步落库、分布式锁或消息队列削峰。表明你既知道当前方案的适用边界也知道演进路径。JWT相比Session有什么优缺点回答要点JWT无状态、适合分布式和前后端分离、可以携带自定义声明。缺点是服务端不能主动让Token失效除非引入黑名单Token体积比SessionID大。这个问题的辩证回答比单方面吹捧更能体现理解深度。为什么商品规格要单独建一张表回答要点一个商品可能有多个不同的价格库存组合属性维度不同导致一对多关系若把多规格塞进商品表会增加冗余和更新异常风险SKU表设计也能与主流电商系统保持一致。定时任务超时取消订单会重复执行吗回答要点单点部署不会有问题集群部署时定时任务可能被多个节点同时触发需要引入分布式锁如Redis SetNX保证同一时间段只有一个节点执行。9.3 这个题目还能怎么玩出差异化如果班级里选这个题的人比较多你需要给自己的作品加一点差异化亮点。下面几个方向我认为都比较适合在毕设评审中讲清楚方向一增加多级分类。土特产往往有按产地和按品类两种分类方式。可以设计两级分类省级产地 具体品类。首页支持按产地筛选这是区别于普通电商的垂直特色。方向二秒杀功能。选择某个SKU做限时低价用Redis预减库存 数据库最终扣减实现秒杀。技术含量高但做好需要一定时间不建议毕设期间临阵尝试除非时间充足。方向三物流轨迹查询对接。接入快递鸟或模拟物流轨迹给订单增加物流轨迹时间线。这个能让用户端体验感强很多还能展示你对第三方API对接的熟悉程度。方向四会员积分体系。用户下单获得积分积分可在下次购买时抵扣。积分流水和抵扣计算的实现相对独立不容易破坏核心订单流程是很稳的加分模块。方向五消息通知。用户下单后给微信模板消息或站内信通知。如果不想接第三方推送在站内实现一个未读消息列表就足够展示消息推送思路。10. 几个值得重点保留的个人经验分享帮不同同学把这份题目从零搭到能跑通、能答辩我最后想浓缩几条最有价值的心得这几条我相信即便去真实电商公司做后端开发也一样适用第一条先画业务流程图再动手写代码。我见过太多同学上来就建表建到一半发现订单状态要对不上又一删了之。把注册→浏览→加购→下单→支付→发货→收货→售后这条主链路画出来每一步的状态和数据流都清楚了代码写起来就是按图索骥。第二条以写生产级代码的心态写毕设代码。所有的用户输入都可能是恶意输入所有的接口都可能被越权调用。校验参数、校验状态、校验归属权这三件事做踏实了系统质量会有质的飞跃。后台管理接口尤其要小心如果一个普通用户能通过修改请求路径访问到后台删除商品接口这个系统就有严重漏洞。第三条千万别只依赖后端403错误提示来判断问题。前后端联调时先把浏览器的Network面板打开看请求是否真的发出去了、状态码是什么、响应体是什么再看控制台报错。大多数问题都是路径404或参数名不一致这种小问题不要一上来就怀疑Spring配置。第四条资料查阅顺序建议排优先级。遇到问题首选官方文档和官方示例代码其次看GitHub上star高的开源电商项目再看社区博客。优先参考最可靠的信息源能少走很多弯路——这不是客套话是真有这方面的教训。某次排查一个JWT解析编码问题翻遍了帖子都没头绪两小时后再查官方手册才发现是HmacSHA256签名验证Key长度受版本影响。如果你也要做基于Spring Boot的电商平台这类题目希望这篇文章里提到的方案、代码和踩坑经历能帮你少走几周弯路。这些设计与实现思路不是单一项目的答案也可以迁移到其他垂直电商、校园二手交易平台、企业商品管理系统等场景。把这些基础能力搭通了后面做什么题目都会顺很多。