☰
Java vue 养老系统源码详解:楼宇床位 care_space 与房态图 Spring Boot 实践
2026/9/30 21:53:28 网站建设 项目流程

# Java养老系统源码详解:楼宇床位 care_space 与房态图 Spring Boot 实践

在线演示:管理端后台

标签:#Java #Spring Boot #MyBatis #养老系统 #建筑管理 #房态图 #care_bed


1. 前言:床号在 care_bed(床位表),不在 care_older(老人档案)

做机构版时,前台同事总问:档案里为啥不直接显示床号?我当时的做法是——老人主档(care_older)只放人;住没住、住哪交给入住单;这张床现在什么颜色看care_bed.status。楼宇、楼层、房间在care_space里级联,选完房型自动生成几张床。

这篇文章把hub(中台模块)里和楼宇、床位、房态图相关的代码串一遍:CareSpaceServiceImpl快速盖楼、CareBedServiceImpl判断能不能删床、房态 JSON 由institution(机构模块)的CareInstitutionBedRoomStateBridge来拼。你排查「快速添加失败、房态不刷新、在住床删不掉」时,按这个顺序看就行。

对照 演示站:建筑管理 · 楼宇设置(四列、快速添加)→房型管理(bedCount决定每间几张床)→接待 · 房态图(按楼层请求GET /care/bed/room-state)。本地接口前缀/admin-api/care/space/*、/admin-api/care/bed/*,请求头带上tenant-id。

图1 楼宇设置 · 四列级联对应 care_space.type 0/1/2

图2 档案列表 · 床号不在 care_older,避免换床时改老人主档

2. 表结构:care_space(楼宇/楼层/房间)与 care_bed.status(床位状态 0~5)

care_space.type:0 楼宇、1 楼层、2 房间;房间可挂layoutId(房型)。care_bed冗余buildingId/floorId/roomId,便于列表与报表,不必每次 join 树。

@TableName("care_bed") public class CareBedDO extends CareOrgBaseDO { @TableId private Long id; private String name; private Long roomId; private Long floorId; private Long buildingId; private Long layoutId; private BigDecimal bedPrice; /** 1养老 2护理 */ private Integer bedAttr; /** 0空闲 1预订 2在住 3请假占用 4维修 5试住 */ private Integer status; private Long olderId; private Integer sort; }
status含义房态图典型色institution 谁写入
0空闲灰/空床退订、腾床、释床
1预订绿订CareReserveServiceImpl
2在住蓝在床CareCheckinServiceImpl.assign
3请假占用橙CareLeaveServiceImpl
5试住专用CareTryliveServiceImpl
@TableName("care_space") public class CareSpaceDO extends CareOrgBaseDO { @TableId private Long id; private String name; private Long parentId; private String ancestors; /** 0楼宇 1楼层 2房间 */ private Integer type; private Long layoutId; private Long areaId; }
  • ancestors:0,buildingId,floorId链,建床时解析出buildingId/floorId写入 bed 冗余列。
  • layoutId:仅房间有效;非房间类型在 Service 里clearLayoutIfNotRoom置空。

房态按楼层拉床时走 Mapper 默认方法,避免 XML 里再写一遍排序:

@Mapper public interface CareBedMapper extends BaseMapperX<CareBedDO> { default List<CareBedDO> selectListByFloorId(Long floorId) { return selectList(new LambdaQueryWrapperX<CareBedDO>() .eq(CareBedDO::getFloorId, floorId) .orderByAsc(CareBedDO::getRoomId) .orderByAsc(CareBedDO::getSort) .orderByAsc(CareBedDO::getId)); } }

3. 快速添加整栋楼:CareSpaceServiceImpl.builder(盖楼)与 insertBeds(建床)

「快速添加」一次插入整栋楼:N 层 × M 间 × 房型床位数,事务内完成。

@Override @Transactional(rollbackFor = Exception.class) public Long builder(CareSpaceBuilderReqVO reqVO) { CareRoomLayoutDO layout = careRoomLayoutMapper.selectById(reqVO.getLayoutId()); int bedCount = layout.getBedCount() == null || layout.getBedCount() < 1 ? 1 : layout.getBedCount(); CareSpaceDO building = new CareSpaceDO(); building.setName(reqVO.getBuildingName()); building.setType(TYPE_BUILDING); building.setParentId(0L); building.setAncestors("0"); careSpaceMapper.insert(building); for (int i = 1; i <= reqVO.getStoreyNumber(); i++) { CareSpaceDO floor = new CareSpaceDO(); floor.setName(i + "层"); floor.setType(TYPE_FLOOR); floor.setParentId(building.getId()); floor.setAncestors("0," + building.getId()); careSpaceMapper.insert(floor); for (int r = 1; r <= reqVO.getRoomNumber(); r++) { CareSpaceDO room = new CareSpaceDO(); room.setName(pad2(r)); room.setType(TYPE_ROOM); room.setParentId(floor.getId()); room.setAncestors(floor.getAncestors() + "," + floor.getId()); room.setLayoutId(layout.getId()); careSpaceMapper.insert(room); insertBeds(room, building.getId(), floor.getId(), layout, bedCount); } } return building.getId(); }
步骤说明
读 layoutbedCount、bedPrice决定每间生成几张床
insert buildingancestors=0根节点
双层循环楼层 sort、房间 sort 与 UI 列顺序一致
insertBeds默认status=0空闲、bedAttr=1养老

真正「一间房几张床」在insertBeds里循环插入,名称01床、02床,价格和房型表一致:

private void insertBeds(CareSpaceDO room, Long buildingId, Long floorId, CareRoomLayoutDO layout, int bedCount) { for (int i = 1; i <= bedCount; i++) { CareBedDO bed = new CareBedDO(); bed.setName(pad2(i) + "床"); bed.setRoomId(room.getId()); bed.setFloorId(floorId); bed.setBuildingId(buildingId); bed.setLayoutId(layout.getId()); bed.setBedPrice(layout.getBedPrice()); bed.setBedAttr(1); bed.setStatus(BED_STATUS_IDLE); bed.setSort(i); careBedMapper.insert(bed); } }

单房间改房型时,createSpace在type=房间 && layoutId!=null时同样调用createBedsForRoom。

@Override @Transactional(rollbackFor = Exception.class) public Long createSpace(CareSpaceSaveReqVO createReqVO) { CareSpaceDO space = BeanUtils.toBean(createReqVO, CareSpaceDO.class); fillParentAndAncestors(space); clearLayoutIfNotRoom(space); careSpaceMapper.insert(space); if (Objects.equals(space.getType(), TYPE_ROOM) && space.getLayoutId() != null) { createBedsForRoom(space); } return space.getId(); }

删改约束(业务层校验,无 DB 外键):有子节点不可删;房间已有床不可乱改父级/房型;在住床存在时不可改房间名。

图3 房态图 · GET /care/bed/room-state?floorId=

4. 删改床位与房态 JSON:isBedOccupied(占用判断)、getRoomState(Bridge 桥接)

hub 侧统一isBedOccupied:在住、请假、预订、试住或olderId!=null时禁止删床、改房间、改养老/护理属性。

private static boolean isBedOccupied(CareBedDO bed) { return Objects.equals(bed.getStatus(), BED_STATUS_OCCUPIED) || Objects.equals(bed.getStatus(), BED_STATUS_LEAVE) || Objects.equals(bed.getStatus(), BED_STATUS_RESERVED) || Objects.equals(bed.getStatus(), BED_STATUS_TRYLIVE) || bed.getOlderId() != null; } @Override public void updateBed(CareBedSaveReqVO updateReqVO) { CareBedDO exists = validateExists(updateReqVO.getId()); if (isBedOccupied(exists) && !Objects.equals(updateReqVO.getRoomId(), exists.getRoomId())) { throw exception(BED_OCCUPIED); } // ... }

getBedTree把care_space与care_bed拼成「楼宇→楼层→房间→床位」树,供床位管理与筛选;房间节点附带layoutName、区域名(CareAreaNameBridge可选 SPI)。

房态图数据不在 hub 硬编码:CareBedRoomStateBridge由institution实现,聚合 checkin、老人姓名、预订态等。

@Autowired(required = false) private CareBedRoomStateBridge roomStateBridge; @Override public CareBedRoomStateRespVO getRoomState(Long floorId) { if (roomStateBridge == null) { throw exception(BED_ROOM_STATE_REQUIRES_INSTITUTION); } return roomStateBridge.buildRoomState(floorId); }
  • required=false:仅 hub 演示包时房态接口会抛业务错,提示需引入 institution JAR(与 Maven 交付一致)。

institution 里CareInstitutionBedRoomStateBridge会按楼层批量查 bed,再 join 预订/试住/在住 checkin/请假,拼成前端房卡需要的viewState(例如请假占床、住院展示态与维修态分开):

@Component public class CareInstitutionBedRoomStateBridge implements CareBedRoomStateBridge { @Override public CareBedRoomStateRespVO buildRoomState(Long floorId) { List<CareBedDO> beds = careBedMapper.selectListByFloorId(floorId); // 统计空床/预订/在住数量 → setEmptyBedCount / setReserveCount / setLiveCount Map<Long, CareReserveDO> reserveByBed = /* state=0 的 care_reserve 按 bedId */; Map<Long, CareCheckinDO> checkinByBed = /* status=3 在住按 bedId */; for (CareSpaceDO room : rooms) { for (CareBedDO bed : bedsByRoom.get(room.getId())) { rn.getChildren().add(toRoomStateBed(bed, olderMap, reserveByBed, ...)); } } return resp; } }
  • 批量 Map:避免房态图一页 N+1 查老人姓名;姓名脱敏在前端或 VO 里做。
  • 预订态:hub 只把 bed 标成status=1,卡片上的计划入住时间来自care_reserve(下一篇讲CareReserveServiceImpl)。

图4 床位统计 · 汇总依赖 bed.status 与在住 checkin 一致

图5 空床从房态图进预订/入住 · 颜色与 bed.status 一致

5. 后台接口:CareSpaceController(楼宇)、CareBedController(床位)

空间与床位 REST 前缀固定,Swagger 分组「机构空间 / 床位」。

@RestController @RequestMapping("/care/space") public class CareSpaceController { @PostMapping("/builder") @PreAuthorize("@ss.hasPermission('care:space:create')") public CommonResult<Long> builder(@Valid @RequestBody CareSpaceBuilderReqVO reqVO) { return success(careSpaceService.builder(reqVO)); } }
@RestController @RequestMapping("/care/bed") public class CareBedController { @GetMapping("/room-state") @PreAuthorize("@ss.hasPermission('care:bed:query')") public CommonResult<CareBedRoomStateRespVO> getRoomState(@RequestParam("floorId") Long floorId) { return success(careBedService.getRoomState(floorId)); } @GetMapping("/tree") @PreAuthorize("@ss.hasPermission('care:bed:query')") public CommonResult<List<CareBedTreeNodeVO>> getBedTree(@Valid CareBedTreeReqVO reqVO) { return success(careBedService.getBedTree(reqVO)); } }
接口前端视图
POST/care/space/builder楼宇设置 · 快速添加
GET/care/bed/tree床位管理树
GET/care/bed/room-state房态图按楼层刷新

6. curl 联调

快速添加(演示站等价于楼宇设置弹窗提交):

curl -X POST "http://localhost:48080/admin-api/care/space/builder" \ -H "Authorization: Bearer {token}" \ -H "tenant-id: 123" \ -H "Content-Type: application/json" \ -d '{"buildingName":"A栋","storeyNumber":3,"roomNumber":10,"layoutId":1}'

查床位树、房态:

curl -G "http://localhost:48080/admin-api/care/bed/tree" \ -H "Authorization: Bearer {token}" \ -H "tenant-id: 123" \ --data-urlencode "layoutId="
curl -G "http://localhost:48080/admin-api/care/bed/room-state" \ -H "Authorization: Bearer {token}" \ -H "tenant-id: 123" \ --data-urlencode "floorId=1001"

成功:{"code":0,"data":{"rooms":[...]}}。若code提示需 institution,检查zhijian-server/pom.xml是否依赖zhijian-module-institution。

application-local.yaml

yudao: tenant: enable: true spring: profiles: active: local

7. 谁改床位状态:institution(机构模块)写 bed.status,hub(中台)只管楼宇结构

同一张care_bed行会被 institution 在预订 / 试住 / assign 在住 / 请假 / 腾床时改status和olderId;hub 不负责写 checkin,只负责能不能删床、能不能改房间。我线上查房态不对,一般是bed.status 和 checkin 不同步(例如手工改库只改了 checkin),其次才是tenant-id 没带看到别园数据。

8. 踩坑

  • 房态接口报需 institution:zhijian-server/pom.xml没引zhijian-module-institution,或 Bridge 实现没进 Spring 扫描。
  • 删房间报 SPACE_HAS_BEDS:房间下还有 bed 行;在住/预订要先走退住或退订释床。
  • 改房间号抛 BED_OCCUPIED:在住、预订、试住、请假占用都拦roomId变更——别和财务对账打架。
  • ancestors 手工改乱:bed.buildingId/floorId和 room 对不上,报表按楼汇总会错;尽量走 Service 改结构。
  • 快速添加后房态空:确认layoutId对应房型bedCount>0,且 floorId 与房态图下拉选的是同一层。

Gitee 社区版源码:https://gitee.com/xinzexingjian-data_0/older_care

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询