
简介面向计算机相关专业毕业生的SpringBoot农产品销售系统毕业设计论文完整记录基于SpringBootVue技术栈的Java项目从选题到实现的全过程。论文共六章依次涵盖系统概述、开发环境搭建、技术/经济/操作三方面可行性需求分析、系统概要设计、核心功能模块详细设计以及系统测试方案并配有结论、致谢与参考文献结构规范可直接作为毕业论文写作的框架参考。内容详细介绍了管理员与普通用户两类角色的功能划分涉及农产品管理、购物车、订单处理、交流论坛、公告信息等模块并给出数据库E-R图、表结构设计及界面截图说明帮助读者理解系统从需求到落地的完整链路。压缩包内为1个doc文档大小3.16MB包含完整摘要、目录、正文及图表适合需要完成毕业设计、撰写JavaWeb方向论文或快速了解SpringBoot项目开发流程的学生使用。目前已有753人学习/下载参考价值经过较多学习者验证。1. SpringBoot农产品销售系统论文兜底、演示能跑才是真交付如果你拿到的是一份“基于SpringBoot农产品销售系统论文.doc”外加一个Java项目压缩包最先要确认的不是哪段代码写得最漂亮而是这套东西能不能在答辩前三天跑起来。这类项目的核心价值是把“商品浏览、下单、订单管理”这条完整电商链路浓缩成一个结构清晰的演示系统SpringBoot负责后端接口与业务规则MySQL保存农产品、订单和用户数据再配一份能自圆其说的论文文档形成毕设或课程设计里最常见的交付形态。适合谁正在赶毕业设计、打算把农产品销售系统当案例研究的人或者想做中小型管理系统练手、又不希望一上来就碰Spring Cloud那套重结构的Java开发初学者。2. 需求拆解与数据库设计三张业务表把销售闭环撑起来2.1 先把使用场景拆成三个格子逛、买、管做农产品销售系统最先要想的不是界面长什么样而是谁在用这套系统。常见的角色有三个买家、管理员、可能还有配送员。买家是前台商城的使用者能注册登录、浏览商品分类、加购物车、下单支付管理员是后台操作者负责商品上下架、处理订单状态、维护公告配送员在农产品这类系统里不是必须有的但如果你在论文里把“订单配送状态”画进了ER图那就要给订单状态机里留出一步。三个角色的权限边界直接决定表结构。买家对应food_user表里role1管理员是role0配送员可以是role2区分方式用int型枚举字段就够了完全不需要引入Spring Security那一套RBAC模型。很多毕设项目在这里翻车是把“用户管理”做成了“用户权限管理”最后代码里塞了几张权限表论文却只字不提答辩时一问就露馅。权限模型跟着业务走这是SpringBoot项目结构里最该克制的地方Controller层只管收参返参Service层处理业务Mapper层碰数据库权限只是User实体里的一个字段和登录拦截器里的一个判断。2.2 核心表结构用户、商品、订单与订单项农产品销售系统不同于普通电商核心差异在商品维度上单位可能是“斤”“箱”“份”价格会有批发价和零售价库存很多时候是当天可卖数量。所以商品表的设计不能直接照搬通用商城模板需要把计量单位字段独立出来。主业务闭环的数据表大概五张food_user用户表、food_category分类表、food_product商品表、food_cart购物车表、food_order订单表、food_order_item订单明细表。订单和订单项是典型的主从表结构购物车则可以看成是“临时订单明细”它的作用只是减少用户重复选择商品的成本。这里先给出核心建表SQL数据库我一般命名为farm_market字符集utf8mb4排序规则utf8mb4_general_ciCREATE TABLE food_user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT BCrypt加密后的密码, phone varchar(20) DEFAULT NULL COMMENT 联系电话, address varchar(200) DEFAULT NULL COMMENT 收货地址, role tinyint(4) NOT NULL DEFAULT 1 COMMENT 角色0管理员1买家2配送员, created_time datetime NOT NULL COMMENT 创建时间, updated_time datetime DEFAULT NULL COMMENT 更新时间, deleted tinyint(1) NOT NULL DEFAULT 0 COMMENT 逻辑删除0未删1已删, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE food_product ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, category_id bigint(20) NOT NULL COMMENT 分类ID关联food_category, name varchar(100) NOT NULL COMMENT 农产品名称, description varchar(500) DEFAULT NULL COMMENT 商品描述, unit varchar(10) NOT NULL DEFAULT 斤 COMMENT 计量单位斤/箱/份, price decimal(10,2) NOT NULL COMMENT 零售价, stock int(11) NOT NULL DEFAULT 0 COMMENT 库存余量, image_url varchar(255) DEFAULT NULL COMMENT 商品图片地址, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 商品状态1上架0下架, created_time datetime NOT NULL COMMENT 创建时间, updated_time datetime DEFAULT NULL COMMENT 更新时间, deleted tinyint(1) NOT NULL DEFAULT 0 COMMENT 逻辑删除, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT农产品商品表;订单表和订单项表的关联逻辑是这样的一次下单产生一张food_order记录包含订单号、总金额、收货信息、订单状态每一样被加入的商品是一条food_order_item记录商品快照的名称、单价、数量、小计。为什么要做商品快照因为商品的名称、价格会变如果订单明细直接关联food_product的实时数据一个月后查历史订单价格和品名都可能对不上论文里“订单可追溯性”这段就没法写。这是农产品销售系统里最容易被忽略的边界点。2.3 状态字段与演示数据为论文里的ER图和答辩准备好证据建表时有几条经验可以直接抄。第一所有表都带逻辑删除字段deleted不要用物理删除第二时间字段用datetime而不是timestamp避免时区导致的时间错乱第三订单金额用十进制decimal而不用float否则合计金额会出现尾数偏差。关于外键我的习惯是不建物理外键只在逻辑上通过id关联。物理外键在删除分类或商品时会带来一串连锁校验毕设演示现场很容易被“删除失败”卡住而逻辑上的一致性由Service层代码来保证。这在论文里也有说法避免数据库级联约束对性能的影响将一致性校验上移到应用层。在准备演示数据时至少要在food_user里放一个管理员账号usernameadminpassword经过BCrypt加密再放两个买家账号。商品数据要有跨度蔬菜、水果、粮油各上架两三件图片链接可以直接用项目static目录下的本地图片不要让演示现场依赖外网资源。论文的ER图就按这五张表来画数据库设计章节里的字段说明直接对照本节给出的建表语句评审老师核对时能一一对应上。3. 技术选型与工程搭设SpringBoot为什么配MyBatis-Plus而不是JPA3.1 选型对比SSM、JPA、MyBatis-Plus谁适合这种论文项目很多人在选型时会纠结用SpringBoot MyBatis还是SpringBoot Spring Data JPA。我倾向MyBatis-Plus理由很现实。SSM是经典组合但要写大量Mapper XML项目代码里一半篇幅都是SQL映射文件论文里又不好解释。Spring Data JPA对于简单的单表CRUD确实方便但农产品销售系统里有大量按条件分页查询、多表关联统计的场景JPA的方法命名或JPQL一旦没写对排查起来非常痛苦而且生成的SQL有时不是最优的。MyBatis-Plus刚好折中单表增删改用BaseMapper自带方法复杂SQL还能用注解或XML手写在Crud能力和可控性之间取得了平衡。另外注意SpringBoot版本和JDK版本的匹配问题。SpringBoot 2.7.x配合JDK8SpringBoot 3.x则必须使用JDK17及以上。如果在Windows上启动项目时报“UnsupportedClassVersionError”基本就是版本不匹配。我写毕设项目一般固定SpringBoot 2.7.18 JDK8 MyBatis-Plus 3.5.3.2这套组合在大多数电脑上打开就能跑兼容性最稳。不要追求版本太高——SpringBoot版本太高反而会让老机器和新依赖之间出现各种莫名冲突。3.2 pom依赖与配置文件一次写对的组合拳pom.xml里不需要堆满依赖核心就五个spring-boot-starter-web提供Web能力mysql-connector-j提供数据库驱动mybatis-plus-boot-starter是持久层核心lombok帮实体类省掉getter/setter再额外加一个JWT库用于登录令牌。下面是可以直接抄的最小pomparent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent properties java.version1.8/java.version mybatis-plus.version3.5.3.2/mybatis-plus.version jjwt.version0.9.1/jjwt.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version${mybatis-plus.version}/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version${jjwt.version}/version /dependency /dependencies配置文件application.yml则要一次性把数据源、端口、MyBatis-Plus的策略和SQL日志全写好server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/farm_market?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 main: allow-circular-references: true mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0这里有几个参数值得解释。url中serverTimezoneAsia/Shanghai解决了MySQL 8.x与JDK8的时区差8小时问题。allow-circular-referencestrue是因为SpringBoot 2.6之后默认禁止Bean循环依赖而毕设项目里Service之间相互调用很常见如果两个Service互相注入就会启动失败先打开这个开关让项目跑起来后续再考虑优化依赖关系。mybatis-plus的log-impl配置会让控制台打印每条SQL这个在答辩演示时非常有用评委问你数据库做了什么你直接指着控制台说“这是刚才那条查询生成的SQL”。3.3 Maven构建与项目目录把骨架先立起来在确保Java和Maven环境变量配置正确之后Windows命令行输入mvn -v能显示版本号就没问题。如果提示“mvn不是内部或外部命令”说明环境变量配置还没到位需要把Maven的bin目录加入PATH。项目骨架推荐按这个结构组织farm-market/ ├── src/main/java/com/example/farm/ │ ├── FarmApplication.java -- SpringBoot启动类 │ ├── config/ -- 拦截器、跨域、MyBatisPlus分页配置 │ ├── controller/ -- category/product/cart/order/user │ ├── service/impl/ -- 业务逻辑与实现类 │ ├── mapper/ -- 继承BaseMapper的自定义接口 │ ├── entity/ -- 与数据库表对应的实体类 │ ├── dto/ -- 前端请求参数对象避免直接接受HttpServletRequest │ ├── vo/ -- 视图返回对象比如聚合商品与分类的VO │ ├── common/ -- Result、异常处理、常量 │ └── utils/ -- JwtUtils等工具类 ├── src/main/resources/ │ ├── application.yml │ ├── sql/ -- 建表脚本与初始化数据 │ └── static/ -- 前端打包产物 └── pom.xml启动类要放在最外层包路径com.example.farm下因为SpringBoot默认扫描启动类所在包及其子包。扫描路径放错会导致Mapper注入失败控制台报“No qualifying bean of type”这是最常见的新手坑。完成骨架后运行mvn spring-boot:run控制台出现“Started FarmApplication”就是一个可运行的地基了。4. 从0跑通销售主流程JWT登录鉴权、商品分页与订单状态机4.1 统一返回结构与JWT登录把接口风格先立住做前后端分离项目接口返回格式不统一是灾难。前端要么从data里拿数据要么从result里拿数据翻来覆去改代码。我一般会用泛型定义一个common/Result类所有接口统一走这个外壳Data public class ResultT { private Integer code; // 状态码200成功500业务失败401未认证 private String msg; // 提示信息 private T data; // 真正的业务数据 public static T ResultT ok(T data) { ResultT r new Result(); r.setCode(200); r.setMsg(操作成功); r.setData(data); return r; } public static T ResultT fail(String msg) { ResultT r new Result(); r.setCode(500); r.setMsg(msg); return r; } }JWT登录的思想是用户登录成功后后端签发一个带过期时间的token前端后续请求都带上这个token后端拦截器解析它来决定放行还是拒绝。和Session方案相比JWT不需要在服务端保存登录状态天然适合SpringBootVue这种前后端分离架构。JwtUtils的核心就两个方法public class JwtUtils { private static final String SECRET farm-market-demo-secret; // 生成token过期时间设为30分钟 public static String createToken(Long userId, String username, Integer role) { return Jwts.builder() .claim(userId, userId) .claim(username, username) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() 30 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } // 解析token非法或过期会抛异常 public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }token里不要放太多信息userId、username、role三个字段足够了。角色放进去是为了在拦截器里直接判断接口权限省一次数据库查询。过期时间设30分钟是基于演示场景平衡的太短演示中要反复重新登录太长又不符合安全习惯。如果想在演示时调长直接把30 * 60 * 1000里的系数改大比如改成60就代表60分钟。拦截器的处理也不复杂实现HandlerInterceptor在preHandle中取出Header里的Authorization解析成功就放行失败返回401状态码。登录接口本身必须放行商品列表这种游客可看的接口也可以放行但订单的创建、查询必须校验token。4.2 商品模块与购物车用MyBatis-Plus的QueryWrapper和分页农产品商品最典型的查询是“按分类浏览”“按关键词搜索名称”“按价格区间过滤”这些用MyBatis-Plus的QueryWrapper完全能搞定。下面是一个商品分页接口的写法RestController RequestMapping(/api/product) public class ProductController { Resource private FoodProductMapper productMapper; // GET /api/product/list?pageNum1pageSize10keyword苹果categoryId2 GetMapping(/list) public ResultIPageFoodProduct list( RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String keyword, RequestParam(required false) Long categoryId) { PageFoodProduct page new Page(pageNum, pageSize); LambdaQueryWrapperFoodProduct wrapper new LambdaQueryWrapper(); wrapper.eq(FoodProduct::getStatus, 1) // 只展示上架商品 .like(StringUtils.hasText(keyword), FoodProduct::getName, keyword) .eq(categoryId ! null, FoodProduct::getCategoryId, categoryId) .orderByDesc(FoodProduct::getCreatedTime); return Result.ok(productMapper.selectPage(page, wrapper)); } }LambdaQueryWrapper的写法是MyBatis-Plus的核心用法。第一行eq约束了商品必须处于上架状态like前面的StringUtils.hasText(keyword)是条件成立才拼接这个条件keyword为空时就不做模糊查询orderByDesc按创建时间倒序让新上架的农产品排前面。IPage返回对象里包含records、total、pages等字段前端展示分页组件时直接用。购物车表的核心字段是user_id、food_product_id、food_count、created_time。加入购物车时先按user_id和product_id查记录存在就累加数量不存在就插入新行。这个逻辑放在Service层完成避免Controller里写太多业务判断看起来也整齐。4.3 订单状态机整个系统最需要讲清楚的一段订单模块是论文里“系统详细设计”的重头戏也是答辩时评委爱问的地方。状态设计要简洁我习惯用五个状态1待付款2待发货3待收货4已完成0已取消。状态转换规则如下表所示状态码状态名可触发操作操作角色1待付款支付订单 / 取消订单买家2待发货发货管理员3待收货确认收货买家4已完成不可操作双方0已取消不可操作用户关闭后冻结从购物车创建订单的Service代码比Controller更能体现业务规则Service public class OrderServiceImpl implements OrderService { Resource private FoodOrderMapper orderMapper; Resource private FoodOrderItemMapper orderItemMapper; Resource private FoodProductMapper productMapper; Resource private FoodCartMapper cartMapper; Override Transactional(rollbackFor Exception.class) public FoodOrder createOrderFromCart(Long userId, ListLong cartIds) { // 1. 把购物车条目查出来校验归属人是不是当前用户 ListFoodCart carts cartMapper.selectBatchIds(cartIds); if (carts.isEmpty()) { throw new RuntimeException(购物车为空); } // 2. 汇总金额同时检查商品是否还在上架 BigDecimal totalAmount BigDecimal.ZERO; ListFoodOrderItem orderItems new ArrayList(); for (FoodCart cart : carts) { FoodProduct product productMapper.selectById(cart.getProductId()); if (product null || product.getStatus() ! 1) { throw new RuntimeException(商品已下架 cart.getProductId()); } if (product.getStock() cart.getCount()) { throw new RuntimeException(库存不足 product.getName()); } totalAmount totalAmount.add(product.getPrice().multiply(new BigDecimal(cart.getCount()))); // 订单明细保存的是商品快照防止后续改价影响历史订单 FoodOrderItem item new FoodOrderItem(); item.setOrderId(null); // 先赋空等订单生成后再回填 item.setProductId(product.getId()); item.setProductName(product.getName()); item.setPrice(product.getPrice()); item.setCount(cart.getCount()); orderItems.add(item); } // 3. 创建订单主记录 FoodOrder order new FoodOrder(); order.setOrderNo(generateOrderNo()); // 用时间戳随机数生成拒绝重复 order.setUserId(userId); order.setTotalAmount(totalAmount); order.setStatus(1); // 新订单统一进入待付款状态 orderMapper.insert(order); // 4. 回填订单明细并批量插入 orderItems.forEach(item - item.setOrderId(order.getId())); orderItemMapper.insertBatch(orderItems); // 5. 清空已下单购物车 cartMapper.deleteBatchIds(cartIds); return order; } }这是典型的Service层业务编排代码。注意几个边界点Transactional保证“减库存、建订单、清购物车”要么全成、要么全败不会出现订单建了但库存没减的中间状态商品快照保存的是下单那一刻的名称与价格库存不足直接抛异常阻断异常由全局处理器转成Result.fail返回给前端。需要解释的参数是rollbackFor Exception.classSpringBoot默认只对RuntimeException回滚checked exception不会触发事务回滚加上这行等于告诉事务管理器“只要是异常就回滚”。这一步在论文的事务设计章节里能写出实打实的内容。支付这类敏感操作不需要真的对接支付网关。论文里描述可以写“模拟支付”代码里把支付状态从1改为2同时扣减对应商品库存。扣库存的SQL要用原子更新UPDATE food_product SET stock stock - #{count} WHERE id #{id} AND stock #{count}这条语句既能扣库存又能防止并发下的超卖问题。如果受影响行数为0说明库存不足直接回滚。5. 毕设项目最常见的6个坑从环境冲突到答辩卡壳5.1 启动失败报“Failed to configure a DataSource”现象是SpringBoot一启动就报错提示无法配置数据源。原因主要有三个方向第一application.yml里的数据库名与本地MySQL实际库名不一致第二MySQL服务没启动第三mysql驱动版本和数据库版本不匹配。排查时先看SpringBoot配置中的url在命令行用mysql -uroot -p进库执行SHOW DATABASES;确认farm_market存在且账号密码正确再回头看代码。有时IDE里改了配置没有触发资源文件重新编译执行一次mvn clean再启动即可。5.2 MyBatis-Plus自动填充时间不生效实体类里标记了TableField(fill FieldFill.INSERT)但插入数据时createTime还是null。原因是MetaObjectHandler没有注入到Spring容器里。自动填充依赖一个实现类需要写类似下面这样的BeanComponent public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createdTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updatedTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updatedTime, LocalDateTime.class, LocalDateTime.now()); } }这个Component标识是关键少了它MyBatis-Plus只会在字段上做映射不会执行填充逻辑。还有一种情况是字段名和实体属性名不一致比如数据库是created_time实体里写的是createTime虽然有驼峰映射但填充器的字符串一定要写实体属性名严格模式才会命中。5.3 跨域配置写完还是报浏览器403现象是前端Vue项目访问SpringBoot接口浏览器的控制台报CORS错误后端也加了跨域配置但不管用。原因在于SpringBoot的拦截器链上跨域过滤器执行顺序在自定义JWT拦截器之后或者被Shiro/SpringSecurity提前拦下。解决方式是使用WebMvcConfigurer统一配置跨域映射并设置setAllowedMethods包含OPTIONS方法。JWT拦截器对OPTIONS请求要直接放行因为浏览器预检请求不会携带自定义Header拦截器若把它拦下来真正的请求就不会发生。这块的排查顺序是先用Postman直连后端接口确认是否正常再在浏览器里测Postman正常而浏览器报CORS错问题就锁定在跨域。5.4 Vue打包放进SpringBoot的static目录后刷新404把前端build产物放进resources/static首页能打开但路由一刷新就404。原因是Vue使用了history模式浏览器请求的URI在SpringBoot里没有对应的Controller映射后端返回404。解决办法有两种一种是把Vue路由切回hash模式URL会出现#号但刷新正常另一种是让后端做前端路由转发把非接口类的请求都重定向到/index.html。第二种方案体验更干净Controller public class PageForwardController { GetMapping(value {/, /home, /order/**, /product/**}) public String forward() { return forward:/index.html; } }注意这个Controller不能拦截/api开头的请求映射路径里要避开后端接口前缀。5.5 删除商品时提示数据库中已存在关联记录很多人在演示后台“删除商品”功能时点击后没有任何反应或报外键冲突。因为表关联被物理外键约束住了。建议建表时不用物理外键而是用deleted逻辑删除字段删除商品时把deleted置为1同时把status置为0。这样订单历史明细不受影响后台也不再展示该商品。如果一定要物理删除那删除前必须先用SQL查一下food_order_item里是否存在该productId存在则拒绝并提示“订单中已包含该商品无法删除”。5.6 论文与代码不一致ER图、表名和前端页面各说各话这是最伤答辩翻车的一类问题。论文中写的数据库表名是t_product代码里实体类叫FoodProduct前端展示的字段又不一样。三个月前写的文档和三个月后的代码必然有出入但评委只会按论文打开代码来核对。我的习惯是论文定稿前把数据库建表SQL语句直接导出成附录ER图用工具从数据库逆向生成确保论文图和实际库结构完全一致。不要用手动画图手绘必出误差。表名如果不统一改代码里的实体映射远比改论文麻烦所以应以代码为准去修订论文。6. 答辩现场用得到的验证方法让论文和代码在演示里互相背书演示环节最稳的做法不是背功能列表而是让每张论文里的图表都有代码证据。我会在项目中维护一份sql/init.sql里面不仅是建表语句还包括完整的演示数据一个admin账号、两个买家账号、六个商品、两笔不同状态的订单。这样打开项目到演示结束全程五分钟内可完成闭环操作且每一步都能和论文章节对应上。比如登录页对应论文“用户登录模块”控制台打印的SQL对应“数据库设计章节”订单状态的跳转对应当前订单状态图。演示时我会按一条主线走先以买家身份登录浏览农产品把两件商品加入购物车并提交订单接着在支付环节停留一下展示控制台打印的SQL和事务日志然后用admin账号登录在后台找到这笔新订单并标记发货最后切回买家确认收货。这套流程走完前端商城、后台管理、订单状态机、数据库表关联全部覆盖同时项目的“前后端分离”“统一返回结果”“JWT认证”等设计点也都有现场画面可讲。一个我踩过多次的教训演示的电脑上务必备份一份空的MySQL初始化脚本答辩前再重建一次数据库用最新的脚本初始化。曾经有一次答辩前几天改了商品表加了一个sort字段但没有同步更新初始化脚本演示现场把旧库删掉重建后商品接口直接报字段不存在只能硬着头皮当场改代码。这种翻车完全可以提前避免。最后建议把JWT过期时间在演示当天适当调长一些免得讲到一半要重新登录。这个方向从项目到论文我是照着这个思路走过来的希望帮到你。本文还有配套的精品资源点击获取