社区养老和传统养老院的管理逻辑差得很远,工作人员要面对的是分散在社区里的大爷大妈、志愿者、第三方服务商,还要把上门护理、健康档案、服务预约、工单派发这些又琐碎又必须闭环的事管起来。拿Spring Boot做一套社区养老服务管理系统,确实是个很合适的练手项目,springboot104这个编号对应的就是一套以老人档案为主线、以服务工单为抓手的后台管理系统。这套东西不光是课程设计里常见,很多社区信息化改造也用类似的模式。如果你正准备做这类项目,或者想搞清楚社区养老系统到底该怎么做,这篇文章会从需求拆解到数据库设计,再落到具体代码和部署排查,给你一条能直接照做的路线。
我实际做过三个版本的养老管理系统,第一个版本光顾着堆功能,结果服务预约和工单对不上,老人信息重复录入,后来推倒重来才把数据模型理顺。下面要讲的这些设计,都是踩过坑之后沉淀下来的方案,不保证每个场景都适用,但至少能让你少走几周弯路。
1. 项目概述与需求拆解
1.1 养老服务系统的真实业务场景
先想清楚一个问题:社区养老服务系统和普通OA、CRM系统的核心差异在哪里?普通系统管的是人和流程,而养老系统管的是持续性的关怀责任。老人可能今天只需要助餐,下周就要临时加一次康复理疗,再过两个月可能转成长期护理。如果系统里只存一条静态档案,后面的服务记录、健康变化全都接不上,那这个系统就是一次性录入工具,没有任何管理价值。
我在需求调研时发现,社区工作人员最头疼的不是"某个老人的档案在哪",而是"这个老人上周的服务有没有完成""家属昨天反映的问题处理到哪一步了""哪位护理员今天上午有空档期"。所以这套系统的核心业务场景可以拆成四条线:
- 档案线:老人基础信息、家属联系方式、既往病史、用药情况、居住状态,这是一个持续更新的动态库。
- 服务线:从家属或护工提交服务申请,到管理员审核、派单给合适的护理员,护理员上门打卡完成,最后回访评价,形成一条完整服务闭环。
- 健康线:定期记录血压、血糖、心率等体检数据,通过趋势变化早发现身体异常,这是社区养老区别于普通信息管理的关键点。
- 管理线:护理员排班、服务工单状态流转、服务量统计、满意度分析,这些是管理人员每天要看的经营数据。
很多新手一上来就画了十几个菜单,觉得功能越多越好。实际上社区养老服务系统的核心价值就是"让服务过程有迹可循"。菜单再多,如果没有把这些业务线串起来,用起来就会非常别扭。
1.2 功能模块的优先级划分
面对一个实际项目,不可能一口吃成胖子。我建议把功能模块分成三个优先级:
- P0 必备模块:老人档案管理、服务项目管理、服务预约与工单管理、护理员信息管理、用户登录与权限控制。没有这些,系统跑不起来。
- P1 增强模块:健康档案与趋势图表、服务回访与评价、工单统计报表、操作日志。没有这些,系统能做但不能持续用。
- P2 拓展模块:排班管理、消息提醒、老人位置定位、家属端微信小程序。这些是加分项,初期可以留接口,不做实现。
这个项目的定位是"社区养老服务管理系统",不需要做成大而全的ERP。哪怕是毕业设计,也要让评委看到你有业务理解能力,而不是盲目堆模块。我当时做的时候,P2只留了定时任务的空接口,后面有需要再往里面填,效果并不差。
1.3 技术栈选型:为什么是Spring Boot + MyBatis-Plus + MySQL
说实话,社区养老管理系统用Spring Boot做后端是再自然不过的选择,网上资料多、招人门槛低、部署方便。但技术选型不是堆最新框架,而是求稳。
- Spring Boot:当前主流还是2.7.x系列,推荐直接用2.7.18,稳定而且兼容JDK8/11/17。除非你明确想体验Spring Boot 3,否则不要让新版本依赖给你带来额外工作量。
- MyBatis-Plus:比原生MyBatis省掉大量XML编写,内置分页插件、代码生成器、LambdaQueryWrapper,特别适合这种中小型管理系统。刚开始学的时候可能觉得魔法过多,但用顺手后真的回不去了。
- MySQL 8.x:社区养老的数据量不会大到需要分布式,单库单表足够。MySQL 8的窗口函数、JSON字段偶尔能用上,记得时区配置要统一。
- 前端:如果用Thymeleaf + Bootstrap,一个Java开发者就能搞定,不需要拉上前端同事。如果团队有前端能力,用Vue + axios分离开发会更清爽。我这次讲的是Thymeleaf方案,因为对初学者最友好。
- 安全控制:没有选择Spring Security,因为这种系统角色固定、页面数量有限,用一个拦截器 + Session角色判断就足够。引入Spring Security意味着大量配置和学习成本,性价比不高。
这套组合的打法就是:快速实现业务闭环,同时保留后续优化的空间。等哪天并发上来、需求复杂了,再升级安全框架也不迟。
2. 数据库设计与核心表结构
2.1 数据表整体规划思路
数据库是这类管理系统的灵魂。你后端的Service写得再漂亮,表结构不合理就必然到处补丁。我设计这套社区养老系统时,遵循了三个原则:
- 以老人档案表为主表,其他业务数据都通过
elder_id关联,不搞重复冗余的老人信息快照。 - 服务预约和工单拆成两条记录,预约记录描述"想要什么服务",工单记录描述"谁来执行、执行到什么状态",两者通过预约单号关联。
- 所有状态字段用int类型保存,不用字符串,因为字符串容易写错、不易做查询优化,配合枚举类在代码里转换即可。
根据业务线划分,我规划的物理表大约有九张:
| 功能域 | 表名 | 说明 |
|---|---|---|
| 基础档案 | elder_info | 老人信息 |
| 健康管理 | health_record | 健康检查记录 |
| 服务资源 | service_item | 服务项目目录 |
| 服务预订 | service_reserve | 服务预约单 |
| 执行管理 | work_order | 服务工单 |
| 人员管理 | worker_info | 护理员信息 |
| 用户权限 | sys_user | 登录账号 |
| 评价反馈 | service_eval | 回访评价 |
| 日志 | sys_log | 操作日志 |
这套表结构基本覆盖了P0和P1的功能需求,而且表与表之间关系清楚,后面写SQL统计时很顺手。
2.2 核心表字段设计详解
先看老人档案表elder_info。这张表决定了整个系统是否好用,字段设计上既要有基础信息,又要保留关键扩展字段:
id:主键,采用数据库自增,不做雪花ID,因为单机系统不需要分布式ID。name、gender、birth_date:基础身份信息。id_card:身份证号,查询时常用,建议建立唯一索引。如果涉及隐私,可考虑加密存储,但为了演示项目可暂时明文,生产环境务必加密。phone、address:联系方式与住址。社区服务最大的特点是"上门",所以地址要尽可能详细,我甚至拆分了community_name和house_number两个字段。emergency_contact、emergency_phone:紧急联系人信息,必须有,这个在实际场景中救过急。living_status:居住状态,比如1独居、2与子女同住、3养老机构,独居老人是社区重点关注对象。chronic_disease:慢性病史,用逗号分隔存JSON数组的字符串形式,方便展示。care_level:照护等级(1轻度/2中度/3重度),这个字段直接关联服务工单派单逻辑。
再来看服务预约表service_reserve。拿实际业务举例,家属在手机上或前台申请"每周二上午上门助浴",这就是一条预约单:
elder_id:关联老人。service_item_id:关联服务项目。reserve_date、time_slot:预约日期和时段,比如上午/下午/晚上。remark:家属备注。status:预约状态,0待审核、1已确认、2已取消、3已完成。
而**服务工单表work_order**是执行层的记录,同一个预约可以被拆成多次工单,也可以一次预约对应一次工单。很多新手把预约和工单混在一张表,后面一旦要做"一次预约分两次服务"就抓瞎。工单表的核心字段包括:
order_no:工单号,手动生成,格式类似WO+年月日+四位序号,方便线下核单。reserve_id:关联预约单。worker_id:负责的护理员。plan_start_time、actual_start_time、actual_end_time:计划与实际执行时间。status:工单状态机,0待派单、1待服务、2服务中、3已完成、4已取消、5异常关闭。service_content:实际操作内容,用于存档。
**健康记录表health_record**是社区养老的亮点,我单独强调一下:
elder_id、record_date:老人和日期。blood_pressure_high、blood_pressure_low:血压高低压,分别存字段,不要合并成一个"血压值"字符串。heart_rate、blood_sugar、weight:参考指标。is_abnormal:是否异常,1表示异常。这个字段不是用户录入的,而是程序根据规则自动判断,后续列表筛选"近期有异常的老人"就非常方便。
2.3 状态机设计与事务边界
状态字段不是随便加几个数字,它对应的是业务流程。服务预约状态和工单状态必须分开管理,两者之间的流转关系要提前定义清楚:
- 新增预约单,
service_reserve.status=0,此时还没有工单。 - 管理员审核通过,预约状态变为1,同时生成一条
work_order,工单状态为0。 - 管理员派单给护理员,工单状态变为1,此时护理员能看到自己的任务。
- 护理员点击开始服务,工单状态变为2;点击完成,状态变为3。
- 服务完成后,预约状态同步变为3。
- 如果中途取消,预约状态变为2,同时当前未执行的工单状态变为4。
这就意味着,一次服务操作可能需要同时更新两张表。写代码时必须在事务里执行,否则可能出现预约已经完成但工单还在服务中的不一致现象。我用的是Spring的@Transactional注解,只读操作不要加,写操作一定加,注意事务只在public方法上生效,并且自调用不生效,这是新手容易掉进的坑。
3. 后端核心模块实现
3.1 Spring Boot项目骨架搭建与基础配置
项目我习惯用IntelliJ IDEA创建,选择Spring Initializr,group用com.community,artifact用service-system,包名com.community.service。依赖选择上,除了spring-boot-starter-web之外,加上mybatis-plus-boot-starter、mysql-connector-java、lombok、validation和thymeleaf。
这里有个小细节:Spring Boot 2.7.x对应MyBatis-Plus 3.5.x,不要用更早的版本,否则分页插件配置会不兼容。在application.yml里我最常遇到的坑点是:
spring: datasource: url: jdbc:mysql://localhost:3306/community_service?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver thymeleaf: cache: false prefix: classpath:/templates/ suffix: .html mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0serverTimezone=Asia/Shanghai这个必须写,否则MySQL 8连接时区报错能把你逼疯。另外我把Thymeleaf缓存关闭,改完页面刷新就能看到效果,开发效率高不少。
3.2 登录认证与拦截器实现
不是所有页面都要登录才能进,但核心业务页面必须受保护。我放弃Spring Security后,用拦截器做了一套非常轻量的权限控制:
- 登录接口校验用户名密码成功后,把用户对象和角色写入
HttpSession。 - 自定义一个
AuthInterceptor,实现HandlerInterceptor接口。 - 通过
preHandle方法检查Session里是否存在用户,没有就重定向到登录页。 - 对管理员专属的URL前缀,比如
/admin/**,再加一层角色判断。 - 注册拦截器时配置
excludePathPatterns排除登录页、静态资源等路径。
代码逻辑不复杂,但要注意WebMvcConfigurer的写法:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/login", "/doLogin", "/css/**", "/js/**", "/images/**"); } }这里有个小坑:Thymeleaf的静态资源放在classpath:/static/下,如果你把静态页面放在templates里当成普通页面渲染,拦截器排除路径可能对不上,导致页面样式加载失败。我吃过一次亏后,统一把CSS、JS、图片全放static下,登录页用Controller渲染,样式通过@{/css/style.css}访问。
密码存储一定要用加密算法,我用的BCrypt,Spring Security的BCryptPasswordEncoder可以单独引包使用,不用引入完整Spring Security。明文密码哪怕只在演示项目里出现,也千万别干。
3.3 服务预约与工单流转的业务闭环
这是整个系统最核心的后端业务,我从实际编码角度完整讲一遍。
Controller层只做参数接收和结果封装,不写业务。Service层处理预约和工单的状态流转。代码大致结构如下:
@Service public class ReserveServiceImpl implements ReserveService { private final ReserveMapper reserveMapper; private final WorkOrderMapper workOrderMapper; @Transactional(rollbackFor = Exception.class) @Override public boolean auditPass(Long reserveId, Long adminId) { ReserveEntity reserve = reserveMapper.selectById(reserveId); if (reserve == null || reserve.getStatus() != 0) { throw new ServiceException("预约单不存在或状态不允许审核"); } ReserveEntity update = new ReserveEntity(); update.setId(reserveId); update.setStatus(1); reserveMapper.updateById(update); WorkOrderEntity workOrder = new WorkOrderEntity(); workOrder.setReserveId(reserveId); workOrder.setElderId(reserve.getElderId()); workOrder.setServiceItemId(reserve.getServiceItemId()); workOrder.setStatus(0); workOrder.setOrderNo(generateOrderNo()); workOrderMapper.insert(workOrder); return true; } }这里尤其需要注意:为什么不在审核时直接给工单指派护理员?因为审核通过只代表"服务可执行",但哪天执行、谁执行可能还要再协调。把派单动作独立出来,后续业务流程会灵活很多。
工单派单给护理员时,也要检查护理员当天该时段是否有已有冲突。我当时写了一个接口去查work_order表,条件是同一worker、日期重叠、状态不是已取消。这个方法很有用,但在一个演示项目里如果不想做太复杂,可以先不做排班冲突检测,但要留好数据库索引,以防后面加功能时性能下降。
护理员点击"开始服务"后,更新actual_start_time,状态从1变为2;点击"完成服务"后,更新actual_end_time,状态变为3,同时通过reserve_id反查预约单,把预约状态也改为3。这一步必须放在一个事务里。如果你发现某个工单已经完成,但预约还是审核状态,通常就是这里忘了同步更新。
3.4 健康档案与趋势统计的实现细节
健康记录部分,最核心的是一场基于时间序列的查询。比如前端要展示某位老人最近7次血压趋势,SQL可以这么写:
SELECT blood_pressure_high, blood_pressure_low, heart_rate, record_date FROM health_record WHERE elder_id = #{elderId} AND deleted = 0 ORDER BY record_date DESC LIMIT 7不用复杂的分组统计,简单查询后,在Service层把结果反转成时间正序,返回给前端绘制折线图就行。MyBatis-Plus里我是这样写的:
List<HealthRecord> records = healthRecordMapper.selectList( new LambdaQueryWrapper<HealthRecord>() .eq(HealthRecord::getElderId, elderId) .eq(HealthRecord::getDeleted, 0) .orderByDesc(HealthRecord::getRecordDate) .last("LIMIT 7") );这里有个容易被忽视的点:record_date要按日期存储,而不是带时间戳,因为趋势查询关心的是天维度。如果你用datetime也不是不行,但分组统计时要多写DATE()函数,还容易带出时分秒干扰排序。
is_abnormal字段的自动判断逻辑放在Service层,比如收缩压大于140或舒张压大于90,则标记异常。录入时执行判断,不依赖前端传值。这样哪怕接口被直接调用,数据也不会被伪造。
统计报表方面,一个典型的SQL是按月和护理员维度统计工单数:
SELECT worker_id, DATE_FORMAT(plan_start_time, '%Y-%m') AS month, COUNT(*) AS total FROM work_order WHERE status = 3 GROUP BY worker_id, DATE_FORMAT(plan_start_time, '%Y-%m') ORDER BY month, total DESC拿到这个结果,前端就可以做柱状图。有一点要注意,工单数量统计时只应该把已完成状态算进去,已取消和异常关闭的不算,否则管理报表会虚高。
4. 前端页面与交互设计
4.1 Thymeleaf + Bootstrap的页面组织
前端页面我采用了Thymeleaf模板,好处是后端能直接在HTML里渲染数据,不需要跨域和接口层,对开发速度极快。页面结构上,我做了一个统一的侧边栏模板,用Thymeleaf的th:fragment提取公共部分:
<div th:fragment="sidebar"> <a th:href="@{/index}">首页面板</a> <a th:href="@{/elder/list}">老人档案</a> <a th:href="@{/reserve/list}">服务预约</a> <a th:href="@{/workorder/list}">工单管理</a> <a th:href="@{/health/list}">健康记录</a> <a th:href="@{/worker/list}">护理员管理</a> </div>然后每个页面通过th:replace引入。这个方式比复制粘贴一万遍侧边栏代码强太多。如果你要改菜单,只需要改一个地方。
表格展示我直接用<table class="table table-bordered">加Bootstrap的样式,配合后端分页插件返回的IPage对象,把current、pages、records等传入前端。这里要特别注意:如果你用MyBatis-Plus分页,必须在配置类里定义MybatisPlusInterceptor这个Bean,并且注册PaginationInnerInterceptor,否则分页查询不生效,返回的还是全量数据。这个坑反馈率极高。
分页组件我手写了一套简化版,不额外引前端插件。翻页链接直接拼?page=1这种参数,后端Controller读取页面参数。新手用这套能快速跑通。
4.2 表单校验与二次确认机制
表单校验分两层。前端用HTML5的required、maxlength做非空和长度拦截,后端再用JSR-303的@NotNull、@Size做二次校验。我只信任后端的校验,前端只是体验优化。
服务预约表单里,老人下拉框需要从后端查已入库老人列表,我用Model把List<ElderEntity>塞进页面,用th:each循环渲染。这里有个细节:如果老人列表很长,下拉框会很难用,至少要按姓名关键字做模糊搜索。一开始我没做搜索,社区的人找了20分钟才找到目标老人,体验非常差。后面加了elder/select2这种接口,用原生<input>监听输入事件,动态加载匹配列表。
工单派单的流程,我建议在点击"派单"时弹出模态框,显示工单号、服务项目、预约时间,并让管理员选择护理员。模态框提交用AJAX,不用整页刷新:
$.ajax({ url: '/workorder/assign', type: 'POST', data: { workOrderId: workOrderId, workerId: workerId }, success: function(res) { if (res.code === 200) { location.reload(); } else { alert(res.msg); } } });这里我踩过坑:AJAX提交时忘记加Content-Type,默认表单提交没问题,但如果后端用@RequestBody接收JSON,就必须设置contentType: 'application/json'。为了避免混淆,统一写POST表单格式,后端用普通参数接收,别混用。
4.3 健康趋势图表展示
图表我用了ECharts,因为它引入简单、文档全、图表类型够用。在health/detail.html页面里,先放一个表格展示最近健康记录,再放一个<div id="chart">作为图表容器。后端返回给页面的数据格式是两列:日期和值。我直接用Thymeleaf的th:inline="javascript"把数据转换成JS数组。
<script th:inline="javascript"> var dates = [[${dates}]]; var highPressure = [[${highPressures}]]; var lowPressure = [[${lowPressures}]]; </script>ECharts配置这里不展开了,但有一个建议:趋势图只展示最近30条数据,过多折线点密密麻麻反而看不清。如果数据集很大,后端做好分页限制,前端循环绑定。
4.4 角色差异化的前端展示
系统里有三种角色:管理员、社区工作人员、护理员。不同角色登录后看到的菜单应该不一样。我用Session里保存的角色,通过Thymeleaf的条件表达式控制菜单显示:
<div class="menu-item" th:if="${session.user.role == 1}"> 管理后台入口 </div> <div class="menu-item" th:if="${session.user.role == 2}"> 服务预约审核 </div> <div class="menu-item" th:if="${session.user.role == 3}"> 我的工单 </div>这种方式实现简单,但由于按钮级权限控制的粒度比较粗,如果后面要精确到接口权限,还是要引入权限框架。对社区业务场景来说,角色级别控制已经能满足九成需求。
5. 部署上线与常见问题排查
5.1 本地跑通的完整步骤
拿到项目后,第一步不是急着敲代码,而是先把项目跑起来。我按照下面的顺序操作:
- 在本地MySQL创建数据库
community_service,字符集选utf8mb4,排序规则选utf8mb4_general_ci。 - 执行项目中提供的
schema.sql和data.sql,把表结构和初始账号数据导入。 - 修改
application.yml里的数据库账号密码。 - 启动Spring Boot应用,访问
http://localhost:8080/。 - 使用初始管理员账号登录,检查首页是否正常展示统计卡。
这里有个关键建议:数据库初始化脚本一定要单独放到src/main/resources/db/目录下,并且注释清楚每张表的用途。有些同学喜欢直接在Navicat里手建表,然后让别人从数据库导出,这样换电脑开发时极其痛苦。我自己曾经因为没管理好数据库脚本,导致版本回退的时候只能靠记忆重建表,浪费了一整晚。
5.2 运行时常见的异常与解决办法
我整理了一份高频问题清单,这几乎是我在社区项目答疑时必发的:
| 症状 | 原因 | 解决方案 |
|---|---|---|
启动报Access denied for user | 数据库账号密码错误或权限不足 | 检查application.yml配置,确认能通过命令行客户端连接 |
启动报Server returns invalid timezone | MySQL时区未设置 | URL加serverTimezone=Asia/Shanghai,或者在MySQL执行set global time_zone='+8:00' |
| 页面中文乱码 | 数据库表字符集不是utf8mb4,或连接字符集不对 | 确认库表和字段字符集,URL加characterEncoding=utf8 |
| 列表页无分页效果 | 分页拦截器未注册 | 在配置类中注入MybatisPlusInterceptor和分页插件 |
| 上传文件后访问404 | 静态资源映射问题 | 确认文件保存路径和映射路径匹配,调试URL是否被拦截器拦截 |
| 修改页面不生效 | Thymeleaf缓存未关闭 | 开发环境配置spring.thymeleaf.cache=false |
| 登录后点击退出还是能访问页面 | 拦截器没有拦截该路径 | 检查addPathPatterns通配符写法,确认Session是否真正失效 |
排查这些问题的最快捷方式,是把MyBatis-Plus的log-impl设置为StdOutImpl,在控制台看到所有SQL。然后再根据SQL执行是否正常来定位,比盲猜前端还是后端的问题高效得多。
5.3 安全细节与性能优化建议
社区养老管理系统的安全级不能低。虽然赶工期,但有几个底线不能碰:
- 密码必须加密,我用的BCrypt加盐,哪怕数据库泄露,也不能直接知道明文密码。
- 防止SQL注入,MyBatis-Plus的
LambdaQueryWrapper参数是用#{}预编译的,不要手工拼SQL字符串。 - 接口幂等性,比如工单完成按钮,如果网络卡顿,用户可能会重复点击。前端按钮要置灰,后端要再加一个"检查工单状态是否为服务中"的判断,避免重复完成。
- 数据权限,护理员登录后只能看自己的工单,不要给全部工单的查询权限,列表查询SQL里必须加
worker_id = #{currentUserId}。
性能方面,这种系统并发不会太高,但可以提前做两件事:一是核心查询字段加索引,比如elder_info.phone、work_order.worker_id、work_order.status;二是列表接口避免select *,只查需要的字段。数据库里老人和工单表的时间字段如果不建索引,统计查询功能一上线就会越来越慢。
6. 功能测试与演示准备
6.1 测试用例设计思路
交付前我会先按业务闭环走一遍流程。以一个虚拟老人为例:
- 管理员录入老人档案,包含姓名、地址、护理等级。
- 管理员添加一条该老人的健康记录,血压偏高,
is_abnormal自动变为1。 - 家属提交助浴服务预约。
- 管理员审核预约,自动生成工单。
- 管理员派工单给某位护理员。
- 护理员登录系统,在我的工单列表里能看到该任务。
- 护理员变更工单为开始、完成。
- 确认预约状态同步变为已完成。
- 管理员查看统计报表,能看到服务量数据。
这一步如果在手工测试中发现某处断掉,说明业务链的某个环节没有闭环,必须立刻修复。我曾经漏掉"完成工单后同步预约状态"这一环节,被测试同学直接打回,后来才补上事务代码。
6.2 演示环境的数据准备
如果是做项目答辩给评委看,千万不要只用一个空库演示,足够场景化。我会准备好:
- 10个真实的老人档案,名字都是常见姓氏,住址分布在不同小区。
- 5个护理员,不同等级和技能标签。
- 30条健康记录,横跨三个月,能明显看出趋势变化。
- 20条服务工单,包含已完成、待服务、已取消等多种状态。
- 一两张ECharts图表能直接看到统计效果。
数据准备这一步看着简单,但很花时间。我的做法是写一个小工具类模拟生成数据,一次性插入。你也直接在数据库里手动造数据,但效率低且容易遗漏字段。
6.3 项目文档与代码版本管理
写代码之外,项目要能持续改进,离不开一套基础文档:
README.md:项目简介、技术栈、运行步骤,放最前面。database.sql:建表语句和初始数据,注释清晰。docs/需求文档.md:记录功能清单和非功能需求。docs/接口文档.md:Controller层核心接口路径、入参、返回。
我在代码里使用Git管理,每次提交信息写清楚"新增派单功能""修复工单状态更新bug",这样回滚时快速定位。参加毕业设计或者项目展示,Git提交记录本身也是你工作量最好的证明。
7. 常见问题与排查技巧实录
7.1 我对两个高频Bug的排查记录
第一个问题是页面显示没问题,但数据库中文乱码。当时我查了一圈,发现数据库表和字段都是utf8,但连接URL没有加characterEncoding=utf8,导致驱动用了系统默认字符集。这是一个低级但很常见的坑,后来我在代码审计时专门加了规则提醒自己。
第二个问题是分页查询失效。前端翻页正常返回数据,但后端的IPage里getTotal()一直是0,而且查询出来的记录数是全量。排查后发现是MybatisPlusInterceptor没有注册成功,因为配置类上忘了加@Configuration注解,导致分页插件没有被Spring容器管理。这个问题让我深刻意识到,框架的"自动配置"并不是真的全自动。
7.2 排查问题的思路框架
我总结出一个排查问题的小框架,特别适合新手:
- 先分清楚是哪一层的问题,浏览器F12看Network请求是否成功、返回什么状态码。
- 如果请求成功但数据不对,再看后端Controller接口返回的JSON或日志输出。
- 如果接口数据有问题,打开SQL日志,检查执行SQL是否符合预期。
- 如果SQL正确但结果不对,回查数据库里的数据,是不是脏数据或字段类型不对。
- 如果都没问题,再考虑是不是缓存、Session或线程并发导致。
绝大多数时候,问题都能被这五步定位到。不要一上来就重启服务、清缓存,那样效率很低。
7.3 最后一个独家技巧:规范统一返回体
在项目里我定义了一个统一的返回结果类Result<T>,包含code、msg、data字段。所有Controller接口都返回这个对象,AJAX请求通过res.code判断成功与否。这个习惯一开始看起来多花了一点代码,但后续排查问题和前后端分离改造时,带来非常大的便利。
之前的项目就是因为每个接口返回结构都不一样,前端写大量分支判断,后来统一规范后,代码一下清爽了很多。我认为哪怕你只是一个人做项目,也一定要有统一返回体意识,这是一分投入十分回报的设计。
社区养老管理系统看着只是个普通CRUD项目,但真做进去,从表结构设计、状态机流转到权限控制,每一层都有需要琢磨的地方。我做这个项目最深的感觉是,业务闭环比技术炫技重要得多,流程上断了一个环节,系统就是半残品。尤其是服务预约和工单这部分,一定要自己先把业务从头到尾走通,再去优化代码。如果你正在做同类型项目,建议先别急着写页面,花两天整理表结构和状态流转,后面开发速度会快好几倍。
这套方案算是我自己趟出来的经验,实际项目里你肯定会碰到新的细节问题,但核心思路可以复用。最后再分享一个小技巧:把工单状态、预约状态的枚举值打印成一张纸贴在电脑旁边,写代码和排查数据时随时对照,能少掉很多头发。