Spring Boot社区养老服务系统设计与实现:从需求拆解到工单闭环实战

接手过几个业务管理类的项目之后,我对“基于Spring Boot的社区养老服务系统”这种题目尤其有感触。这类系统表面上是个常规的管理后台,把老人信息、工单、健康档案、费用这些表建好,CRUD写好就算完事。但真正做起来你会发现,难点根本不在增删改查,而在业务流程怎么闭环、数据怎么为人所用、权限边界怎么划清楚。这篇文章就围绕这个系统的设计与实现,把我在实际开发中的需求拆解、表结构设计、核心接口逻辑、以及踩过的坑完整地梳理一遍,给正在做类似项目的朋友一个能直接参考的路线。

这套系统到底解决什么问题?说白了,社区养老服务站点平时要管理大量的老人档案、上门服务记录、健康数据和费用账单。如果没有一套信息系统,靠的是Excel表格、微信群消息、纸质台账,数据散落且容易出错。老人家属想知道今天护理员有没有上门、血压测了没有,只能打电话问;管理员想统计这个月完成了多少单服务、哪些护工工作量饱和,更是要翻半天表格。而一个基于Spring Boot的社区养老服务系统,就是把“服务预约—派单—执行—确认—评价”的完整链路搬到线上,让管理员、护理员、家属三方在同一个平台上协作,信息实时同步。

这个项目适合谁参考呢?如果你是正在做毕业设计或者中小型信息管理系统的开发者,这篇文章里面的方案可以直接拿来落地。文章会覆盖需求分析、技术选型、MySQL表结构、JWT鉴权、工单状态流转、健康档案采集、数据看板,以及常见的部署问题和调试经验,都是我实际写代码时验证过的方案,不是纸面理论。

1. 整体设计与需求拆解

1.1 系统定位:核心不是“管理”而是“服务闭环”

我在接到这个题目后,第一件事是调研市面上同类社区养老系统的使用场景。市面很多系统设计得过于庞大,什么都要做,结果每个环节都浅。实际上社区养老服务站的规模并不大,一个站点通常就是一位管理员加若干护理员,服务几十位老人。这时候系统最重要的不是“管理”这两个字,而是把每一次上门服务的流程跑顺。

举个例子,家属在平台上预约一次生活照料服务,选好时间段和服务项目;管理员收到预约后安排护理员接单;护理员上门扫码或者确认开始服务;服务完成后填写服务记录、测量老人血压血糖;家属确认服务完成并可以对质量打分。这个流程看起来简单,但每一步都涉及状态的变更和数据的落库,最后还要联动生成费用账单。这个“预约—派单—执行—确认—评价—结算”的闭环,就是整个系统的主干。

我做需求拆分时,会把主干流程单独列出来,把支线功能(比如健康档案查询、统计报表)都挂在这个主干下。这样做的好处是开发时能分清主次,不会在枝节功能上浪费太多时间。

1.2 角色模型与功能模块

系统设计了三类核心角色,每类角色的操作边界完全不同:

角色典型操作数据权限范围
管理员老人档案维护、护工排班、派单改单、费用审核、数据统计全站数据
护理员查看我的工单、接单、填写服务记录、录入健康数据仅本人被分派的工单
老人/家属提交服务预约、查看服务记录、健康趋势、在线支付、评价仅关联本人的数据

根据角色划分,功能模块自然就出来了:

  • 用户与权限模块:登录、JWT鉴权、角色权限拦截。
  • 老人档案模块:基本信息、健康基础信息、家属联系人、护理等级。
  • 服务工单模块:预约、派单、接单、服务记录、确认、评价,状态机贯穿全程。
  • 健康档案模块:日常测量的血压、血糖、心率、体温记录,以及异常提醒。
  • 费用账单模块:根据工单和计价规则生成月度账单,支持缴费状态管理。
  • 数据看板模块:服务完成量、工单状态分布、健康异常统计、收入汇总。

这里有一个我的实际心得:不要在第一个版本中做太复杂的排班功能。很多类似项目死磕排班算法,导致核心服务流程没做扎实。社区养老场景中,管理员手动选择护理员去派单就够了,动态算法排班可以先放后台作为优化项。

1.3 模块边界为什么要刻意控制规模

这个判断可能和一些人的直觉相悖——既然是系统设计,不应该设计得越完整越好吗?其实社区养老系统的核心价值是实用,而不是演示功能多。我做过的某类似系统一开始规划了10多个模块,包括投诉建议、志愿者管理、活动报名、物资领用等,结果开发周期拉了很长,反而核心的服务工单模块做得很粗糙。后来我果断砍掉了不必要模块,集中精力保证老人档案和工单流转的稳定性和易用性,项目才真正可交付。

做这类系统要学会“克制”。你设计的每个模块都需要后续的维护成本、测试成本和用户学习成本。功能边界清晰、操作路径短,对使用系统的中老年家属和护理员反而更友好。

2. 技术选型背后的取舍

2.1 为什么是Spring Boot + MyBatis Plus + MySQL

Spring Boot在中小型管理系统中几乎是默认选择,理由不用多说:自动配置丰富、社区资料多、遇到问题容易搜到答案。针对这类系统,我用的是Spring Boot 2.7.x版本,JDK 1.8,这套组合稳定性和兼容性都经过了大量项目验证,足够支撑毕业设计或者中小型商用项目。

ORM层面我更倾向于MyBatis Plus而不是JPA。原因也很实际:社区养老系统的查询场景复杂且多变,比如工单列表要根据状态、时间范围、护理员ID、老人姓名多个条件动态组合查询。MyBatis Plus的QueryWrapper/LambdaQueryWrapper写这种动态条件非常顺手,分页插件也是开箱即用。JPA虽然写简单CRUD更省事,但复杂查询需要写JPQL或者Specification,学习成本和调试成本都偏高。

数据库选择MySQL 8.0,字符集统一utf8mb4,排序规则utf8mb4_general_ci。MySQL的稳定性、易用性都很好,配合5.7以上的版本还能使用JSON类型,在某些场景下可以简化表结构设计。

2.2 权限认证:JWT和Session之间我为什么选JWT

我在做这个系统的时候,一开始也纠结过权限认证到底是用传统的Session还是JWT。最终我选了JWT,核心原因是系统后续要考虑多端接入:管理员和护理员用Web端没问题,但老人家属端很可能会用小程序或者H5。如果使用Session,跨域和跨端共享登录态会很麻烦;JWT是无状态Token,后端只负责签发生效和验证,天然适合前后端分离架构,也能轻松支持多端。

Spring Security对我来说太重了,社区养老系统的权限规则其实很清晰——角色就三种,接口权限基本是通过拦截器判断角色就能满足。引入Spring Security反而会让配置复杂度上升。所以我选了轻量方案:拦截器 + JWT + Redis(可选),实现简单且容易排查问题。

JWT库选择上,我用的是io.jsonwebtoken:jjwt:0.11.5。0.9.1版本之前需要额外引入javax.xml.bind依赖,0.11.5不再需要,API也更清爽。需要注意的是JWT中不要存放敏感数据,只用userId和role这些必要信息。

2.3 环境准备与核心依赖配置

开发环境我建议这样搭配,省心且兼容:

  • JDK 1.8(或11,视Spring Boot版本而定)。
  • Maven 3.6+,配置阿里云镜像加速。
  • MySQL 8.0.28+,数据库连接池用默认HikariCP即可,性能足够。
  • IDEA + Lombok插件,减少实体类样板代码。

pom.xml中的核心依赖大致是这个结构:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> </parent> <dependencies> <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.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>

这些依赖里没有包含Spring Security,也没有引入额外的Redis starter。我的原则是“能用最简单的方案解决,就不引入额外组件”,这样部署时能少踩很多环境上的坑。

3. 数据库设计:一张张表拆给你看

3.1 核心表结构与字段设计

数据库设计是这个系统的重中之重。我见过很多同学一上来就写代码,做到半路发现表结构不合理,回到数据库改表,改完又要连带改一堆Mapper和Service,非常痛苦。所以做这种业务系统,我强烈建议先花半天到一天时间把表设计好,再动手写代码。

这个系统的核心表我总结为以下八张:

  • 用户表(sys_user):用户ID、姓名、手机号、密码(BCrypt加密)、角色(1管理员/2护理员/3家属)、状态、创建时间。

  • 老人信息表(elder_info):老人ID、姓名、性别、出生日期、身份证号、联系电话、住址、家属姓名、家属联系电话、护理等级(1自理/2半自理/3全护理)、既往病史、过敏史、状态、逻辑删除标记。

  • 护理员表(nurse_info):护理员ID、姓名、手机号、性别、入职日期、服务技能标签、状态。这里和用户表的关系是护理员往往也是一个登录用户,可以维护nurse_info.user_id关联用户表。

  • 服务项目表(service_item):项目ID、项目名称、服务内容描述、计价单位(次/小时)、单价、状态。

  • 工单表(service_order):工单号、老人ID、护理员ID、服务项目ID、预约服务日期、预约时间段、服务地址、状态(0待派单/1待接单/2服务中/3已完成/4已取消/5已评价)、服务开始时间、服务结束时间、服务备注、实际费用、创建时间。

  • 健康档案表(health_record):记录ID、老人ID、测量类型(1血压/2血糖/3心率/4体温)、测量值1(如收缩压)、测量值2(如舒张压)、单位、异常标识(0正常/1偏高/2偏低)、测量时间、备注。

  • 费用账单表(bill_info):账单ID、老人ID、账单月份、服务明细摘要、总金额、支付状态(0未支付/1已支付)、支付时间、生成时间。

  • 评价表(order_evaluation):评价ID、工单ID、老人ID、评分(1-5分)、评价内容、评价时间。

以老人信息表为例,看一下实际建表时的核心字段:

CREATE TABLE `elder_info` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` VARCHAR(30) NOT NULL COMMENT '老人姓名', `gender` TINYINT NOT NULL DEFAULT 1 COMMENT '性别 1男 2女', `birth_date` DATE DEFAULT NULL COMMENT '出生日期', `id_card` VARCHAR(18) DEFAULT NULL COMMENT '身份证号', `phone` VARCHAR(20) DEFAULT NULL COMMENT '联系电话', `address` VARCHAR(255) DEFAULT NULL COMMENT '住址', `family_name` VARCHAR(30) DEFAULT NULL COMMENT '家属姓名', `family_phone` VARCHAR(20) DEFAULT NULL COMMENT '家属电话', `care_level` TINYINT NOT NULL DEFAULT 1 COMMENT '护理等级 1自理 2半自理 3全护理', `medical_history` VARCHAR(500) DEFAULT NULL COMMENT '既往病史', `allergy_info` VARCHAR(255) DEFAULT NULL COMMENT '过敏史', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态 1正常 0停用', `deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除 0未删除 1已删除', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_name` (`name`), KEY `idx_family_phone` (`family_phone`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='老人信息表';

这里有几个细节值得说明。第一,id_card我保留字段但不在前端明文展示,查询时要脱敏处理。第二,deleted字段做物理逻辑删除标记是很必要的,比如某个老人退出服务后,历史工单还是要保留,不能真删掉,否则关联数据会全部断裂。第三,所有表统一带create_time和update_time,后续做统计看板时时间字段是刚需。

3.2 工单表的状态流转设计

工单表是整个系统最核心的表,它的状态设计直接影响代码逻辑复杂程度。我把状态设计成整数,并且把状态值写进代码枚举或者常量类中,避免魔法数字散落各处。

工单状态流转路径这样设计:

  • 0待派单:家属提交预约后生成,此时护理员还没有确定,管理员需要进入工单列表进行派单。
  • 1待接单:管理员派单后,护理员在“我的工单”中可以看到,护理员确认前往服务即接单。
  • 2服务中:护理员点击“开始服务”,工时开始记录。
  • 3已完成:护理员点击“完成服务”,填写服务记录,健康数据可选录入,工单进入待家属确认状态。
  • 4已取消:家属在服务开始前可以取消预约,管理员也可以取消。
  • 5已评价:家属确认完成并提交评价,整个工单闭环结束。

这里要特别注意:单纯的“已完成”不能直接视为最终状态。我之前做过的类似系统里,家属没有确认环节,护理员填写完就算结束,结果很多服务记录其实并没有真正发生,家属投诉投诉无门。所以“家属确认”这一步不要省,它既是对服务的监督,也是后续账单生成的依据——只有已评价的工单才进入月账单统计。

工单状态之间的非法跳转也要做限制。比如已取消的工单不能直接变成已完成,已评价的不能回头再修改服务记录。我的做法是在Service层写一个状态校验方法,每次变更状态前先校验当前状态和目标状态是否在合法的流转路径上。

3.3 健康档案与费用模块的设计要点

健康档案表我一开始也犹豫过:是设计成一行一条记录,还是用横表把血压、血糖、心率都放在一行?后来实测发现一行一条记录最灵活。原因很简单:护理员上门服务时不一定每次都会测所有指标,有时候只测血压和心率,有时候老人要求测一下餐后血糖。如果用横表设计,就会产生大量空字段,扩展新指标还得改表结构。纵表模型虽然查询时要多一个group by,但配合数据库层的条件聚合,性能完全不是问题,还可以随时新增指标类型。

费用这块容易被忽视,但实际场景中很关键。社区养老不是一次性缴费,而是按月结算。为了保证结算可追溯,我没有直接用程序自动生成所有账单,而是提供了一个“按月生成账单”的操作,管理员确认后生成账单。账单生成逻辑是:

1. 查询指定月份内所有状态为“已评价”的工单。 2. 按老人分组,累加工单的实际费用。 3. 生成账单记录,摘要字段保存服务明细JSON字符串。 4. 重复执行时检查账单编号是否已存在,防止重复生成。

这里还有个小经验:打折、减免这种规则,建议不要写死在代码里,而是在账单表里增加一个“减免金额”字段,由管理员手工填写。你永远不知道业务方会提出什么样的优惠规则,字段远比硬编码灵活。

4. 后端核心功能实现

4.1 统一接口返回与全局异常处理的正确姿势

接口风格上我选择了RESTful API + 统一返回体。先定义一个统一返回的通用类,所有接口都返回这个结构,这样前端处理数据时逻辑统一,不用为每个接口单独处理错误情况。

@Data public class R<T> { private Integer code; private String msg; private T data; public static <T> R<T> ok(T data) { R<T> r = new R<>(); r.setCode(200); r.setMsg("success"); r.setData(data); return r; } public static <T> R<T> fail(Integer code, String msg) { R<T> r = new R<>(); r.setCode(code); r.setMsg(msg); return r; } }

我再配合一个全局异常处理器,兜住业务异常和系统异常,避免把堆栈信息直接暴露给前端:

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public R<Void> handleBusinessException(BusinessException e) { return R.fail(e.getCode(), e.getMessage()); } @ExceptionHandler(Exception.class) public R<Void> handleException(Exception e) { // 这里应该记录日志 return R.fail(500, "系统繁忙,请稍后重试"); } }

业务异常建议自己在项目中定义一个BusinessException类,构造时可以传code和msg。这样在Service层做参数校验或状态校验时,直接throw new BusinessException(...) 就能被全局处理器统一返回,代码会干净很多。

4.2 登录鉴权:JWT过滤器的落地写法

实现登录接口,我使用BCrypt加密密码存储。登录成功后签发JWT,然后把token返回给前端。前端后续请求在Header中带上Authorization: Bearer 。

登录核心代码大致如下:

@Service public class AuthService { @Autowired private SysUserMapper sysUserMapper; public String login(String username, String password) { SysUser user = sysUserMapper.selectOne( new LambdaQueryWrapper<SysUser>() .eq(SysUser::getUsername, username) ); if (user == null || !BCrypt.checkpw(password, user.getPassword())) { throw new BusinessException(1001, "用户名或密码错误"); } if (user.getStatus() == 0) { throw new BusinessException(1002, "账号已被停用"); } return JwtUtil.generateToken(user.getId(), user.getRole()); } }

JWT生成和解析工具类,我封装成JwtUtil:

public class JwtUtil { private static final String SECRET = "your-512-bit-secret-key-change-in-production"; private static final long EXPIRE = 7 * 24 * 60 * 60 * 1000L; // 7天 public static String generateToken(Long userId, Integer role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim("role", role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) .signWith(Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8))) .compact(); } public static Claims parseToken(String token) { return Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8))) .build() .parseClaimsJws(token) .getBody(); } }

拦截器里做的事情就三件:从Header取token,校验是否过期和签名,放行并将userId和role放入ThreadLocal或request attribute。对于只需要登录就能访问的接口,校验token存在即可;对于需要特定角色的接口,再判断role是否匹配。

这里有一个我踩过的坑:把密钥硬编码在代码里,在本地开发没问题,但部署到服务器后如果别人通过反编译或者其他途径拿到你的jar包,密钥就泄露了。建议把密钥放到配置文件,通过@Value注入,并且确保生产环境的密钥和开发环境不同。

4.3 工单模块:完整服务流程闭环的实现

工单模块是核心中的核心,这一部分我重点讲几个关键逻辑。

创建工单时,家属选择服务项目和期望的服务时间,后台清理掉同一老人同一时间段已经存在的工单,防止重复预约:

public Long createOrder(OrderCreateDTO dto) { // 校验老人是否存在且状态正常 // 校验服务时间不能早于当前时间 Long count = orderMapper.selectCount( new LambdaQueryWrapper<ServiceOrder>() .eq(ServiceOrder::getElderId, dto.getElderId()) .eq(ServiceOrder::getServiceDate, dto.getServiceDate()) .eq(ServiceOrder::getTimeSlot, dto.getTimeSlot()) .in(ServiceOrder::getStatus, Arrays.asList(0, 1, 2)) ); if (count > 0) { throw new BusinessException(2001, "该时段已有待处理工单,请选择其他时间"); } ServiceOrder order = new ServiceOrder(); order.setOrderNo(generateOrderNo()); order.setElderId(dto.getElderId()); order.setServiceItemId(dto.getServiceItemId()); // ... 其他字段 order.setStatus(OrderStatus.WAIT_ASSIGN.getCode()); orderMapper.insert(order); return order.getId(); }

这里我建议工单号生成规则采用日期+随机序列:比如20250112153012345678,既能看出创建时间,也方便后续对接账单。

派单操作要注意,派单后需要给护理员端一个可见的入口。这里我采用的是最简单有效的方式:护理员登录后直接查询“待接单”状态的工单列表,不需要再做消息推送。系统上线后如果护理员嫌麻烦,再考虑对接微信服务号或者短信提醒,一开始不要过度设计。

护理员接单和开始服务,属于状态流转的典型写法:

public void acceptOrder(Long orderId, Long nurseId) { ServiceOrder order = getOrderById(orderId); if (order.getStatus() != OrderStatus.WAIT_ACCEPT.getCode()) { throw new BusinessException(2002, "当前工单状态不可接单"); } if (!order.getNurseId().equals(nurseId)) { throw new BusinessException(2003, "该工单不是分配给当前护理员"); } ServiceOrder update = new ServiceOrder(); update.setId(orderId); update.setStatus(OrderStatus.SERVING.getCode()); update.setServiceStartTime(new Date()); orderMapper.updateById(update); }

在写这类状态流转逻辑的时候,一定要先校验再更新,并且更新SQL要带上当前状态条件,防止并发操作下状态被覆盖。我习惯在update时用LambdaUpdateWrapper,SQL条件里带上status = 原状态,返回影响行数为0就说明状态已经被其他操作改过了。

其实这个系统是没有必要引入分布式锁的,MySQL的行锁和状态条件已经能够保证并发安全。如果要做并发控制,可以在工单表中增加version字段做乐观锁,但这个场景下条件更新已经足够。

4.4 健康趋势与看板统计的实现方式

健康档案录入接口相对简单,核心在“查询趋势图”。家属端要看老人最近30天的血压变化曲线,后端接口需要聚合最近N条记录。MyBatis Plus里可以用LambdaQueryWrapper按时间和老人ID查询,然后数据在Java层把同一天多次测量取平均。

如果要写原生SQL做更复杂的统计,比如统计本月完成工单数和累计服务时长,可以在Mapper里定义方法:

@Mapper public interface StatsMapper { List<Map<String, Object>> countOrderByStatus(); List<Map<String, Object>> serviceHoursThisMonth(@Param("month") String month); List<Map<String, Object>> topServiceItems(@Param("month") String month); }

对应的XML或者注解SQL就不难了。统计看板在这个项目里我建议做成“数据报表页”,主要是给管理员看整体运营情况,不需要实时大屏那种酷炫效果。数据量不超过几万条的情况下,直接查数据库然后用Java做聚合,性能完全在可接受范围内。

看板数据我认为最有价值的是两个角度:一个是“服务是否真正发生”,即已评价工单的占比和分布;另一个是“健康异常追踪”,比如最近一个月哪些老人有血压偏高记录,需要重点回访。这两个数据能切实地帮助站点提升服务质量。

5. 常见问题与排查经验实录

5.1 启动阶段的几个坑

Spring Boot项目启动报错,我碰到过好几次,而且每次原因都不太一样,这里列几个典型的。

第一个,MySQL版本和驱动不匹配导致无法连接。MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver,老项目里用com.mysql.jdbc.Driver会直接报错。同时URL里必须加上时区参数,推荐serverTimezone=Asia/Shanghai,否则会抛异常提示时区无法识别。

第二个,MyBatis Plus分页插件不生效。如果只引入依赖但没在配置类中注册PaginationInnerInterceptor,分页查询时limit并不会实际拼接,接口会返回全量数据。我的配置类是:

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

第三个,Lombok版本和JDK版本不匹配导致getter/setter生成失败。遇到这种情况,优先检查Lombok依赖版本是否支持和当前JDK对应,不要盲目升级到最高版本。

5.2 业务运行期的隐蔽问题

项目能启动了,不代表没有坑。业务运行期有几个问题比启动报错更隐蔽,一旦出现会让用户对系统完全失去信任。

第一是日期格式化不一致问题。后端返回LocalDateTime时,如果不配置Jackson序列化格式,前端默认拿到的是类似“2025-01-12T10:30:00”的格式,和页面期待的不一样。在配置文件中可以全局配置:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai

第二是跨域问题。如果你前后端分离开发,前端跑在8080端口,后端跑在9090端口,那没有配置CORS时,浏览器就会拦截所有请求。我建议在后端写一个统一的CORS配置类,而不是在每个接口加注解。

第三是逻辑删除和唯一索引的冲突问题。如果老人表中对手机号做了唯一索引,逻辑删除后又重新添加同一个老人,就会因为deleted=0的数据已经存在而插入失败。这个问题在业务中真实存在,我的解决方式是不做数据库层唯一索引,而是在业务层查询时先校验是否存在未删除记录。

5.3 部署上线要注意的事项

部署环节,我习惯把Spring Boot项目用Maven打成jar包,然后在服务器上用systemd做守护进程。JVM参数里设置初始堆内存和最大堆内存,这个对于小型项目同样重要,否则默认堆内存可能过大或过小。

Jar包启动的核心命令:

nohup java -Xms256m -Xmx512m -jar elder-care-system.jar --spring.profiles.active=prod > app.log 2>&1 &

配置文件方面,我建议把数据库账号、JWT密钥等环境相关配置放到application-prod.yml中,并且通过环境变量替代明文。至少要做到代码仓库里不出现生产环境的密码。

还有一点值得重视:上线之前至少要把“工单闭环”的完整链路跑三遍,从家属预约,到管理员派单,到护理员接单服务,再到家属确认评价、生成账单,每个状态都亲手点一遍,不要只测试登录和查询。很多系统上线后出问题,都是因为主流程在某个环节断了。

写在最后的一点个人体会

做一个业务系统,技术上其实没有太多高深的难点,真正的功夫在业务流程的理解和细节的把控。我在这个项目中比较深的体会是:社区养老这类系统,表面上服务的对象是管理员和护理员,但真正要照顾好的是老人和家属的体验。每一次工单状态的准确流转,每一条健康记录的及时录入,背后都对应着一次真实的上门服务。开发时多想一层“这条数据是谁在什么场景下录入的”,设计出来的系统才能真的用起来。

软件开发从来不是写代码本身,而是为真实的问题找到一条被技术支撑的解决路径。如果你的系统正在开发过程中遇到了我上面提到的某个具体问题,不妨把报错信息拉出来对照着排查一遍。踩坑不可怕,踩完坑,把经验沉淀下来,这才是做项目最值钱的部分。

相关新闻

CCS工程中文注释乱码怎么办?从编码原理到批量转码的完整方案

CCS工程中文注释乱码怎么办?从编码原理到批量转码的完整方案

接手一套DSP控制工程&#xff0c;导入CCS之后还没来得及看代码&#xff0c;编译窗口先蹦出一堆警告&#xff1b;等真正打开源文件想加注释&#xff0c;满屏中文注释全变成了“锟斤拷”和“鐢靛瓨”这种根本没法读的乱码。这场景做CCS开发的人大概都遇到过&#xff0c;特别是接手…

2026/10/20 2:32:33 阅读更多 →
魔改Informer滚动长期预测:科研代码实战与避坑指南

魔改Informer滚动长期预测:科研代码实战与避坑指南

简介&#xff1a;这份资源面向时间序列预测方向的研究者与毕业设计开发者&#xff0c;针对官方Informer模型仅支持固定长度预测、难以满足论文实验需求的痛点&#xff0c;提供了可滚动长期预测的科研改进版本。核心改动在于新增自动填补机制&#xff1a;首次预测未来24个时间点…

2026/10/20 2:31:32 阅读更多 →
单层感知机为什么只能解决线性可分问题?从XOR到多层网络的必经之路

单层感知机为什么只能解决线性可分问题?从XOR到多层网络的必经之路

上世纪60年代&#xff0c;人工智能第一次经历寒冬&#xff0c;导火索之一就是单层感知机被证明连异或&#xff08;XOR&#xff09;这种简单问题都解决不了。你可能会觉得奇怪&#xff1a;一个能识别简单模式、在当时被寄予厚望的算法&#xff0c;为什么遇到稍微复杂一点的数据分…

2026/10/20 2:31:32 阅读更多 →

最新新闻

CAT12 VBM全流程实战:从安装配置到多模态形态学建模

CAT12 VBM全流程实战:从安装配置到多模态形态学建模

简介&#xff1a;本资源是面向神经影像学研究者与计算神经科学初学者的CAT12工具箱完整安装与分析套件&#xff0c;专为基于体素的形态计量学&#xff08;VBM&#xff09;研究设计&#xff0c;解决sMRI数据预处理、组织分割、统计建模与结果可视化等核心问题。包内共518个文件&…

2026/10/20 7:59:20 阅读更多 →
Flutter 动画还能这么玩:从框架分析到渲染管线,性能翻倍的底层秘密

Flutter 动画还能这么玩:从框架分析到渲染管线,性能翻倍的底层秘密

Flutter 动画还能这么玩&#xff1a;从框架分析到渲染管线&#xff0c;性能翻倍的底层秘密 【免费下载链接】flutter Flutter makes it easy and fast to build beautiful apps for mobile and beyond 项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutter …

2026/10/20 7:59:20 阅读更多 →
SecureCRT中文版深度适配原理与实战指南

SecureCRT中文版深度适配原理与实战指南

简介&#xff1a;SecureCRT中文版是一款面向网络工程师、系统管理员及Linux/Unix运维人员的免安装终端仿真工具&#xff0c;专为安全、高效地管理SSH远程服务器与网络设备而设计。资源包共198个文件&#xff0c;含15个可执行程序&#xff08;主程序及配套工具&#xff09;、52个…

2026/10/20 7:59:20 阅读更多 →
Matlab肌肉激活度计算:从sEMG信号到%MVC的完整实现

Matlab肌肉激活度计算:从sEMG信号到%MVC的完整实现

简介&#xff1a;这份资源面向运动生物力学、康复工程与人体运动分析方向的学习者和研究者&#xff0c;提供一套用Matlab求解肌肉激活度的完整实现方案&#xff0c;解决从表面肌电信号到激活度曲线这一环节缺少可运行参考代码的问题。压缩包共5个文件&#xff0c;约135KB&#…

2026/10/20 7:59:20 阅读更多 →
鄱阳湖水系shp数据在ArcGIS中的处理全流程:坐标系、属性表与出图

鄱阳湖水系shp数据在ArcGIS中的处理全流程:坐标系、属性表与出图

简介&#xff1a;长江流域鄱阳湖水系的地形图矢量资料&#xff0c;以通用的shp格式提供湖泊与河流要素&#xff0c;同时包含90米分辨率数字高程模型&#xff08;DEM&#xff09;和山体阴影栅格&#xff0c;并配有ArcGIS工程文件&#xff08;mxd&#xff09;&#xff0c;可直接用…

2026/10/20 7:59:20 阅读更多 →
Python数据分析与可视化源码包实战:从数据清洗到图表输出的完整链路拆解

Python数据分析与可视化源码包实战:从数据清洗到图表输出的完整链路拆解

简介&#xff1a;这份资源面向希望系统掌握Python数据分析与可视化的学习者&#xff0c;覆盖从编程基础到实战项目的完整技能链路&#xff0c;适合高校学生、转行者及需要处理结构化数据的从业者。压缩包共162个文件&#xff0c;约31.27MB&#xff0c;以67个ipynb实验代码、11个…

2026/10/20 7:58:20 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →