feat(dept): 创建部门管理后端业务层与路由

ISysDeptService + SysDeptService: CRUD + 子部门删除校验 + 审计日志;
DeptCreateRequest/DeptUpdateRequest: 验证注解与前端 VALIDATION 对齐;
SysDeptHandler: RESTful API (GET/POST/PUT/DELETE /api/depts);
SystemRouter: 注册部门路由。
This commit is contained in:
张翔
2026-05-06 16:15:41 +08:00
parent f38616c8d8
commit 6159febb07
6 changed files with 277 additions and 1 deletions
@@ -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)
@@ -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<SysDept> findAll();
Flux<SysDept> findByParentId(Long parentId);
Mono<SysDept> findById(Long id);
Mono<SysDept> save(SysDept dept);
Mono<Void> deleteById(Long id);
}
@@ -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<SysDept> findAll() {
return repository.findAll();
}
@Override
public Flux<SysDept> findByParentId(Long parentId) {
return repository.findByParentId(parentId);
}
@Override
public Mono<SysDept> findById(Long id) {
return repository.findById(id);
}
@Override
public Mono<SysDept> save(SysDept dept) {
return repository.save(dept)
.flatMap(saved -> AuditLogHelper.record(auditLogService, "Dept", saved.getId(), "CREATE", saved)
.thenReturn(saved));
}
@Override
public Mono<Void> 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();
}
}
@@ -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; }
}
@@ -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; }
}
@@ -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<ServerResponse> getAllDepts(ServerRequest request) {
return ServerResponse.ok()
.body(deptService.findAll(), SysDept.class);
}
@Operation(summary = "根据ID获取部门", description = "根据部门ID获取详细信息")
public Mono<ServerResponse> 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<ServerResponse> 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<ServerResponse> 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<ServerResponse> 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<ServerResponse> badRequest(String message) {
return ServerResponse.badRequest()
.bodyValue(Map.of("code", HttpStatus.BAD_REQUEST.value(), "message", message, "timestamp", LocalDateTime.now()));
}
}