拓冰建站拓冰建站
首页 / 资讯中心 / 正文

SpringBoot单体应用实战:基于guns构建社区养老医疗平台

简介基于SpringBoot的guns社区养老医疗服务综合平台管理系统源码面向Java毕业设计及SpringBoot初学者围绕社区养老医疗场景覆盖健康档案、预约医疗、紧急呼叫等核心业务。项目完整呈现Spring Boot整合Spring MVC、Data JPA、Security等组件的过程并采用RESTful API实现前后端分离分层结构清晰。资源包共428个文件以178个Java源码、84个JavaScript、58个HTML为主辅以XML配置、SQL脚本、CSS和图片等压缩包仅2.12MB便于导入IDE直接阅读。目前已有77人学习下载适合需要参考完整系统进行毕业设计或提升SpringBoot实战能力的读者。除可运行代码外还包含数据库脚本、前端页面与工程配置可帮助读者梳理从启动装配、业务逻辑到数据持久化的完整开发链路并理解安全控制与接口交互学习价值较高。1. 从“guns社区养老医疗平台”看SpringBoot单体应用的现实边界一个社区养老平台看起来无非是“老人信息录进去、服务工单派出去、体检数据传上来”。可一旦要拿它做真实的管理工具你会发现用户登录、菜单权限、操作日志、字典管理这些和业务没有直接关系的部分占掉了大半工作量。SpringBoot本身只是一套应用骨架不会告诉你模块怎么划分权限怎么控制。因此很多项目组会选择guns这类基于SpringBoot的后台开发平台把通用能力直接拿过来。这里以“基于SpringBoot的guns社区养老医疗服务综合平台管理系统”的交付场景为起点讲清楚用SpringBoot和guns实现时哪部分可直接复用哪部分必须自己写。目标读者是接到类似单子的后端工程师或者准备把校内项目改成可上线系统的同学。我的经验是如果你已经有一套SpringBoot后台界面把guns代码拆开重写的成本往往比直接在它基础上扩展更高真正需要投入精力的是医疗数据结构和工单状态约束。2. 搭建SpringBoot guns项目从生成器到关键参数调优Guns作为SpringBoot生态里的老牌后台脚手架常见版本已经内置了用户管理、角色管理、菜单管理、字典管理、登录日志与操作日志。这些小模块在社区养老平台里全是刚需不需要改动就能用。我拿到一个新的guns项目时不会先去看业务代码而是先把配置文件扫一遍再按下面方式把模块生成一遍确认代码生成器绑定的模板是否和当前SpringBoot版本匹配。2.1 为什么用guns承载养老医疗平台而不是从零写一套RBAC社区养老平台的管理端至少要有三类角色街道管理员可以看全部数据医疗机构人员只能看本机构的老人护理员只看自己被派单的老人。如果用原生SpringBoot开发角色、菜单、数据权限这套RBAC从建表到实现少说要两周。guns的权限体系是现成的角色和菜单的关联关系由系统维护操作日志通过过滤器自动记录。对于“综合平台”类项目这些能力直接从界面上配置代码里只保留业务接口整体维护成本会低很多。尤其当团队里有初级开发时现成菜单管理可以避免每个人用自己的方式去写权限判断。2.2 使用guns自带代码生成器重建业务模块的步骤常见的guns版本会提供一个在线代码生成页面或者一个独立运行的主类。无论是哪种形态模板都会生成entity、mapper、service、controller和前端页面。社区养老平台中“老人档案管理”这种典型模块最适合用生成器创建。我一般先建好数据库表再在代码生成器填上表名和中文注释。生成完成后把controller里的路由前缀改成业务路径比如“/elder”然后在菜单管理里挂一条记录设置一个标识字段为“elder:list”的权限编码。具体操作流程如下在MySQL中创建数据库表统一前缀如sr_表示“社区服务”。修改application.yml里的数据源指向该数据库并确认mybatis-plus.mapper-locations匹配当前mapper目录。启动项目访问代码生成器页面选择表sr_elder_info填入模块名elder。生成代码后刷新菜单缓存分配权限给测试角色。重启应用用测试账号登录后台验证菜单入口是否出现。这些步骤并不是gun专属但关键点是生成代码之后需要检查实体类上的TableName注解是否带有实际表名否则MyBatis-Plus会默认把类名转下划线作为表名导致启动时SQL语句找不到表。多数“生成后页面打不开”的问题其实出在这个注解上而不是模板本身。2.3 application.yml中与SpringBoot集成的关键参数社区养老平台的医疗模块对事务和缓存比较敏感参数不能全用默认值。下面这份配置节选是项目里相对重要的部分spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/community_elder?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: elder_app password: ${DB_PASSWORD} redis: host: ${REDIS_HOST:127.0.0.1} port: 6379 timeout: 5000ms mybatis-plus: global-config: db-config: id-type: assign_id logic-delete-field: deleted configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl参数说明数据源URL必须显式声明serverTimezone否则新版MySQL驱动会把本地时区当成服务器时区导致时间字段偏移。id-type: assign_id依赖MyBatis-Plus内置雪花算法避免在分布式部署时产生重复主键。spring.redis.timeout只控制连接池获取连接的超时并不控制业务方法级缓存过期时间。若系统里保存老人健康数据建议把Redis的默认序列化器改为Jackson否则缓存里存入对象后取出来转型时会报ClassCastException。server.servlet.session.timeout建议设置为30分钟因为管理端操作人员多为工作人员会话不宜过短。上传体检报告时spring.servlet.multipart.max-file-size要调到20MB以上否则稍微清晰一点的报告拍照图都传不上去。日志文件写到./logs/elder-platform.log并通过logback或log4j2的SizeAndTimeBasedRollingPolicy做按日分割和50MB上限避免单个文件膨胀到几个GB之后排查困难。guns框架里还有一个常见配置项guns.temp-file-path对应临时文件目录供文档预览和导出使用如果该目录不存在上传导出功能会直接报FileNotFoundException部署脚本里增加mkdir -p即可。配置项推荐值说明server.servlet.session.timeout30m管理端工作人员会话保持时间spring.servlet.multipart.max-file-size20MB体检报告与证件图片上传上限logging.file.name./logs/elder-platform.log日志落盘路径需确保目录存在guns.temp-file-path/data/elder-temp导出/预览临时文件目录2.4 SpringBoot版本太高的兼容处理很多同学从IDEA新建SpringBoot项目时选了3.x而guns的常见版本基于SpringBoot 2.x。如果直接把guns代码放在SpringBoot 3环境下运行最典型的问题是javax.servlet变成jakarta.servlet以及spring.factories自动装配机制被AutoConfiguration.imports取代。遇到这类情况我一般把项目降级到SpringBoot 2.7.x并统一使用JDK 8或11。如果你坚持用SpringBoot 3常见做法是将所有javax.*引用改成jakarta.*并在resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports里重新声明xxAutoConfiguration类。这已经不是guns本身的问题而是整个SpringBoot生态迁移成本问题。这里其实涉及SpringBoot自动装配原理guns自己提供的配置类通过spring.factories注册后在应用启动时被SpringFactoriesLoader加载。版本太高时SpringBoot不再读取spring.factories中的自动配置项所以控制台会看见guns相关Bean确实存在但实际没有进入容器。排查时先看启动日志中是否有“Negative matches: GunsAutoConfiguration”有就是没被装配。3. 养老医疗综合体建模老人档案、健康记录与工单接口实现当后台框架跑通后真正的业务建模才开始。社区养老医疗综合平台的数据域可以拆成四个基础档案域、健康管理域、服务派工域和医疗资源域。下面以“老人档案”和“服务工单”为主线给出一套可以落地的表设计和接口写法。3.1 核心表结构设计要点我习惯禁用外键所有关联关系在Service层维护。原因很简单后续增加“慢病随访”或“家属授权”字段时外键会成为改版的障碍。下面给出老人档案和服务工单两张表的精简版本CREATE TABLE sr_elder_info ( id BIGINT PRIMARY KEY, name VARCHAR(50) NOT NULL, id_card VARCHAR(18) NOT NULL, residence_address VARCHAR(255), contact_phone VARCHAR(20), health_level TINYINT DEFAULT 1 COMMENT 1-自理 2-半失能 3-失能, org_id BIGINT NOT NULL COMMENT 归属街道/机构ID, deleted TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE sr_service_order ( id BIGINT PRIMARY KEY, order_no VARCHAR(32) NOT NULL, elder_id BIGINT NOT NULL, service_type VARCHAR(20) COMMENT nursing/medical/meal/cleaning, worker_id BIGINT NOT NULL COMMENT 护理员用户ID, status TINYINT DEFAULT 0 COMMENT 0-待派单 1-进行中 2-已完成 3-已取消, plan_time DATETIME, finish_time DATETIME, deleted TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_elder_org ON sr_elder_info(org_id); CREATE INDEX idx_order_worker ON sr_service_order(worker_id, status);设计意图sr_elder_info.org_id指向guns系统里的机构表这样街道管理员才能按机构过滤出自己管辖下的老人。sr_service_order中的service_type不要直接存中文业务里用枚举翻译避免后续增加服务项目时改代码。工单表上必须建联合索引(worker_id, status)否则护理员端查“我的进行中工单”会走全表扫描。这个联合索引的触发条件要说明一下查询条件先等值worker_id再进行范围status索引顺序不能反过来否则扫描范围还是很大。3.2 用MyBatis-Plus实现按条件分页查询Guns内置了MyBatis-Plus查询接口不需要手写复杂XML。实现老人档案的模糊搜索时Controller和Service可以这样写RestController RequestMapping(/elder) public class ElderController { Resource private ElderService elderService; GetMapping(/page) public LayuiPageResultElderInfoVO page(ElderQuery query) { return elderService.queryPage(query); } }Service实现类里构造器注入是推荐写法但guns老版本普遍使用Resource延续原有风格即可Service public class ElderServiceImpl extends ServiceImplElderMapper, ElderInfo implements ElderService { Override public LayuiPageResultElderInfoVO queryPage(ElderQuery query) { LambdaQueryWrapperElderInfo wrapper Wrappers.lambdaQuery(); wrapper.like(StringUtils.hasText(query.getName()), ElderInfo::getName, query.getName()); wrapper.eq(query.getOrgId() ! null, ElderInfo::getOrgId, query.getOrgId()); wrapper.eq(ElderInfo::getDeleted, 0); wrapper.orderByDesc(ElderInfo::getCreateTime); PageElderInfo page new Page(query.getPage(), query.getLimit()); PageElderInfo result this.page(page, wrapper); // 转换VO填充orgName等字段 return new LayuiPageResult((int) result.getTotal(), convertToVO(result.getRecords())); } }这段代码的解释LambdaQueryWrapper避免了硬编码数据库字段名重构时比较安全。like方法第一个参数boolean condition为false时该条件不会拼进SQL。对于健康档案查询条件可能很多这个特性可以少写一整个if-else树。page参数query.getLimit()对应前端每页条数如果大于100最好在Controller层做统一校验。返回代码里通过result.getTotal()取总数再返回LayuiPageResultguns后台的表格组件直接认这个结构。分页查询之外健康记录和时间点还需要一个状态流转的设计。这里把工单状态机单独列出来方便后端实现和前端按钮渲染同步当前状态触发操作下一状态备注待派单管理员选择护理员进行中同时写入worker_id和plan_time进行中护理员上报完成已完成生成结算记录待派单管理员取消已取消不可恢复已完成管理员作废已取消同步删除结算记录这个表就是状态流转的“唯一事实”后端的if判断必须和表严格一致。状态字段建议用TINYINT存前端展示时通过字典翻译“待派单”“进行中”这些中文名不要写在SQL注释之外的地方避免后续改字典时同步更新一堆代码。3.3 工单状态流转与结算联动服务完成时系统需要同时更新工单状态、生成一条结算记录。这个动作必须放在一个事务里。Guns项目里事务有两种做法在Service方法上加Transactional或者使用guns自带的事务拦截器。我建议方法级注解最直观因为后续拆微服务时这种本地事务边界更好判断。Transactional(rollbackFor Exception.class) public void finishOrder(String orderNo) { SrServiceOrder order this.getByOrderNo(orderNo); if (order.getStatus() ! 1) { throw new ServiceException(500, 工单状态不允许完成); } order.setStatus(2); order.setFinishTime(LocalDateTime.now()); this.updateById(order); SrSettlement settlement new SrSettlement(); settlement.setOrderNo(orderNo); settlement.setElderId(order.getElderId()); settlement.setAmount(calculateCharge(order.getServiceType())); settlement.setStatus(0); settlementMapper.insert(settlement); }代码逻辑上方的注解rollbackForException.class必须写如果只写Transactional默认只回滚RuntimeExceptionSQLException等受检异常会导致工单失败但结算记录照常插入对账时出现脏数据。calculateCharge这里是价格策略建议把服务单价放到字典表不要做成硬编码。字典项可以统一存到guns的sys_dict里然后通过缓存加载比每次查库少一次IO。3.4 与guns内置资源整合操作日志和机构维度业务表保存org_id操作由guns的登录用户完成登录用户信息可以拿当前请求上下文获取。常见做法是把机构ID、用户ID通过LoginContextHolder传入然后使用AOP记录业务日志。这一步不要只依赖guns的全局日志因为全局日志记录的是请求方法名无法还原“哪个工单被作废了”。更好的做法是在业务Service方法上增加一个业务日志注解将orderNo和操作动作拼接后写入业务日志表。这样既保留管理后台的访问审计又满足养老机构对服务过程的追溯要求。4. 权限模型与数据隔离让医生和护理员只看职责范围内的老人在社区养老医疗平台里数据权限比菜单权限更关键。街道管理员需要看到全部机构数据医生只能看到本机构的老人档案护理员只能看到自己名下的工单。如果只控制菜单三个角色都能打开同一个列表接口无法识别他们各自的数据范围。Guns自带的RBAC只解决“能不能进”数据行级的隔离要做扩展。4.1 guns内置的RBAC权限模型Guns的权限体系把权限拆成菜单和按钮两级。菜单权限决定左侧导航显示按钮权限决定增删改查是否可见。数据库层面有sys_user_role、sys_role_menu、sys_menu三张核心表。登录成功后会生成用户对应的菜单标识集合例如elder:list、order:finish。前端按钮用v-has-perm指令做校验后端接口的Permission注解做二次校验。要注意的是这些权限只覆盖“功能”并不覆盖“数据”所以处理养老平台的组织边界时数据权限必须另做。角色与数据范围的默认映射关系可以这样定义角色功能范围数据范围实现方式街道管理员全部菜单全部机构不强制注入org_id机构医生医疗健康模块本机构老人查询前注入org_id护理员工单与随访模块本人负责工单查询前注入worker_id4.2 基于机构ID扩展数据权限过滤器我一般的做法是自定义一个拦截器在请求进入Controller前把当前用户的数据范围写入请求属性或者用更简单的方案把过滤条件写进Query对象。前者对业务透明但排查SQL时容易懵后者直观但每个接口都要传入。如果团队经验一般建议先做Query方案。下面是一种基于请求上下文的实现public class DataScopeInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { LoginUser loginUser LoginContextHolder.get().getLoginUser(); if (loginUser null) { return true; } Boolean isAdmin loginUser.isAdmin(); if (!isAdmin) { request.setAttribute(dataScopeOrgId, loginUser.getOrgId()); } return true; } }然后在Controller层通过RequestAttribute获取dataScopeOrgId组装进ElderQueryGetMapping(/page) public LayuiPageResultElderInfoVO page(ElderQuery query, RequestAttribute(required false) Long dataScopeOrgId) { if (dataScopeOrgId ! null) { query.setOrgId(dataScopeOrgId); } return elderService.queryPage(query); }这段代码不处理角色组的概念但已经能把“只看本机构”落到查询条件上。如果需要更细粒度比如“机构下某个科室只能看到慢性病随访名单”那就需要在系统中维护一个“数据范围规则表”规则里存机构ID、科室ID、老人标签然后在查询前解析规则。注意“管理员不强制注入org_id”这个判断不能只看角色名称一定要看角色标识或admin标记因为角色名称可以被业务部门随意修改。4.3 Spring Security与guns的整合踩坑guns项目里既有自己写的过滤器又可能引入Spring Security。在SpringBoot 2.x下两个权限体系叠加会出现请求被拦截两次。常见解决办法是关闭Spring Security自带登录页把token认证抽成OncePerRequestFilter注册到Security过滤链前面并放行静态资源和登录接口。要特别留意的是如果guns的url权限控制在拦截器中做Spring Security放过之后拦截器再校验一次顺序不能反。顺序反了会出现“提示未认证信息”和“账号未登录”两个错误交替出现。http.authorizeRequests() .antMatchers(/login, /assets/**, /actuator/health).permitAll() .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .csrf().disable();配置解释/login与静态资源放行是必要项否则连登录页都打不开。/actuator/health放行用于运维探活。addFilterBefore中的jwtAuthenticationFilter会解析请求头中的token解析成功后手动写入SecurityContext这样guns自己的拦截器也能从同一上下文获取用户ID。不要使用http.formLogin()开启表单登录否则SpringSecurity的默认session机制会与guns的登录状态冲突。4.4 缓存权限时容易出现的缓存雪崩权限信息如果每次都查数据库菜单树会变成性能瓶颈。常见方案是把用户权限列表存Redis设置过期时间。但要区分两类缓存菜单树缓存和用户权限缓存。菜单树是所有登录用户共用用固定key存即可用户权限缓存包含用户IDkey要与数据权限隔离区分。社区养老平台的使用人数有限不需要复杂缓存策略但要注意不要在Controller里直接redisTemplate.delete(menuTree)否则同一时间所有用户请求都会穿透到数据库在校验中心高峰期可能出现接口整体变慢。正确做法是等待缓存自然过期或者在后端提供“刷新菜单权限”接口只在权限变更时调用一次删除操作。5. 打包部署与验收用Actuator、日志和接口探活确认系统稳定项目交付时常见方式是打成可执行Jar包放到服务器跑。对于guns项目第一步是确认pom.xml中packaging为jar然后执行Maven命令。如果你的环境没有Maven直接用IDEA右侧Maven面板执行clean package跳过测试参数可以节省不少编译时间。mvn clean package -Dmaven.test.skiptrue java -jar elder-platform.jar --spring.profiles.activeprod--spring.profiles.activeprod会加载application-prod.yml数据源密码用环境变量传入。如果想把整个配置目录放到jar外面用--spring.config.additional-location/opt/elder/这样升级版本时不用重新维护配置文件。启动完成后用下面命令验证服务是否真正就绪curl -s http://127.0.0.1:8080/actuator/health | jq .status如果返回UP说明SpringBoot应用健康。但业务是否就绪还要再验证登录接口可以用一条SQL先检查sys_user表中测试账号是否存在然后通过登录接口获取token。这一步要尤其注意SpringBoot框架起来不代表数据库连接、Redis缓存都正常/actuator/health只能看到应用级别探活。若环境中需要国产中间件比如宝兰德完整替换内嵌Tomcat需要将打包方式换成war并编写web.xml描述符guns项目对容器Servlet版本的兼容性通常按Tomcat8/9的标准验证这一点在更换中间件之前就要测不要等到部署再暴露。上线前还需要检查spring-boot-maven-plugin是否配置了executable标签没有配置时直接java -jar是没问题的但不方便注册成systemd服务。给一个简化的systemd unit写法的思想同时设置EnvironmentDB_PASSWORDxx与EnvironmentJAVA_OPTS-Xmx1024m启动命令ExecStart使用/usr/bin/java $JAVA_OPTS -jar /opt/elder-platform.jar。日志输出不要只依赖SpringBoot文件建议从systemd层面用StandardOutputjournal限制避免整个系统盘被日志打满。对于数据准确性可以用如下SQL快速核对今日工单和结算数SELECT t.settlement_date, SUM(CASE WHEN s.status 2 THEN 1 ELSE 0 END) AS done_cnt FROM (SELECT CURDATE() AS settlement_date) t LEFT JOIN sr_service_order s ON DATE(s.finish_time) t.settlement_date GROUP BY t.settlement_date;最后启动后查看logs/elder-platform.log中是否出现Started ... Application字样。若出现异常优先排查是否有端口被占用再查看数据源连接池的Connection refused。生产环境最值得做的事是打开logging.level.sql为DEBUG把MyBatis-Plus打印的SQL和参数对齐到一次工单操作逐条观察有没有出现笛卡尔积或超出预期的数据范围。确认无异常后把sql日志级别重新调整回INFO避免高频日志拖慢IO。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门