每年毕业季都有同学倒在做系统上:环境配好、代码导入、数据库一还原,项目不报错,登录页也能显示,可一到“注册—登录—发布闲置—下单—生成订单”整套流程走下来,就各种卡壳。旧物回收商城系统这类项目,表面看起来就是个普通电商,实际上订单状态流转、回收单与商品单的类型转换、图片上传路径、分页查询、权限拦截这几个环节,才是真正卡人的地方。这篇就围绕用SpringBoot+SSM搭旧物回收商城系统这件事,把架构方案、数据库设计、关键代码套路、调试方法完整捋一遍。无论是做毕业设计、课程设计,还是想自己搞一个二手交易平台练手,这篇都适用。
1. 项目整体设计与技术选型思路
1.1 为什么是“SpringBoot+SSM”这种组合
很多同学一看到题目里既有SpringBoot又有SSM,就觉得是两套框架并存。实际做过项目的都知道,SSM指的是Spring、SpringMVC、MyBatis这三件套,而SpringBoot本身是基于Spring生态的一套快速开发脚手架,它默认整合了SpringMVC并简化了大量XML配置。所以“SpringBoot+SSM”这个组合在实际项目里,通常理解为:SpringBoot作为主框架提供自动配置和内嵌服务,持久层用MyBatis操作数据库,前端控制器基于SpringMVC规范开发。这种方案的好处是既保留了SSM体系中MyBatis那种“SQL手写可控”的灵活度,又享受了SpringBoot“零配置起步”的便利,不需要再自己配一堆DispatcherServlet和XML内容。
我见过不少同学一开始想用纯SSM框架手写配置,结果光spring-mvc.xml、mybatis-config.xml、web.xml三套文件就能折腾两天,中间还有各种jar包版本冲突。而SpringBoot把这些公共配置全部收敛到自动配置里,你只需要在application.yml里写数据源、MyBatis映射路径、端口这些核心参数,框架就能自己把上下文拉起来。对于旧物回收商城这种典型CRUD型管理系统,这种组合是最高效的,也最贴近现在Java岗位的实际开发习惯。市面上大部分Java开发岗都在用SpringBoot,但真正的业务冗杂度又依赖MyBatis这种“半自动”持久层来兜底,所以两者一起出现的频率很高。
1.2 系统的核心定位:这不是普通商城
旧物回收商城系统这个名字里有两个关键词:旧物、回收。跟普通B2C商城不同,它涉足的不只是“用户下单买商品”这条链路,还需要处理“用户把闲置物品发布出来、平台审核或回收、然后转成可售卖商品”这条回收链路。简单说,一条是前端商城展示和交易,一条是后台回收单管理和审核入库。
从用户角色上看,这个系统一般都分成三类:普通用户(买家+卖家双重身份)、回收管理员(审核回收单、上架商品)、系统管理员(用户管理、分类管理、订单管理、统计报表)。很多同学容易忽略“用户既是买家又是卖家”这个设定,导致数据库设计的时候把买和卖拆得太僵化,后面做接口才发现,用户的user_id要同时出现在商品表和订单表里,而且两张表之间还要互相带出名称。这个定位想清楚,后面所有模块都能顺理成章地展开。
技术实现方面,这个系统的核心模块通常有:用户模块(注册登录、个人信息、地址管理)、商品模块(发布闲置、商品列表、详情、搜索)、回收模块(提交回收单、回收单审核、回收价格评估)、订单模块(下单、订单列表、订单状态变化)、购物车模块(加购、结算)、后台管理模块(用户管理、商品审核、订单管理等)。每个模块都能单独拉出来当面试题细问,这就是这个项目含金量的来源。
2. 数据库设计与核心模块拆解
2.1 核心表结构:一张表都不能少
做这类系统如果直接在代码里写死业务逻辑,后期大概率要翻车。核心表至少要包含这几张:
- user用户表:user_id、username、password、nickname、phone、avatar、address、role(区分普通用户和管理员)、create_time。
- category商品分类表:category_id、name、parent_id、sort_order,方便做一级和二级分类。
- product商品表:product_id、user_id(发布者)、category_id、title、description、cover_image、images、price、original_price、status(在售/下架/已售/待审核)、create_time。
- recycle回收表:recycle_id、user_id、category_id、title、description、estimated_price、status(待审核/已通过/已回收/已拒绝)、create_time。
- orders订单表:order_id、order_no、user_id、product_id、quantity、total_price、status(待付款/待发货/待收货/已完成/已取消)、create_time、pay_time。
- order_item订单明细表:order_item_id、order_id、product_id、product_name、price、quantity。
- cart购物车表:cart_id、user_id、product_id、quantity、checked。
- address收货地址表:address_id、user_id、receiver_name、phone、province、city、district、detail、is_default。
这里要特别解释一下recycle表的定位。回收单和商品单在旧物回收系统里是两条平行但会交集的业务线:用户提交的回收单被审核通过后,可以由管理员将其“转成”商品单发布到商城上架。这实际上就是编程里典型的“状态机驱动数据流转”,回收单从待审核变成已回收,同时生成的商品记录进入“待上架”。如果不单独设计recycle表,而是直接在product表里塞一个recycle_flag字段,那回收单的审核历史、拒绝原因就全丢了,后面排查问题会非常痛苦。
2.2 状态字段设计:用int小数字代替冗长字符串
我见过很多新手设计表,喜欢用status字段直接存“待审核”“已发货”这种中文,看起来直观,实际上坑很多:首先存储长度不可控,其次每次业务判断都要写字符串比较,最后想给状态加个排序条件都无从下手。实战里最稳妥的方式是用int枚举值,比如商品状态0待审核、1在售、2已下架、3已售;订单状态0待付款、1待发货、2待收货、3已完成、4已取消。在Java代码里包一层枚举类,前端用字典映射文本,后端只处理数字。这样数据库排序、索引、查询都高效,代码里也不会出现魔法值乱飞。
另外注意一点,凡是涉及钱相关的字段,比如estimated_price、total_price,在表结构里一定要用decimal,别用float或double。二进制浮点数在计算金额时会有精度丢失问题,金额除法减法一旦累积误差,订单总额对不上、退款金额差几毛钱这种线上事故都是这么来的。Java代码里对应使用BigDecimal,这才是电商类项目的基本素养。
2.3 关键业务流程:回收单到商品单的“变身逻辑”
回收模块是这个项目最有区分度的部分。完整流程是这样的:用户在小程序或Web端填写回收信息(分类、标题、描述、期望价格、图片),提交后生成一条recycle记录,状态为待审核;管理员在后台看到待审核列表,审核通过后,系统根据回收单内容自动创建product记录,并标记卖家user_id为原用户,商品状态设为待上架,回收单状态改为已通过;管理员还可以手动编辑商品标题、价格和图片后再上架。如果审核拒绝,则回收单状态变为已拒绝,后台记录拒绝理由,用户可以在前台看到这条反馈。
这个流程拆开来看,本质上是两张表之间的一次“数据搬运+字段映射”。controller层调用回收审核接口 -> service层开启事务 -> 先更新recycle状态 -> 再insert product记录 -> 提交事务。事务性要求极高,如果只是update了recycle状态而没有成功创建product,数据就不一致。所以业务逻辑必须用@Transactional注解包起来,运行时异常自动回滚。很多人在这一块忽略事务管理,导致项目测试时明明回收单显示已通过,商城列表却搜不到商品,这就是典型的一致性事故。
3. 从0到1搭建环境:版本选择与系统初始化
3.1 环境准备:JDK、Maven、MySQL、IDEA四件套
做SpringBoot+SSM项目,环境版本一定不能乱配。我建议用下面这一套,实测兼容性最稳:
- JDK 1.8(SpringBoot 2.x系列对JDK 8支持最成熟,别一上来就装JDK 17,除非你用的是SpringBoot 3.x)
- Maven 3.6.3(版本太低拉不了新依赖,太高有时候和IDEA内置版本冲突)
- MySQL 5.7(如果电脑上装了MySQL 8.0+,注意驱动地址变成了com.mysql.cj.jdbc.Driver,且要手动指定时区,否则容易报时区错)
- IDEA 2020.3及以上(自带Spring Initializr和Maven插件,导入项目更方便)
数据库连接配置在application.yml里这么写:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/recycle_mall?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource servlet: multipart: max-file-size: 10MB max-request-size: 50MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.recycle.mall.entity configuration: map-underscore-to-camel-case: true注意几个关键点。第一,数据库名要在MySQL里先建好,create database recycle_mall default character set utf8mb4,否则项目一启动直接报Unknown database。第二,mybatis.mapper-locations必须指向mapper xml所在包,很多人项目能启动但接口报Invalid bound statement,原因就是这里没配。第三,map-underscore-to-camel-case这个配置能让你数据库字段product_id直接映射到Java属性productId,少写一堆resultMap。
3.2 Maven依赖:哪些jar包是必须的
pom.xml里的依赖不需要多,但要全。我这里贴一套常见组合,覆盖大部分需求:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.12.RELEASE</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>这里重点说下为什么需要PageHelper。商品列表、回收单列表、订单列表全部都是分页场景,不可能一次把所有数据查出来渲染。PageHelper是一个MyBatis的分页插件,它通过拦截器自动在SQL末尾拼接limit,你在service层只需要写PageHelper.startPage(pageNum, pageSize),后面紧跟的第一次查询就会被分页,同时返回PageInfo对象里带total等分页数据。这比自己手动拼接limit再count一遍方便得多,也是实际开发中最常用的方式。
3.3 启动类结构:SpringBoot无缝接住SSM
主启动类很简单,核心是@SpringBootApplication注解和MyBatis的Mapper扫描:
@SpringBootApplication @MapperScan("com.recycle.mall.mapper") public class RecycleMallApplication { public static void main(String[] args) { SpringApplication.run(RecycleMallApplication.class, args); } }这里用@MapperScan扫mapper接口包后,就不再需要每个Mapper接口写@Mapper注解了,代码更干净。项目启动后,SpringBoot会自动加载application.yml里的数据源配置,自动配置一个SqlSessionFactory,然后把mapper xml的位置绑定到MyBatis。至此,SpringMVC的DispatcherServlet、MyBatis的SqlSession、Druid连接池全部由SpringBoot自动装配完成,不需要任何XML配置文件。
4. 核心功能代码套路:从Controller到Mapper一整套写法
4.1 用户登录与拦截器:权限控制不能只写在页面里
用户模块最核心的除了增删改查,就是登录状态的保持。常用方案是Session登录:用户登录成功后把user对象放进session,同时前端所有页面通过拦截器判断session里有没有user,没有就跳转登录页。
拦截器的实现分两步。第一步定义拦截器处理类:
@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object user = session.getAttribute("user"); if (user == null) { response.sendRedirect("/login"); return false; } return true; } }第二步在WebMvcConfigurer里注册,并设置放行路径:
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/**") .excludePathPatterns("/login", "/register", "/product/list", "/product/detail", "/css/**", "/js/**", "/images/**"); } }这里特别提醒一点:静态资源的放行一定要配好。很多同学项目能登录,但页面CSS、图片全挂,F12一看全是404,就是因为静态资源配置在assets目录下,但拦截器把它们拦下来跳转到登录页了。静态资源路径要根据你自己的项目结构灵活调整,别照搬网上配置。我习惯把前端静态资源统一放在static目录,然后css、js、images、fonts、plugins这几个前缀全部放行,然后项目根目录下的首页也要放行,不然用户访问/就被拦截了。
4.2 商品发布与图片上传:文件存储的三种常见选择
商品发布模块涉及图片上传,这是最容易出问题的一关。图片上传有几种方案,难度不同:
- 方案一:把图片Base64编码后存到数据库字段里。实现简单,但数据库膨胀严重,一张图片几MB编码后更大,查询速度也受影响,只适合做demo。
- 方案二:把图片保存到服务器本地目录,数据库里存相对路径。这是当前最推荐的方式,开发阶段在本地跑非常方便。
- 方案三:接入OSS/MinIO等对象存储,生产环境推荐,但本地阶段需要另外搭服务,对毕设来说属于加分项。
方案二的具体做法是在SpringBoot的配置文件里添加自定义上传路径:
recycle: upload: path: /Users/admin/upload/然后定义一个WebMvcConfigurer中的addResourceHandlers方法,把/upload/**这个URL映射到磁盘路径,这样才能在前端通过src="/upload/xxx.jpg"访问到图片:
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadPath); }Controller里上传文件的写法:
@PostMapping("/upload") @ResponseBody public Result upload(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return Result.error("文件不能为空"); } String originalFilename = file.getOriginalFilename(); String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); String fileName = UUID.randomUUID().toString().replace("-", "") + suffix; File dest = new File(uploadPath + fileName); try { file.transferTo(dest); return Result.success("/upload/" + fileName); } catch (IOException e) { e.printStackTrace(); return Result.error("上传失败"); } }生成文件名用了UUID,目的是防止重名。如果不做这一步,A上传的a.jpg会被B上传的同名文件覆盖,非常危险。同时要校验一下文件类型和大小,最好只允许jpg、png、jpeg、gif,限制在10MB以内。这些限制在配置文件和代码里双保险,防止有人绕过前端直接POST大文件导致服务器磁盘被塞满。
4.3 订单状态机:如何优雅处理“待付款到已收货”
订单模块是电商系统的“心脏”。订单状态一共有四五个,如果直接用if else嵌套写业务判断,代码会越来越乱。我习惯用状态机思路拆解:每个状态对应一组允许的操作,每个操作只改变一个状态。比如:
- 待付款 -> 用户点击付款,调用pay接口,状态改为待发货。
- 待发货 -> 管理员点击发货(后台操作),状态改为待收货。
- 待收货 -> 用户点击确认收货,状态改为已完成。
- 已完成 -> 用户可以评价,订单流转结束。
- 待付款状态,超过一定时间后,可以取消订单,状态改为已取消。
在订单Controller里,核心就是订单状态更新的幂等性。比如用户连续点两次付款,如果第一次成功改了状态,第二次应该提示“订单状态不允许操作”,而不是再执行一次扣款逻辑。实现上很简单,写update语句时带上status条件:
UPDATE orders SET status = 1, pay_time = NOW() WHERE order_id = #{orderId} AND status = 0如果返回的受影响行数是0,说明订单已经不是待付款状态,直接提示异常。这个写法是数据库层面的乐观锁思想,在高并发场景下能避免两个请求同时操作同一张订单引起的状态错乱。在实际项目中比先查再改稳得多。
4.4 回收单与商品单的关联事务:保证数据一致性
前面提到回收单审核通过后要生成商品记录,这里给出一个简化的service代码:
@Service public class RecycleServiceImpl implements RecycleService { @Autowired private RecycleMapper recycleMapper; @Autowired private ProductMapper productMapper; @Override @Transactional(rollbackFor = Exception.class) public void approveRecycle(Integer recycleId) { Recycle recycle = recycleMapper.selectById(recycleId); if (recycle == null || recycle.getStatus() != 0) { throw new RuntimeException("回收单不存在或已被处理"); } // 更新回收单状态为已通过(status=1) recycleMapper.updateStatus(recycleId, 1); // 根据回收单信息创建商品记录 Product product = new Product(); product.setUserId(recycle.getUserId()); product.setCategoryId(recycle.getCategoryId()); product.setTitle(recycle.getTitle()); product.setDescription(recycle.getDescription()); product.setCoverImage(recycle.getImage()); product.setPrice(recycle.getEstimatedPrice()); product.setStatus(0); // 待上架 productMapper.insert(product); } }@Transactional(rollbackFor = Exception.class)这里注意一定要写rollbackFor,因为Spring默认只对RuntimeException回滚,而像IOException这类受检异常默认是不会触发回滚的。如果审核过程中商品插入失败,而回收单状态已经改成已通过,数据就不一致了。加了这个注解后,方法内任何异常,整个事务都会回滚,回收单状态保持原样,下次还能再审核。这就是事务控制的经典场景。
4.5 分页查询与关键字搜索:PageHelper的完整用法
商品列表页、后台管理页最核心的查询套路就是,PageHelper + 动态SQL + 关键字搜索。前端接口传参包含pageNum、pageSize、keyword、categoryId、sortType,Service里写:
PageHelper.startPage(pageNum, pageSize); Example example = new Example(Product.class); Example.Criteria criteria = example.createCriteria(); criteria.andEqualTo("status", 1); // 只查在售商品 if (StringUtils.hasText(keyword)) { criteria.andLike("title", "%" + keyword + "%"); } if (categoryId != null) { criteria.andEqualTo("categoryId", categoryId); } example.setOrderByClause("create_time desc"); List<Product> productList = productMapper.selectByExample(example); PageInfo<Product> pageInfo = new PageInfo<>(productList);然后返回给前端的数据结构里包含list、total、pageNum、pageSize、pages这几个字段,前端就能正常渲染分页组件了。这里再说个经验,Product的image字段在数据库里存的是多张图片路径,用逗号分隔,比如/upload/a.jpg,/upload/b.jpg,取详情的时候要split成数组返回给前端,方便轮播图展示。如果设计时就把多图存成单字段,查询接口返回时记得做处理,别直接把整串字符串渲染到img的src里。
5. 调试文档与项目讲解:学会让代码“开口说话”
5.1 调试文档怎么读、怎么写
拿到项目压缩包以后,不要一上来就点运行,先花半小时梳理项目结构和文档,看这几个东西:database目录下的sql脚本、application.yml里的配置信息、默认账号密码、项目启动说明。先看这四样,能帮你省下大量排查时间。比如数据库脚本没执行或者连错库是最常见启动失败原因,而默认账号密码往往就写在README或调试文档里,能直接进管理员后台验证项目是否正常。
调试文档里有用的东西通常是这些:项目用到哪些技术、模块怎么划分、每个模块的核心流程是什么、数据库表字段说明、不同角色有哪些功能权限。这些信息对于后期答辩或者代码重构判断都非常有参考价值。写调试文档时遵循“问题背景 -> 问题现象 -> 排查过程 -> 问题根因 -> 解决方案”五步走,这样无论是自己复盘还是给别人看,都能快速定位。
5.2 项目讲解的重点:功能演示比技术名词更重要
讲解项目的时候,很多同学喜欢把技术名词堆在开头,说“我这个项目用了SpringBoot加SSM加MyBatis加PageHelper”之类的,其实听众最想听的是这个系统能干什么、流程怎么跑、为什么这么设计。
一个好的讲解顺序是:先讲项目背景(旧物回收的痛点、线上线下结合场景),再讲系统功能(用户前台可以从哪几个模块操作,管理员后台能管理哪些内容),接着打开界面实操演示一遍完整流程,然后讲数据库表设计(几张核心表、表之间的关系、状态字段怎么设计),最后讲技术难点(回收单转商品单的事务控制、拦截器权限控制、PageHelper分页原理)。这样一套下来,评委或者面试官基本能判断出你真正理解了项目,而不是背了一堆名词。
5.3 源码阅读顺序:从一条用户主流程打通全局
如果你拿到一套陌生源码,最快的上手方式不是挨个看文件,而是找一条“最短主链路”走一遍。比如从登录操作开始:登录页提交请求 -> controller的login接口 -> service层校验用户名密码 -> session写入用户状态 -> 前端跳转首页。把这一条链路走完,你就知道项目里Controller、Service、Mapper分别是如何串联的。
然后再走一条业务链路,比如用户发布闲置商品:打开发布页 -> 前端先调图片上传接口 -> success后再调商品保存接口 -> 商品状态为待审核 -> 后台管理员审核上架。两条链路走完,整个项目的基本框架八九不离十了。碰到看不懂的代码就先在关键位置打上System.out.println或通过断点调试看变量值,比啃代码快得多。
6. 常见问题与排查思路实录
6.1 启动报错“Invalid bound statement (not found)”
这个报错在SpringBoot+MyBatis项目里出现频率极高。造成的原因有几种,按经验排序:第一,application.yml里mybatis.mapper-locations没配或者路径不对,导致mapper xml没被加载;第二,xml文件里的namespace写错了,没有对应当前Mapper接口全限定名;第三,Mapper接口和xml文件不在同一个包下,且没在pom里配置resources扫描xml。解决方案就是逐个排查:先看target目录下有没有xml文件,没有就是资源扫描问题,在pom的build节点里加resources配置,把src/main/java目录下的xml也打进去。
6.2 页面中文乱码
中文乱码是SSM老项目的祖传问题,但SpringBoot下简单得多。无非三处检查:第一,MySQL连接URL里加characterEncoding=utf8,同时数据库本身创建时指定utf8mb4;第二,前端页面设置 ;第三,SpringBoot默认的HttpMessageConverter已经配置UTF-8,只要你的Java文件编码也是UTF-8,基本不会乱码。如果数据库已经建好了但数据全是乱码,那只能重导sql或者改数据表的charset。
6.3 图片能上传但访问不到
这类问题通常是访问路径和磁盘路径没映射。检查思路:先看图片保存到了哪个目录,确认文件落地了;再浏览器访问http://localhost:8080/upload/xx.jpg,看返回404还是200;404就看WebMvcConfig里的addResourceHandlers映射路径写没写对,尤其是末尾的斜杠,file:/Users/admin/upload/末尾必须有/。这个斜杠少了,很多人排查一下午都找不出原因。
6.4 登录拦截导致CSS样式全部丢失
现象:能访问首页但是页面样式完全错乱,F12控制台大量404。原因:拦截器把所有请求都拦了,静态资源没放行。解决办法就是我前面说的,在excludePathPatterns里把css、js、images这些静态目录前缀加进去。注意SpringBoot的静态资源默认映射路径是/static/、/public/、/resources/、/META-INF/resources/这几个,如果你的前端文件放在static下,访问路径不带static前缀,比如src="/css/style.css",但拦截器排除路径很可能写成/css/**,这时候需要你根据自己的实际访问路径调整排除配置。
6.5 PageHelper分页数据不对
PageHelper分页最需要注意的是,startPage之后只能跟一条SQL查询语句,如果startPage之后又执行了其他非查询业务逻辑或第二次查询,分页就会串台。比如你在startPage和查询之间Debug了一段代码、或者又调了一次select,分页效果就会异常。正确用法是startPage紧挨着需要分页的Mapper方法调用。另外PageHelper对多表关联查询本身没问题,但如果你用了自定义SQL且SQL里有left join,那统计total的count查询在复杂场景下可能数据不对,这时可以开启PageHelper的count优化参数。
7. 结尾部分
在实操里走完整个旧物回收商城项目,我个人最大的体会是:这类系统真正考察的难点从来不是某个框架的API怎么用,而是多个模块之间的数据流转和事务一致性。回收单转商品、订单状态流转、图片存取这三条链路,每一条拿出来都能问出不少候选人底细。所以无论是自己做项目还是接手别人源码,都别急着表现“我会用SpringBoot”,而是把重心放在“我清楚这个系统怎么处理数据和状态”。如果以后想把这个项目扩展加分,可以往这几个方向走:引入Redis缓存商品热门列表、使用RabbitMQ做回收单审核后的异步通知、接入Spring Security做更细粒度的权限管理、把图片上传换成MinIO对象存储。这些升级点其实都不复杂,但每加一个都能让项目在技术深度上提升一个档次。
最后分享一个调试心得,也是在实战里踩过几次坑后总结的:改动任何跟MyBatis有关的配置或SQL后,一定要先把项目完整重启,确认启动日志里出现“Loaded mapper”或者Mapper xml的报错,再继续调前端。很多“接口拿不到数据”的诡异问题,其实都是Mapper xml更新后没被重新编译,target目录下的还是旧文件。这个细节虽然不起眼,但在开发过程中会反复折磨人,知道一次就能避开很多无意义的排错时间。