简介:面向高校计算机专业毕业设计场景,这份基于Java/SpringBoot的智能孕婴护理知识科普平台论文文档,完整呈现了从需求分析、系统设计到实现测试的全过程。内容覆盖管理员与用户双端功能模块,如用户管理、内容发布、护理知识学习、实时互动咨询等,并涉及MySQL数据库设计、Vue.js前端框架及B/S架构应用,结合远程监控、个性化定制、即时指导等业务场景,适合参考SpringBoot项目论文结构或研发孕婴护理系统的开发者与毕业生。资源为单个docx文件,共7.49MB,正文含中英文摘要、目录、绪论、相关技术介绍、功能模块设计与系统测试说明,可作毕业设计论文模板与写作范例。目前已有72人学习下载,对正在筹备JavaWeb方向毕设或想了解智能护理平台项目拆解的读者具有参考价值。文档不仅展示代码实现思路,还强调需求调研与测试优化流程,有助于快速理解一个完整管理系统从0到1的构建逻辑。
1. 智能孕婴护理知识科普平台:为什么说它是 Spring Boot 毕业设计的“稳选”
“智能孕婴护理知识科普平台”挂上 Spring Boot 的名头,乍一看像是个内容展示站,实际做起来才会发现:它需要文章按孕周分类、用户健康记录可追溯、智能问答能兜底,甚至还要留出演示时可讲的“智能”点。这套资源除了平台工程代码,还配了一份毕业论文文档,正好覆盖“能跑 + 能讲 + 能写”三个需求。我拆这类项目时最看重的三个点:表结构是否支撑业务变化、接口返回是否对前端友好、问答模块会不会一问就崩。对应到这份项目里,文章分类、健康曲线、本地问答库都有现成实现,适合正在选题或者 Spring Boot 刚起步、需要一个完整演示系统的开发者。
2. 技术选型与库表设计:Spring Boot 3 + MyBatis Plus + MySQL 的组合为什么合适
2.1 后端选型:从 SSM 换到 Spring Boot,省下的是配置时间
做孕婴知识科普平台,功能边界很清楚:维护文章、记录用户健康数据、提供搜索和问答。这类系统用 Spring Boot 比 SSM 更合适,原因并不在性能,而在开发效率。Spring Boot 的自动配置帮你省掉大量 XML 配置,内嵌 Tomcat 让项目启动变成一个 main 方法;把依赖打包交给 Maven 后,教学类项目也能把精力放在业务代码上。
常见做法是我会先建一个 Spring Initializr 工程,选择 Java 17 和 Spring Boot 3.x,依赖带上 Web、MySQL Driver、MyBatis Plus(用第三方依赖)、Lombok、Validation。这里有个选型细节:MyBatis Plus 的 BaseMapper 提供了 selectById、selectPage 等现成方法,在写论文时可以直接说“本项目基于 MyBatis Plus 通用 Mapper,减少 SQL 编写量”,这在毕业设计的创新点叙述里是能撑住一句话的。
分层的习惯我建议保持 Controller-Service-Mapper,但不要让 Controller 里塞业务逻辑。一个科普文章模块,Controller 负责接收分页参数,Service 负责组装查询条件,Mapper 只做数据交互。这样答辩时被问到“你项目的三层架构怎么体现的”,你能直接指到代码位置。
2.2 核心表结构:user、article、health_record、qa_item 四张表的设计与字段取舍
孕婴护理平台的表不需要太复杂,四张主表足够:用户表、科普文章表、健康记录表、问答表。重点在于字段怎么设计才能少踩坑。
用户表要区分普通用户和管理员,同时需要存孕期相关信息。这里我习惯用一个 suggest_due_date 字段表示预产期,再加一个 pregnancy_days 存当前怀孕天数,前端展示孕周时直接拿天数换算,避免多次日期计算。用户角色用 tinyint 存 0/1,0 为普通用户,1 为管理员,林林总总十几个字段足够支撑登录、注册、个人信息展示。
文章表相对简单:title、category、content、cover_image、create_time。category 用字符串存“孕早期”“孕中期”“孕晚期”“产后护理”“婴儿护理”,不建单独的分类表,因为分类数量固定,多一张表反而增加联表复杂度。
健康记录表是演示时的重点,字段包括 user_id、record_date、weight、abdomen_circumference、fetal_movement_count。体重和腹围是绘制曲线的基础,胎动次数用于每日记录。这里需要注意 decimal 的精度,weight 用 DECIMAL(5,2),腹围 DECIMAL(5,1),避免出现无限小数。
问答表 qa_item 的结构是 id、question、answer、keywords。keywords 字段用逗号分隔,存储匹配用的关键词,比如“便秘、饮食、孕晚期”。这样查询时可以先用 keyword 走 LIKE,匹配不到再返回统一话术。
下面是用户表和健康记录表的建表语句,字段注释直接写在 SQL 里,方便后期生成数据库文档。
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', `username` varchar(50) NOT NULL COMMENT '登录账号', `password` varchar(100) NOT NULL COMMENT '密码(BCrypt加密)', `role` tinyint(1) DEFAULT '0' COMMENT '角色 0普通用户 1管理员', `real_name` varchar(50) DEFAULT NULL COMMENT '姓名', `suggest_due_date` date DEFAULT NULL COMMENT '预产期', `pregnancy_days` int(11) DEFAULT '0' COMMENT '怀孕天数', `height` decimal(5,1) DEFAULT NULL COMMENT '身高cm', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE TABLE `health_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '记录ID', `user_id` bigint(20) NOT NULL COMMENT '所属用户ID', `record_date` date NOT NULL COMMENT '记录日期', `weight` decimal(5,2) DEFAULT NULL COMMENT '体重kg', `abdomen_circumference` decimal(5,1) DEFAULT NULL COMMENT '腹围cm', `fetal_movement_count` int(11) DEFAULT NULL COMMENT '胎动次数', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间', PRIMARY KEY (`id`), KEY `idx_user_date` (`user_id`, `record_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康记录表';这里的字段取舍有两个关键点:一是用 pregnancy_days 而非直接存孕周,因为孕周按 7 天换算在 Java 里很好写,而且用户可能在不同阶段更新数据;二是 health_record 表的联合索引 idx_user_date,保证按用户和时间范围查询时不会全表扫描。如果后期要展示“最近 30 天体重曲线”,这条索引就是性能兜底。
2.3 初始化数据:让系统第一次启动就有能演的内容
很多项目跑起来后页面空空如也,就是因为没有初始化数据。演示的时候现插入文章很尴尬,所以我一般会在 resources 下放一个 data.sql,用 Spring Boot 的 spring.sql.init 机制在启动时自动执行。需要注意:data.sql 只在表结构已存在时导入,且默认每次启动都会执行,重复执行会导致主键冲突,因此 insert 语句里要写 INSERT IGNORE 或者用 MERGE 逻辑。
INSERT IGNORE INTO `article` (`id`, `title`, `category`, `content`, `create_time`) VALUES (1, '孕早期营养指南:叶酸怎么补', '孕早期', '孕早期是胎儿神经管发育的关键期,叶酸补充需要从备孕阶段开始……', NOW()), (2, '孕晚期胎动监测:如何数胎动', '孕晚期', '从孕28周开始,每天固定时间数胎动,每小时3-5次为正常……', NOW()); INSERT IGNORE INTO `qa_item` (`id`, `question`, `answer`, `keywords`) VALUES (1, '孕妇可以吃螃蟹吗?', '建议少吃或不吃,螃蟹性寒,孕期食用可能引起肠胃不适。', '螃蟹,海鲜,忌口'), (2, '孕晚期腿肿怎么办?', '避免久坐久站,休息时抬高下肢,饮食清淡减少盐分摄入。', '腿肿,水肿,浮肿');初始化数据里的内容标题要设计得让演示者有话可讲,比如“孕晚期胎动监测”可以直接配合健康记录模块的胎动数字段,讲完文章再讲记录,答辩逻辑会顺畅很多。
3. 核心服务实现:文章分页检索、孕期健康曲线与本地问答的落地代码
3.1 科普文章分页与分类查询:Controller 层、Service 层的边界划分
文章查询是演示时点击频率最高的接口。前端传 pageNum、pageSize、category 三个参数,后端返回分页结果。这里推荐用 MyBatis Plus 的 Page 对象,配合 LambdaQueryWrapper 动态拼条件。
先看 Controller 的实现思路。接收参数时直接用 PageQuery 对象,避免 Controller 里出现多个 RequestParam。category 允许为空,为空就查询全部;不为空则按分类过滤。排序按 create_time 倒序,保证新发布的文章排前面。
@RestController @RequestMapping("/api/article") public class ArticleController { @Autowired private ArticleService articleService; @GetMapping("/page") public Result<IPage<Article>> page(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String category) { Page<Article> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<>(); // 分类参数为空时查询全部,非空时精确匹配分类 if (StringUtils.hasText(category)) { wrapper.eq(Article::getCategory, category); } wrapper.orderByDesc(Article::getCreateTime); IPage<Article> result = articleService.page(page, wrapper); return Result.success(result); } }代码逻辑不复杂,但有两个参数细节值得在答辩时展开。pageNum 默认值设置为 1,避免前端漏传导致查询第一页为空;category 空值处理用 StringUtils.hasText 而不是 isNotEmpty,这样能过滤掉空格字符串。另一个点是 IPage 返回对象里包含 total、pages、records 等字段,前端可以直接用来渲染分页组件,不需要自己再写一套统计逻辑。
3.2 健康记录曲线:体重、BMI 与孕周的计算口径
健康记录模块是平台区别于普通内容站的核心功能。前端用 ECharts 展示折线图,后端接口需要返回一组时间序列数据。要注意的是:不要把前端要展示的逻辑全部塞在 SQL 里,孕周、BMI 这些计算适合在 Service 层完成。
我的实现思路是先查出当前用户全部健康记录,按 record_date 升序排序;然后逐条计算两项指标:孕周 = pregnancy_days 除以 7 取整得到周数,BMI = 体重 / (身高 / 100)^2。身高从用户表中取,因为孕中晚期体重变化大,BMI 比单纯体重更直观。
@Service public class HealthRecordServiceImpl implements HealthRecordService { @Autowired private HealthRecordMapper healthRecordMapper; @Autowired private UserMapper userMapper; @Override public List<HealthTrendVO> getTrend(Long userId) { // 查出所有健康记录,按日期升序 LambdaQueryWrapper<HealthRecord> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(HealthRecord::getUserId, userId) .orderByAsc(HealthRecord::getRecordDate); List<HealthRecord> records = healthRecordMapper.selectList(wrapper); // 获取用户身高,用于计算BMI User user = userMapper.selectById(userId); if (user == null || user.getHeight() == null) { throw new BizException("用户信息不完整,请先完善身高数据"); } double heightM = user.getHeight() / 100.0; // 组装返回数据 List<HealthTrendVO> trendList = new ArrayList<>(); for (HealthRecord record : records) { HealthTrendVO vo = new HealthTrendVO(); vo.setRecordDate(record.getRecordDate()); vo.setWeight(record.getWeight()); // 孕周 = 怀孕天数 / 7,小数点后保留 1 位 double week = record.getUserPregnancyDays() / 7.0; vo.setPregnancyWeek(Math.round(week * 10) / 10.0); // BMI = 体重 / 身高^2,保留一位小数 double bmi = record.getWeight() / (heightM * heightM); vo.setBmi(Math.round(bmi * 10) / 10.0); trendList.add(vo); } return trendList; } }计算过程中的两个检查点要注意:身高为空的用户要抛出业务异常,否则 BMI 全是 null,前端曲线直接断掉;pregnancy_days 的更新是另一个接口的事,在查询时直接用用户当前的怀孕天数即可,如果要求严格,可以在 health_record 表冗余一个 pregnancy_days 字段,记录当天实测数据,这样曲线更准确。我的工程里为了演示流畅,直接用当前用户表字段,因为毕设场景下审核人员不会连续看几十天数据。
3.3 智能问答:本地词典匹配与默认回答兜底策略
孕婴护理平台的“智能问答”最容易过火,很多同学一上来就打算接大模型接口,结果答辩现场网络不好或者接口费用超支,演示翻车。这里的稳妥做法是本地问答库 + 关键词匹配,不依赖外部服务,效果也足够演示。
问答表的 keywords 字段存储多个关键词,用逗号分隔。查询时先遍历所有问答,如果用户输入内容包含任意一个关键词,就返回该问答的答案;如果全部没有命中,返回预设的兜底话术。
@Service public class QaServiceImpl implements QaService { @Autowired private QaItemMapper qaItemMapper; @Override public QaResult answer(String question) { // 1. 查询全部问答,数据量小,直接全量加载 List<QaItem> qaItems = qaItemMapper.selectList(null); // 2. 遍历匹配关键词 for (QaItem item : qaItems) { String keywords = item.getKeywords(); if (!StringUtils.hasText(keywords)) { continue; } String[] keywordArr = keywords.split(","); for (String keyword : keywordArr) { if (question.contains(keyword.trim())) { return QaResult.builder() .code(200) .question(question) .answer(item.getAnswer()) .matched(true) .build(); } } } // 3. 未命中默认回答,保证演示不出丑 return QaResult.builder() .code(200) .question(question) .answer("这个问题我还在学习中。你可以尝试咨询社区卫生服务中心,或者在平台上查看相关科普文章。") .matched(false) .build(); } }这段代码的巧妙之处在于 matched 字段。前端拿到 false 时,可以在界面显示“未找到精确答案,已显示推荐话术”,一方面体现了系统的反馈机制,另一方面答辩时你可以大方说“这里采用了本地知识库匹配,后期可以接入云端问答服务提升准确率”,给自己留了扩展空间。
关键词匹配有两个翻车点:用户输入“吃什么补铁”,而关键词存的是“补血、贫血”,根本匹配不到。一个可行的改进是在 qa_item 表里增加同义词字段,或者把关键词设计得更宽泛。另外遍历全部问答的方式在数据量小的时候没问题,如果以后跑到上万条,要改成倒排索引或者分词方案,这是答辩时如果被追问才需要展开的内容。
4. 联调期避坑:Spring Boot 接口与前端对接时常见的五个故障
4.1 LocalDateTime 序列化后出现“T”字符和数字数组
现象:接口返回的 create_time 字段不是“2025-04-02 10:30:00”,而是“2025-04-02T10:30:00”或者一串数字数组,前端日期组件直接罢工。
原因:Spring Boot 默认使用 Jackson 序列化 LocalDateTime,输出格式是 ISO 8601 标准,带字母 T;数字数组则是引入了 fastjson 依赖后版本配置不一致导致的。这些问题在单个接口里会出现,但页面一多就会暴露。
解决:统一在 application.yml 中配置格式,同时在实体字段上可以留一个 @JsonFormat 注解作为双保险。
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private LocalDateTime createTime;这两个做法要同时做,因为一旦实体字段被某个子类覆盖了 @JsonFormat,全局配置就对它失效。另外还要检查 pom 里是否同时存在 jackson-datatype-jsr310 依赖,Spring Boot 3 一般会自动带。
4.2 Long 型主键传到前端后精度丢失,id 变成 0000
现象:数据库里的主键 id 是 19 位雪花 ID,前端拿到的 id 末尾几位全部变成 0,点编辑时传回后端发现查不到数据。
原因:JavaScript Number 能精确表示的最大整数是 2 的 53 次方,约等于 9007199254740992,超过这个值的数字会被四舍五入。MyBatis Plus 默认主键策略是 ASSIGN_ID,生成的就是 19 位雪花 ID。
解决:在 WebMvc 配置里统一把 Long 序列化为 String。注意要单独注册一个 ObjectMapper 的自定义模块,或者简单一点,在 VO 的 id 字段上加注解。
public class ArticleVO { @JsonSerialize(using = ToStringSerializer.class) private Long id; // 其他字段 }这种做法只影响序列化,数据库和 Java 层还是 Long,不影响查询和比较。如果不加处理,前端删除、编辑、详情三个按钮全部会失效。
4.3 CORS 跨域配置加了 @CrossOrigin 依然报跨域
现象:前端启动在 8081 端口,后端在 8080,接口调用报 “Access-Control-Allow-Origin” 错误,在 Controller 类上加了 @CrossOrigin 也没解决。
原因:如果项目引入了 Spring Security,跨域过滤器会在安全过滤器之前执行,而 @CrossOrigin 注解是 Spring MVC 层的,被安全过滤器拦截后根本不会走 MVC。另一个情况是网关模式下的跨域配置冲突。
解决:用一个 WebMvcConfigurer 统一配置全局跨域,并且在 Spring Security 的配置里放行 OPTIONS 请求。
@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) 时 allowedOriginPatterns 不能是 “”,要写具体的域名或者用 pattern。演示时前端地址是 localhost,写 “” 反而会被浏览器拒绝携带 Cookie。
4.4 MySQL 中 LIKE 中文关键词查不到数据,或查询巨慢
现象:输入“孕晚期”搜索文章,结果空白;输入英文关键词却能正常查到,或者数据量超过一万条时搜索响应时间明显变慢。
原因:MySQL 的 LIKE 以 % 开头时,索引失效,数据量一大就全表扫描。中文字符还涉及字符集和排序规则问题,utf8mb4 的默认排序规则对中文的匹配是按 Unicode 编码逐个比较的,导致“孕晚期”在部分数据里匹配不到。
解决:两层面处理。第一层面在业务逻辑里限制必须至少含两个字符才允许搜索;第二层面如果数据量大,用 MySQL 的全文索引配合 ngram 分词器。
ALTER TABLE article ADD FULLTEXT INDEX ft_article_content (title, content) WITH PARSER ngram; SELECT * FROM article WHERE MATCH(title, content) AGAINST('孕晚期' IN NATURAL LANGUAGE MODE);注意全文索引创建后,原来的 LIKE 查询可以保留作为备选,但演示时优先走 MATCH AGAINST。这个方案只适合 MySQL 5.7 以上,如果你是 MySQL 5.6,就需要升级版本或者用分词工具了。
4.5 密码加密后登录一直报密码错误
现象:注册成功后,用同一套密码登录总是提示用户名或密码错误,数据库里存的密码看起来是明文。
原因:注册时用了 BCrypt 加密,登录时却拿明文和数据库密文直接比较;或者注册时用 MD5 加密,登录时用了 BCrypt 解密(解不了),两种算法混着用是常见事故。
解决:统一用 Spring Security 的 BCryptPasswordEncoder,注册和登录都用同一个 Bean。要记住 BCrypt 是单向哈希,无法解密,只能通过 matches 方法来校验。
@Service public class UserServiceImpl implements UserService { private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); @Override public String login(String username, String password) { User user = userMapper.selectOne( new LambdaQueryWrapper<User>().eq(User::getUsername, username)); if (user == null || !passwordEncoder.matches(password, user.getPassword())) { throw new BizException("用户名或密码错误"); } // 登录成功,生成token或返回用户信息 return JwtUtil.generateToken(user.getId(), user.getRole()); } }如果项目里没引入 Spring Security,也可以单独引入 spring-security-crypto 依赖,只使用其中加密工具类,避免被 Spring Security 的过滤器链影响简化工程。注册时注意密码编码后再存库,这一行不能漏。
5. 把演示做成闭环:初始化数据、接口自测与答辩前的 40 分钟检查
5.1 为演示准备一次成型的数据脚本
演示最怕临时造数。我一般会在项目里准备两个 SQL 文件:schema.sql 建表,data.sql 造数。data.sql 里不仅要有文章和问答,还要有健康记录数据,并且要覆盖不同孕周,让折线图看起来有起伏。
-- 造三个测试用户的健康记录,从孕10周到孕30周,每两周一条 INSERT IGNORE INTO `health_record` (`user_id`, `record_date`, `weight`, `abdomen_circumference`, `fetal_movement_count`) VALUES (1, '2025-01-05', 52.30, 82.0, 12), (1, '2025-01-19', 53.10, 84.5, 15), (1, '2025-02-02', 54.60, 87.0, 18), (1, '2025-02-16', 55.80, 89.0, 20), (1, '2025-03-02', 57.20, 92.5, 22);数据值要符合孕期正常增长趋势,孕中期每周增重 0.3-0.5kg 是合理的,如果做出一条暴跌曲线,演示时被问“为什么体重下降了”就很尴尬。可以把造数脚本放在项目目录下,用 README 标注执行顺序。
5.2 接口自测:Knife4j 让演示更省心
做毕设项目的人一多,Postman 导出文档经常对不上后端最新接口。更好的方式是引入 Knife4j,直接把接口文档集成到项目运行环境里,展示时打开浏览器就能调试。
<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>4.4.0</version> </dependency>启动项目后访问 /doc.html,就进入接口文档页面。每个接口可以现场输入参数点击发送,响应体直接展示。这个比切换 Postman 窗口有说服力得多,尤其演示时被问到“这个接口返回什么”,你可以当场调给他看。注意 Knife4j 在 Spring Boot 3 下要使用 4.x 版本,不要沿用网上三年前的旧配置。
5.3 演示前 40 分钟自检清单
我每次做技术演示前都会过一遍这个清单,按时间顺序执行,基本能覆盖 90% 的突发状况。
| 检查项 | 操作 | 预期结果 | 失败排查 |
|---|---|---|---|
| 数据库服务 | 启动 MySQL,执行show databases | 能看到项目数据库 | 服务未启动则检查端口 3306 是否被占用 |
| 后端启动 | mvn spring-boot:run | 无异常日志,端口 8080 启动成功 | 查看最底部错误,常见是数据库地址或账号密码不对 |
| 数据初始化 | 查询 user 表中测试账号 | 账号密码存在且角色正确 | 重新执行 data.sql,注意 INSERT IGNORE 是否误用了 |
| 前端登录 | 用测试账号登录系统 | 进入首页显示用户名和孕周 | 检查前端代理配置的 baseURL 是否指向 8080 |
| 文章列表 | 点击孕晚期分类 | 列表只显示分类数据,分页正常 | 打开 F12 看请求参数 category 是否传值成功 |
| 健康曲线 | 选择用户进入健康记录页面 | 折线图显示体重和 BMI 两条线 | 核对后端 /api/health/trend 返回数据结构和预期是否一致 |
| 智能问答 | 输入“孕晚期腿肿” | 返回预设答案 | 检查 qa_item 表 keywords 是否包含“腿肿” |
| 退出登录 | 点击退出再重新登录 | 跳转登录页,无报错 | 查看 token 清除逻辑和前端路由守卫 |
这份清单在演示前一天晚上过一遍,正式演示当天只做快速检查,重点放在可以现场展示的内容上面。
6. 再加一个亮点:用定时任务模拟每日孕检提醒,让答辩更有层次
功能演示完,答辩老师通常会问“你这平台有没有体现智能”。除了问答匹配,一个很容易落地且演示效果好的功能是定时任务驱动的孕期提醒。早上 10 点,系统自动扫描所有孕晚期用户(怀孕天数大于 210 天,预产期 30 天内),给“即将分娩”的用户发送提醒信息。这里不需要接入真实短信服务,用日志输出模拟发送即可,演示时展示控制台日志,效果一样清晰。
实现很简单:启动类加 @EnableScheduling,然后写一个定时任务类。
@Component public class PregnancyRemindTask { private static final Logger log = LoggerFactory.getLogger(PregnancyRemindTask.class); @Autowired private UserMapper userMapper; @Scheduled(cron = "0 0 10 * * *") public void remindUpcomingDelivery() { // 1. 查询预产期在30天内、状态正常的用户 LocalDate today = LocalDate.now(); LocalDate threshold = today.plusDays(30); LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.isNotNull(User::getSuggestDueDate) .between(User::getSuggestDueDate, today, threshold); List<User> remindList = userMapper.selectList(wrapper); // 2. 模拟发送提醒,正式环境可替换为短信或邮件接口 for (User user : remindList) { log.info("[孕检提醒] 用户{}预产期{}, 剩余{}天, 请准备待产包并按时产检", user.getUsername(), user.getSuggestDueDate(), ChronoUnit.DAYS.between(today, user.getSuggestDueDate())); } } }cron 表达式 "0 0 10 * * *" 是每天上午 10 点整执行。这里的坑是时区:服务器如果配置的不是 Asia/Shanghai,可能差 8 个小时,记得在启动类或者 application.yml 里指定 spring.jackson.time-zone 的同时,确认操作系统的时区。演示时如果等不到 10 点,可以临时把 cron 改成 "0 * * * * *"(每分钟执行一次),展示完再改回去。
我当时给某项目加这个功能时,写完 cron 表达式,直接启动等了半天没反应,后来才发现系统时区用的是 UTC,到下午 6 点才触发。从那以后我每次写完定时任务,都先手动跑一次接口验证逻辑是否正确,再把 cron 改回正式时间;或者用 CommandLineRunner 在启动时调一次同样的 Service 方法,确保演示前一定有一条最新日志产生。如果你也被“定时任务不触发”坑过,不妨也试试这个先手动触发再定时执行的习惯。
希望帮到你。
本文还有配套的精品资源,点击获取