简介:一份面向计算机专业毕业设计论文的参考文档,主题为洛川县苹果销售管理平台的设计与实现。内容严格遵循需求分析、技术选型、系统设计、编码实现、系统测试的软件开发流程,从选题意义与研究目标切入,详细论述了Spring Boot框架与B/S架构在农产品销售系统中的应用;针对用户管理、商品管理、订单处理等核心模块,给出了数据库E-R图、功能模块图及前端界面设计的完整思路。全文结构完整,包含中英文摘要、目录、开发技术介绍、系统分析、数据库查询结构设计和测试方案,知识点覆盖从数据库关系到页面交互的完整链路,可帮助毕业生快速搭建同类型论文框架,也可为开发农产品电商平台的工程师提供工程参考。资源为单个docx文档,约6.58MB,以论文正文、架构图和数据表说明为主。已有395人学习,适合毕业设计开题、论文撰写及答辩准备等阶段使用。
1. 用SpringBoot做农产品销售管理平台:毕设选题的性价比与真实工作量
如果你正在为毕设选题发愁,又不想选那种“图书管理”或“学生信息管理”的烂大街题目,农产品销售管理平台是个被低估的选择。它不是纯Demo,而是能挂上订单、库存、价格、营销的完整业务闭环,SpringBoot做后端、Vue做前端,正好覆盖了当前主流的企业开发栈。更关键的是,这个题目可以往深做,也可以往浅做,答辩时既能讲出“我实现了什么”,也能讲出“我踩过什么坑”,比堆砌一堆CRUD页面强得多。本文不讨论怎么写Word论文,只讲怎么把这个平台的代码和设计做成能答辩、能复现、能说清原理的真实项目,顺带把论文里最需要的数据流和架构图给你理出来。
适合谁:有一点Java基础、知道SpringBoot但没完整做过项目的人,以及那些想靠毕设冲一个“项目经验”写在简历上的同学。下面从选型开始,一步步拆解。
2. 先把论文骨架搭起来:技术选型、功能模块与数据库设计
2.1 技术选型为什么是SpringBoot + MyBatis Plus + Vue
这个组合在目前SpringBoot毕设生态里几乎成了标准答案,原因不是它最新,而是它最好解释、最好调试、最好写进论文。
SpringBoot解决了项目配置繁琐的问题。以前SSH或SSM方案里要写一堆XML,SpringBoot的自动装配把数据源、事务、Web容器都变成了“依赖加进来就能用”。论文里解释SpringBoot时,重点不是背“简化开发”这种空话,而是说清楚自动装配的原理:SpringBoot在启动时通过@EnableAutoConfiguration去加载META-INF/spring.factories里的配置类,条件注解@ConditionalOnClass决定这个配置类是否生效。比如你引入了spring-boot-starter-data-redis,它检测到RedisTemplate类存在,才自动创建连接工厂。这个机制能让你在答辩时回答“为什么我不用XML就有Bean”。
MyBatis Plus不是新技术,但它的BaseMapper让单表CRUD零SQL化,极大减少重复代码,让你的论文里能省出一大块篇幅去写业务逻辑而不是insert语句。和纯MyBatis相比,它保留了手写SQL的能力,适合订单统计这类复杂查询。
Vue前端是加分项,不是必须项。如果你时间紧,用Thymeleaf模板引擎也可以,但SpringBoot + Vue前后端分离是目前热词里出现频率最高的组合,答辩时能展示你理解跨域、Token、统一返回体这些真实开发概念,比JSP时代的老方案高一个档次。我的建议是:哪怕你前端只写几个页面,也值得用Vue,因为这里能展示“接口设计”能力。
2.2 核心功能模块拆解:商品、订单、库存、用户、营销
农产品销售平台的特征是:SKU不像图书那么稳定,价格随行就市,库存受季节影响,订单可能包含预付款和退款,用户分买家、卖家和管理员。论文里的功能模块图一般画成树状,但真正实现时你要按角色和数据流来拆。
我建议按这样拆:
- 用户模块:注册、登录、角色权限。买家下单、卖家管理商品、管理员审核和统计。
- 商品模块:农产品分类、商品信息、规格(斤、箱、份)、上下架、价格维护。这里需要支持“今日价格”或“历史价格”,为后面的价格分析留数据。
- 库存模块:入库、出库、实时库存、预警阈值。农产品有损耗,要支持手动调整库存并记录原因。
- 订单模块:购物车生成订单、下单扣库存、支付模拟、取消订单、发货签收。这是业务核心,必须用事务。
- 营销模块:满减、优惠券、限时打折。农产品营销活动频繁,这个模块虽然简单,但能让论文里多一个“创新点”。
- 数据统计:销售额按天/按月统计、热销商品排行、价格波动曲线。
这样拆的好处是每个模块的表之间关系清晰,论文里的ER图和数据流图都能直接画。
2.3 数据库表设计与关键字段:从ER图到建表SQL
数据库设计是论文里老师最爱看的部分,也是后续代码能不能少踩坑的关键。农产品平台至少要有这些表:用户表、角色表、商品分类表、商品表、库存表、订单表、订单明细表、价格记录表。
订单表要注意:不能只存总金额,还要存下单时的商品快照,比如商品名、单价、单位,因为商品信息可能改,如果只关联商品表,以后查订单会发现价格对不上。这是一个经典的现实问题,写在论文里能体现你的思考。
建表SQL不追求一次到位,但有三个字段强烈建议加上:create_time、update_time、deleted。前两个用来做统计和审计,deleted用来做逻辑删除。原因很简单:商品删除了,历史订单里的商品信息还需要能查到;用户注销了,订单记录不能变成孤儿。MyBatis Plus的@TableLogic可以很优雅地支持逻辑删除,不需要你手动在每次查询里过滤deleted=0。
库存表的唯一约束应该有(goods_id, spec),避免同一商品同一规格出现两条库存记录。订单明细表的索引至少要覆盖order_id和goods_id,否则你写“热销商品排行”查询时会发现就是慢。
数据库名可以叫farm_market,字符集用utf8mb4,不是utf8,因为农产品介绍里可能写“🧑🌾”这种表情。这个问题看着小,答辩时选错会被老师当场指出来。
3. 从零到能答辩:搭建SpringBoot项目结构与核心接口实现
3.1 SpringBoot项目结构:包分层与自动装配理解
很多人SpringBoot项目结构混乱,控制器里直接写SQL,工具类随手扔,最后论文里的架构图都不好意思画。我参考主流企业级SpringBoot + MyBatis Plus + Vue项目结构,建议包分这么几层:
com.example.farm ├── controller // 接口层,只做参数接收和结果包装 ├── service // 业务逻辑层,事务边界在这里 ├── mapper // MyBatis Plus的BaseMapper继承 ├── entity // 数据库表对应的实体类 ├── dto // 接口入参出参对象,不直接暴露实体 ├── vo // 视图对象,聚合数据 ├── config // 跨域、拦截器、WebMvc配置 └── common // 统一返回体、异常处理、工具类这里有个答辩陷阱:SpringBoot自动装配到底自动了什么?很多人只会说“不用写配置文件了”。你要能说清楚:你引入了spring-boot-starter-web后,SpringBoot通过DispatcherServletAutoConfiguration自动配置了DispatcherServlet和SpringMVC的组件;你引入了mybatis-plus-boot-starter后,它自动配置了SqlSessionFactory和数据源。但如果你自己写了一个@Configuration里的DataSource,那么自动配置会因为你用了自定义Bean而失效,这就是@ConditionalOnMissingBean的键作用。论文里写上一句“条件装配是基于条件的自动配置”会显专业。
3.2 用MyBatis Plus实现商品CRUD的最小代码
商品模块是最标准的演示模块,适合先跑通整个链路。建一个Goods实体,对应表goods。实体上加@TableName("goods"),主键用@TableId(type = IdType.AUTO),逻辑删除字段加@TableLogic。然后GoodsMapper extends BaseMapper<Goods>,这样最基础的增删改查方法就都有了。
下面是Service层常用的写法,直接能在项目里落地:
@Service public class GoodsServiceImpl implements GoodsService { @Autowired private GoodsMapper goodsMapper; @Override public Page<Goods> pageGoods(int page, int size, String name) { LambdaQueryWrapper<Goods> wrapper = new LambdaQueryWrapper<>(); // 按名称模糊查询,配合逻辑删除,自动过滤 deleted=1 wrapper.like(StringUtils.hasText(name), Goods::getName, name); wrapper.orderByDesc(Goods::getCreateTime); return goodsMapper.selectPage(new Page<>(page, size), wrapper); } @Override @Transactional(rollbackFor = Exception.class) public boolean createGoods(Goods goods) { // 价格最低不能为0或负数,实际项目里可以做成JSR 303校验 if (goods.getPrice() == null || goods.getPrice().compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException("商品价格必须大于0"); } return goodsMapper.insert(goods) > 0; } }LambdaQueryWrapper是MyBatis Plus的查询条件构造器,like(boolean condition, column, value)里第一个参数是是否拼接这个条件,这样就能避免你在SQL里写一堆if判断。selectPage是物理分页,MyBatis Plus会拦截并生成LIMIT,性能比List全查出再内存分页好得多。
注意,@Transactional一定要加在Service方法上,不是Controller上。而且rollbackFor = Exception.class是必须写的,因为Spring默认只回滚RuntimeException,你如果抛的是自定义ServiceException(继承RuntimeException)没问题,但如果你在事务里抛了个IOException,默认是不回滚的,库存就扣错了。这个细节很多老手都容易忽略。
3.3 订单流程中的事务与状态机设计
订单是农产品销售平台里最容易出“逻辑漏洞”的地方。最简单的流程是:用户下单 → 扣库存 → 生成订单 → 模拟支付(现金/余额)→ 发货。
一个常见的错误写法是:先减库存,再插入订单,如果插入订单失败,库存就莫名其妙少了。正确做法是把扣库存和创建订单放在同一个事务里。代码如下:
@Override @Transactional(rollbackFor = Exception.class) public Long createOrder(OrderCreateDTO dto) { // 1. 校验库存并尝试锁定 int updated = stockMapper.deductStock(dto.getGoodsId(), dto.getSpec(), dto.getQuantity()); if (updated == 0) { throw new ServiceException("库存不足或商品已下架"); } // 2. 创建订单主表 Order order = new Order(); order.setOrderNo(OrderNoGenerator.generate()); order.setUserId(dto.getUserId()); order.setTotalAmount(calcTotal(dto.getItems())); orderMapper.insert(order); // 3. 创建订单明细 for (OrderItemDTO item : dto.getItems()) { OrderItem orderItem = new OrderItem(); // ... 填充快照信息 orderItemMapper.insert(orderItem); } return order.getId(); }deductStock的SQL是原子更新:
UPDATE stock SET quantity = quantity - #{quantity} WHERE goods_id = #{goodsId} AND spec = #{spec} AND quantity >= #{quantity};这里用更新的行数来判定库存是否足够,而不是先查后改,避免并发下两个请求都读到库存10,然后都扣成负数。这个写法叫乐观锁的变体,不需要额外版本号字段,也不依赖SELECT FOR UPDATE,对MySQL InnoDB来说性能更好,而且能直接应对“秒杀”级别的并发。论文里画状态图时,订单状态不要只做一个status字段,建议用整数枚举:0待付款、1已支付、2已发货、3已完成、4已取消。退款状态可以单独一张退款表,否则订单状态会膨胀得没法维护。
3.4 使用SpringBoot定时任务处理订单超时和库存预警
农产品销售场景里,有些水果蔬菜是现摘现发的,用户下单后不付款会占用有限库存,订单超时后自动取消是刚需。用SpringBoot的@Scheduled做定时任务是最常见的方案,示例:
@Component public class OrderTimeoutTask { @Autowired private OrderMapper orderMapper; @Autowired private StockMapper stockMapper; // 每30秒扫描一次过期订单 @Scheduled(cron = "0/30 * * * * ?") @Transactional(rollbackFor = Exception.class) public void cancelTimeoutOrders() { // 查超过10分钟未支付的订单 LocalDateTime deadline = LocalDateTime.now().minusMinutes(10); List<Order> orders = orderMapper.selectList(new LambdaQueryWrapper<Order>() .eq(Order::getStatus, 0) .lt(Order::getCreateTime, deadline) .last("LIMIT 100")); for (Order order : orders) { // 这里标记取消 order.setStatus(4); orderMapper.updateById(order); // 回补库存,注意要根据订单明细逐条回补 List<OrderItem> items = orderItemMapper.selectList( new LambdaQueryWrapper<OrderItem>() .eq(OrderItem::getOrderId, order.getId())); for (OrderItem item : items) { stockMapper.increaseStock(item.getGoodsId(), item.getSpec(), item.getQuantity()); } } } }需要在实际项目中加开关,避免在本地调试时频繁触发,同时在测试环境里确保定时任务不会因为用户设置了错误的cron表达式就都跑飞了。你可以把cron表达式配置在application.yml里,用@Scheduled(cron = "${order.timeout.cron}")。这样论文里可以写“配置化定时任务”,比写死在代码里会是个亮点。
但要注意,@Scheduled默认是单线程执行的,多个定时任务会互相排队。如果你的平台同时有“订单超时取消”和“库存预警检查”两个任务,建议在配置类里增加一个TaskScheduler线程池:
@Configuration public class SchedulingConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5)); } }答辩时能说出这个线程池配置,远比说完“我用SpringBoot集成定时任务”就收住好得多。
另外,定时任务里的LIMIT 100是为了避免大事务扫全表。如果你一次性处理几千个过期订单,事务会很长,锁的行也多,容易把数据库拖垮,所以分页处理是必须的。
4. 让论文有亮点:价格波动、推荐或营销模块怎么做得像样
4.1 农产品价格波动分析:用简单统计替代复杂算法
很多人的毕设都以“没有任何业务深度”为失败点,因为全系统都是教材上的CRUD。农产品销售和普通商品销售的差别,就是价格随时间波动。所以做一个价格记录表,然后写一个查询接口,返回某个商品近30天的每日均价,前端用ECharts画折线图,这个功能性价比极高。
关键计算逻辑如下:
@Override public List<PriceTrendVO> trend(Long goodsId, Integer days) { LocalDateTime start = LocalDateTime.now().minusDays(days == null ? 30 : days); // 查出订单明细下单时的商品快照价格 List<OrderItem> items = orderItemMapper.selectList( new LambdaQueryWrapper<OrderItem>() .eq(OrderItem::getGoodsId, goodsId) .ge(OrderItem::getCreateTime, start) .select(OrderItem::getPrice, OrderItem::getCreateTime)); // 按天分组,计算平均值 Map<LocalDate, DoubleSummaryStatistics> stat = items.stream() .collect(Collectors.groupingBy( item -> item.getCreateTime().toLocalDate(), Collectors.summarizingDouble(item -> item.getPrice().doubleValue()))); return stat.entrySet().stream() .map(e -> new PriceTrendVO( e.getKey(), BigDecimal.valueOf(e.getValue().getAverage()).setScale(2, RoundingMode.HALF_UP))) .sorted(Comparator.comparing(PriceTrendVO::getDate)) .collect(Collectors.toList()); }这里的DoubleSummaryStatistics是Java标准库的统计工具,不需要引入额外依赖。但你注意,price必须取订单明细里的快照价格,而不是商品表里的当前价格,否则历史趋势就是“今天改价前/改价后”的假数据。这个细节写进论文里,能解释为什么设计上要存快照。
如果你还想更“亮点”一点,可以算“环比涨幅”:昨天的均价对比前天,如果涨幅超过阈值,生成一条营销提示,比如“该蔬菜近3天价格上涨15%,建议加大采购”。这就是一个可答辩的规则引擎,不需要机器学习。
4.2 购物车与结算:并发扣库存的三种写法
购物车有两种存储方式,一种是用浏览器的localStorage存,结算时一次性传到后端;一种是后端建购物车表。毕设建议用后者,因为这样能画出一个“新增购物车”的接口,论文里的接口列表长度会好看一些。不过要注意,后端购物车表的核心字段是user_id、goods_id、spec、quantity,并且要有唯一约束(user_id, goods_id, spec),这样同一个商品加购时就做数量累加,而不是插入多行。
结算时并发扣库存是个必考问题。有三种常见做法:
- 悲观锁:
SELECT * FROM stock WHERE goods_id=? FOR UPDATE,锁住行再检查库存,修改完再提交。 - 乐观锁:在库存表加
version字段,UPDATE stock SET quantity = quantity - ?, version = version + 1 WHERE goods_id = ? AND version = ?,变化为0则重试。 - 条件更新:就是前面第3.3节写的
quantity >= ?原生条件。
顺序推荐:条件更新 > 乐观锁 > 悲观锁。条件更新对单商品库存足够用,而且不需要额外字段。悲观锁虽然最直观,但在高并发下会把数据库的并发度拖没。答辩时如果能说出三种方案的取舍,老师会觉得你是真的做过,不是抄代码。
购物车结算还连接着一个“删除”语义:用户结算成功之后,要把对应的购物车记录删掉。注意这里一定是物理删除,而不是逻辑删除,因为购物车记录没有审计需求,留着反而是垃圾数据。
4.3 对接Vue前端:统一返回体与跨域配置
如果你用的是SpringBoot + Vue前后端分离,后端接口不能直接返回Map或者裸对象,要统一包一层返回体,比如{code:200, message:"success", data:...}。这样前端拦截器可以统一处理错误,不需要每个页面都写try catch。
我在这个项目里一般这样设计返回体:
@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.setCode(200); r.setMessage("success"); r.setData(data); return r; } public static <T> Result<T> error(Integer code, String message) { Result<T> r = new Result<>(); r.setCode(code); r.setMessage(message); return r; } }Controller里类似:
@PostMapping("/api/goods") public Result<Long> addGoods(@RequestBody Goods goods) { return Result.success(goodsService.createGoods(goods)); }同时把全局异常处理加上,用@RestControllerAdvice捕获业务异常、参数校验异常和其他异常,返回对应的错误码。这个设计能让前端拿到结构一致的数据,让你的项目看起来像企业级,而不是教学Demo。
跨域配置是Vue开发服务器的常见问题,你如果用了vue-cli或Vite,默认端口是8080或5173,后端端口是8088,那么浏览器会拦截跨域请求。常见做法是在SpringBoot后端里加一个CORS配置:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowCredentials(true) .maxAge(3600); } }这里要注意,allowedOrigins("*")和allowCredentials(true)是不能同时使用的,所以要用allowedOriginPatterns("*")。如果你用了JWT Token放在Header里,还需要额外放行Authorization头。这个坑非常经典,直接放到第5章讲。
5. 必踩的坑与排查:从SpringBoot版本到MyBatis映射
5.1 SpringBoot版本太高导致自动配置失效
现象:项目能启动,但DataSource没有自动创建,提示找不到SqlSessionFactory或者报Failed to configure a DataSource。
原因:很多人直接用了SpringBoot最新大版本,比如某个3.x版本,而后面的MyBatis Plus启动器还没有适配。mybatis-plus-boot-starter的旧版本使用了JavaEE相关的javax命名空间,SpringBoot 3.x里改成了jakarta,旧包启动时找不到类,自动配置就全部失效。这不是你写错代码,是生态没跟上。
解决:建议不要跟最新版本,直接锁定一个你同学或教程验证过的稳定版本组合,比如SpringBoot 2.7.x搭配MyBatis Plus 3.5.x。在pom.xml里明确版本号,不要用RELEASE或者SNAPSHOT。如果已经用了SpringBoot 3.x,也可以升级MyBatis Plus到3.4.x以上,但要看官方兼容表。另外注意:JDK版本也要匹配,SpringBoot 3.x强制JDK17,2.7.x支持JDK8,如果你用JDK8硬生生改了一个3.x项目,启动会直接类版本错误。
5.2 MyBatis的XML映射路径与驼峰命名问题
现象:接口只写了selectById,没有在Mapper里自定义SQL,但运行时报Invalid bound statement (not found)。或者查询出来的实体里createTime字段为null,而数据库里create_time有值。
原因:第一种是你在application.yml没有配置Mapper的XML扫描路径,或者Mapper接口和XML文件没有同名同包。第二种是MyBatis默认不开启驼峰映射。
解决:在配置文件里加上:
mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml type-aliases-package: com.example.farm.entity configuration: map-underscore-to-camel-case: true如果你用的全是BaseMapper方法,没有自定义XML,其实不用写mapper-locations。但为了将来扩展,建议把Mapper接口上的@Mapper注解加上,或者在启动类上写@MapperScan("com.example.farm.mapper")。这两个做法二选一即可。我习惯用@MapperScan,因为它能统一管理,而且不用每个Mapper都加注解。
5.3 事务不生效:自调用与代理的坑
现象:你在Service里的一个方法A调用了本类的另一个方法B,B上加了@Transactional,B执行时报异常时,数据没有回滚。
原因:@Transactional是通过SpringAOP动态代理实现的,只有通过代理对象调用方法才能进入切面。在本类中A直接调用B,走的是原生对象也就是this,不是代理对象,所以B的事务配置完全未被解析。这个问题面试也常问,毕设里你如果不小心写了自调用,答辩时被老师揪出来就得靠这个回答。
解决:拆成两个Service,把B方法放到另一个Service里互相调用。如果你非要在同一个类里实现,可以用ApplicationContext.getBean(Class)重新拿到代理对象,但不推荐,代码很丑。最干净的方案是“事务方法尽量放在外面一层,不在同类里套娃”。
5.4 定时任务在其他环境不跑:时区与线程池
现象:本地运行@Scheduled能执行,部署到服务器上怎么都不跑。检查日志也没报错。
原因:最大的可能是你的服务器系统时区和JVM默认时区不一致,导致cron表达式里写的是北京时间,但服务器跑的是UTC,时间对不上,任务就按UTC的0点去触发,看起来像没跑。还有一个可能是项目里有多个定时任务,某个任务内部出现异常没有catch,导致这个任务的线程卡死或者退出。
解决:在application.yml里固定时区:
spring: jackson: time-zone: GMT+8 task: scheduling: timezone: Asia/Shanghai定时任务方法入口要加try catch,记录日志后再继续,别让异常中断整个调度线程。另外,如果你用的是MySQL存储创建时间,也要确认连接URL上写了serverTimezone=Asia/Shanghai,否则日期差8小时,你的订单超时任务会把刚下的单当成已经超时。这个bug很隐蔽,但一旦发生,几乎无法通过常规测试发现。
5.5 论文查重与代码一致性的提醒
现象:论文写完了,代码也写完了,但答辩老师问某个类名,你在项目里找不到。
原因:代码是抄的教程,论文是自己翻译的,两张皮。SpringBoot项目的命名风格和模块结构在论文和代码里不一致,或者代码里根本没有论文里写的定时任务、事务机制。这个比技术错误还致命。解决其实很简单:打开论文的目录,对照项目里的包结构,逐字检查。论文里每个接口都要在代码里能找到Mapping路径,每个类名都要能在项目中找到文件,每个表的字段能在实体类里找到属性。这一步花1小时,能避免答辩时直接翻车。另一个常见做法是:把论文里的核心截图改成实际运行的界面,不要让截图里的按钮布局跟代码演示时对不上。
6. 收尾:答辩前如何验证与给系统加一个说服力十足的功能
答辩前两三天,不要再去加功能了,要做三件事:
第一,准备一套干净的演示数据。插入几个分类、十来个商品、一些库存,以及数条不同状态(待付款、已支付、已发货、已完成)的订单。注意演示数据里不要包含“测试”“aabb”这种词,用真实的农产品名称和价格,比如“红富士苹果 5.8元/斤”“云南蜜桔 3.5元/斤”。这样你给老师演示时,界面看起来像一个真实系统,而不是一张空畸形。
第二,把所有的日志级别调出来。在application.yml里设置:
logging: level: com.example.farm.mapper: debug这样你运行的时候能直接看到MyBatis生成的SQL。当老师问你“你怎么确认库存扣对了”,你可以一边演示一边指着控制台说“这里有一条原子更新的SQL”,说服力立刻拉满。
第三,给系统加一个“报表导出”或“健康检查”功能。如果时间不够,最少要把SpringBoot Actuator加进来,依赖只有一行:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>因为Actuator能暴露/actuator/health端点,你可以当场展示这个系统能自动监测自己的状态。答辩时你说“我的平台有健康检查接口”,比“我的平台有增删改查”高一个级别。
如果还有多余时间,我强烈建议你做一个“今日推荐”接口,它逻辑简单但能作为营销模块的入口:从热销商品的前10名里随机取3个,同时排除库存为0的商品。别小看这个功能,它把订单数据、库存数据和营销串在一个接口里,写进论文里就是“基于用户行为的推荐策略”。
我的个人习惯是,在答辩前一天晚上,把项目用Maven打成jar包,在本地命令行用java -jar farm-platform.jar启动一遍。为什么?因为很多人的毕设在IDEA里能跑,但打包后缺配置、缺静态资源,或者端口冲突,启动直接失败。你可能在IDEA里调了很久都没问题,但在干净环境里启动一次,就能暴露隐藏的依赖和配置问题。这步做完,心里就有底了。
希望这些方法和从第2章到第6章的代码思路,能帮你把毕设做成一个自己真正吃得透的完整项目,而不是一个能跑但说不清的空壳。祝你顺利。
本文还有配套的精品资源,点击获取