From 2b61e2aaef34e19d013975c2e28e6cb36a50fdaf Mon Sep 17 00:00:00 2001 From: wanglei <3085637232@qq.com> Date: Fri, 19 Dec 2025 19:12:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=AE=A1=E7=90=86=E5=91=98?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=9C=B0=E5=8C=BA=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../water/controller/web/AdminController.java | 23 ++- .../water/controller/web/AreaController.java | 174 ++++++++++++++++ .../java/com/campus/water/entity/Admin.java | 4 + .../java/com/campus/water/entity/Area.java | 17 +- .../campus/water/mapper/AdminRepository.java | 6 + .../campus/water/mapper/AreaRepository.java | 14 ++ .../campus/water/service/AdminService.java | 38 ++++ .../com/campus/water/service/AreaService.java | 187 ++++++++++++++++++ 8 files changed, 460 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/campus/water/controller/web/AreaController.java create mode 100644 src/main/java/com/campus/water/service/AreaService.java diff --git a/src/main/java/com/campus/water/controller/web/AdminController.java b/src/main/java/com/campus/water/controller/web/AdminController.java index 3407f85..783bc59 100644 --- a/src/main/java/com/campus/water/controller/web/AdminController.java +++ b/src/main/java/com/campus/water/controller/web/AdminController.java @@ -39,6 +39,23 @@ public class AdminController { } } + /** + * 新增:获取指定区域的管理员列表 + */ + @GetMapping("/by-area/{areaId}") + @PreAuthorize("hasRole('SUPER_ADMIN')") // 只有超级管理员可以查看 + @Operation(summary = "按区域查询管理员", description = "查询指定区域下的所有管理员") + public ResponseEntity>> getAdminsByArea( + @PathVariable String areaId + ) { + try { + List admins = adminService.getAdminsByAreaId(areaId); + return ResponseEntity.ok(ResultVO.success(admins)); + } catch (Exception e) { + return ResponseEntity.ok(ResultVO.error(500, "查询失败:" + e.getMessage())); + } + } + /** * 获取所有管理员角色枚举 */ @@ -56,11 +73,13 @@ public class AdminController { /** * 新增/编辑管理员 + * 重写保存接口的注释,明确区域关联说明 */ @PostMapping("/save") - @PreAuthorize("hasRole('SUPER_ADMIN')") // 仅超级管理员可新增/编辑 - @Operation(summary = "保存管理员", description = "新增/编辑管理员,支持指定角色") + @PreAuthorize("hasRole('SUPER_ADMIN')") + @Operation(summary = "保存管理员", description = "新增/编辑管理员,区域管理员必须指定areaId") public ResponseEntity> saveAdmin(@RequestBody Admin admin) { + // 实现保持不变 try { Admin savedAdmin = adminService.saveAdmin(admin); return ResponseEntity.ok(ResultVO.success(savedAdmin)); diff --git a/src/main/java/com/campus/water/controller/web/AreaController.java b/src/main/java/com/campus/water/controller/web/AreaController.java new file mode 100644 index 0000000..aebfddf --- /dev/null +++ b/src/main/java/com/campus/water/controller/web/AreaController.java @@ -0,0 +1,174 @@ +package com.campus.water.controller.web; + +import com.campus.water.entity.Area; +import com.campus.water.service.AreaService; +import com.campus.water.util.ResultVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 区域管理控制器 + * 处理校园/楼宇/区域的增删改查接口请求 + */ +@RestController +@RequestMapping("/api/web/area") +@RequiredArgsConstructor +@Tag(name = "区域管理接口", description = "校园、楼宇、区域的层级管理(增删改查)") +public class AreaController { + + private final AreaService areaService; + + /** + * 新增区域(校园/楼宇/区域) + * 仅超级管理员可操作 + */ + @PostMapping("/add") + @PreAuthorize("hasRole('SUPER_ADMIN')") + @Operation(summary = "新增区域", description = "创建校园/楼宇/区域,严格校验层级关联规则") + public ResponseEntity> addArea( + @RequestBody @Parameter(description = "区域信息(名称/类型为必填)") Area area + ) { + try { + Area newArea = areaService.addArea(area); + return ResponseEntity.ok(ResultVO.success(newArea, "新增区域成功")); + } catch (RuntimeException e) { + return ResponseEntity.ok(ResultVO.error(500, "新增失败:" + e.getMessage())); + } + } + + /** + * 删除区域 + * 仅超级管理员可操作,需校验无关联管理员和子区域 + */ + @DeleteMapping("/delete/{areaId}") + @PreAuthorize("hasRole('SUPER_ADMIN')") + @Operation(summary = "删除区域", description = "删除指定区域,需确保无关联管理员和子区域") + public ResponseEntity> deleteArea( + @PathVariable @Parameter(description = "区域ID") String areaId + ) { + try { + areaService.deleteArea(areaId); + return ResponseEntity.ok(ResultVO.success(null, "删除区域成功")); + } catch (RuntimeException e) { + return ResponseEntity.ok(ResultVO.error(500, "删除失败:" + e.getMessage())); + } + } + + /** + * 修改区域信息 + * 仅超级管理员可操作,不允许修改区域类型 + */ + @PutMapping("/update") + @PreAuthorize("hasRole('SUPER_ADMIN')") + @Operation(summary = "修改区域", description = "更新区域名称/父级/地址/负责人等信息,不允许修改区域类型") + public ResponseEntity> updateArea( + @RequestBody @Parameter(description = "区域信息(areaId为必填)") Area area + ) { + try { + if (area.getAreaId() == null || area.getAreaId().trim().isEmpty()) { + return ResponseEntity.ok(ResultVO.error(400, "区域ID不能为空")); + } + Area updatedArea = areaService.updateArea(area); + return ResponseEntity.ok(ResultVO.success(updatedArea, "修改区域成功")); + } catch (RuntimeException e) { + return ResponseEntity.ok(ResultVO.error(500, "修改失败:" + e.getMessage())); + } + } + + /** + * 按ID查询区域详情 + * 超级管理员/区域管理员均可查询 + */ + @GetMapping("/detail/{areaId}") + @PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')") + @Operation(summary = "查询区域详情", description = "按ID查询单个区域的完整信息") + public ResponseEntity> getAreaDetail( + @PathVariable @Parameter(description = "区域ID") String areaId + ) { + try { + Area area = areaService.getAreaById(areaId); + return ResponseEntity.ok(ResultVO.success(area)); + } catch (RuntimeException e) { + return ResponseEntity.ok(ResultVO.error(500, "查询失败:" + e.getMessage())); + } + } + + /** + * 条件查询区域列表 + * 超级管理员/区域管理员均可查询,支持多条件筛选 + */ + @GetMapping("/list") + @PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')") + @Operation(summary = "查询区域列表", description = "支持按父级ID/区域类型/名称关键词筛选区域") + public ResponseEntity>> listAreas( + @RequestParam(required = false) @Parameter(description = "父级区域ID") String parentAreaId, + @RequestParam(required = false) @Parameter(description = "区域类型(campus/building/zone)") Area.AreaType areaType, + @RequestParam(required = false) @Parameter(description = "名称关键词(模糊匹配)") String keyword + ) { + try { + List areas = areaService.listAreas(parentAreaId, areaType, keyword); + return ResponseEntity.ok(ResultVO.success(areas)); + } catch (RuntimeException e) { + return ResponseEntity.ok(ResultVO.error(500, "查询失败:" + e.getMessage())); + } + } + + /** + * 查询所有校园(顶级节点) + * 超级管理员/区域管理员均可查询 + */ + @GetMapping("/list-campus") + @PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')") + @Operation(summary = "查询所有校园", description = "快速获取所有校园级别的顶级区域") + public ResponseEntity>> listCampus() { + try { + List campusList = areaService.listAreas(null, Area.AreaType.campus, null); + return ResponseEntity.ok(ResultVO.success(campusList)); + } catch (RuntimeException e) { + return ResponseEntity.ok(ResultVO.error(500, "查询失败:" + e.getMessage())); + } + } + + /** + * 查询指定校园下的所有楼宇 + * 超级管理员/区域管理员均可查询 + */ + @GetMapping("/list-building/{campusId}") + @PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')") + @Operation(summary = "查询校园下的楼宇", description = "按校园ID查询该校园下的所有楼宇") + public ResponseEntity>> listBuildingByCampus( + @PathVariable @Parameter(description = "校园ID") String campusId + ) { + try { + List buildingList = areaService.listAreas(campusId, Area.AreaType.building, null); + return ResponseEntity.ok(ResultVO.success(buildingList)); + } catch (RuntimeException e) { + return ResponseEntity.ok(ResultVO.error(500, "查询失败:" + e.getMessage())); + } + } + + /** + * 查询指定楼宇下的所有区域 + * 超级管理员/区域管理员均可查询 + */ + @GetMapping("/list-zone/{buildingId}") + @PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')") + @Operation(summary = "查询楼宇下的区域", description = "按楼宇ID查询该楼宇下的所有子区域") + public ResponseEntity>> listZoneByBuilding( + @PathVariable @Parameter(description = "楼宇ID") String buildingId + ) { + try { + List zoneList = areaService.listAreas(buildingId, Area.AreaType.zone, null); + return ResponseEntity.ok(ResultVO.success(zoneList)); + } catch (RuntimeException e) { + return ResponseEntity.ok(ResultVO.error(500, "查询失败:" + e.getMessage())); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/campus/water/entity/Admin.java b/src/main/java/com/campus/water/entity/Admin.java index d75431c..5c0a125 100644 --- a/src/main/java/com/campus/water/entity/Admin.java +++ b/src/main/java/com/campus/water/entity/Admin.java @@ -25,6 +25,10 @@ public class Admin { @Column(name = "phone", length = 20) private String phone; + // 新增:管理员负责的区域ID(区域管理员专用) + @Column(name = "area_id", length = 20) + private String areaId; + // 恢复三个角色枚举 @Enumerated(EnumType.STRING) @Column(name = "role", length = 50, nullable = false) diff --git a/src/main/java/com/campus/water/entity/Area.java b/src/main/java/com/campus/water/entity/Area.java index 45a1da0..74afa7c 100644 --- a/src/main/java/com/campus/water/entity/Area.java +++ b/src/main/java/com/campus/water/entity/Area.java @@ -8,12 +8,15 @@ package com.campus.water.entity; import lombok.Data; import jakarta.persistence.*; import java.time.LocalDateTime; +import org.hibernate.annotations.GenericGenerator; @Data @Entity @Table(name = "area") public class Area { @Id + @GeneratedValue(generator = "uuid") // 新增:自动生成UUID + @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator") @Column(name = "area_id", length = 20) private String areaId; @@ -43,6 +46,18 @@ public class Area { private LocalDateTime updatedTime = LocalDateTime.now(); public enum AreaType { - campus, building, zone + campus("校园"), + building("楼宇"), + zone("区域"); + + private final String desc; + + AreaType(String desc) { + this.desc = desc; + } + + public String getDesc() { + return desc; + } } } \ No newline at end of file diff --git a/src/main/java/com/campus/water/mapper/AdminRepository.java b/src/main/java/com/campus/water/mapper/AdminRepository.java index 0e3374e..bc3efbd 100644 --- a/src/main/java/com/campus/water/mapper/AdminRepository.java +++ b/src/main/java/com/campus/water/mapper/AdminRepository.java @@ -21,6 +21,12 @@ public interface AdminRepository extends JpaRepository { // 按手机号查询 Optional findByPhone(String phone); + // 新增:按区域ID查询管理员 + List findByAreaId(String areaId); + + // 新增:按角色和区域ID查询(用于区域管理员的权限控制) + List findByRoleAndAreaId(Admin.AdminRole role, String areaId); + // 按角色查询管理员(核心:恢复角色筛选) List findByRole(Admin.AdminRole role); diff --git a/src/main/java/com/campus/water/mapper/AreaRepository.java b/src/main/java/com/campus/water/mapper/AreaRepository.java index 3638d5b..da22769 100644 --- a/src/main/java/com/campus/water/mapper/AreaRepository.java +++ b/src/main/java/com/campus/water/mapper/AreaRepository.java @@ -14,6 +14,20 @@ public interface AreaRepository extends JpaRepository { // 根据父区域ID查询子区域 List findByParentAreaId(String parentAreaId); + // 新增按区域类型查询 + List findByAreaTypeOrderByCreatedTimeDesc(Area.AreaType areaType); + + // 按父级ID+类型查询(如查询某校园下的所有楼宇) + List findByParentAreaIdAndAreaType(String parentAreaId, Area.AreaType areaType); + + + + // 按名称模糊查询 + List findByAreaNameContaining(String keyword); + + // 查询所有(按创建时间倒序) + List findAllByOrderByCreatedTimeDesc(); + // 根据管理员姓名查询区域 List findByManager(String manager); diff --git a/src/main/java/com/campus/water/service/AdminService.java b/src/main/java/com/campus/water/service/AdminService.java index 6f319bb..c720dbe 100644 --- a/src/main/java/com/campus/water/service/AdminService.java +++ b/src/main/java/com/campus/water/service/AdminService.java @@ -2,6 +2,7 @@ package com.campus.water.service; import com.campus.water.entity.Admin; import com.campus.water.mapper.AdminRepository; +import com.campus.water.mapper.AreaRepository; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; @@ -16,6 +17,8 @@ import java.util.Optional; public class AdminService { private final AdminRepository adminRepository; + private final AreaRepository areaRepository; // 新增注入 + @Autowired private PasswordEncoder passwordEncoder; @@ -38,6 +41,17 @@ public class AdminService { } } + /** + * 新增:获取指定区域的管理员列表 + */ + public List getAdminsByAreaId(String areaId) { + // 校验区域是否存在 + if (!areaRepository.existsById(areaId)) { + throw new RuntimeException("区域不存在:" + areaId); + } + return adminRepository.findByAreaId(areaId); + } + /** * 按ID查询管理员 */ @@ -47,12 +61,28 @@ public class AdminService { /** * 新增/修改管理员(支持指定角色) + * 重写保存方法,增加区域校验(区域管理员必须关联区域) */ public Admin saveAdmin(Admin admin) { admin.setUpdatedTime(LocalDateTime.now()); if (admin.getCreatedTime() == null) { admin.setCreatedTime(LocalDateTime.now()); } + + // 区域管理员必须关联区域 + if (admin.getRole() == Admin.AdminRole.ROLE_AREA_ADMIN) { + if (admin.getAreaId() == null || admin.getAreaId().trim().isEmpty()) { + throw new RuntimeException("区域管理员必须关联具体区域"); + } + // 校验关联的区域是否存在 + if (!areaRepository.existsById(admin.getAreaId())) { + throw new RuntimeException("关联的区域不存在:" + admin.getAreaId()); + } + } else { + // 非区域管理员清空区域ID + admin.setAreaId(null); + } + return adminRepository.save(admin); } @@ -77,4 +107,12 @@ public class AdminService { public Admin.AdminRole[] getAllRoles() { return Admin.AdminRole.values(); } + + public AreaRepository getAreaRepository() { + return areaRepository; + } + + public void setPasswordEncoder(PasswordEncoder passwordEncoder) { + this.passwordEncoder = passwordEncoder; + } } \ No newline at end of file diff --git a/src/main/java/com/campus/water/service/AreaService.java b/src/main/java/com/campus/water/service/AreaService.java new file mode 100644 index 0000000..f59a170 --- /dev/null +++ b/src/main/java/com/campus/water/service/AreaService.java @@ -0,0 +1,187 @@ +package com.campus.water.service; + +import com.campus.water.entity.Area; +import com.campus.water.mapper.AreaRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +/** + * 区域管理服务层 + * 处理校园/楼宇/区域的增删改查业务逻辑 + */ +@Service +@RequiredArgsConstructor +public class AreaService { + + private final AreaRepository areaRepository; + private final AdminService adminService; // 用于删除时校验管理员关联 + + /** + * 新增区域(校园/楼宇/区域) + * 校验规则: + * 1. 名称和类型不能为空 + * 2. 楼宇必须关联校园作为父级 + * 3. 区域必须关联楼宇作为父级 + * 4. 校园无需父级(顶级节点) + */ + public Area addArea(Area area) { + // 基础字段校验 + if (area.getAreaName() == null || area.getAreaName().trim().isEmpty()) { + throw new RuntimeException("区域名称不能为空"); + } + if (area.getAreaType() == null) { + throw new RuntimeException("区域类型不能为空(校园/楼宇/区域)"); + } + + // 层级关联校验 + handleAreaLevelCheck(area); + + // 初始化时间字段(兜底,防止手动修改) + area.setCreatedTime(LocalDateTime.now()); + area.setUpdatedTime(LocalDateTime.now()); + + return areaRepository.save(area); + } + + /** + * 删除区域 + * 校验规则: + * 1. 区域必须存在 + * 2. 无管理员关联该区域 + * 3. 无下级子区域 + */ + public void deleteArea(String areaId) { + // 1. 校验区域是否存在 + Area existArea = areaRepository.findById(areaId) + .orElseThrow(() -> new RuntimeException("区域不存在:" + areaId)); + + // 2. 校验是否有管理员关联该区域 + List relatedAdmins = adminService.getAdminsByAreaId(areaId); + if (!relatedAdmins.isEmpty()) { + throw new RuntimeException("该区域关联了" + relatedAdmins.size() + "个管理员,无法删除"); + } + + // 3. 校验是否有下级子区域 + List childAreas = areaRepository.findByParentAreaId(areaId); + if (!childAreas.isEmpty()) { + throw new RuntimeException("该区域包含" + childAreas.size() + "个子区域,无法删除(请先删除子区域)"); + } + + // 执行删除 + areaRepository.delete(existArea); + } + + /** + * 修改区域信息 + * 支持修改:名称、父级、地址、负责人、联系电话 + * 不允许修改:区域类型(避免层级混乱) + */ + public Area updateArea(Area area) { + // 1. 校验区域是否存在 + Area existArea = areaRepository.findById(area.getAreaId()) + .orElseThrow(() -> new RuntimeException("区域不存在:" + area.getAreaId())); + + // 2. 基础字段校验 + if (area.getAreaName() == null || area.getAreaName().trim().isEmpty()) { + throw new RuntimeException("区域名称不能为空"); + } + + // 3. 层级关联校验(如果修改了父级ID) + if (!equalsWithNull(existArea.getParentAreaId(), area.getParentAreaId())) { + handleAreaLevelCheck(area); + } + + // 4. 赋值(仅更新允许修改的字段) + existArea.setAreaName(area.getAreaName()); + existArea.setParentAreaId(area.getParentAreaId()); + existArea.setAddress(area.getAddress()); + existArea.setManager(area.getManager()); + existArea.setManagerPhone(area.getManagerPhone()); + existArea.setUpdatedTime(LocalDateTime.now()); + + // 5. 保存修改 + return areaRepository.save(existArea); + } + + /** + * 按ID查询区域详情 + */ + public Area getAreaById(String areaId) { + return areaRepository.findById(areaId) + .orElseThrow(() -> new RuntimeException("区域不存在:" + areaId)); + } + + /** + * 条件查询区域列表 + * 支持筛选条件:父级ID、区域类型、名称关键词 + */ + public List listAreas(String parentAreaId, Area.AreaType areaType, String keyword) { + if (parentAreaId != null && !parentAreaId.trim().isEmpty()) { + // 按父级ID查询 + if (areaType != null) { + // 父级ID + 类型 + return areaRepository.findByParentAreaIdAndAreaType(parentAreaId, areaType); + } else { + // 仅父级ID + return areaRepository.findByParentAreaId(parentAreaId); + } + } else if (areaType != null) { + // 按类型查询 + return areaRepository.findByAreaTypeOrderByCreatedTimeDesc(areaType); + } else if (keyword != null && !keyword.trim().isEmpty()) { + // 按名称模糊查询 + return areaRepository.findByAreaNameContaining(keyword); + } else { + // 查询所有(按创建时间倒序) + return areaRepository.findAllByOrderByCreatedTimeDesc(); + } + } + + /** + * 辅助方法:校验区域层级关联规则 + */ + private void handleAreaLevelCheck(Area area) { + Area.AreaType type = area.getAreaType(); + String parentId = area.getParentAreaId(); + + // 1. 校园(顶级节点):不允许设置父级 + if (type == Area.AreaType.campus) { + if (parentId != null && !parentId.trim().isEmpty()) { + throw new RuntimeException("校园作为顶级节点,不允许关联父级区域"); + } + return; + } + + // 2. 楼宇/区域:必须设置父级 + if (parentId == null || parentId.trim().isEmpty()) { + throw new RuntimeException(type.getDesc() + "必须关联父级区域"); + } + + // 3. 校验父级区域是否存在且类型匹配 + Area parentArea = areaRepository.findById(parentId) + .orElseThrow(() -> new RuntimeException("父级区域不存在:" + parentId)); + + if (type == Area.AreaType.building && parentArea.getAreaType() != Area.AreaType.campus) { + throw new RuntimeException("楼宇的父级必须是校园"); + } + if (type == Area.AreaType.zone && parentArea.getAreaType() != Area.AreaType.building) { + throw new RuntimeException("区域的父级必须是楼宇"); + } + } + + /** + * 辅助方法:判断两个值是否相等(兼容null) + */ + private boolean equalsWithNull(Object a, Object b) { + if (a == null && b == null) { + return true; + } + if (a == null || b == null) { + return false; + } + return a.equals(b); + } +} \ No newline at end of file -- 2.34.1