ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SpringBoot+Vue3社区养老服务平台全栈项目实战解析

SpringBoot+Vue3社区养老服务平台全栈项目实战解析

1. 项目背景与整体技术选型

近两年社区养老信息化一直是个热门的落地方向,特别是“人口老龄化社区服务与管理平台”这种系统,典型的SpringBoot 2 + Vue 3组合,配合MyBatis-Plus和MySQL 8.0,基本就是Java Web全栈项目的标准答案。我前后也参与过几个类似的模拟项目,这套架构用下来最大的感受是:业务边界清晰、改造成本低、前后端分离后各环节都能独立测试,尤其适合拿来当作毕设或简历项目。

先说清楚这个平台到底做什么。它本质上是一个面向社区居委会、养老服务站点、志愿者和老人家属的多端管理系统,核心任务是围绕“老年人建档—健康监测—服务派单—志愿者管理—补贴发放—数据分析”这条主线,把散落在纸面上的台账变成可查询、可流转、可统计的信息流。实际项目中我见过很多类似系统最后做成“信息孤岛”,原因基本都是模块设计没想清楚,从需求阶段就把服务、健康、工单全搅在一起。

再聊技术选型。SpringBoot 2.5.x是目前中小型系统里用得最稳的版本,搭配MyBatis-Plus 3.5.x之后,单表CRUD几乎不用写SQL,内置分页插件、逻辑删除、自动填充这些功能对开发效率的提升实在太明显。前端Vue 3 + Element Plus + Vite 4的组合,组合式API写业务逻辑比Vue 2的选项式API清爽很多,组件复用和状态管理都更顺手。MySQL 8.0的窗口函数、公用表表达式(CTE)在统计报表场景下也明显比5.7舒服,后面会专门讲到。

这套系统适合谁来参考?如果你正在做Java Web方向的课程设计、毕业设计,或者想往老龄化服务领域做一套可用于演示的业务系统,那这份源码的模块划分、权限设计、报表实现方式都值得照着捋一遍。即便你已经有SpringBoot开发经验,光看它的表结构设计和工单流转状态机也能有收获。

注意:本文所有代码片段基于模拟项目“人口老龄化社区服务与管理平台”的实际开发调试过程整理,业务数据均为演示数据,不涉及任何真实社区信息。

2. 业务模块拆解与数据库设计

2.1 六大核心业务模块的边界划分

这个项目的业务模块划分并不复杂,但边界一定要清晰。按我的习惯,第一版设计就把系统拆成六大块:

  • 老人信息管理:建档、家属绑定、居住信息、健康状况、紧急联系人,这是基础主数据。
  • 服务工单管理:助餐、助洁、助医、陪护、代购等服务的预约、派单、完成、评价全流程。
  • 健康监测管理:血压、血糖、心率等指标周期记录,异常数据提醒,生成健康趋势报表。
  • 志愿者管理:志愿者注册、服务时长统计、积分体系、团队活动发布。
  • 补贴与费用管理:高龄补贴、护理补贴的申请、审批、发放记录,以及服务费用的结算。
  • 数据可视化看板:社区老人分布、服务完成率、志愿者活跃度、健康异常预警等统计图表。

模块划清楚之后,前后端路由会非常直白。后端controller层按模块拆包,service层只处理本模块业务逻辑,跨模块调用统一走接口。举个例子,工单派给志愿者时,工单服务会调用志愿者服务的查询接口,而不是直接去查志愿者表,这样后续如果要做微服务拆分,边界还是清晰的。

2.2 数据库建表的几个关键设计细节

MySQL 8.0下建表,我建议统一用InnoDB引擎、utf8mb4字符集。项目里一共有二十多张表,其中几个核心表的设计直接影响后续开发工作量,值得单独拿出来讲。

老人信息表(elder_info)我加了一个elder_no业务编号字段,格式为“SQ+社区编码+四位流水号”。这个字段在表单展示、工单关联、线下台账核对时非常有用,光靠自增主键找不到业务意义。同时加了data_status逻辑删除标记(0正常,1删除),配合MyBatis-Plus的@TableLogic,所有查询自动过滤已删除数据,避免物理删除后关联数据断裂。

服务工单表(service_order)是整张业务闭环最关键的表。我设计的字段包括:

CREATE TABLE service_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT '工单编号', elder_id BIGINT NOT NULL COMMENT '老人ID', service_type VARCHAR(20) NOT NULL COMMENT '服务类型:meal/clean/medical/escort', service_time DATETIME NOT NULL COMMENT '预约服务时间', volunteer_id BIGINT DEFAULT NULL COMMENT '指派志愿者ID', status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0待派单 1已派单 2服务中 3待评价 4已完成 5已取消', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_elder_status (elder_id, status), INDEX idx_volunteer_time (volunteer_id, service_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务工单表';

状态字段我故意用TINYINT而不是字符串枚举,目的就是为了在Java代码里写状态机流转判断时更干净。后端对应一个OrderStatusEnum,把每个状态的允许流转路径写清楚,比如“待派单”只能流转到“已派单”或“已取消”,“服务中”只能流转到“待评价”。实际项目中很多脏数据都是因为状态流转没控制好,直接从待派单跳到已完成,后续统计全乱。

健康监测表(health_record)我按天存汇总数据,同时保留明细表。老人每天可能测多次血压,明细表记录每一次读数,汇总表记录当日最高、最低、平均。报表页面查询的时候走汇总表,点进去看趋势再查明细,这样能避免报表页一次拉几百条明细数据导致接口变慢。

2.3 逻辑删除、自动填充与全局ID策略

MyBatis-Plus在这里面省了不少事。主键我用ASSIGN_ID(雪花算法),不依赖数据库自增,后续做数据迁移或者分库分表时不用改代码。逻辑删除、创建时间、更新时间全部用MP的自动填充功能:

@Data public class BaseEntity { @TableId(type = IdType.ASSIGN_ID) private Long id; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @TableLogic @TableField(select = false) private Integer deleted; }

实体类统一继承这个BaseEntity,配合实现MetaObjectHandler接口,插入和更新的时候自动塞时间值。这一套组合拳打下来,所有表的基础字段都不用手工维护,写业务代码时可以把精力全部放在逻辑上。

有一点必须提醒:逻辑删除字段deleted在查询时MP会自动追加WHERE deleted=0,但如果写了自定义SQL,比如联表查询,必须自己在XML里加上这个条件。我就踩过这个坑,有个统计接口忘了加,导致删掉的数据还占着名额。

3. SpringBoot后端核心实现解析

3.1 统一返回结构与全局异常处理

后端接口设计我强烈建议从第一天就统一返回结构。项目里用的是最经典的Result<T>泛型封装,code为0表示成功,非0表示业务异常,message携带错误描述,data存放实际数据。

@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(0); result.setMessage("success"); result.setData(data); return result; } public static <T> Result<T> error(Integer code, String message) { Result<T> result = new Result<>(); result.setCode(code); result.setMessage(message); return result; } }

配合@RestControllerAdvice全局异常处理器,把参数校验异常、业务异常、未知异常分别处理。特别是参数校验,实体类上标注@NotNull、@NotBlank、@Min这些注解,Controller参数加个@Validated,能挡掉一大部分前端传参不规范的情况。我见过太多项目每个Controller里都写if判断参数为空的代码,重复且容易漏。

全局异常处理器里我还会统一打印异常日志,用SLF4J记录异常堆栈和请求参数。排查问题的时候这个日志价值极高,不然线上出了问题连哪个接口哪个参数都查不到。

3.2 登录鉴权与权限控制方案

这个项目采用Sa-Token做登录鉴权和权限控制,相比Shiro和Spring Security,Sa-Token轻量很多,不需要配置那么多过滤器链和SecurityConfig。登录成功会拿到一个Token,前端存储后每次请求塞进Header的Authorization字段,后端通过拦截器校验Token有效性。

我在项目里用Sa-Token的StpUtil做了三端角色控制:管理员、社区工作人员、志愿者。核心做法是登录时给用户赋予角色标识,然后通过注解@SaCheckRole("admin")控制接口权限。

@PostMapping("/order/dispatch") @SaCheckRole("admin") public Result<?> dispatchOrder(@RequestBody DispatchDTO dto) { return Result.success(orderService.dispatch(dto)); }

有几个需要注意的点:

  • Token有效期设置2小时,同时开启续期功能,用户操作时会自动刷新过期时间;Redis做缓存存储后,Token的集中管理也会方便很多。
  • 密码不能明文存储,项目中用BCrypt加密,自带盐值,每次加密结果不同,安全性比MD5好太多。
  • 权限粒度上不用搞得太细,控制在“角色-接口”级别就够用。如果每个按钮都做权限控制,开发工作量会翻倍,而业务上根本用不到那么细的粒度。这个项目我分为系统管理、业务操作、查询统计三层,非常清晰。

3.3 关键业务接口的实现思路

服务派单是这个项目里最有业务深度的功能。流程是这样的:老人或家属提交服务预约,生成待派单工单;管理员在派单页面看到待派单列表,筛选空闲志愿者,派单成功后志愿者在小程序端或APP端收到通知;志愿者上门服务后点击开始、完成,老人端确认并进行评价。

这个流程里最核心的控制逻辑是状态机,我把它写在Service层:

@Transactional(rollbackFor = Exception.class) public boolean dispatch(OrderDispatchDTO dto) { ServiceOrder order = getById(dto.getOrderId()); if (order == null || !OrderStatus.PENDING.equals(order.getStatus())) { throw new BusinessException("工单不存在或状态不允许派单"); } Volunteer volunteer = volunteerService.getAvailableVolunteer(dto.getVolunteerId()); if (volunteer == null) { throw new BusinessException("所选志愿者当前不可用"); } order.setStatus(OrderStatus.DISPATCHED.getCode()); order.setVolunteerId(dto.getVolunteerId()); order.setDispatchTime(LocalDateTime.now()); updateById(order); // 发送通知 notifyService.sendDispatchMessage(dto.getVolunteerId(), order.getId()); return true; }

方法上加@Transactional保证状态修改和通知记录在同一个事务里,否则工单状态变了但通知发送失败,会出现志愿者完全不知情的情况。

另外还有一个统计数据接口值得讲。看板页面要展示“近7天服务完成趋势”,我用MySQL 8.0的递归CTE生成连续日期序列,再用左连接补零:

WITH RECURSIVE date_range AS ( SELECT CURDATE() - INTERVAL 6 DAY AS d UNION ALL SELECT d + INTERVAL 1 DAY FROM date_range WHERE d < CURDATE() ) SELECT dr.d AS date, COUNT(o.id) AS completed_count FROM date_range dr LEFT JOIN service_order o ON DATE(o.update_time) = dr.d AND o.status = 4 GROUP BY dr.d ORDER BY dr.d;

这段SQL的关键在于“补零”。如果直接按订单表group by日期,没有订单的日期会直接缺席,前端ECharts折线图上就会断掉,非常难看。加了递归CTE之后保证7天每天都有一条记录,count没有数据就是0,图就完整了。

3.4 文件上传与本地存储策略

系统里有老人证件照片上传和健康报告导入的功能,文件存储我建议本地磁盘存储。生产环境用OSS,但在开发环境和演示项目里,本地存储最简单。

@PostMapping("/upload") public Result<String> upload(@RequestParam("file") MultipartFile file) { String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String filename = UUID.randomUUID() + ext; String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); File dir = new File(uploadPath + "/" + datePath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir, filename)); return Result.success("/files/" + datePath + "/" + filename); }

文件名用UUID重命名有两个原因:一是避免中文文件名乱码,二是避免重名文件互相覆盖。按日期分目录是为了后续清理历史文件方便,一个月的文件打包归档就好。

上传文件一定要做大小限制和类型白名单。SpringBoot里配置spring.servlet.multipart.max-file-size=10MB,然后代码里再校验一次文件后缀。我见过不校验后缀的项目,被人传了个jsp上去,直接变成安全漏洞。

前端把文件上传封装成独立组件后,在表单里调用很方便。用Element Plus的el-upload结合自定义上传方法,缩略图列表回显时直接拼接文件访问URL就行。

4. Vue 3前端架构与联调实践

4.1 Vite + Vue 3 + Element Plus的工程搭建

前端工程我用的Vite 4作为构建工具,相比Webpack最大的优势就是快,冷启动基本在1秒以内,热更新也及时。项目结构按模块分包:

src/ ├── api/ # 接口请求封装 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── layout/ # 布局组件 ├── router/ # 路由配置 ├── store/ # Pinia状态管理 ├── views/ # 页面组件 └── utils/ # 工具函数

Vue 3的组合式API写业务逻辑确实比Vue 2的选项式API清晰。比如工单列表页,以前要写data、methods、computed、watch好几个块,逻辑分散,现在一个setup里按业务维度组织,可读性强很多。

const orderList = ref([]) const loading = ref(false) const queryParams = reactive({ pageNum: 1, pageSize: 10, status: null, elderName: '' }) const loadOrderList = async () => { loading.value = true const res = await getOrderList(queryParams) orderList.value = res.data.records total.value = res.data.total loading.value = false } const handleSearch = () => { queryParams.pageNum = 1 loadOrderList() } const handleReset = () => { queryParams.status = null queryParams.elderName = '' loadOrderList() }

4.2 Axios封装与请求拦截

前端请求必须做统一封装,否则每个页面都要单独处理Token过期和错误提示。我在utils/request.js里封装了一个Axios实例:

const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code === 0) { return res } if (res.code === 401) { localStorage.removeItem('token') router.push('/login') return Promise.reject(new Error('登录已过期')) } ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) }, error => { ElMessage.error('网络异常,请稍后重试') return Promise.reject(error) } )

401跳转登录这个逻辑很重要。Token过期后,第一次接口请求就会返回401,此时应清除本地登录态并跳转登录页。我之前见过项目里不处理401,用户操作到一半突然所有按钮都报错也不知道为什么,体验很糟糕。

请求参数和返回数据的结构也要约定清晰。列表接口统一接收pageNum、pageSize、条件参数,返回{ records, total, pageNum, pageSize }格式,前端分页组件直接绑定就行,不需要每个页面各自处理。

4.3 数据看板页面的图表实现

看板页面用ECharts展示统计图表,这是项目的门面,视觉效果直接决定第一印象。我把看板拆成四个区域:顶部四个统计卡片(总老人数、本月服务单量、志愿者人数、待处理工单),中间两个大图表(服务类型占比饼图、近7天服务趋势折线图),下方一个社区老人年龄分布柱状图。

图表组件我封装成ChartCard.vue,接收option作为props,通过watch监听option变化并调用chart.setOption更新。这样业务页面只关注数据组装,不用操心ECharts实例生命周期。

<template> <div ref="chartRef" class="chart-container"></div> </template> <script setup> import * as echarts from 'echarts' import { onMounted, onBeforeUnmount, ref, watch } from 'vue' const props = defineProps({ option: { type: Object, required: true } }) const chartRef = ref(null) let chartInstance = null onMounted(() => { chartInstance = echarts.init(chartRef.value) chartInstance.setOption(props.option) window.addEventListener('resize', handleResize) }) watch(() => props.option, newOption => { chartInstance.setOption(newOption) }, { deep: true }) const handleResize = () => { chartInstance.resize() } onBeforeUnmount(() => { window.removeEventListener('resize', handleResize) chartInstance.dispose() }) </script>

有个细节:容器初始化时一定要有明确的高度。ECharts如果容器高度为0,图表不会显示。我统一设置了.chart-container { height: 320px; },避免出现“图表空白但控制台无报错”的诡异问题。

4.4 后台上传组件与表单联调技巧

系统里老人建档表单包含照片上传、所属社区下拉选择、健康信息多项填写,字段多且校验复杂。我用Element Plus的el-form配合rules做表单校验,上传组件单独拆出来复用。

照片上传回显有个常见问题:编辑已有老人档案时,el-upload的file-list需要组装成{ name, url }格式,而后端返回的是文件路径字符串。我在组件里用computed做了格式转换,保证编辑回显时缩略图能正常展示。

下拉选择器的数据来源也要处理好。社区列表这种基本不会变的数据,在页面的onMounted里直接拉取并缓存到store里,避免每次打开弹窗都重复请求。服务类型这种固定枚举值,直接在前端写死配置文件就行,不需要请求后端接口。

5. 部署上线与常见问题排查

5.1 本地环境启动配置备忘

拿到源码后第一步是配置环境。我按实际调试经历整理了启动步骤:

  1. 确认本机已安装JDK 1.8+、Maven 3.6+、MySQL 8.0+、Node.js 16+。
  2. 创建数据库并导入项目根目录下的sql/community_service.sql脚本。
  3. 修改后端application.yml中的数据库连接信息,这里提个醒:MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver,URL中建议显式加上serverTimezone=Asia/Shanghai,否则时间字段会相差8小时。
  4. 后端启动:mvn spring-boot:run,看到“Started Application”日志表示成功。
  5. 前端安装依赖:npm install,然后npm run dev,默认端口配置的是5173,浏览器访问即可。

如果npm install速度很慢,可以用npx cnpm install或者配置镜像源。前端依赖装不上经常是网络问题,跟代码本身无关。

5.2 跨域配置的三种方式

前后端分离开发时必然遇到跨域问题。项目里有三种处理方式,按推荐程度排序:

第一种是后端CORS配置。写一个WebMvcConfigurer实现类,允许指定来源和Header。

@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); } }

第二种是前端Vite配置代理。在vite.config.js里设置:

server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

推荐同时用这两种方式。后端CORS解决浏览器直接请求接口的跨域问题,Vite代理解决前端开发时的同源问题。用代理之后,前端请求URL不需要写完整后端地址,统一用/api前缀,后续切换环境只需改配置文件。

第三种是生产环境用Nginx反向代理,把前端静态资源和后端接口放在同一个域名下,彻底规避跨域。这也是最推荐的部署方案。

5.3 MyBatis-Plus查询条件与分页的坑

实际开发中用MyBatis-Plus踩过不少坑,重点说几个。

第一个是分页查询必须配置分页插件,不配置PaginationInnerInterceptor的话,调用selectPage查出来的total永远是0。这个坑很多人第一次用都会遇到。

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

第二个是条件构造器QueryWrapper的字段名必须对应数据库列名,不是实体类属性名。比如实体类属性是elderName,但数据库列是elder_name,用lambdaQueryWrapper.eq(ElderInfo::getElderName, value)这种Lambda写法可以避免拼错字段名。LambdaQueryWrapper的类型安全特性在这里体现得特别明显,编译期就能发现字段名错误,推荐优先使用。

第三个是大量IN查询的性能问题。比如按多个社区ID批量查老人列表,如果用in()方法拼一个很长的ID列表,可能超过MySQL的max_allowed_packet限制。项目里我做了分批处理,每批最多500个ID,分批查询后内存合并结果,实测性能稳定。

5.4 前端接口联调常见报错清单

联调阶段最容易出的问题,我整理成了一份速查表:

现象可能原因解决方案
前端报404,接口地址确认无误Vite代理没生效,或后端接口路径前缀不对检查vite.config.js的proxy配置,统一API前缀
请求成功但数据列表为空后端SQL条件错误,逻辑删除字段没过滤查看后端日志SQL,检查QueryWrapper条件
提交表单返回400后端参数校验失败,字段类型不匹配查看响应message,检查字段类型对应关系
图片上传后无法访问上传路径和访问映射路径不一致配置资源映射或使用Nginx静态资源路径
页面刷新后404前端路由是history模式,后端没有兜底Nginx配置try_files $uri $uri/ /index.html
时间字段显示“2024-01-01T08:00:00”Java返回的LocalDateTime序列化格式问题统一配置Jackson格式为yyyy-MM-dd HH:mm:ss
图表数据正常但显示空白容器高度为0或ECharts初始化时机不对设置容器固定高度,在nextTick中初始化

LocalDateTime序列化格式这个坑常见,因为Jackson默认序列化LocalDateTime会带个T字母,前端展示特别丑。在application.yml里加一行配置就好:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss

Java的日期类也要注意,后端实体统一用LocalDateTime,不要混用Date,否则时间格式化会出现各种不一致。

5.5 性能优化与安全加固经验

模拟项目不追求极端性能,但基本优化还是要做的。我做了这几项:

数据库层面,核心表全部建了联合索引。工单表按“老人ID+状态”建索引,查询老人名下工单时走索引非常快;志愿者表按“服务区域+状态”建索引,派单查询可用志愿者时秒级响应。

SQL层面,列表接口用MyBatis-Plus的select指定查询字段,不查大字段(如备注、报告内容),减少数据传输量和服务端解析开销。健康报告表单独建了一张,避免和主表的大字段混在一起。统计报表的慢查询如果不够快,优先给聚合字段建索引,再看能不能用覆盖索引减少回表。

安全方面,除了密码BCrypt加密和Sa-Token鉴权,我还做了两个增强:接口防止重复提交,用一个简易的Redis标记+过期时间实现;敏感操作记录操作日志,包括操作人、接口路径、请求参数(脱敏处理)、IP地址、耗时。前者用短时间内的重复点击拦截,后者为追溯数据变更提供审计线索。

这些操作在面试的时候也很加分。别人只写了CRUD,你讲了索引优化、防重复提交、操作审计,至少能说明你是真的在做一个完整的系统,而不是照猫画虎。

6. 项目测试与交付文档整理

6.1 功能测试用例的设计思路

项目交付前至少要走一遍核心业务流程的测试。我设计测试用例有个原则:先跑通“主流程”,再扩展“异常分支”。主流程是“建档—预约—派单—服务—评价”,每步都要验证正常输入下的结果;异常分支则覆盖权限不足、状态非法、数据为空、参数越界这些情况。

拿派单功能举例,我至少测了这几个用例:

  • 管理员对待派单工单派单,志愿者状态为“空闲”,预期成功。
  • 管理员对待派单工单派单,志愿者状态为“服务中”,预期弹出“不可用”提示。
  • 普通工作人员尝试派单,预期提示无权限。
  • 对已完成的工单再次派单,预期提示“状态不允许派单”。
  • 不传志愿者ID直接派单,预期参数校验拦截。

让前端配合做接口测试时,我会先确认响应格式符合预期,再交给测试人员按页面操作。这样能避免把前端传参问题误判成后端Bug。

6.2 项目文档应该包含哪些内容

这套源码自带文档,但很多同学拿到后不知道怎么整理成自己的东西。我建议文档至少包含四部分:需求说明、数据库设计、接口文档、部署手册。

需求说明要写清楚系统定位、角色划分、核心业务流程,这是答辩时最常被问的部分。数据库设计文档里每个表都要写字段说明,特别是一对多、多对多关系的说明。接口文档建议用Swagger注解自动生成,开发的时候顺手加上,比后补文档效率高得多。

我还习惯在项目根目录写一份README.md,把启动步骤、默认账号、技术栈版本一条条列清楚。自己过了几个月再回来看项目,有这份README能快速回忆起来;给别人演示的时候,照着README也能顺利跑起来,不至于现场翻车。

6.3 演示环境的搭建与答辩准备

如果你是用这个项目做毕业设计或面试演示,演示环境一定要提前准备。我踩过的坑有两个:一是数据库编码问题,如果导入SQL脚本时报编码错误,用source命令导入前先执行set names utf8mb4;二是端口被占用,后端8080、前端5173、MySQL3306,哪个端口被占用都会直接导致演示失败。

答辩或演示前的自测清单,按重要性排序:

  1. 清空数据后重新导入SQL脚本,从零走一遍完整流程,确保没有依赖之前的手工数据。
  2. 换一台电脑或浏览器访问,验证环境无关性。
  3. 准备几组演示数据,比如不同服务类型的工单、健康趋势明显的记录、异常预警数据,让看板页面展示效果丰满。
  4. 预演一遍边界操作,比如输入错误密码、删除一个正在服务中的工单,看系统是否表现正常。

最后分享一个我个人的习惯:演示前一定重启一次后端和前端,确保跑起来的是最新代码,而不是开发过程中改了一半的版本。这个细节虽然简单,但能避免很多尴尬场面。

我在实际开发这套系统时的体会是,真正花时间的不是某个技术点,而是把散乱的需求梳理成可落地的业务模块,再让前后端在这个清晰的结构里互相咬合。只要模块边界清晰、表设计合理、状态流转可控,SpringBoot 2 + Vue 3 + MyBatis-Plus这套组合能帮你把工期压缩到很短的范围内。希望这份源码分析能给你带来一些直接的启发,照着它本地跑一遍,再去改动、扩展成自己的东西,比从零开始摸索要高效得多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进