diff --git a/backend/src/main/java/com/qqchen/deploy/backend/api/DepartmentApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/api/DepartmentApiController.java new file mode 100644 index 00000000..8b246d21 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/api/DepartmentApiController.java @@ -0,0 +1,36 @@ +package com.qqchen.deploy.backend.api; + +import com.qqchen.deploy.backend.entity.Department; +import com.qqchen.deploy.backend.framework.api.Response; +import com.qqchen.deploy.backend.framework.controller.BaseController; +import com.qqchen.deploy.backend.model.DepartmentDTO; +import com.qqchen.deploy.backend.model.query.DepartmentQuery; +import com.qqchen.deploy.backend.service.IDepartmentService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Tag(name = "部门管理") +@RestController +@RequestMapping("/api/v1/department") +public class DepartmentApiController extends BaseController { + + @Resource + private IDepartmentService departmentService; + + @Operation(summary = "获取部门树") + @GetMapping("/tree") + public Response> getTree() { + return Response.success(departmentService.getTree()); + } + + @Override + protected void exportData(jakarta.servlet.http.HttpServletResponse response, List data) { + // TODO: 实现导出功能 + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/converter/DepartmentConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/converter/DepartmentConverter.java new file mode 100644 index 00000000..2a6df677 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/converter/DepartmentConverter.java @@ -0,0 +1,12 @@ +package com.qqchen.deploy.backend.converter; + +import com.qqchen.deploy.backend.entity.Department; +import com.qqchen.deploy.backend.framework.converter.BaseConverter; +import com.qqchen.deploy.backend.model.DepartmentDTO; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface DepartmentConverter extends BaseConverter { + // 继承了 BaseConverter 的方法,不需要再声明 +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java index 76bf39a0..c52ca6c7 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java @@ -49,7 +49,14 @@ public enum ResponseCode { // 角色标签相关错误码 (4300-4399) ROLE_TAG_NAME_EXISTS(4300, "role.tag.name.exists"), ROLE_TAG_NOT_FOUND(4301, "role.tag.not.found"), - ROLE_TAG_IN_USE(4302, "role.tag.in.use"); + ROLE_TAG_IN_USE(4302, "role.tag.in.use"), + + // 部门相关错误码 (2300-2399) + DEPARTMENT_NOT_FOUND(2300, "department.not.found"), + DEPARTMENT_CODE_EXISTS(2301, "department.code.exists"), + DEPARTMENT_NAME_EXISTS(2302, "department.name.exists"), + DEPARTMENT_PARENT_NOT_FOUND(2303, "department.parent.not.found"), + DEPARTMENT_HAS_CHILDREN(2304, "department.has.children"); private final int code; private final String messageKey; // 国际化消息key diff --git a/backend/src/main/java/com/qqchen/deploy/backend/model/DepartmentDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/model/DepartmentDTO.java new file mode 100644 index 00000000..6a4a6d45 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/model/DepartmentDTO.java @@ -0,0 +1,30 @@ +package com.qqchen.deploy.backend.model; + +import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +public class DepartmentDTO extends BaseDTO { + + private String code; + + private String name; + + private String description; + + private Boolean enabled; + + private Long leaderId; + + private String leaderName; + + private Long parentId; + + private Integer sort; + + private List children; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/model/query/DepartmentQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/model/query/DepartmentQuery.java new file mode 100644 index 00000000..5fa79025 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/model/query/DepartmentQuery.java @@ -0,0 +1,40 @@ +package com.qqchen.deploy.backend.model.query; + +import com.qqchen.deploy.backend.framework.query.BaseQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class DepartmentQuery extends BaseQuery { + + /** + * 部门编码 + */ + private String code; + + /** + * 部门名称 + */ + private String name; + + /** + * 上级部门ID + */ + private Long parentId; + + /** + * 是否启用 + */ + private Boolean enabled; + + /** + * 部门负责人ID + */ + private Long leaderId; + + /** + * 部门负责人姓名 + */ + private String leaderName; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/IDepartmentService.java b/backend/src/main/java/com/qqchen/deploy/backend/service/IDepartmentService.java index b12c2432..047e2b04 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/service/IDepartmentService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/IDepartmentService.java @@ -1,18 +1,15 @@ -//package com.qqchen.deploy.backend.service; -// -// -//import com.qqchen.deploy.backend.framework.service.IBaseService; -//import com.qqchen.deploy.backend.entity.Department; -// -//import java.util.List; -// -//public interface IDepartmentService extends IBaseService { -// -// List getTree(); -// -// void validateCode(String code); -// -// void validateName(String name); -// -// Integer getNextSort(Long parentId); -//} \ No newline at end of file +package com.qqchen.deploy.backend.service; + +import com.qqchen.deploy.backend.entity.Department; +import com.qqchen.deploy.backend.framework.service.IBaseService; +import com.qqchen.deploy.backend.model.DepartmentDTO; + +import java.util.List; + +public interface IDepartmentService extends IBaseService { + + /** + * 获取部门树 + */ + List getTree(); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/DepartmentServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/DepartmentServiceImpl.java index df494e5a..a476703f 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/DepartmentServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/DepartmentServiceImpl.java @@ -1,105 +1,76 @@ -//package com.qqchen.deploy.backend.service.impl; -// -//import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; -//import com.qqchen.deploy.backend.entity.Department; -//import com.qqchen.deploy.backend.repository.IDepartmentRepository; -//import com.qqchen.deploy.backend.service.IDepartmentService; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.stereotype.Service; -//import jakarta.transaction.Transactional; -//import java.util.ArrayList; -//import java.util.List; -//import java.util.Map; -//import java.util.stream.Collectors; -// -//@Service -//@Transactional -//public class DepartmentServiceImpl extends BaseServiceImpl implements IDepartmentService { -// -// private final IDepartmentRepository repository; -// -// @Autowired -// public DepartmentServiceImpl(IDepartmentRepository repository) { -// super(repository); -// this.repository = repository; -// } -// -// @Override -// public Department create(Department entity) { -// validateCode(entity.getCode()); -// validateName(entity.getName()); -// return repository.save(entity); -// } -// -// public Department update(Long id, Department entity) { -// Department existing = repository.findById(id) -// .orElseThrow(() -> new RuntimeException("部门不存在")); -// -// entity.setVersion(existing.getVersion()); -// entity.setId(id); -// -// if (!existing.getCode().equals(entity.getCode())) { -// if (repository.existsByCodeAndDeletedFalse(entity.getCode())) { -// throw new RuntimeException("部门编码已存在"); -// } -// } -// -// if (!existing.getName().equals(entity.getName())) { -// if (repository.existsByNameAndDeletedFalse(entity.getName())) { -// throw new RuntimeException("部门名称已存在"); -// } -// } -// -// entity.setCreateTime(existing.getCreateTime()); -// entity.setCreateBy(existing.getCreateBy()); -// entity.setDeleted(existing.getDeleted()); -// -// return repository.save(entity); -// } -// -// @Override -// public List getTree() { -// List departments = repository.findByDeletedFalseOrderBySort(); -// return buildTree(departments); -// } -// -// @Override -// public void validateCode(String code) { -// if (repository.existsByCodeAndDeletedFalse(code)) { -// throw new RuntimeException("部门编码已存在"); -// } -// } -// -// @Override -// public void validateName(String name) { -// if (repository.existsByNameAndDeletedFalse(name)) { -// throw new RuntimeException("部门名称已存在"); -// } -// } -// -// @Override -// public Integer getNextSort(Long parentId) { -// return repository.findMaxSortByParentId(parentId) + 1; -// } -// -// private List buildTree(List departments) { -// Map> parentIdMap = departments.stream() -// .collect(Collectors.groupingBy(d -> d.getParentId() == null ? 0L : d.getParentId())); -// -// return buildTreeNodes(0L, parentIdMap); -// } -// -// private List buildTreeNodes(Long parentId, Map> parentIdMap) { -// List nodes = new ArrayList<>(); -// -// List children = parentIdMap.get(parentId); -// if (children != null) { -// for (Department child : children) { -//// child.setChildren(buildTreeNodes(child.getId(), parentIdMap)); -//// nodes.add(child); -// } -// } -// -// return nodes; -// } -//} \ No newline at end of file +package com.qqchen.deploy.backend.service.impl; + +import com.qqchen.deploy.backend.converter.DepartmentConverter; +import com.qqchen.deploy.backend.entity.Department; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.framework.exception.UniqueConstraintException; +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.model.DepartmentDTO; +import com.qqchen.deploy.backend.repository.IDepartmentRepository; +import com.qqchen.deploy.backend.service.IDepartmentService; +import com.qqchen.deploy.backend.framework.annotation.ServiceType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; + +import java.util.*; + +import static com.qqchen.deploy.backend.framework.annotation.ServiceType.Type.DATABASE; + +@Slf4j +@Service +@ServiceType(DATABASE) +public class DepartmentServiceImpl extends BaseServiceImpl implements IDepartmentService { + + @Resource + private IDepartmentRepository departmentRepository; + + @Resource + private DepartmentConverter departmentConverter; + + @Override + protected void validateUniqueConstraints(DepartmentDTO dto) { + // 检查部门编码唯一性 + if (departmentRepository.existsByCodeAndDeletedFalse(dto.getCode())) { + throw new UniqueConstraintException(ResponseCode.DEPARTMENT_CODE_EXISTS, "code", dto.getCode()); + } + + // 检查部门名称唯一性 + if (departmentRepository.existsByNameAndDeletedFalse(dto.getName())) { + throw new UniqueConstraintException(ResponseCode.DEPARTMENT_NAME_EXISTS, "name", dto.getName()); + } + } + + @Override + public List getTree() { + List departments = departmentRepository.findByDeletedFalseOrderBySort(); + return buildTree(departments); + } + + private List buildTree(List departments) { + Map dtoMap = new HashMap<>(); + List roots = new ArrayList<>(); + + // 转换为DTO并建立映射 + departments.forEach(dept -> { + DepartmentDTO dto = departmentConverter.toDto(dept); + dto.setChildren(new ArrayList<>()); + dtoMap.put(dto.getId(), dto); + }); + + // 构建树形结构 + departments.forEach(dept -> { + DepartmentDTO dto = dtoMap.get(dept.getId()); + if (dept.getParentId() == null || dept.getParentId() == 0) { + roots.add(dto); + } else { + DepartmentDTO parent = dtoMap.get(dept.getParentId()); + if (parent != null) { + parent.getChildren().add(dto); + } + } + }); + + return roots; + } +} \ No newline at end of file diff --git a/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql b/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql index f60e2da6..8036acae 100644 --- a/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql +++ b/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql @@ -174,4 +174,24 @@ CREATE TABLE sys_template_menu ( CONSTRAINT FK_template_menu_template FOREIGN KEY (template_id) REFERENCES sys_permission_template (id), CONSTRAINT FK_template_menu_menu FOREIGN KEY (menu_id) REFERENCES sys_menu (id) ) COMMENT '模板菜单关联表'; + +-- 创建部门表 +CREATE TABLE sys_department ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + create_by VARCHAR(255) NULL, + create_time DATETIME(6) NULL, + deleted BIT NOT NULL DEFAULT 0, + update_by VARCHAR(255) NULL, + update_time DATETIME(6) NULL, + version INT NOT NULL DEFAULT 0, + code VARCHAR(255) NOT NULL, + description VARCHAR(255) NULL, + enabled BIT NOT NULL DEFAULT 1, + leader_id BIGINT NULL, + leader_name VARCHAR(255) NULL, + name VARCHAR(255) NOT NULL, + parent_id BIGINT NULL, + sort INT NOT NULL DEFAULT 0, + CONSTRAINT UK_mho5hjhq35wmm63mswah975ic UNIQUE (code) +); \ No newline at end of file diff --git a/backend/src/main/resources/db/migration/V1.0.1__init_data.sql b/backend/src/main/resources/db/migration/V1.0.1__init_data.sql index 55331352..fb914d54 100644 --- a/backend/src/main/resources/db/migration/V1.0.1__init_data.sql +++ b/backend/src/main/resources/db/migration/V1.0.1__init_data.sql @@ -95,4 +95,29 @@ INSERT INTO sys_role_tag_relation (role_id, tag_id) VALUES (1, 3), -- 超级管理员 -> 安全标签 (2, 1), -- 开发主管 -> 研发标签 -(3, 2); -- 运维主管 -> 运维标签 \ No newline at end of file +(3, 2); -- 运维主管 -> 运维标签 + +-- 初始化部门数据 +INSERT INTO sys_department (id, create_time, update_time, deleted, version, code, name, description, parent_id, sort, enabled, leader_id, leader_name) +VALUES + -- 总公司 + (1, NOW(), NOW(), false, 1, 'HQ', '总公司', '公司总部', null, 1, true, null, null), + + -- 一级部门 + (2, NOW(), NOW(), false, 1, 'TECH', '技术部', '负责公司技术研发', 1, 1, true, null, null), + (3, NOW(), NOW(), false, 1, 'HR', '人力资源部', '负责人力资源管理', 1, 2, true, null, null), + (4, NOW(), NOW(), false, 1, 'FIN', '财务部', '负责公司财务管理', 1, 3, true, null, null), + (5, NOW(), NOW(), false, 1, 'MKT', '市场部', '负责市场营销', 1, 4, true, null, null), + + -- 技术部下属部门 + (6, NOW(), NOW(), false, 1, 'DEV', '研发部', '负责产品研发', 2, 1, true, null, null), + (7, NOW(), NOW(), false, 1, 'TEST', '测试部', '负责产品测试', 2, 2, true, null, null), + (8, NOW(), NOW(), false, 1, 'OPS', '运维部', '负责系统运维', 2, 3, true, null, null), + + -- 人力资源部下属部门 + (9, NOW(), NOW(), false, 1, 'REC', '招聘部', '负责人员招聘', 3, 1, true, null, null), + (10, NOW(), NOW(), false, 1, 'TRAIN', '培训部', '负责员工培训', 3, 2, true, null, null), + + -- 市场部下属部门 + (11, NOW(), NOW(), false, 1, 'SALES', '销售部', '负责产品销售', 5, 1, true, null, null), + (12, NOW(), NOW(), false, 1, 'PR', '公关部', '负责公共关系', 5, 2, true, null, null); \ No newline at end of file diff --git a/backend/src/main/resources/messages.properties b/backend/src/main/resources/messages.properties index e102c95c..cb5cb426 100644 --- a/backend/src/main/resources/messages.properties +++ b/backend/src/main/resources/messages.properties @@ -49,4 +49,11 @@ role.admin.cannot.delete=\u4E0D\u80FD\u5220\u9664\u8D85\u7EA7\u7BA1\u7406\u5458\ role.admin.cannot.update=\u4E0D\u80FD\u4FEE\u6539\u8D85\u7EA7\u7BA1\u7406\u5458\u89D2\u8272 role.tag.name.exists=\u6807\u7B7E\u540D\u79F0\u5DF2\u5B58\u5728 role.tag.not.found=\u6807\u7B7E\u4E0D\u5B58\u5728 -role.tag.in.use=\u6807\u7B7E\u6B63\u5728\u4F7F\u7528\u4E2D\uFF0C\u65E0\u6CD5\u5220\u9664 \ No newline at end of file +role.tag.in.use=\u6807\u7B7E\u6B63\u5728\u4F7F\u7528\u4E2D\uFF0C\u65E0\u6CD5\u5220\u9664 + +# 部门相关 +department.not.found=部门不存在 +department.code.exists=部门编码已存在 +department.name.exists=部门名称已存在 +department.parent.not.found=上级部门不存在 +department.has.children=该部门下有子部门,无法删除 \ No newline at end of file diff --git a/backend/src/main/resources/messages_en_US.properties b/backend/src/main/resources/messages_en_US.properties new file mode 100644 index 00000000..1d49aba6 --- /dev/null +++ b/backend/src/main/resources/messages_en_US.properties @@ -0,0 +1,6 @@ +# Department +department.not.found=Department not found +department.code.exists=Department code already exists +department.name.exists=Department name already exists +department.parent.not.found=Parent department not found +department.has.children=Cannot delete department with children \ No newline at end of file diff --git a/backend/src/main/resources/messages_zh_CN.properties b/backend/src/main/resources/messages_zh_CN.properties index f61c4b98..1a7584b2 100644 --- a/backend/src/main/resources/messages_zh_CN.properties +++ b/backend/src/main/resources/messages_zh_CN.properties @@ -44,4 +44,11 @@ role.admin.cannot.delete=\u4E0D\u80FD\u5220\u9664\u8D85\u7EA7\u7BA1\u7406\u5458\ role.admin.cannot.update=\u4E0D\u80FD\u4FEE\u6539\u8D85\u7EA7\u7BA1\u7406\u5458\u89D2\u8272 role.tag.name.exists=\u6807\u7B7E\u540D\u79F0\u5DF2\u5B58\u5728 role.tag.not.found=\u6807\u7B7E\u4E0D\u5B58\u5728 -role.tag.in.use=\u6807\u7B7E\u6B63\u5728\u4F7F\u7528\u4E2D\uFF0C\u65E0\u6CD5\u5220\u9664 \ No newline at end of file +role.tag.in.use=\u6807\u7B7E\u6B63\u5728\u4F7F\u7528\u4E2D\uFF0C\u65E0\u6CD5\u5220\u9664 + +# 部门相关 +department.not.found=部门不存在 +department.code.exists=部门编码已存在 +department.name.exists=部门名称已存在 +department.parent.not.found=上级部门不存在 +department.has.children=该部门下有子部门,无法删除 \ No newline at end of file