删除中间表实体类,添加菜单功能

This commit is contained in:
戚辰先生 2024-11-30 15:18:07 +08:00
parent ee7e0a44c4
commit 3517f38282
31 changed files with 637 additions and 409 deletions

View File

@ -37,7 +37,7 @@ public class UserApiController extends BaseController<User, UserDTO, Long, UserQ
@Operation(summary = "获取当前用户信息")
@GetMapping("/current")
public Response<LoginResponse> getCurrentUser() {
return Response.success(userService.getCurrentUser());
return Response.success(userService.getCurrentUserResponse());
}
@Override

View File

@ -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<Menu, MenuDTO> {
MenuDTO requestToDto(MenuRequest request);
@Override
MenuDTO toDto(Menu entity);
MenuResponse toResponse(Menu menu);
@Override
List<MenuDTO> toDtoList(List<Menu> entityList);
List<MenuResponse> toResponseList(List<MenuDTO> dtos);
@Mapping(target = "children", expression = "java(toResponseList(dto.getChildren()))")
MenuResponse toResponse(MenuDTO dto);
List<MenuResponse> toResponseList(List<MenuDTO> dtoList);
}

View File

@ -12,8 +12,14 @@ public interface UserConverter extends BaseConverter<User, UserDTO> {
// 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);
}

View File

@ -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<Long> {
* 是否启用true启用 false禁用
*/
private Boolean enabled;
/**
* 菜单关联的角色列表
*/
@ManyToMany(mappedBy = "menus")
private Set<Role> roles = new HashSet<>();
/**
* 菜单关联的权限模板列表
*/
@ManyToMany(mappedBy = "menus")
private Set<PermissionTemplate> templates = new HashSet<>();
}

View File

@ -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<Long> {
/**
* 模板编码
*/
@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<Menu> menus = new HashSet<>();
}

View File

@ -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<Long> {
@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<User> users = new HashSet<>();
/**
* 角色关联的菜单列表
*/
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "sys_role_menu",
joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns = @JoinColumn(name = "menu_id")
)
private Set<Menu> 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<RoleTag> tags = new HashSet<>();
}

View File

@ -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<Long> {
@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;
}
}

View File

@ -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<Long> {
/**
* 标签名称
*/
@Column(nullable = false, length = 50)
private String name;
/**
* 标签颜色十六进制颜色码
*/
@Column(length = 20)
private String color;
/**
* 关联的角色列表
*/
@ManyToMany(mappedBy = "tags")
private Set<Role> roles = new HashSet<>();
}

View File

@ -38,15 +38,9 @@ public class User extends Entity<Long> {
@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<Role> roles = new HashSet<>();

View File

@ -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<Long> {
@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;
}
}

View File

@ -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<Long> {
//
// @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;
// }
//}

View File

@ -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

View File

@ -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);
}
}

View File

@ -315,13 +315,18 @@ public abstract class BaseServiceImpl<T extends Entity<ID>, 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
);
}

View File

@ -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;
}

View File

@ -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;

View File

@ -9,6 +9,8 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
public class UserResponse extends BaseResponse {
private Long id;
private String username;
private String email;

View File

@ -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<Menu, Long> {
/**
* 查询所有未删除的菜单按排序字段排序
*/
List<Menu> findByDeletedFalseOrderBySort();
List<Menu> findByParentIdAndDeletedFalseOrderBySort(Long parentId);
/**
* 根据角色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<Menu> findByRoleIdsAndEnabledTrue(@Param("roleIds") List<Long> roleIds);
List<Menu> findByTypeAndDeletedFalseOrderBySort(Integer type);
/**
* 检查菜单编码是否存在
*/
boolean existsByPermissionAndDeletedFalse(String permission);
List<Menu> findByIdInAndDeletedFalseOrderBySort(List<Long> ids);
List<Menu> findByPermissionLikeAndDeletedFalse(String permission);
List<Menu> findByTypeAndPermissionIsNotNullAndDeletedFalse(Integer type);
List<Menu> findByParentIdAndDeletedFalse(Long parentId);
List<Menu> findByTypeInAndDeletedFalseOrderBySort(List<Integer> types);
List<Menu> findByEnabledAndDeletedFalseOrderBySort(Boolean enabled);
/**
* 检查同级菜单下是否存在相同名称的菜单
*/
boolean existsByNameAndParentIdAndDeletedFalse(String name, Long parentId);
}

View File

@ -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<RoleMenu, Long> {
@Query("SELECT rm.roleId FROM RoleMenu rm where rm.roleId = :roleId")
List<Long> findByRoleId(Long roleId);
//
// void deleteByRoleId(Long roleId);
//
// List<RoleMenu> findByRoleIdIn(List<Long> roleIds);
// 通过角色ID查询
}

View File

@ -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<Role, Long> {
List<Role> findByDeletedFalseOrderBySort();
/**
* 检查角色编码是否存在排除已删除的
*/
boolean existsByCodeAndDeletedFalse(String code);
/**
* 检查角色名称是否存在排除已删除的
*/
boolean existsByNameAndDeletedFalse(String name);
Optional<Role> findByCodeAndDeletedFalse(String code);
/**
* 获取用户的所有角色ID
*/
@Query(value = "SELECT role_id FROM sys_user_role WHERE user_id = :userId AND deleted = false",
nativeQuery = true)
List<Long> findRoleIdsByUserId(@Param("userId") Long userId);
}

View File

@ -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<UserRole, Long> {
//
// @Query("SELECT ur FROM UserRole ur WHERE ur.userId = :userId")
// Set<UserRole> 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);
//
//}

View File

@ -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<Role, Long> {
//
// void validateCode(String code);
//
// void validateName(String name);
//
// List<Long> getRoleMenuIds(Long roleId);
//
// void updateRoleMenus(Long roleId, List<Long> menuIds);
//}
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<Role, RoleDTO, Long> {
/**
* 验证角色编码是否存在
* @param code 角色编码
*/
void validateCode(String code);
/**
* 验证角色名称是否存在
* @param name 角色名称
*/
void validateName(String name);
/**
* 获取用户的所有角色ID
* @param userId 用户ID
* @return 角色ID列表
*/
List<Long> getUserRoleIds(Long userId);
}

View File

@ -24,6 +24,18 @@ public interface IUserService extends IBaseService<User, UserDTO, Long> {
@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();
}

View File

@ -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<Menu, MenuDTO, Long> implem
@Resource
private IUserService userService;
@Resource
private IRoleService roleService;
@Override
public List<MenuResponse> getMenuTree() {
List<Menu> allMenus = repository.findByDeletedFalseOrderBySort();
@ -48,21 +54,37 @@ public class MenuServiceImpl extends BaseServiceImpl<Menu, MenuDTO, Long> implem
@Override
public List<MenuResponse> getUserMenus() {
LoginResponse user = userService.getCurrentUser();
// TODO: 根据用户角色获取菜单
return getMenuTree();
// 1. 获取当前用户信息(使用内部服务方法)
UserDTO user = userService.getCurrentUser();
// 2. 通过roleService获取用户的所有角色ID
List<Long> roleIds = roleService.getUserRoleIds(user.getId());
// 3. 获取这些角色关联的所有菜单
List<Menu> userMenus = repository.findByRoleIdsAndEnabledTrue(roleIds);
// 4. 转换为DTO并构建树形结构
List<MenuDTO> menuDTOs = converter.toDtoList(userMenus);
List<MenuDTO> menuTree = buildMenuTree(menuDTOs);
// 5. 转换为响应对象
return converter.toResponseList(menuTree);
}
private List<MenuDTO> buildMenuTree(List<MenuDTO> menus) {
// 使用Map存储所有菜单便于查找
Map<Long, MenuDTO> menuMap = menus.stream()
.collect(Collectors.toMap(MenuDTO::getId, menu -> menu));
List<MenuDTO> 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) {
@ -73,6 +95,25 @@ public class MenuServiceImpl extends BaseServiceImpl<Menu, MenuDTO, Long> implem
}
}
// 对每一级菜单进行排序
sortMenuTree(rootMenus);
return rootMenus;
}
private void sortMenuTree(List<MenuDTO> 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());
}
}
}
}

View File

@ -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<Role, RoleQuery> implements RoleService {
//
// private final RoleRepository roleRepository;
// private final RoleMenuRepository roleMenuRepository;
//
// @Override
// protected JpaRepository<Role, Long> getRepository() {
// return roleRepository;
// }
//
// @Override
// public List<Long> getRoleMenuIds(Long roleId) {
// return roleMenuRepository.findByRoleId(roleId)
// .stream()
// .map(RoleMenu::getMenuId)
// .collect(Collectors.toList());
// }
//
// @Override
// @Transactional
// public void updateRoleMenus(Long roleId, List<Long> 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<RoleMenu> 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<Role> findAll() {
// return roleRepository.findByDeletedFalseOrderBySort();
// }
//}
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<Role, RoleDTO, Long> implements IRoleService {
@Resource
private IRoleRepository repository;
@Override
public List<Long> 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);
}
}
}

View File

@ -132,13 +132,20 @@ public class UserServiceImpl extends BaseServiceImpl<User, UserDTO, Long> 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);
}
}

View File

@ -77,3 +77,101 @@ CREATE TABLE sys_menu (
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 '模板菜单关联表';

View File

@ -1,4 +1,4 @@
-- 插入初始租户
-- 初始租户
INSERT INTO sys_tenant
(create_by, create_time, deleted, update_by, update_time, version,
address, code, contact_name, contact_phone, email, enabled, name)
@ -7,56 +7,92 @@ VALUES
'北京市朝阳区望京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)
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'),
('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');
('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,
(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); -- 运维主管 -> 运维标签

View File

@ -39,3 +39,11 @@ dependency.injection.entitypath.failed=初始化实体 {0} 的EntityPath失败:
jwt.token.expired=登录已过期,请重新登录
jwt.token.invalid=无效的登录凭证
jwt.token.missing=未提供登录凭证
# 角色相关错误消息
role.not.found=角色不存在
role.code.exists=角色编码已存在
role.name.exists=角色名称已存在
role.in.use=角色正在使用中,无法删除
role.admin.cannot.delete=不能删除超级管理员角色
role.admin.cannot.update=不能修改超级管理员角色

View File

@ -39,3 +39,11 @@ dependency.injection.entitypath.failed=Failed to initialize EntityPath for entit
jwt.token.expired=Login expired, please login again
jwt.token.invalid=Invalid token
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

View File

@ -34,3 +34,14 @@ dependency.injection.service.not.found=找不到实体 {0} 对应的服务 (尝
dependency.injection.repository.not.found=找不到实体 {0} 对应的Repository: {1}
dependency.injection.converter.not.found=找不到实体 {0} 对应的Converter: {1}
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}