From 6159febb07983b1147c57caff652e13e316037d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Wed, 6 May 2026 16:15:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(dept):=20=E5=88=9B=E5=BB=BA=E9=83=A8?= =?UTF-8?q?=E9=97=A8=E7=AE=A1=E7=90=86=E5=90=8E=E7=AB=AF=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E5=B1=82=E4=B8=8E=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ISysDeptService + SysDeptService: CRUD + 子部门删除校验 + 审计日志; DeptCreateRequest/DeptUpdateRequest: 验证注解与前端 VALIDATION 对齐; SysDeptHandler: RESTful API (GET/POST/PUT/DELETE /api/depts); SystemRouter: 注册部门路由。 --- .../manage/app/config/SystemRouter.java | 11 ++- .../sys/core/service/ISysDeptService.java | 13 +++ .../sys/core/service/impl/SysDeptService.java | 58 ++++++++++++ .../sys/dto/request/DeptCreateRequest.java | 52 ++++++++++ .../sys/dto/request/DeptUpdateRequest.java | 50 ++++++++++ .../sys/handler/dept/SysDeptHandler.java | 94 +++++++++++++++++++ 6 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/ISysDeptService.java create mode 100644 novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysDeptService.java create mode 100644 novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/dto/request/DeptCreateRequest.java create mode 100644 novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/dto/request/DeptUpdateRequest.java create mode 100644 novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/handler/dept/SysDeptHandler.java diff --git a/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/SystemRouter.java b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/SystemRouter.java index 6e1f086..5eba592 100644 --- a/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/SystemRouter.java +++ b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/SystemRouter.java @@ -7,6 +7,7 @@ import cn.novalon.manage.sys.handler.dictionary.DictionaryHandler; import cn.novalon.manage.sys.handler.dict.SysDictHandler; import cn.novalon.manage.sys.handler.log.SysLogHandler; import cn.novalon.manage.sys.handler.log.OperationLogHandler; +import cn.novalon.manage.sys.handler.dept.SysDeptHandler; import cn.novalon.manage.sys.handler.menu.MenuHandler; import cn.novalon.manage.sys.handler.role.SysRoleHandler; import cn.novalon.manage.sys.handler.permission.SysPermissionHandler; @@ -51,7 +52,8 @@ public class SystemRouter { SysUserMessageHandler messageHandler, SysFileHandler fileHandler, SysPermissionHandler permissionHandler, - PasswordDiagnosticHandler passwordDiagnosticHandler) { + PasswordDiagnosticHandler passwordDiagnosticHandler, + SysDeptHandler deptHandler) { return route() // ========== 诊断路由 ========== @@ -115,6 +117,13 @@ public class SystemRouter { .PUT("/api/config/{id}", configHandler::updateConfig) .DELETE("/api/config/{id}", configHandler::deleteConfig) + // ========== 部门路由 ========== + .GET("/api/depts", deptHandler::getAllDepts) + .GET("/api/depts/{id}", deptHandler::getDeptById) + .POST("/api/depts", deptHandler::createDept) + .PUT("/api/depts/{id}", deptHandler::updateDept) + .DELETE("/api/depts/{id}", deptHandler::deleteDept) + // ========== 日志路由 ========== .GET("/api/logs/login", logHandler::getAllLoginLogs) .GET("/api/logs/login/page", logHandler::getLoginLogsByPage) diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/ISysDeptService.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/ISysDeptService.java new file mode 100644 index 0000000..02c9490 --- /dev/null +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/ISysDeptService.java @@ -0,0 +1,13 @@ +package cn.novalon.manage.sys.core.service; + +import cn.novalon.manage.sys.core.domain.SysDept; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface ISysDeptService { + Flux findAll(); + Flux findByParentId(Long parentId); + Mono findById(Long id); + Mono save(SysDept dept); + Mono deleteById(Long id); +} diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysDeptService.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysDeptService.java new file mode 100644 index 0000000..5f912c8 --- /dev/null +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/SysDeptService.java @@ -0,0 +1,58 @@ +package cn.novalon.manage.sys.core.service.impl; + +import cn.novalon.manage.sys.audit.AuditLogHelper; +import cn.novalon.manage.sys.audit.service.IAuditLogService; +import cn.novalon.manage.sys.core.domain.SysDept; +import cn.novalon.manage.sys.core.repository.ISysDeptRepository; +import cn.novalon.manage.sys.core.service.ISysDeptService; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Service +public class SysDeptService implements ISysDeptService { + + private final ISysDeptRepository repository; + private final IAuditLogService auditLogService; + + public SysDeptService(ISysDeptRepository repository, IAuditLogService auditLogService) { + this.repository = repository; + this.auditLogService = auditLogService; + } + + @Override + public Flux findAll() { + return repository.findAll(); + } + + @Override + public Flux findByParentId(Long parentId) { + return repository.findByParentId(parentId); + } + + @Override + public Mono findById(Long id) { + return repository.findById(id); + } + + @Override + public Mono save(SysDept dept) { + return repository.save(dept) + .flatMap(saved -> AuditLogHelper.record(auditLogService, "Dept", saved.getId(), "CREATE", saved) + .thenReturn(saved)); + } + + @Override + public Mono deleteById(Long id) { + return repository.findById(id) + .flatMap(dept -> repository.countByParentId(id) + .flatMap(count -> { + if (count > 0) { + return Mono.error(new IllegalArgumentException("该部门下存在子部门,无法删除")); + } + return repository.deleteById(id) + .then(AuditLogHelper.record(auditLogService, "Dept", id, "DELETE", dept, null)); + })) + .then(); + } +} diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/dto/request/DeptCreateRequest.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/dto/request/DeptCreateRequest.java new file mode 100644 index 0000000..529ca79 --- /dev/null +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/dto/request/DeptCreateRequest.java @@ -0,0 +1,52 @@ +package cn.novalon.manage.sys.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +@Schema(description = "部门创建请求") +public class DeptCreateRequest { + + @Schema(description = "上级部门ID", example = "0") + private Long parentId; + + @Schema(description = "部门名称", example = "研发部") + @NotBlank(message = "部门名称不能为空") + @Size(min = 1, max = 100, message = "部门名称长度必须在1-100之间") + private String deptName; + + @Schema(description = "排序", example = "0") + @Min(value = 0, message = "排序不能为负数") + private Integer orderNum; + + @Schema(description = "负责人", example = "张三") + @Size(max = 50, message = "负责人长度不能超过50") + private String leader; + + @Schema(description = "手机号", example = "13800138000") + @Size(max = 20, message = "手机号长度不能超过20") + private String phone; + + @Schema(description = "邮箱", example = "dept@example.com") + @Size(max = 100, message = "邮箱长度不能超过100") + private String email; + + @Schema(description = "状态:0-禁用,1-正常", example = "1") + private Integer status; + + public Long getParentId() { return parentId; } + public void setParentId(Long parentId) { this.parentId = parentId; } + public String getDeptName() { return deptName; } + public void setDeptName(String deptName) { this.deptName = deptName; } + public Integer getOrderNum() { return orderNum; } + public void setOrderNum(Integer orderNum) { this.orderNum = orderNum; } + public String getLeader() { return leader; } + public void setLeader(String leader) { this.leader = leader; } + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public Integer getStatus() { return status; } + public void setStatus(Integer status) { this.status = status; } +} diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/dto/request/DeptUpdateRequest.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/dto/request/DeptUpdateRequest.java new file mode 100644 index 0000000..be085b2 --- /dev/null +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/dto/request/DeptUpdateRequest.java @@ -0,0 +1,50 @@ +package cn.novalon.manage.sys.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Size; + +@Schema(description = "部门更新请求") +public class DeptUpdateRequest { + + @Schema(description = "上级部门ID", example = "0") + private Long parentId; + + @Schema(description = "部门名称", example = "研发部") + @Size(min = 1, max = 100, message = "部门名称长度必须在1-100之间") + private String deptName; + + @Schema(description = "排序", example = "0") + @Min(value = 0, message = "排序不能为负数") + private Integer orderNum; + + @Schema(description = "负责人", example = "张三") + @Size(max = 50, message = "负责人长度不能超过50") + private String leader; + + @Schema(description = "手机号", example = "13800138000") + @Size(max = 20, message = "手机号长度不能超过20") + private String phone; + + @Schema(description = "邮箱", example = "dept@example.com") + @Size(max = 100, message = "邮箱长度不能超过100") + private String email; + + @Schema(description = "状态:0-禁用,1-正常", example = "1") + private Integer status; + + public Long getParentId() { return parentId; } + public void setParentId(Long parentId) { this.parentId = parentId; } + public String getDeptName() { return deptName; } + public void setDeptName(String deptName) { this.deptName = deptName; } + public Integer getOrderNum() { return orderNum; } + public void setOrderNum(Integer orderNum) { this.orderNum = orderNum; } + public String getLeader() { return leader; } + public void setLeader(String leader) { this.leader = leader; } + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public Integer getStatus() { return status; } + public void setStatus(Integer status) { this.status = status; } +} diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/handler/dept/SysDeptHandler.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/handler/dept/SysDeptHandler.java new file mode 100644 index 0000000..969935b --- /dev/null +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/handler/dept/SysDeptHandler.java @@ -0,0 +1,94 @@ +package cn.novalon.manage.sys.handler.dept; + +import cn.novalon.manage.sys.core.domain.SysDept; +import cn.novalon.manage.sys.core.service.ISysDeptService; +import cn.novalon.manage.sys.dto.request.DeptCreateRequest; +import cn.novalon.manage.sys.dto.request.DeptUpdateRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +import java.time.LocalDateTime; +import java.util.Map; + +@Component +@Tag(name = "部门管理", description = "部门树形结构相关操作") +public class SysDeptHandler { + + private final ISysDeptService deptService; + + public SysDeptHandler(ISysDeptService deptService) { + this.deptService = deptService; + } + + @Operation(summary = "获取所有部门", description = "获取系统中所有部门列表(树形结构)") + public Mono getAllDepts(ServerRequest request) { + return ServerResponse.ok() + .body(deptService.findAll(), SysDept.class); + } + + @Operation(summary = "根据ID获取部门", description = "根据部门ID获取详细信息") + public Mono getDeptById(ServerRequest request) { + Long id = Long.valueOf(request.pathVariable("id")); + return deptService.findById(id) + .flatMap(dept -> ServerResponse.ok().bodyValue(dept)) + .switchIfEmpty(ServerResponse.notFound().build()); + } + + @Operation(summary = "创建部门", description = "创建新部门") + public Mono createDept(ServerRequest request) { + return request.bodyToMono(DeptCreateRequest.class) + .flatMap(req -> { + SysDept dept = new SysDept(); + dept.setParentId(req.getParentId() != null ? req.getParentId() : 0L); + dept.setDeptName(req.getDeptName()); + dept.setOrderNum(req.getOrderNum() != null ? req.getOrderNum() : 0); + dept.setLeader(req.getLeader()); + dept.setPhone(req.getPhone()); + dept.setEmail(req.getEmail()); + dept.setStatus(req.getStatus() != null ? req.getStatus() : 1); + return deptService.save(dept); + }) + .flatMap(saved -> ServerResponse.status(HttpStatus.CREATED).bodyValue(saved)) + .onErrorResume(IllegalArgumentException.class, e -> badRequest(e.getMessage())); + } + + @Operation(summary = "更新部门", description = "更新部门信息") + public Mono updateDept(ServerRequest request) { + Long id = Long.valueOf(request.pathVariable("id")); + return request.bodyToMono(DeptUpdateRequest.class) + .flatMap(req -> deptService.findById(id) + .flatMap(existing -> { + if (req.getParentId() != null) existing.setParentId(req.getParentId()); + if (req.getDeptName() != null) existing.setDeptName(req.getDeptName()); + if (req.getOrderNum() != null) existing.setOrderNum(req.getOrderNum()); + if (req.getLeader() != null) existing.setLeader(req.getLeader()); + if (req.getPhone() != null) existing.setPhone(req.getPhone()); + if (req.getEmail() != null) existing.setEmail(req.getEmail()); + if (req.getStatus() != null) existing.setStatus(req.getStatus()); + existing.setUpdatedAt(LocalDateTime.now()); + return deptService.save(existing); + })) + .flatMap(updated -> ServerResponse.ok().bodyValue(updated)) + .switchIfEmpty(ServerResponse.notFound().build()) + .onErrorResume(IllegalArgumentException.class, e -> badRequest(e.getMessage())); + } + + @Operation(summary = "删除部门", description = "删除指定部门(有子部门时拒绝)") + public Mono deleteDept(ServerRequest request) { + Long id = Long.valueOf(request.pathVariable("id")); + return deptService.deleteById(id) + .then(ServerResponse.noContent().build()) + .onErrorResume(IllegalArgumentException.class, e -> badRequest(e.getMessage())); + } + + private Mono badRequest(String message) { + return ServerResponse.badRequest() + .bodyValue(Map.of("code", HttpStatus.BAD_REQUEST.value(), "message", message, "timestamp", LocalDateTime.now())); + } +}