我去年帮朋友做了一个基于SpringBoot和Vue的孕婴护理平台,从需求梳理到上线跑通,前后花了三周。做完之后最深的感受是:这类项目真正难的从来不是技术,而是把"护理"这两个字落到具体的业务逻辑里。今天就把我实际动手过程中的设计思路、核心代码、踩坑记录全写下来,给正在做毕设或者想接这类项目的同学做个参考。
这个平台本质上解决的是这样一个问题:孕产妇和新生儿父母需要一套工具,帮她们管理产检记录、跟踪孕周变化、按时获得提醒、按阶段获取护理知识,而不是像大多数管理系统那样堆一堆用不上的功能。下面我会按照"需求定位、技术选型、后端设计、前端实现、部署验收"这条线完整拆解一遍。
1. 孕婴护理平台:先想明白要做什么,再动工写代码
1.1 这类项目最常见的误区:把护理平台做成了后台管理系统
我看到过太多同类项目,标题叫"智能孕婴护理平台",点进去一看全是后台管理功能:用户列表、角色管理、菜单管理、权限配置……整得跟一个通用的管理后台脚手架一样。孕婴护理领域最核心的东西——孕期档案、产检记录、孕周计算、护理知识推荐、提醒任务——反而做得非常敷衍。
为什么会出现这种情况?核心原因是很多开发者拿到题目后,第一反应是"我有现成的管理系统模板",于是直接套上去,再往里面塞几个孕婴相关的表就算完事。这样的项目答辩时很容易被问住:"你的智能化体现在哪?"答不上来。
我建议拿到这类题目后,先做一件事:把"护理"这个词拆开。护理不是管理,它包含的是持续关注、按时提醒、阶段适配、知识支持。对应到系统功能上,至少要有:孕妇档案管理、产检数据记录与趋势分析、按孕周自动计算的提醒任务、按孕周和标签匹配的知识内容推荐。想清楚了这些,后面的表结构设计和接口设计才会有方向。
1.2 从需求里拆解出来的核心业务闭环
我把核心业务闭环梳理成了一条主线:
孕妇注册登录后,先填写个人档案,包括末次月经日期(LMP)、预产期、孕前体重、身高、过敏史等。系统拿到末次月经日期后,自动计算当前孕周。接着,孕妇每次产检后录入体重、腹围、胎心率等数据,系统把这些数据按时间排列,生成趋势曲线,并和医学参考范围做对比。同时,系统根据当前孕周和档案里的标签,在知识库里匹配对应的护理文章推送给用户;另外有一个定时任务,每天扫描一遍档案,提前生成产检提醒、营养补充提醒等任务。
管理员这边的职责比较简单:维护知识文章,审核用户产生的记录(如果有社区功能的话),查看系统运行情况。
我把这个闭环拆成的核心模块如下:
- 用户模块:注册、登录、JWT鉴权、角色区分
- 档案模块:孕妇基本信息、孕周计算、档案编辑
- 产检模块:产检记录增删改查、趋势图表数据聚合
- 提醒模块:基于孕周规则的任务生成、提醒列表、标记完成
- 知识模块:文章分类、标签体系、按孕周匹配推荐
如果按这个列表去设计接口,每个模块的边界非常清楚,写代码时不会东一榔头西一棒子。这算是我给所有做类似系统的人的第一个建议:先画业务闭环图,再写代码。这个图不用很正式,你自己能看懂就行,但它能帮你省掉后面改表的很多时间。
2. 技术选型:为什么是SpringBoot + Vue这套组合
2.1 后端为什么选SpringBoot
这个问题我几乎每次都会被问到,尤其是有同学纠结要不要用SSH(Spring + Struts + Hibernate)或者直接用Servlet写。我的答案很简单:SpringBoot就是目前Java做中小型业务系统最务实的选择,没有之一。
SpringBoot的核心价值在于"约定大于配置"。对于孕婴护理平台这种业务逻辑以CRUD为主、附带一些定时任务和推荐算法的中型项目,SpringBoot把大量繁琐的配置都自动处理掉了,你只需要关注业务代码本身。它和MyBatis-Plus配合起来,单表CRUD几乎不用写SQL,开发速度会快很多。
我的建议版本组合是这样的(这个组合我实测非常稳):
| 组件 | 版本 | 说明 |
|---|---|---|
| JDK | 8 | 最稳妥,SpringBoot 2.x全家桶都支持 |
| SpringBoot | 2.7.x | 稳定版,教程多,坑少 |
| MyBatis-Plus | 3.5.x | 单表CRUD神器,分页插件好用 |
| MySQL | 8.0 | 字符集用utf8mb4 |
| JJWT | 0.9.x | JWT工具包,代码简单 |
| Lombok | 1.18.x | 减少实体类样板代码 |
这里要特别提醒一句:SpringBoot 3.x虽然已经出了很久,但它要求JDK17,而且javax包名改成了jakarta,网上很多老教程直接抄会编译不过去。如果你不是对SpringBoot 3特别熟悉,做这类项目直接用2.7.x + JDK8最省心。我见过太多同学卡在"javax.servlet不存在"这种问题上,其实就是版本不匹配导致的。
pom.xml核心依赖大致长这样:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>2.2 前端为什么选Vue
前端我选了Vue 3 + Vite + Element Plus + Pinia + Vue Router + Axios这套组合。Vue在国内的社区活跃度很高,中文资料多,遇到问题很容易搜到答案。Element Plus的组件覆盖了表单、表格、弹窗、日历、上传等常见场景,做管理后台和业务页面效率极高,不需要自己从头写UI组件。
我建议的package.json关键依赖:
{ "dependencies": { "vue": "^3.3.0", "vue-router": "^4.2.0", "pinia": "^2.1.0", "element-plus": "^2.4.0", "axios": "^1.4.0", "echarts": "^5.4.0", "dayjs": "^1.11.0" }, "devDependencies": { "vite": "^4.3.0", "@vitejs/plugin-vue": "^4.2.0" } }这里有一个很多人忽略的问题:Node版本。Vite 4要求Node 14.18+,但Node 20以上的某些版本和Vite 4又有兼容性问题。我实测下来Node 16或18最稳。如果你用nvm管理Node版本,建议切到16.x或者18.x再跑npm install,能省掉一堆莫名其妙的报错。
2.3 数据库与中间件:够用就行,别过度设计
很多人在技术选型时会纠结:要不要上Redis?要不要用ElasticSearch?要不要搞消息队列?
我的看法很直接:如果是做项目、做毕设,一个MySQL就够用了。Redis在你的业务没有明显热点缓存需求之前,加了只会增加复杂度。孕婴护理平台的知识推荐,本质上就是按标签和孕周SQL查询,数据量几千篇撑死了,一个带索引的MySQL查询毫秒级返回,根本不需要ElasticSearch。
那什么时候需要加Redis?我想到两个场景:一是登录验证码存缓存,二是首页热门文章缓存。这两个场景用Redis确实更优雅,但如果你不想引入额外依赖,验证码也可以用数据库表实现。我的建议是:把核心功能跑通后再考虑加中间件,而不是一开始就上一堆组件,最后连数据都没填几条。
3. 后端核心设计:从数据表到接口实现
3.1 数据库表设计:抓住五个关键表就够了
我设计的表结构,核心就是五张表:用户表、孕妇档案表、产检记录表、提醒任务表、知识文章表。另外加一张文章标签表(或者文章里直接存标签ID列表,看你要不要做筛选)。
我把每张表的关键字段列出来,这些字段都是实际项目里会用到的最小集,可以直接照着建表:
| 表名 | 关键字段 | 说明 |
|---|---|---|
| user | id, username, password, role, nickname, avatar, create_time | role区分ADMIN/USER |
| pregnant_profile | id, user_id, lmp_date, due_date, pre_pregnancy_weight, height, allergy_history, current_week, week_updated_date | current_week是冗余字段,加快查询 |
| checkup_record | id, user_id, checkup_date, weight, abdominal_circumference, fetal_heart_rate, blood_pressure, note | 每次产检一条记录 |
| remind_task | id, user_id, task_date, task_type, task_title, task_content, status, create_time | task_type区分产检/营养/运动 |
| knowledge_article | id, title, content, cover, week_start, week_end, tags, view_count, status | week_start/week_end是适配的孕周范围 |
这里有个设计细节值得展开说:current_week为什么要冗余存储?因为孕周是根据末次月经日期动态算出来的,如果每次都实时算,那在列表页、首页、推荐模块都要写一遍计算逻辑,而且数据库没法直接按孕周筛选。我选择在更新档案时顺便算出当前孕周存到字段里,每天定时任务再统一更新一次。这样查询时直接按current_week过滤,不需要在SQL里写复杂的日期函数。
另一个需要注意的点是:体重、腹围这些数值字段要设计好精度。体重我用DECIMAL(5,1),腹围DECIMAL(5,1),胎心率用INT就行。不要用DOUBLE,浮点计算在数据库里容易出精度问题。
3.2 项目分包结构与启动配置
后端项目结构我习惯这样分:
src/main/java/com/example/pregnancy/ ├── controller/ # 接口层 ├── service/ # 业务逻辑层 ├── mapper/ # MyBatis-Plus mapper接口 ├── entity/ # 数据库实体类 ├── dto/ # 请求/响应对象 ├── config/ # 配置类(跨域、拦截器、自动填充) ├── common/ # 工具类、统一返回结果、异常处理 └── PregnancyApplication.java这种包结构是我做过很多项目后固定下来的习惯。核心原则是:controller只做参数接收和结果返回,业务逻辑全部放service,mapper只跟数据库打交道。不要图省事把业务逻辑全写在controller里,后面改需求或者排查问题时你会非常痛苦。
application.yml里最值得注意的有三个地方:数据库连接的时区、MyBatis-Plus的日志输出、Jackson的日期格式。
spring: datasource: url: jdbc:mysql://localhost:3306/pregnancy?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai关于字符集多说一句:数据库连接串里的characterEncoding=utf8mb4,和建库时的DEFAULT CHARSET=utf8mb4必须一致,否则存emoji或者生僻字会出现乱码。孕婴护理平台这种场景,用户填写的备注里经常有特殊字符,用utf8mb4准没错。
3.3 核心接口的设计逻辑
我实际实现的接口里,下面这几个是最核心的,把它们的逻辑捋清楚了,整个后端就完成了一大半。
登录接口
@PostMapping("/api/auth/login") public Result login(@RequestBody LoginDTO dto) { User user = userService.lambdaQuery() .eq(User::getUsername, dto.getUsername()) .one(); if (user == null || !BCrypt.checkpw(dto.getPassword(), user.getPassword())) { return Result.error("用户名或密码错误"); } String token = JwtUtil.generateToken(user.getId(), user.getRole()); return Result.success(new LoginVO(token, user.getNickname(), user.getRole())); }密码存库前用BCrypt加密,登录时用BCrypt.checkpw校验。这是安全底线,不要用MD5,MD5在现在来说已经非常容易被彩虹表撞库了。
创建档案并计算孕周
孕周计算是这个项目的标尺,所有智能推荐和提醒都依赖它。计算规则是:从末次月经第一天算起,到今天为止的总天数除以7,向下取整。预产期是末次月经月份减3、日期加7(年份相应调整)。
public int calculateWeek(LocalDate lmpDate) { long days = ChronoUnit.DAYS.between(lmpDate, LocalDate.now()); int week = (int) (days / 7); return Math.max(week, 0); } public LocalDate calculateDueDate(LocalDate lmpDate) { return lmpDate.minusMonths(3).plusDays(7); }这里有个边界情况要处理:如果用户填的末次月经日期是未来时间,或者怀孕超过42周还没更新,都要做校验。我在前端加了一层日期范围限制,后端接口里也做了二次判断。不要指望前端帮你拦截所有非法数据,后端必须有校验。
产检趋势接口
这个接口供前端画ECharts折线图用。返回的数据结构是每个日期的体重和腹围值:
@GetMapping("/api/checkup/trend") public Result getTrend(@RequestParam Long userId) { List<CheckupRecord> records = checkupRecordService.lambdaQuery() .eq(CheckupRecord::getUserId, userId) .orderByAsc(CheckupRecord::getCheckupDate) .list(); List<String> dates = records.stream().map(r -> r.getCheckupDate().toString()).collect(...); List<BigDecimal> weights = records.stream().map(CheckupRecord::getWeight).collect(...); return Result.success(new TrendVO(dates, weights)); }前端拿到这个结构,直接让ECharts绑定两个数组就能画出趋势图,不需要前端做二次加工,减少出错可能。
3.4 定时提醒与智能推荐的实现逻辑
提醒模块的设计思路是:不搞复杂的规则引擎,就用一个每天凌晨跑的定时任务,扫描所有孕妇档案,根据当前孕周判断需要生成哪些提醒任务。
我用的Spring自带的@Scheduled,没有引Quartz,因为这种按天扫表的场景定时效果完全够用:
@Component public class RemindTaskScheduler { @Scheduled(cron = "0 30 2 * * ?") public void generateDailyTasks() { List<PregnantProfile> profiles = pregnantProfileService.list(); for (PregnantProfile profile : profiles) { int week = profile.getCurrentWeek(); // 按孕周规则生成提醒 if (week >= 11 && week <= 13) { createTask(profile.getUserId(), "NT产检提醒", "当前孕周" + week + ",建议预约NT检查", getToday()); } if (week >= 16 && week <= 19) { createTask(profile.getUserId(), "唐筛产检提醒", "当前孕周" + week + ",建议进行唐氏筛查", getToday()); } if (week >= 24) { createTask(profile.getUserId(), "补钙提醒", "孕晚期注意补钙,每天600mg", getToday()); } // 更多规则自行添加 } } }生成任务时要注意幂等:同一个用户同一天同一类型不能生成重复任务。我在remind_task表加了一个唯一索引(user_id, task_date, task_type),插入时捕获DuplicateKeyException,这样定时任务重复执行也不会产生脏数据。
知识推荐的实现更简单:给每篇文章打一个孕周适用范围(比如week_start=20, week_end=24),用户请求推荐时按当前孕周查:
@GetMapping("/api/knowledge/recommend") public Result recommend(@RequestParam Long userId, @RequestParam int week) { List<KnowledgeArticle> articles = knowledgeArticleService.lambdaQuery() .le(KnowledgeArticle::getWeekStart, week) .ge(KnowledgeArticle::getWeekEnd, week) .eq(KnowledgeArticle::getStatus, 1) .orderByDesc(KnowledgeArticle::getViewCount) .last("limit 10") .list(); return Result.success(articles); }这种设计朴素但实用。你可以在前端把week参数换成当前用户的孕周,用户每次打开知识页看到的就是当前阶段最需要的护理内容。如果后面想做得更智能,可以加标签匹配和用户行为反馈的分,但那是锦上添花,先把按孕周适配这条主干跑通再说。
4. 前端Vue架构与核心页面实现
4.1 目录结构与路由权限设计
前端项目结构我这样组织:
src/ ├── api/ # 所有接口请求封装 ├── components/ # 公共组件 ├── views/ # 页面组件 │ ├── dashboard/ # 首页仪表盘 │ ├── profile/ # 档案管理 │ ├── checkup/ # 产检记录 │ ├── remind/ # 提醒日历 │ ├── knowledge/ # 知识库 │ └── login/ # 登录页 ├── router/ # 路由配置 ├── store/ # Pinia状态 ├── utils/ # 工具函数 └── App.vue路由这块,我用了两种方式结合:静态路由放公共页面(登录页、首页),需要权限的页面在路由守卫里做判断。简单说就是:用户没登录时访问任何业务页面,都重定向到登录页;登录后如果是普通用户,能看到自己的档案、产检、提醒、知识页面;管理员多一个知识管理和用户管理的入口。
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path === '/login') { next() } else if (!token) { next('/login') } else { next() } })如果你还要做角色权限细分,可以在meta里标记这个路由需要的角色,然后在守卫里用角色判断一下。对这类项目来说,这个量级的权限控制完全够了,不需要引入复杂的动态路由方案。
4.2 API请求封装与状态管理
axios封装是我每次写前端项目都要做的事。核心目的是统一处理token、统一处理错误码、让接口调用代码尽量简洁。
// src/utils/request.js import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = 'Bearer ' + token } return config }) request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.msg || '请求失败') return Promise.reject(new Error(res.msg)) } return res }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') router.push('/login') } ElMessage.error(error.message || '网络异常') return Promise.reject(error) } ) export default requestPinia的store我主要存用户信息和token。注意token要同步放到localStorage里,因为Pinia是内存态,刷新页面就丢了。
4.3 核心页面的实现重点
首页仪表盘:进入后展示当前孕周(大数字)、预产期倒计时、今天的提醒任务数量、最近一次产检的信息摘要。这些内容基本都是后端接口直接返回聚合数据,前端只需要排布展示。孕周数字用今天日期和LMP实时算,我为了性能考虑也直接使用后端返回的currentWeek。
体重趋势页:这是整个平台视觉上最像"智能"的页面。用ECharts折线图展示每次产检的体重记录,再加一条参考范围的上限和下限。参考范围怎么定?可以用BMI对应的孕期增重范围,或者简单地画一个上下浮动区间。这个页面不需要很复杂,但图表做出来效果好,演示时观感非常好。
提醒日历页:按月展示提醒任务,这个用Element Plus的日历组件改造即可,标有提醒任务的日期打上圆点标记,点开看当天任务列表。做这个页面的价值是让用户对接下来的产检安排一目了然。
知识库页:左侧是孕周筛选条件,右侧是文章列表。用户也可以直接搜索标题或标签。文章详情页用v-html渲染富文本内容。这里要注意一点:后端返回的文章内容如果是富文本HTML,前端必须注意XSS风险。我的做法是保存文章时不接受HTML,只接受纯文本+换行格式化,或者用第三方库对HTML做清洗。
4.4 前后端联调中的几个坑
第一个坑是跨域。Vite开发服务器默认跑在5173端口,后端跑在8080端口,直接请求必然跨域。我用了两个方案双保险:前端在Vite配置文件里设置proxy代理,把/api开头的请求转发到后端;后端同时配置CorsConfig允许跨域。
// vite.config.js export default { server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }第二个坑是日期格式。前端传给后端的日期字段,如果用了new Date()会带时分秒,和LocalDate格式不匹配。我统一用dayjs格式化后再传,后端Jackson也配置了yyyy-MM-dd的格式。前后端日期格式必须提前商量好,这是联调时最容易拉锯的问题。
第三个坑是文件上传大小。知识库需要传封面图,SpringBoot默认单文件1MB限制,如果上传大图会直接报错。我在application.yml里做了调整:
spring: servlet: multipart: max-file-size: 10MB max-request-size: 10MB5. 直接决定项目成败的细节:安全、一致性与性能
5.1 登录鉴权与密码安全
JWT的方案我在上面已经提过,这里展开讲讲完整的链路。
用户登录成功后,后端生成一个有效期24小时的JWT,返回给前端。前端存到localStorage,在每个请求头里带上Authorization: Bearer token。后端通过拦截器统一解析token,把用户信息放到ThreadLocal里,方便后续接口取当前用户ID和角色。
拦截器里要做两件事:白名单放行(比如登录接口、注册接口、文章列表接口不需要token),其余接口校验token有效性。校验失败统一返回401,前端响应拦截器看到401就清token跳登录页。
@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String auth = request.getHeader("Authorization"); if (auth != null && auth.startsWith("Bearer ")) { String token = auth.substring(7); try { Long userId = JwtUtil.parseToken(token); UserContext.set(userId); return true; } catch (Exception e) { // token无效 } } response.setStatus(401); return false; } }关于密码,我前端注册时也建议不要明文传输。简单做法是前端用SHA-256哈希再传到后端,后端再用BCrypt加密存储。注意,SHA-256在前端传输并不能防中间人攻击,但至少能防止日志里出现明文密码。这个安全度对这类项目已经够用了。
5.2 数据一致性:并发场景怎么防重复
这类项目虽然并发量不高,但有两个场景容易出数据问题,我实际开发时都遇到了。
场景一是用户快速双击提交产检记录,导致同一记录插入两条。解决办法:前端按钮提交后置灰,后端在数据层面加约束。我在checkup_record表加了(user_id, checkup_date)唯一索引,如果同一天重复插入,数据库直接报错,后端捕获异常返回友好提示。
场景二是定时任务生成提醒时,如果服务器做了集群部署或者任务意外重跑,会产生重复提醒。这个我前面提到过,用唯一索引+捕获DuplicateKeyException解决。这里的关键哲学是:永远不要依赖代码层面的先查再插来判断是否存在,两个请求之间有时间窗,插入时让数据库来兜底最保险。
事务管理方面,涉及多表更新时要用@Transactional。比如创建档案时要同时更新user表和pregnant_profile表,一个失败另一个必须回滚。注意@Transactional默认只回滚RuntimeException,如果方法里抛了受检异常是不会回滚的,需要指定rollbackFor = Exception.class。
@Transactional(rollbackFor = Exception.class) public void createProfile(ProfileDTO dto) { // 更新用户信息 // 插入档案信息 }5.3 性能优化:什么时候需要加缓存
这种体量的项目,性能问题往往出在SQL上,而不是系统层面。
我建议优先做三件事,成本低收益高。第一,给所有表的逻辑外键字段加索引——user_id、task_date、week_start、week_end这些经常出现在WHERE条件里的字段都要有索引。第二,列表接口一定要分页,MyBatis-Plus的Page对象用法很简单,不要一次性返回全表数据让前端自己翻。第三,知识文章列表只查必要字段,列表页不返回content大字段,详情接口再单独返回全文,这样列表接口会明显变快。
做完这三步,知识列表在万级数据量下都能毫秒级返回。这时候Redis加不加都不影响体验。如果一定要加,我建议先做查询频率最高的首页聚合数据和热门知识列表的缓存,设置5分钟过期时间,续期策略用被动刷新即可。不要试图一开始就设计完美的缓存架构,先跑通再说。
6. 从开发到部署:我踩过的坑与验收清单
6.1 环境版本不匹配的连环坑
开发过程中我踩过最耗时间的坑,几乎全是版本问题。这里列一个我实测稳的版本组合清单:
| 组件 | 推荐版本 | 避坑说明 |
|---|---|---|
| JDK | 8 | 不要用JDK17跑SpringBoot 2.x项目,会出一堆奇怪的反射错误 |
| Maven | 3.6+ | 3.8以上需要注意中央仓库的HTTPS配置 |
| Node.js | 16.x/18.x | Node 20+配合Vite 4有时会报digital envelope routines错误 |
| npm | 8.x | 跟随Node版本自动带的就行 |
| MySQL | 8.0 | 5.7兼容性也没问题,但8.0对utf8mb4支持更好 |
如果你用的是SpringBoot 3.x,需要把javax包名改成jakarta,拦截器、JWT库的选择都要换。如果不是特别原因,我不推荐在毕设阶段用3.x,教程和资料都少很多,排错成本高。
还有个很隐蔽的坑:Maven项目里Lombok版本要和JDK匹配。JDK8环境用1.18.30是没问题的,但如果你用高版本Lombok配合JDK8,编译时会报"java.lang.ExceptionInInitializerError"之类的错误。解决办法是锁定Lombok为1.18.x版本。
6.2 数据库字段设计时就要留出余地
我做了太多项目后养成了一个习惯:任何业务表都会有三个通用字段,宁可不用也不要后期加。
- status(状态字段):默认0,后续有逻辑上线下线需求直接能用
- deleted(逻辑删除标记):MyBatis-Plus有全局逻辑删除配置,改写不删,避免外键关联问题
- create_time / update_time(时间字段):配合MyBatis-Plus自动填充,不需要代码里手动set时间
@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)标注一下,代码里就不用在每个service里手动赋值了。省代码是其次,关键是不会漏。
6.3 部署与演示环境搭建
最后说说部署。演示项目最省事的方案是:后端打jar包,前端打包后放到后端的静态资源目录里,一个进程跑完整个系统。好处是演示时不担心端口跨域问题,拷贝目录就能迁移。
后端打包跳过测试:
mvn clean package -DskipTests然后后台运行jar包:
nohup java -jar pregnancy-platform-1.0.0.jar > app.log 2>&1 &前端构建完的dist目录,直接拷贝到src/main/resources/static目录下,重新打包后端,SpringBoot会自动托管静态文件。这是最省事的方案。
如果你希望前端独立部署,那就用Nginx托管dist目录,配置一个代理转发/api到后端服务。两种方式我都在项目里用过,如果是给别人演示、答辩,强烈推荐第一种;如果后续准备长期线上运营,建议用Nginx。
演示数据的初始化也很重要。我在数据库里预置了一个测试账号,档案里的末次月经日期设置为30周前,这样登录进去不用手动填数据,首页就有完整的孕周信息、产检记录、提醒任务和推荐知识可以展示。这个测试账号一定不要忘了在答辩前准备好,不然现场从注册开始演示,体验会差很多。
这次做完这个平台后,我最大的一条心得是:做业务系统,先把"业务语言"翻译成"技术语言",再动手写代码。孕婴护理平台这种项目,技术难度不算高,但业务逻辑的完整性和细节打磨决定它的上限。如果你也是第一次做这类项目,不如先花一天时间把角色流程图和数据关系图画清楚,后面写代码会顺很多。等你把主体功能跑通,自然会发现哪些地方需要补充智能化,再迭代也不迟。