From 3517f38282c82e92365c09256016791d49e9a8d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=9A=E8=BE=B0=E5=85=88=E7=94=9F?= Date: Sat, 30 Nov 2024 15:18:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=B8=AD=E9=97=B4=E8=A1=A8?= =?UTF-8?q?=E5=AE=9E=E4=BD=93=E7=B1=BB=EF=BC=8C=E6=B7=BB=E5=8A=A0=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../deploy/backend/api/UserApiController.java | 2 +- .../backend/converter/MenuConverter.java | 20 +- .../backend/converter/UserConverter.java | 6 + .../qqchen/deploy/backend/entity/Menu.java | 15 ++ .../backend/entity/PermissionTemplate.java | 61 ++++++ .../qqchen/deploy/backend/entity/Role.java | 66 ++++++- .../deploy/backend/entity/RoleMenu.java | 31 --- .../qqchen/deploy/backend/entity/RoleTag.java | 37 ++++ .../qqchen/deploy/backend/entity/User.java | 12 +- .../deploy/backend/entity/UserDepartment.java | 31 --- .../deploy/backend/entity/UserRole.java | 32 --- .../backend/framework/enums/ResponseCode.java | 16 +- .../handler/GlobalExceptionHandler.java | 36 +++- .../service/impl/BaseServiceImpl.java | 9 +- .../qqchen/deploy/backend/model/RoleDTO.java | 8 +- .../qqchen/deploy/backend/model/UserDTO.java | 3 + .../backend/model/response/UserResponse.java | 2 + .../backend/repository/IMenuRepository.java | 48 +++-- .../repository/IRoleMenuRepository.java | 21 -- .../backend/repository/IRoleRepository.java | 27 ++- .../repository/IUserRoleRepository.java | 27 --- .../deploy/backend/service/IRoleService.java | 47 +++-- .../deploy/backend/service/IUserService.java | 14 +- .../backend/service/impl/MenuServiceImpl.java | 53 ++++- .../backend/service/impl/RoleServiceImpl.java | 184 +++++------------- .../backend/service/impl/UserServiceImpl.java | 13 +- .../db/migration/V1.0.0__init_schema.sql | 98 ++++++++++ .../db/migration/V1.0.1__init_data.sql | 94 ++++++--- .../src/main/resources/messages.properties | 10 +- .../src/main/resources/messages_en.properties | 10 +- .../main/resources/messages_zh_CN.properties | 13 +- 31 files changed, 637 insertions(+), 409 deletions(-) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/entity/PermissionTemplate.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/entity/RoleMenu.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/entity/RoleTag.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/entity/UserDepartment.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/entity/UserRole.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/repository/IRoleMenuRepository.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/repository/IUserRoleRepository.java diff --git a/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java index 29062235..949ec0db 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java @@ -37,7 +37,7 @@ public class UserApiController extends BaseController getCurrentUser() { - return Response.success(userService.getCurrentUser()); + return Response.success(userService.getCurrentUserResponse()); } @Override diff --git a/backend/src/main/java/com/qqchen/deploy/backend/converter/MenuConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/converter/MenuConverter.java index 80cdfa95..a6f39455 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/converter/MenuConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/converter/MenuConverter.java @@ -3,19 +3,23 @@ package com.qqchen.deploy.backend.converter; import com.qqchen.deploy.backend.entity.Menu; import com.qqchen.deploy.backend.framework.converter.BaseConverter; import com.qqchen.deploy.backend.model.MenuDTO; -import com.qqchen.deploy.backend.model.request.MenuRequest; import com.qqchen.deploy.backend.model.response.MenuResponse; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import java.util.List; @Mapper(config = BaseConverter.class) public interface MenuConverter extends BaseConverter { - - MenuDTO requestToDto(MenuRequest request); - - MenuResponse toResponse(Menu menu); - - List toResponseList(List dtos); - + + @Override + MenuDTO toDto(Menu entity); + + @Override + List toDtoList(List entityList); + + @Mapping(target = "children", expression = "java(toResponseList(dto.getChildren()))") + MenuResponse toResponse(MenuDTO dto); + + List toResponseList(List dtoList); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/converter/UserConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/converter/UserConverter.java index 95808382..ec31f97b 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/converter/UserConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/converter/UserConverter.java @@ -12,8 +12,14 @@ public interface UserConverter extends BaseConverter { // MapStruct 会自动实现所有方法 @Mapping(target = "token", ignore = true) + @Mapping(target = "id", ignore = true) LoginResponse toLoginResponse(User user); @Mapping(target = "token", source = "token") + @Mapping(target = "id", ignore = true) LoginResponse toLoginResponse(User user, String token); + + @Mapping(target = "token", ignore = true) + @Mapping(target = "id", ignore = true) + LoginResponse toLoginResponse(UserDTO userDTO); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/Menu.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/Menu.java index 8852da88..55c125d7 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/entity/Menu.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/Menu.java @@ -3,9 +3,12 @@ package com.qqchen.deploy.backend.entity; import com.qqchen.deploy.backend.framework.annotation.LogicDelete; import com.qqchen.deploy.backend.framework.domain.Entity; import jakarta.persistence.Column; +import jakarta.persistence.ManyToMany; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.HashSet; +import java.util.Set; @Data @@ -67,4 +70,16 @@ public class Menu extends Entity { * 是否启用(true:启用 false:禁用) */ private Boolean enabled; + + /** + * 菜单关联的角色列表 + */ + @ManyToMany(mappedBy = "menus") + private Set roles = new HashSet<>(); + + /** + * 菜单关联的权限模板列表 + */ + @ManyToMany(mappedBy = "menus") + private Set templates = new HashSet<>(); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/PermissionTemplate.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/PermissionTemplate.java new file mode 100644 index 00000000..19c90a08 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/PermissionTemplate.java @@ -0,0 +1,61 @@ +package com.qqchen.deploy.backend.entity; + +import com.qqchen.deploy.backend.framework.domain.Entity; +import jakarta.persistence.Column; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.Table; +import lombok.Data; + +import java.util.HashSet; +import java.util.Set; + +/** + * 权限模板实体 + */ +@Data +@jakarta.persistence.Entity +@Table(name = "sys_permission_template") +public class PermissionTemplate extends Entity { + + /** + * 模板编码 + */ + @Column(nullable = false, length = 100) + private String code; + + /** + * 模板名称 + */ + @Column(nullable = false, length = 100) + private String name; + + /** + * 模板类型(1:系统模板 2:自定义模板) + */ + @Column(nullable = false) + private Integer type; + + /** + * 模板描述 + */ + private String description; + + /** + * 是否启用 + */ + private Boolean enabled; + + /** + * 模板包含的菜单列表 + */ + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable( + name = "sys_template_menu", + joinColumns = @JoinColumn(name = "template_id"), + inverseJoinColumns = @JoinColumn(name = "menu_id") + ) + private Set menus = new HashSet<>(); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/Role.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/Role.java index 53b66131..21595499 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/entity/Role.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/Role.java @@ -3,6 +3,9 @@ package com.qqchen.deploy.backend.entity; import com.qqchen.deploy.backend.framework.annotation.LogicDelete; import com.qqchen.deploy.backend.framework.domain.Entity; import jakarta.persistence.Column; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; import jakarta.persistence.Table; import jakarta.validation.constraints.NotBlank; @@ -12,6 +15,9 @@ import java.util.HashSet; import java.util.Set; +/** + * 角色实体 + */ @Data @EqualsAndHashCode(callSuper = true) @jakarta.persistence.Entity @@ -19,20 +25,68 @@ import java.util.Set; @LogicDelete public class Role extends Entity { - @NotBlank(message = "角色名称不能为空") - @Column(nullable = false) - private String name; - + /** + * 角色编码 + */ @NotBlank(message = "角色编码不能为空") - @Column(nullable = false, unique = true) + @Column(nullable = false, length = 100, unique = true) private String code; + /** + * 角色名称 + */ + @NotBlank(message = "角色名称不能为空") + @Column(nullable = false, length = 100) + private String name; + + /** + * 角色类型(1:系统角色 2:自定义角色) + */ + @Column(nullable = false) + private Integer type; + + /** + * 角色描述 + */ private String description; + /** + * 显示顺序 + */ @Column(nullable = false) private Integer sort = 0; - + + /** + * 是否启用 + */ + private Boolean enabled; + + /** + * 角色关联的用户列表 + */ @ManyToMany(mappedBy = "roles") private Set users = new HashSet<>(); + + /** + * 角色关联的菜单列表 + */ + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable( + name = "sys_role_menu", + joinColumns = @JoinColumn(name = "role_id"), + inverseJoinColumns = @JoinColumn(name = "menu_id") + ) + private Set menus = new HashSet<>(); + + /** + * 角色关联的标签列表 + */ + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable( + name = "sys_role_tag_relation", + joinColumns = @JoinColumn(name = "role_id"), + inverseJoinColumns = @JoinColumn(name = "tag_id") + ) + private Set tags = new HashSet<>(); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/RoleMenu.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/RoleMenu.java deleted file mode 100644 index 3e54845a..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/entity/RoleMenu.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.qqchen.deploy.backend.entity; - -import com.qqchen.deploy.backend.framework.annotation.LogicDelete; -import com.qqchen.deploy.backend.framework.domain.Entity; -import jakarta.persistence.Column; -import jakarta.persistence.Table; -import lombok.Data; -import lombok.EqualsAndHashCode; - - -@Data -@EqualsAndHashCode(callSuper = true) -@jakarta.persistence.Entity -@Table(name = "sys_role_menu") -@LogicDelete -public class RoleMenu extends Entity { - - @Column(name = "role_id") - private Long roleId; - - @Column(name = "menu_id") - private Long menuId; - - protected RoleMenu() { - } - - public RoleMenu(Long roleId, Long menuId) { - this.roleId = roleId; - this.menuId = menuId; - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/RoleTag.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/RoleTag.java new file mode 100644 index 00000000..561e1aef --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/RoleTag.java @@ -0,0 +1,37 @@ +package com.qqchen.deploy.backend.entity; + +import com.qqchen.deploy.backend.framework.domain.Entity; +import jakarta.persistence.Column; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.Table; +import lombok.Data; + +import java.util.HashSet; +import java.util.Set; + +/** + * 角色标签实体 + */ +@Data +@jakarta.persistence.Entity +@Table(name = "sys_role_tag") +public class RoleTag extends Entity { + + /** + * 标签名称 + */ + @Column(nullable = false, length = 50) + private String name; + + /** + * 标签颜色(十六进制颜色码) + */ + @Column(length = 20) + private String color; + + /** + * 关联的角色列表 + */ + @ManyToMany(mappedBy = "tags") + private Set roles = new HashSet<>(); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/User.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/User.java index b64c7789..142e39ef 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/entity/User.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/User.java @@ -38,15 +38,9 @@ public class User extends Entity { @ManyToMany(fetch = FetchType.LAZY) @JoinTable( - name = "sys_user_role", // 中间表名称 - joinColumns = @JoinColumn( - name = "user_id", // 中间表中关联当前实体的外键名 - referencedColumnName = "id" // 当前实体的主键 - ), - inverseJoinColumns = @JoinColumn( - name = "role_id", // 中间表中关联目标实体的外键名 - referencedColumnName = "id" // 目标实体的主键 - ) + name = "sys_user_role", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "role_id") ) private Set roles = new HashSet<>(); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/UserDepartment.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/UserDepartment.java deleted file mode 100644 index f7b40565..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/entity/UserDepartment.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.qqchen.deploy.backend.entity; - -import com.qqchen.deploy.backend.framework.annotation.LogicDelete; -import com.qqchen.deploy.backend.framework.domain.Entity; -import jakarta.persistence.Column; -import jakarta.persistence.Table; -import lombok.Data; -import lombok.EqualsAndHashCode; - -@Data -@EqualsAndHashCode(callSuper = true) -@jakarta.persistence.Entity -@Table(name = "sys_user_department") -@LogicDelete -public class UserDepartment extends Entity { - - @Column(name = "user_id") - private Long userId; - - @Column(name = "dept_id") - private Long deptId; - - protected UserDepartment() { - // JPA需要无参构造函数 - } - - public UserDepartment(Long userId, Long deptId) { - this.userId = userId; - this.deptId = deptId; - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/UserRole.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/UserRole.java deleted file mode 100644 index f7d9814a..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/entity/UserRole.java +++ /dev/null @@ -1,32 +0,0 @@ -//package com.qqchen.deploy.backend.entity; -// -//import com.qqchen.deploy.backend.framework.annotation.LogicDelete; -//import com.qqchen.deploy.backend.framework.domain.Entity; -//import jakarta.persistence.Column; -//import jakarta.persistence.Table; -//import lombok.Data; -//import lombok.EqualsAndHashCode; -// -//@Data -//@EqualsAndHashCode(callSuper = true) -//@jakarta.persistence.Entity -//@Table(name = "sys_user_role") -//@LogicDelete -//public class UserRole extends Entity { -// -// @Column(name = "user_id") -// private Long userId; -// -// @Column(name = "role_id") -// private Long roleId; -// -// // 添加构造方法 -// protected UserRole() { -// // JPA需要无参构造方法 -// } -// -// public UserRole(Long userId, Long roleId) { -// this.userId = userId; -// this.roleId = roleId; -// } -//} \ 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 dd0c1a06..fb52ac27 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 @@ -33,10 +33,18 @@ public enum ResponseCode { EMAIL_EXISTS(2005, "user.email.exists"), LOGIN_ERROR(2006, "user.login.error"), - // JWT相关错误码 (2100-2199) - JWT_EXPIRED(2100, "jwt.token.expired"), - JWT_INVALID(2101, "jwt.token.invalid"), - JWT_MISSING(2102, "jwt.token.missing"); + // 角色相关错误码 (2100-2199) + ROLE_NOT_FOUND(2100, "role.not.found"), + ROLE_CODE_EXISTS(2101, "role.code.exists"), + ROLE_NAME_EXISTS(2102, "role.name.exists"), + ROLE_IN_USE(2103, "role.in.use"), + ROLE_ADMIN_CANNOT_DELETE(2104, "role.admin.cannot.delete"), + ROLE_ADMIN_CANNOT_UPDATE(2105, "role.admin.cannot.update"), + + // JWT相关错误码 (2200-2299) + JWT_EXPIRED(2200, "jwt.token.expired"), + JWT_INVALID(2201, "jwt.token.invalid"), + JWT_MISSING(2202, "jwt.token.missing"); private final int code; private final String messageKey; // 国际化消息key diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/handler/GlobalExceptionHandler.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/handler/GlobalExceptionHandler.java index 740bd1db..e4152b07 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/handler/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/handler/GlobalExceptionHandler.java @@ -9,8 +9,11 @@ import io.jsonwebtoken.MalformedJwtException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; +import org.springframework.security.authentication.BadCredentialsException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.NoHandlerFoundException; import java.security.SignatureException; @@ -33,18 +36,12 @@ public class GlobalExceptionHandler { return Response.error(ResponseCode.ERROR); } - @ExceptionHandler(org.springframework.security.authentication.BadCredentialsException.class) - public Response handleBadCredentialsException(org.springframework.security.authentication.BadCredentialsException e) { + @ExceptionHandler(BadCredentialsException.class) + public Response handleBadCredentialsException(BadCredentialsException e) { log.warn("Login failed: Bad credentials", e); return Response.error(ResponseCode.LOGIN_ERROR); } - @ExceptionHandler(Exception.class) - public Response handleException(Exception e) { - log.error("Unexpected error occurred", e); - return Response.error(ResponseCode.ERROR); - } - @ExceptionHandler(ExpiredJwtException.class) public Response handleExpiredJwtException(ExpiredJwtException e) { log.warn("JWT token expired", e); @@ -56,4 +53,27 @@ public class GlobalExceptionHandler { log.warn("Invalid JWT token", e); return Response.error(ResponseCode.JWT_INVALID); } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public Response handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) { + String message = String.format("参数[%s]类型错误, 期望类型: %s, 实际值: %s", + e.getName(), + e.getRequiredType().getSimpleName(), + e.getValue()); + log.warn("Parameter type mismatch: {}", message); + return Response.error(ResponseCode.INVALID_PARAM, message); + } + + @ExceptionHandler(NoHandlerFoundException.class) + public Response handleNoHandlerFoundException(NoHandlerFoundException e) { + String message = String.format("接口不存在: %s %s", e.getHttpMethod(), e.getRequestURL()); + log.warn("API not found: {}", message); + return Response.error(ResponseCode.NOT_FOUND, message); + } + + @ExceptionHandler(Exception.class) + public Response handleException(Exception e) { + log.error("Unexpected error occurred", e); + return Response.error(ResponseCode.ERROR); + } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/service/impl/BaseServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/service/impl/BaseServiceImpl.java index 1f06021a..e79e9a64 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/service/impl/BaseServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/service/impl/BaseServiceImpl.java @@ -315,13 +315,18 @@ public abstract class BaseServiceImpl, D extends BaseDTO, I return PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "createTime")); } + // 处理分页参数 + int pageNum = query.getPageNum() != null ? Math.max(1, query.getPageNum()) : 1; + int pageSize = query.getPageSize() != null ? Math.max(1, Math.min(query.getPageSize(), 100)) : 10; + + // 处理排序 Sort sort = StringUtils.hasText(query.getSortField()) ? Sort.by(Sort.Direction.fromString(query.getSortOrder()), query.getSortField()) : Sort.by(Sort.Direction.DESC, "createTime"); return PageRequest.of( - query.getPageNum() - 1, - query.getPageSize(), + pageNum - 1, // 转换为从0开始的页码 + pageSize, sort ); } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/model/RoleDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/model/RoleDTO.java index 48140e87..9ae3defa 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/model/RoleDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/model/RoleDTO.java @@ -8,11 +8,15 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) public class RoleDTO extends BaseDTO { - private String name; - private String code; + private String name; + private String description; + private Integer type; + private Integer sort; + + private Boolean enabled; } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/model/UserDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/model/UserDTO.java index af622d10..9517d02c 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/model/UserDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/model/UserDTO.java @@ -9,6 +9,9 @@ import java.util.Set; @Data @EqualsAndHashCode(callSuper = true) public class UserDTO extends BaseDTO { + + private Long id; + private String username; private String nickname; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/model/response/UserResponse.java b/backend/src/main/java/com/qqchen/deploy/backend/model/response/UserResponse.java index 2522f9f0..e9669cce 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/model/response/UserResponse.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/model/response/UserResponse.java @@ -9,6 +9,8 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) public class UserResponse extends BaseResponse { + private Long id; + private String username; private String email; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/IMenuRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IMenuRepository.java index a820b2ed..9c5966d1 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/IMenuRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IMenuRepository.java @@ -1,29 +1,39 @@ package com.qqchen.deploy.backend.repository; import com.qqchen.deploy.backend.entity.Menu; -import com.qqchen.deploy.backend.entity.User; import com.qqchen.deploy.backend.framework.repository.IBaseRepository; -import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; + @Repository public interface IMenuRepository extends IBaseRepository { + + /** + * 查询所有未删除的菜单,按排序字段排序 + */ List findByDeletedFalseOrderBySort(); - - List findByParentIdAndDeletedFalseOrderBySort(Long parentId); - - List findByTypeAndDeletedFalseOrderBySort(Integer type); - - List findByIdInAndDeletedFalseOrderBySort(List ids); - - List findByPermissionLikeAndDeletedFalse(String permission); - - List findByTypeAndPermissionIsNotNullAndDeletedFalse(Integer type); - - List findByParentIdAndDeletedFalse(Long parentId); - - List findByTypeInAndDeletedFalseOrderBySort(List types); - - List findByEnabledAndDeletedFalseOrderBySort(Boolean enabled); -} \ No newline at end of file + + /** + * 根据角色ID列表查询对应的菜单 + */ + @Query(value = "SELECT DISTINCT m.* FROM sys_menu m " + + "INNER JOIN sys_role_menu rm ON m.id = rm.menu_id " + + "WHERE rm.role_id IN :roleIds " + + "AND m.enabled = true " + + "AND m.deleted = false " + + "ORDER BY m.sort", nativeQuery = true) + List findByRoleIdsAndEnabledTrue(@Param("roleIds") List roleIds); + + /** + * 检查菜单编码是否存在 + */ + boolean existsByPermissionAndDeletedFalse(String permission); + + /** + * 检查同级菜单下是否存在相同名称的菜单 + */ + boolean existsByNameAndParentIdAndDeletedFalse(String name, Long parentId); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/IRoleMenuRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRoleMenuRepository.java deleted file mode 100644 index 5203b66f..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/IRoleMenuRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.qqchen.deploy.backend.repository; - - -import com.qqchen.deploy.backend.framework.repository.IBaseRepository; -import com.qqchen.deploy.backend.entity.RoleMenu; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface IRoleMenuRepository extends IBaseRepository { - - @Query("SELECT rm.roleId FROM RoleMenu rm where rm.roleId = :roleId") - List findByRoleId(Long roleId); -// -// void deleteByRoleId(Long roleId); -// -// List findByRoleIdIn(List roleIds); - // 通过角色ID查询 -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/IRoleRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRoleRepository.java index b2344d8e..a7a79248 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/IRoleRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRoleRepository.java @@ -1,21 +1,30 @@ package com.qqchen.deploy.backend.repository; - -import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.Role; +import com.qqchen.deploy.backend.framework.repository.IBaseRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; -import java.util.Optional; @Repository public interface IRoleRepository extends IBaseRepository { - - List findByDeletedFalseOrderBySort(); - + + /** + * 检查角色编码是否存在(排除已删除的) + */ boolean existsByCodeAndDeletedFalse(String code); - + + /** + * 检查角色名称是否存在(排除已删除的) + */ boolean existsByNameAndDeletedFalse(String name); - - Optional findByCodeAndDeletedFalse(String code); + + /** + * 获取用户的所有角色ID + */ + @Query(value = "SELECT role_id FROM sys_user_role WHERE user_id = :userId AND deleted = false", + nativeQuery = true) + List findRoleIdsByUserId(@Param("userId") Long userId); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/IUserRoleRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IUserRoleRepository.java deleted file mode 100644 index 6c38b656..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/IUserRoleRepository.java +++ /dev/null @@ -1,27 +0,0 @@ -//package com.qqchen.deploy.backend.repository; -// -// -//import com.qqchen.deploy.backend.framework.repository.IBaseRepository; -//import com.qqchen.deploy.backend.entity.UserRole; -//import org.springframework.data.jpa.repository.Modifying; -//import org.springframework.data.jpa.repository.Query; -//import org.springframework.data.repository.query.Param; -//import org.springframework.stereotype.Repository; -// -//import java.util.Set; -// -//@Repository -//public interface IUserRoleRepository extends IBaseRepository { -// -// @Query("SELECT ur FROM UserRole ur WHERE ur.userId = :userId") -// Set findByUserId(@Param("userId") Long userId); -// -// @Modifying -// @Query("DELETE FROM UserRole ur WHERE ur.userId = :userId AND ur.roleId = :roleId") -// void deleteByUserIdAndRoleId(@Param("userId") Long userId, @Param("roleId") Long roleId); -// -// -// @Query("SELECT COUNT(ur) > 0 FROM UserRole ur WHERE ur.userId = :userId AND ur.roleId = :roleId") -// boolean existsByUserIdAndRoleId(@Param("userId") Long userId, @Param("roleId") Long roleId); -// -//} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/IRoleService.java b/backend/src/main/java/com/qqchen/deploy/backend/service/IRoleService.java index 06477b10..30c9ef89 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/service/IRoleService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/IRoleService.java @@ -1,18 +1,29 @@ -//package com.qqchen.deploy.backend.service; -// -// -//import com.qqchen.deploy.backend.framework.service.IBaseService; -//import com.qqchen.deploy.backend.entity.Role; -// -//import java.util.List; -// -//public interface IRoleService extends IBaseService { -// -// void validateCode(String code); -// -// void validateName(String name); -// -// List getRoleMenuIds(Long roleId); -// -// void updateRoleMenus(Long roleId, List menuIds); -//} \ No newline at end of file +package com.qqchen.deploy.backend.service; + +import com.qqchen.deploy.backend.framework.service.IBaseService; +import com.qqchen.deploy.backend.entity.Role; +import com.qqchen.deploy.backend.model.RoleDTO; + +import java.util.List; + +public interface IRoleService extends IBaseService { + + /** + * 验证角色编码是否存在 + * @param code 角色编码 + */ + void validateCode(String code); + + /** + * 验证角色名称是否存在 + * @param name 角色名称 + */ + void validateName(String name); + + /** + * 获取用户的所有角色ID + * @param userId 用户ID + * @return 角色ID列表 + */ + List getUserRoleIds(Long userId); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/IUserService.java b/backend/src/main/java/com/qqchen/deploy/backend/service/IUserService.java index 5c5909dc..3885c619 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/service/IUserService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/IUserService.java @@ -24,6 +24,18 @@ public interface IUserService extends IBaseService { @Audited(action = "CHANGE_PASSWORD", detail = "修改密码") void changePassword(Long userId, String oldPassword, String newPassword); + /** + * 获取当前用户信息(内部服务使用) + * @return 当前用户DTO,包含完整信息 + */ @Transactional(readOnly = true) - LoginResponse getCurrentUser(); + @Audited(action = "GET_CURRENT_USER", detail = "获取当前用户信息") + UserDTO getCurrentUser(); + + /** + * 获取当前用户信息(前端使用) + * @return 当前用户响应,不包含敏感信息 + */ + @Transactional(readOnly = true) + LoginResponse getCurrentUserResponse(); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/MenuServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/MenuServiceImpl.java index 15387368..37ab2e11 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/MenuServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/MenuServiceImpl.java @@ -9,11 +9,13 @@ import com.qqchen.deploy.backend.framework.exception.BusinessException; import com.qqchen.deploy.backend.framework.security.SecurityUtils; import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; import com.qqchen.deploy.backend.model.MenuDTO; +import com.qqchen.deploy.backend.model.UserDTO; import com.qqchen.deploy.backend.model.response.LoginResponse; import com.qqchen.deploy.backend.model.response.MenuResponse; import com.qqchen.deploy.backend.repository.IMenuRepository; import com.qqchen.deploy.backend.service.IMenuService; import com.qqchen.deploy.backend.service.IUserService; +import com.qqchen.deploy.backend.service.IRoleService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -21,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.ArrayList; import java.util.stream.Collectors; +import java.util.Comparator; import static com.qqchen.deploy.backend.framework.annotation.ServiceType.Type.DATABASE; @@ -38,6 +41,9 @@ public class MenuServiceImpl extends BaseServiceImpl implem @Resource private IUserService userService; + @Resource + private IRoleService roleService; + @Override public List getMenuTree() { List allMenus = repository.findByDeletedFalseOrderBySort(); @@ -48,21 +54,37 @@ public class MenuServiceImpl extends BaseServiceImpl implem @Override public List getUserMenus() { - LoginResponse user = userService.getCurrentUser(); - // TODO: 根据用户角色获取菜单 - return getMenuTree(); + // 1. 获取当前用户信息(使用内部服务方法) + UserDTO user = userService.getCurrentUser(); + + // 2. 通过roleService获取用户的所有角色ID + List roleIds = roleService.getUserRoleIds(user.getId()); + + // 3. 获取这些角色关联的所有菜单 + List userMenus = repository.findByRoleIdsAndEnabledTrue(roleIds); + + // 4. 转换为DTO并构建树形结构 + List menuDTOs = converter.toDtoList(userMenus); + List menuTree = buildMenuTree(menuDTOs); + + // 5. 转换为响应对象 + return converter.toResponseList(menuTree); } private List buildMenuTree(List menus) { + // 使用Map存储所有菜单,便于查找 Map menuMap = menus.stream() .collect(Collectors.toMap(MenuDTO::getId, menu -> menu)); - + List rootMenus = new ArrayList<>(); - + + // 遍历所有菜单,构建树形结构 for (MenuDTO menu : menus) { if (menu.getParentId() == null || menu.getParentId() == 0) { + // 如果是根菜单,直接添加到结果列表 rootMenus.add(menu); } else { + // 如果不是根菜单,找到其父菜单并添加到children中 MenuDTO parent = menuMap.get(menu.getParentId()); if (parent != null) { if (parent.getChildren() == null) { @@ -72,7 +94,26 @@ public class MenuServiceImpl extends BaseServiceImpl implem } } } - + + // 对每一级菜单进行排序 + sortMenuTree(rootMenus); + return rootMenus; } + + private void sortMenuTree(List menus) { + if (menus == null || menus.isEmpty()) { + return; + } + + // 根据sort字段排序 + menus.sort(Comparator.comparing(MenuDTO::getSort)); + + // 递归排序子菜单 + for (MenuDTO menu : menus) { + if (menu.getChildren() != null && !menu.getChildren().isEmpty()) { + sortMenuTree(menu.getChildren()); + } + } + } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/RoleServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/RoleServiceImpl.java index 3dc9512c..3f35eb41 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/RoleServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/RoleServiceImpl.java @@ -1,139 +1,45 @@ -//package com.qqchen.deploy.backend.service.impl; -// -//import com.qqchen.application.query.RoleQuery; -//import com.qqchen.application.service.RoleService; -//import com.qqchen.common.exception.ApiException; -//import com.qqchen.common.service.impl.BaseServiceImpl; -//import com.qqchen.domain.entity.Role; -//import com.qqchen.domain.entity.RoleMenu; -//import com.qqchen.domain.repository.RoleMenuRepository; -//import com.qqchen.domain.repository.RoleRepository; -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.data.jpa.repository.JpaRepository; -//import org.springframework.stereotype.Service; -//import org.springframework.transaction.annotation.Transactional; -// -//import java.util.List; -//import java.util.stream.Collectors; -// -//@Slf4j -//@Service -//@RequiredArgsConstructor -//public class RoleServiceImpl extends BaseServiceImpl implements RoleService { -// -// private final RoleRepository roleRepository; -// private final RoleMenuRepository roleMenuRepository; -// -// @Override -// protected JpaRepository getRepository() { -// return roleRepository; -// } -// -// @Override -// public List getRoleMenuIds(Long roleId) { -// return roleMenuRepository.findByRoleId(roleId) -// .stream() -// .map(RoleMenu::getMenuId) -// .collect(Collectors.toList()); -// } -// -// @Override -// @Transactional -// public void updateRoleMenus(Long roleId, List menuIds) { -// // 验证角色是否存在 -// Role role = findById(roleId) -// .orElseThrow(() -> new ApiException("角色不存在")); -// -// // 不允许修改超级管理员的权限 -// if ("ROLE_ADMIN".equals(role.getCode())) { -// throw new ApiException("不能修改超级管理员的权限"); -// } -// -// // 删除原有权限 -// roleMenuRepository.deleteByRoleId(roleId); -// -// // 保存新的权限 -// if (menuIds != null && !menuIds.isEmpty()) { -// List roleMenus = menuIds.stream() -// .map(menuId -> { -// RoleMenu roleMenu = new RoleMenu(); -// roleMenu.setRoleId(roleId); -// roleMenu.setMenuId(menuId); -// return roleMenu; -// }) -// .collect(Collectors.toList()); -// roleMenuRepository.saveAll(roleMenus); -// } -// -// log.info("角色 {} 的权限已更新", role.getName()); -// } -// -// @Override -// @Transactional -// public void delete(Long id) { -// Role role = findById(id) -// .orElseThrow(() -> new ApiException("角色不存在")); -// -// // 不允许删除超级管理员角色 -// if ("ROLE_ADMIN".equals(role.getCode())) { -// throw new ApiException("不能删除超级管理员角色"); -// } -// -// // 执行软删除 -// role.setDeleted(true); -// roleRepository.save(role); -// log.info("角色 {} 已删除", role.getName()); -// } -// -// @Override -// @Transactional -// public Role save(Role entity) { -// validateCode(entity.getCode()); -// validateName(entity.getName()); -// entity.setDeleted(false); -// return super.save(entity); -// } -// -// @Override -// @Transactional -// public Role update(Long id, Role entity) { -// Role existing = findById(id) -// .orElseThrow(() -> new ApiException("角色不存在")); -// -// // 不允许修改超级管理员角色的编码 -// if ("ROLE_ADMIN".equals(existing.getCode()) && !existing.getCode().equals(entity.getCode())) { -// throw new ApiException("不能修改超级管理员角色的编码"); -// } -// -// if (!existing.getCode().equals(entity.getCode())) { -// validateCode(entity.getCode()); -// } -// if (!existing.getName().equals(entity.getName())) { -// validateName(entity.getName()); -// } -// -// entity.setVersion(existing.getVersion()); -// entity.setDeleted(existing.getDeleted()); -// return super.update(id, entity); -// } -// -// @Override -// public void validateCode(String code) { -// if (roleRepository.existsByCodeAndDeletedFalse(code)) { -// throw new ApiException("角色编码已存在"); -// } -// } -// -// @Override -// public void validateName(String name) { -// if (roleRepository.existsByNameAndDeletedFalse(name)) { -// throw new ApiException("角色名称已存在"); -// } -// } -// -// @Override -// public List findAll() { -// return roleRepository.findByDeletedFalseOrderBySort(); -// } -//} \ No newline at end of file +package com.qqchen.deploy.backend.service.impl; + +import com.qqchen.deploy.backend.entity.Role; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.framework.exception.BusinessException; +import com.qqchen.deploy.backend.model.RoleDTO; +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.repository.IRoleRepository; +import com.qqchen.deploy.backend.service.IRoleService; +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.List; + +import static com.qqchen.deploy.backend.framework.annotation.ServiceType.Type.DATABASE; + +@Slf4j +@Service +@ServiceType(DATABASE) +public class RoleServiceImpl extends BaseServiceImpl implements IRoleService { + + @Resource + private IRoleRepository repository; + + @Override + public List getUserRoleIds(Long userId) { + return repository.findRoleIdsByUserId(userId); + } + + @Override + public void validateCode(String code) { + if (repository.existsByCodeAndDeletedFalse(code)) { + throw new BusinessException(ResponseCode.ROLE_CODE_EXISTS); + } + } + + @Override + public void validateName(String name) { + if (repository.existsByNameAndDeletedFalse(name)) { + throw new BusinessException(ResponseCode.ROLE_NAME_EXISTS); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserServiceImpl.java index b7e5bf58..ad3eff71 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserServiceImpl.java @@ -132,13 +132,20 @@ public class UserServiceImpl extends BaseServiceImpl implem @Override @Transactional(readOnly = true) @Audited(action = "GET_CURRENT_USER", detail = "获取当前用户信息") - public LoginResponse getCurrentUser() { + public UserDTO getCurrentUser() { String username = SecurityUtils.getCurrentUsername(); User user = userRepository.findByUsernameAndDeletedFalse(username) .orElseThrow(() -> new BusinessException(ResponseCode.USER_NOT_FOUND)); - LoginResponse response = userConverter.toLoginResponse(user); log.debug("获取当前用户信息: {}", user.getUsername()); - return response; + return userConverter.toDto(user); + } + + @Override + @Transactional(readOnly = true) + public LoginResponse getCurrentUserResponse() { + // 复用getCurrentUser方法,直接转换DTO为LoginResponse + UserDTO userDTO = getCurrentUser(); + return userConverter.toLoginResponse(userDTO); } } \ 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 b5850d35..f60e2da6 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 @@ -76,4 +76,102 @@ CREATE TABLE sys_menu ( hidden BIT NOT NULL DEFAULT 0 COMMENT '是否隐藏(0显示 1隐藏)', enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用(0禁用 1启用)' ) COMMENT '菜单表'; + +-- 角色表 +CREATE TABLE sys_role ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + create_by VARCHAR(255), + create_time DATETIME(6), + deleted BIT NOT NULL DEFAULT 0, + update_by VARCHAR(255), + update_time DATETIME(6), + version INT NOT NULL DEFAULT 0, + + code VARCHAR(100) NOT NULL COMMENT '角色编码', + name VARCHAR(100) NOT NULL COMMENT '角色名称', + type INT NOT NULL DEFAULT 2 COMMENT '角色类型(1:系统角色 2:自定义角色)', + description VARCHAR(255) COMMENT '角色描述', + sort INT NOT NULL DEFAULT 0 COMMENT '显示顺序', + enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用', + + CONSTRAINT UK_role_code UNIQUE (code) +) COMMENT '角色表'; + +-- 角色标签表 +CREATE TABLE sys_role_tag ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + create_by VARCHAR(255), + create_time DATETIME(6), + deleted BIT NOT NULL DEFAULT 0, + update_by VARCHAR(255), + update_time DATETIME(6), + version INT NOT NULL DEFAULT 0, + + name VARCHAR(50) NOT NULL COMMENT '标签名称', + color VARCHAR(20) COMMENT '标签颜色' +) COMMENT '角色标签表'; + +-- 角色标签关联表 +CREATE TABLE sys_role_tag_relation ( + role_id BIGINT NOT NULL COMMENT '角色ID', + tag_id BIGINT NOT NULL COMMENT '标签ID', + PRIMARY KEY (role_id, tag_id), + CONSTRAINT FK_role_tag_role FOREIGN KEY (role_id) REFERENCES sys_role (id), + CONSTRAINT FK_role_tag_tag FOREIGN KEY (tag_id) REFERENCES sys_role_tag (id) +) COMMENT '角色标签关联表'; + +-- 用户角色关联表 +CREATE TABLE sys_user_role ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + create_by VARCHAR(255), + create_time DATETIME(6), + deleted BIT NOT NULL DEFAULT 0, + update_by VARCHAR(255), + update_time DATETIME(6), + version INT NOT NULL DEFAULT 0, + + user_id BIGINT NOT NULL COMMENT '用户ID', + role_id BIGINT NOT NULL COMMENT '角色ID', + + CONSTRAINT UK_user_role UNIQUE (user_id, role_id), + CONSTRAINT FK_user_role_user FOREIGN KEY (user_id) REFERENCES sys_user (id), + CONSTRAINT FK_user_role_role FOREIGN KEY (role_id) REFERENCES sys_role (id) +) COMMENT '用户角色关联表'; + +-- 角色菜单关联表 +CREATE TABLE sys_role_menu ( + role_id BIGINT NOT NULL COMMENT '角色ID', + menu_id BIGINT NOT NULL COMMENT '菜单ID', + PRIMARY KEY (role_id, menu_id), + CONSTRAINT FK_role_menu_role FOREIGN KEY (role_id) REFERENCES sys_role (id), + CONSTRAINT FK_role_menu_menu FOREIGN KEY (menu_id) REFERENCES sys_menu (id) +) COMMENT '角色菜单关联表'; + +-- 权限模板表 +CREATE TABLE sys_permission_template ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + create_by VARCHAR(255), + create_time DATETIME(6), + deleted BIT NOT NULL DEFAULT 0, + update_by VARCHAR(255), + update_time DATETIME(6), + version INT NOT NULL DEFAULT 0, + + code VARCHAR(100) NOT NULL COMMENT '模板编码', + name VARCHAR(100) NOT NULL COMMENT '模板名称', + type INT NOT NULL DEFAULT 1 COMMENT '模板类型(1:系统模板 2:自定义模板)', + description VARCHAR(255) COMMENT '模板描述', + enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用', + + CONSTRAINT UK_template_code UNIQUE (code) +) COMMENT '权限模板表'; + +-- 模板菜单关联表 +CREATE TABLE sys_template_menu ( + template_id BIGINT NOT NULL COMMENT '模板ID', + menu_id BIGINT NOT NULL COMMENT '菜单ID', + PRIMARY KEY (template_id, menu_id), + 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 '模板菜单关联表'; \ 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 497a3dba..55331352 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 @@ -1,62 +1,98 @@ --- 插入初始租户 -INSERT INTO sys_tenant +-- 初始化租户 +INSERT INTO sys_tenant (create_by, create_time, deleted, update_by, update_time, version, address, code, contact_name, contact_phone, email, enabled, name) VALUES ('system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, -'北京市朝阳区望京SOHO T1 C座', 'default', '张三', '13900000001', +'北京市朝阳区望京SOHO T1 C座', 'default', '张三', '13900000001', 'admin@deploy-ease.com', 1, '默认租户'); --- 插入管理员用户 -INSERT INTO sys_user -(create_by, create_time, deleted, update_by, update_time, version, -email, enabled, nickname, password, phone, username) -VALUES -('system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, -'admin@deploy-ease.com', 1, '系统管理员', -'$2a$10$B5qWUPyWyXxfm6Jq4JKcAOp/XcQLjtv3jifafsqPqZvzY9owae2ve', -- 密码: admin123 -'13800138000', 'admin'); - -- 系统参数初始化 -INSERT INTO sys_param (code, name, value, type, description, enabled, create_by, create_time) -VALUES -('SYSTEM_NAME', '系统名称', 'DevOps平台', 'TEXT', '系统显示名称', 1, 'system', '2024-01-01 00:00:00'), -('SYSTEM_LOGO', '系统Logo', '/logo.png', 'TEXT', '系统Logo路径', 1, 'system', '2024-01-01 00:00:00'), -('GENDER_ENUM', '性别枚举', '[{"code":"1","name":"男"},{"code":"2","name":"女"}]', 'ENUM', '性别枚举值', 1, 'system', '2024-01-01 00:00:00'), -('USER_STATUS_ENUM', '用户状态枚举', '[{"code":"0","name":"禁用"},{"code":"1","name":"启用"}]', 'ENUM', '用户状态枚举值', 1, 'system', '2024-01-01 00:00:00'); +INSERT INTO sys_param (code, name, value, type, description, enabled, create_by, create_time, deleted, version) +VALUES +('SYSTEM_NAME', '系统名称', 'DevOps平台', 'TEXT', '系统显示名称', 1, 'system', '2024-01-01 00:00:00', 0, 0), +('SYSTEM_LOGO', '系统Logo', '/logo.png', 'TEXT', '系统Logo路径', 1, 'system', '2024-01-01 00:00:00', 0, 0), +('GENDER_ENUM', '性别枚举', '[{"code":"1","name":"男"},{"code":"2","name":"女"}]', 'ENUM', '性别枚举值', 1, 'system', '2024-01-01 00:00:00', 0, 0), +('USER_STATUS_ENUM', '用户状态枚举', '[{"code":"0","name":"禁用"},{"code":"1","name":"启用"}]', 'ENUM', '用户状态枚举值', 1, 'system', '2024-01-01 00:00:00', 0, 0); -- 初始化系统管理菜单 -INSERT INTO sys_menu (create_by, create_time, deleted, update_by, update_time, version, +INSERT INTO sys_menu (id, create_by, create_time, deleted, update_by, update_time, version, name, path, component, icon, type, parent_id, sort, hidden, enabled) VALUES -- 系统管理 -('system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, +(1, 'system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, '系统管理', '/system', null, 'setting', 1, null, 1, 0, 1), -- 用户管理 -('system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, +(2, 'system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, '用户管理', '/system/user', '/System/User', 'user', 2, 1, 1, 0, 1), -- 角色管理 -('system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, +(3, 'system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, '角色管理', '/system/role', '/System/Role', 'team', 2, 1, 2, 0, 1), -- 菜单管理 -('system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, +(4, 'system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, '菜单管理', '/system/menu', '/System/Menu', 'menu', 2, 1, 3, 0, 1), -- 部门管理 -('system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, +(5, 'system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, '部门管理', '/system/department', '/System/Department', 'apartment', 2, 1, 4, 0, 1), -- 租户管理 -('system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, +(6, 'system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, '租户管理', '/system/tenant', '/System/Tenant', 'cluster', 2, 1, 5, 0, 1), -- Jenkins管理 -('system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, +(7, 'system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, 'Jenkins管理', '/system/jenkins', '/System/Jenkins', 'cloud-server', 2, 1, 6, 0, 1), -- 代码仓库 -('system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, - '代码仓库', '/system/repository', '/System/Repository', 'code', 2, 1, 7, 0, 1); \ No newline at end of file +(8, 'system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0, + '代码仓库', '/system/repository', '/System/Repository', 'code', 2, 1, 7, 0, 1); + +-- 初始化角色标签 +INSERT INTO sys_role_tag (id, name, color, create_by, create_time, deleted, version) VALUES +(1, '研发', '#1890FF', 'system', '2024-01-01 00:00:00', 0, 0), +(2, '运维', '#52C41A', 'system', '2024-01-01 00:00:00', 0, 0), +(3, '安全', '#FF4D4F', 'system', '2024-01-01 00:00:00', 0, 0), +(4, '临时', '#FAAD14', 'system', '2024-01-01 00:00:00', 0, 0), +(5, '外部', '#722ED1', 'system', '2024-01-01 00:00:00', 0, 0); + +-- 初始化用户 +INSERT INTO sys_user (id, username, password, nickname, email, phone, enabled, create_by, create_time, deleted, version) +VALUES +(1, 'admin', '$2a$10$zXuXOF3C.w/ZpjayaE1P/eFRov/3vJAAOBs.uy2kHUoK3SyuR/Wte', '超级管理员', 'admin@deploy-ease.com', '13800138000', 1, 'system', '2024-01-01 00:00:00', 0, 0), +(2, 'dev_manager', '$2a$10$zXuXOF3C.w/ZpjayaE1P/eFRov/3vJAAOBs.uy2kHUoK3SyuR/Wte', '开发主管', 'dev@deploy-ease.com', '13800138001', 1, 'system', '2024-01-01 00:00:00', 0, 0), +(3, 'ops_manager', '$2a$10$zXuXOF3C.w/ZpjayaE1P/eFRov/3vJAAOBs.uy2kHUoK3SyuR/Wte', '运维主管', 'ops@deploy-ease.com', '13800138002', 1, 'system', '2024-01-01 00:00:00', 0, 0); + +-- 初始化角色 +INSERT INTO sys_role (id, code, name, type, description, sort, enabled, create_by, create_time, deleted, version) +VALUES +(1, 'SUPER_ADMIN', '超级管理员', 1, '系统超级管理员', 1, 1, 'system', '2024-01-01 00:00:00', 0, 0), +(2, 'DEV_MANAGER', '开发主管', 2, '开发团队主管', 2, 1, 'system', '2024-01-01 00:00:00', 0, 0), +(3, 'OPS_MANAGER', '运维主管', 2, '运维团队主管', 3, 1, 'system', '2024-01-01 00:00:00', 0, 0); + +-- 用户角色关联 +INSERT INTO sys_user_role (user_id, role_id, create_by, create_time) +VALUES +(1, 1, 'system', '2024-01-01 00:00:00'), -- 超级管理员 -> 超级管理员角色 +(2, 2, 'system', '2024-01-01 00:00:00'), -- 开发主管 -> 开发主管角色 +(3, 3, 'system', '2024-01-01 00:00:00'); -- 运维主管 -> 运维主管角色 + +-- 角色菜单关联 +INSERT INTO sys_role_menu (role_id, menu_id) +VALUES +-- 超级管理员拥有所有菜单权限 +(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), +-- 开发主管权限 +(2, 7), (2, 8), +-- 运维主管权限 +(3, 7), (3, 8); + +-- 角色标签关联 +INSERT INTO sys_role_tag_relation (role_id, tag_id) +VALUES +(1, 3), -- 超级管理员 -> 安全标签 +(2, 1), -- 开发主管 -> 研发标签 +(3, 2); -- 运维主管 -> 运维标签 \ No newline at end of file diff --git a/backend/src/main/resources/messages.properties b/backend/src/main/resources/messages.properties index 365cde69..4999ec83 100644 --- a/backend/src/main/resources/messages.properties +++ b/backend/src/main/resources/messages.properties @@ -38,4 +38,12 @@ dependency.injection.entitypath.failed=初始化实体 {0} 的EntityPath失败: # JWT相关 jwt.token.expired=登录已过期,请重新登录 jwt.token.invalid=无效的登录凭证 -jwt.token.missing=未提供登录凭证 \ No newline at end of file +jwt.token.missing=未提供登录凭证 + +# 角色相关错误消息 +role.not.found=角色不存在 +role.code.exists=角色编码已存在 +role.name.exists=角色名称已存在 +role.in.use=角色正在使用中,无法删除 +role.admin.cannot.delete=不能删除超级管理员角色 +role.admin.cannot.update=不能修改超级管理员角色 \ No newline at end of file diff --git a/backend/src/main/resources/messages_en.properties b/backend/src/main/resources/messages_en.properties index 85e35d25..4223b0e1 100644 --- a/backend/src/main/resources/messages_en.properties +++ b/backend/src/main/resources/messages_en.properties @@ -38,4 +38,12 @@ dependency.injection.entitypath.failed=Failed to initialize EntityPath for entit # JWT jwt.token.expired=Login expired, please login again jwt.token.invalid=Invalid token -jwt.token.missing=No token provided \ No newline at end of file +jwt.token.missing=No token provided + +# Role related error messages +role.not.found=Role not found +role.code.exists=Role code already exists +role.name.exists=Role name already exists +role.in.use=Role is in use and cannot be deleted +role.admin.cannot.delete=Cannot delete admin role +role.admin.cannot.update=Cannot update admin role \ 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 76d59054..7b86c1af 100644 --- a/backend/src/main/resources/messages_zh_CN.properties +++ b/backend/src/main/resources/messages_zh_CN.properties @@ -33,4 +33,15 @@ entity.not.found.name.id=找不到ID为{1}的{0} dependency.injection.service.not.found=找不到实体 {0} 对应的服务 (尝试过的bean名称: {1}) dependency.injection.repository.not.found=找不到实体 {0} 对应的Repository: {1} dependency.injection.converter.not.found=找不到实体 {0} 对应的Converter: {1} -dependency.injection.entitypath.failed=初始化实体 {0} 的EntityPath失败: {1} \ No newline at end of file +dependency.injection.entitypath.failed=初始化实体 {0} 的EntityPath失败: {1} + +# 角色相关错误消息 +role.not.found=角色不存在 +role.code.exists=角色编码已存在 +role.name.exists=角色名称已存在 +role.in.use=角色正在使用中,无法删除 +role.admin.cannot.delete=不能删除超级管理员角色 +role.admin.cannot.update=不能修改超级管理员角色 + +# 请求相关错误消息 +request.api.not.found=请求的API不存在: {0} \ No newline at end of file