Commit d043ba58 by Lizh

调整用户登录,用户添加等功能

parent d0367d25
......@@ -15,4 +15,5 @@
CLAUDE.md
docs
plans
logs
package com.jomalls.custom.app.constant;
/**
* @Author: Lizh
* @Date: 2026/7/15 15:27
* @Description: 角色常量
* @Version: 1.0
*/
public class RoleConstants {
/**
* 超级管理员admin 名称
*/
public static final String ADMIN_USER = "admin";
/**
* 角色状态
*/
public static final String STATUS_NORMAL = "0";
}
......@@ -111,4 +111,25 @@ public class UserConstants {
*/
public static final int PASSWORD_MIN_LENGTH = 5;
public static final int PASSWORD_MAX_LENGTH = 20;
/**
* 初始化密码长度
*/
public static final int INIT_PASSWORD_LENGTH = 8;
/**
* 黑名单key
*/
public static final String BLACK_IP_KEY = "sys.login.blackIPList";
/**
* 是否修改过初始化密码
*/
public static final String INIT_PWD_MODIFY_KEY = "sys.account.initPasswordModify";
/**
* 密码过期
*/
public static final String PWD_VALIDATE_KEY = "sys.account.passwordValidateDays";
}
......@@ -10,6 +10,7 @@ import lombok.Getter;
*/
@Getter
public enum SysUserStatusEnums {
NORMAL("0", "正常"),
DELETED("2", "已被删除"),
DISABLED("1", "已被停用");
......
......@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jomalls.custom.app.dto.system.*;
import com.jomalls.custom.app.vo.system.SysDeptVO;
import com.jomalls.custom.app.vo.system.SysRoleVO;
import com.jomalls.custom.app.vo.system.SysUserVO;
import java.util.List;
......@@ -112,12 +113,21 @@ public interface SysUserService {
void updatePwd(SysUserUpdatePwdDTO dto);
/**
* 查询用户已分配的角色ID列表
* 查询用户详情(仅返回用户 VO,不含角色/岗位)
*
* @param userId 用户ID
* @return 角色ID列表
* @return 用户 VO
*/
List<Long> getAuthRoleList(Long userId);
SysUserVO getById(Long userId);
/**
* 查询用户已分配的角色列表(与 custom-back selectRolesByUserId + selectRoleAll 对齐)
* <p>返回全部角色,已分配给用户的角色设置 flag=true
*
* @param userId 用户ID
* @return 角色 VO 列表(含 flag 标识)
*/
List<SysRoleVO> getAuthRoleList(Long userId);
/**
* 批量插入用户角色关联
......
......@@ -9,10 +9,13 @@ import com.jomalls.custom.app.utils.BeanMapper;
import com.jomalls.custom.app.vo.system.SysDeptVO;
import com.jomalls.custom.dal.entity.SysDeptEntity;
import com.jomalls.custom.domain.service.system.SysDeptDomainService;
import com.jomalls.custom.security.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
/**
......@@ -69,8 +72,18 @@ public class SysDeptServiceImpl implements SysDeptService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public void add(SysDeptSaveDTO dto) {
SysDeptEntity dept = BeanMapper.mapper().convert(dto, SysDeptEntity.class);
// 检查部门名称唯一性
long count = sysDeptDomainService.count(
new LambdaQueryWrapper<SysDeptEntity>()
.eq(SysDeptEntity::getDeptName, dept.getDeptName())
.eq(SysDeptEntity::getParentId, dept.getParentId() != null ? dept.getParentId() : 0L)
.ne(dept.getDeptId() != null, SysDeptEntity::getDeptId, dept.getDeptId() != null ? dept.getDeptId() : -1L));
if (count > 0) {
throw new ServiceException("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
}
// 设置祖级列表
if (dept.getParentId() == null || dept.getParentId() == 0L) {
dept.setAncestors("0");
......@@ -79,8 +92,14 @@ public class SysDeptServiceImpl implements SysDeptService {
if (parentDept == null) {
throw new ServiceException("父部门不存在");
}
dept.setAncestors(parentDept.getAncestors() + "," + dept.getParentId());
if ("1".equals(parentDept.getStatus())) {
throw new ServiceException("父部门已停用,不允许新增子部门");
}
dept.setAncestors(parentDept.getAncestors() != null ? parentDept.getAncestors() + "," + parentDept.getDeptId() : String.valueOf(parentDept.getDeptId()));
}
// 设置审计字段
dept.setCreateBy(SecurityUtils.getUsername());
dept.setCreateTime(LocalDateTime.now());
boolean saved = sysDeptDomainService.save(dept);
if (saved) {
log.info("新增部门成功: deptName={}", dept.getDeptName());
......@@ -88,13 +107,85 @@ public class SysDeptServiceImpl implements SysDeptService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public void edit(SysDeptUpdateDTO dto) {
SysDeptEntity dept = BeanMapper.mapper().convert(dto, SysDeptEntity.class);
// 检查部门名称唯一性(排除自身)
long count = sysDeptDomainService.count(
new LambdaQueryWrapper<SysDeptEntity>()
.eq(SysDeptEntity::getDeptName, dept.getDeptName())
.eq(SysDeptEntity::getParentId, dept.getParentId() != null ? dept.getParentId() : 0L)
.ne(dept.getDeptId() != null, SysDeptEntity::getDeptId, dept.getDeptId() != null ? dept.getDeptId() : -1L));
if (count > 0) {
throw new ServiceException("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
}
// 获取旧部门信息
SysDeptEntity oldDept = sysDeptDomainService.getById(dept.getDeptId());
if (oldDept == null) {
throw new ServiceException("部门不存在");
}
String oldAncestors = oldDept.getAncestors();
// 计算新的祖级列表
boolean parentChanged = dept.getParentId() != null && !dept.getParentId().equals(oldDept.getParentId());
if (parentChanged) {
String newAncestors;
if (dept.getParentId() == 0L) {
newAncestors = "0";
} else {
SysDeptEntity newParent = sysDeptDomainService.getById(dept.getParentId());
if (newParent == null) {
throw new ServiceException("父部门不存在");
}
if ("1".equals(newParent.getStatus())) {
throw new ServiceException("父部门已停用,不允许移动子部门");
}
newAncestors = newParent.getAncestors() + "," + newParent.getDeptId();
}
dept.setAncestors(newAncestors);
} else {
// 父部门未变更,保持原有祖级列表
dept.setAncestors(oldAncestors);
}
// 设置审计字段
dept.setUpdateBy(SecurityUtils.getUsername());
dept.setUpdateTime(LocalDateTime.now());
// 更新部门
sysDeptDomainService.updateById(dept);
// 更新子部门 ancestors(仅父部门变更时)
if (parentChanged) {
String oldPrefix = oldAncestors + "," + dept.getDeptId();
String newPrefix = dept.getAncestors() + "," + dept.getDeptId();
List<SysDeptEntity> descendants = sysDeptDomainService.list(
new LambdaQueryWrapper<SysDeptEntity>()
.likeRight(SysDeptEntity::getAncestors, oldPrefix));
for (SysDeptEntity descendant : descendants) {
descendant.setAncestors(
descendant.getAncestors().replace(oldPrefix, newPrefix));
sysDeptDomainService.updateById(descendant);
}
}
// 如果部门状态启用,更新父部门链状态为正常
if ("0".equals(dept.getStatus()) && dept.getAncestors() != null) {
String[] ancestorIds = dept.getAncestors().split(",");
for (String ancestorId : ancestorIds) {
if (ancestorId.isEmpty() || "0".equals(ancestorId)) continue;
SysDeptEntity ancestor = new SysDeptEntity();
ancestor.setDeptId(Long.parseLong(ancestorId));
ancestor.setStatus("0");
sysDeptDomainService.updateById(ancestor);
}
}
log.info("修改部门成功: deptId={}", dept.getDeptId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void remove(Long deptId) {
// 检查是否有子部门
if (sysDeptDomainService.hasChildByDeptId(deptId)) {
......
......@@ -9,12 +9,16 @@ import com.jomalls.custom.app.utils.BeanMapper;
import com.jomalls.custom.app.vo.system.SysMenuVO;
import com.jomalls.custom.app.vo.system.TreeSelectVO;
import com.jomalls.custom.dal.entity.SysMenuEntity;
import com.jomalls.custom.dal.entity.SysRoleEntity;
import com.jomalls.custom.dal.entity.SysRoleMenuEntity;
import com.jomalls.custom.domain.service.system.SysMenuDomainService;
import com.jomalls.custom.domain.service.system.SysRoleDomainService;
import com.jomalls.custom.domain.service.system.SysRoleMenuDomainService;
import com.jomalls.custom.security.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.*;
......@@ -32,6 +36,7 @@ public class SysMenuServiceImpl implements SysMenuService {
private final SysMenuDomainService sysMenuDomainService;
private final SysRoleMenuDomainService sysRoleMenuDomainService;
private final SysRoleDomainService sysRoleDomainService;
@Override
public List<SysMenuVO> selectMenuList(SysMenuVO menu) {
......@@ -70,23 +75,67 @@ public class SysMenuServiceImpl implements SysMenuService {
List<Long> checkedKeys = roleMenus.stream()
.map(SysRoleMenuEntity::getMenuId)
.collect(Collectors.toList());
// 如果菜单树选择项不关联显示(menuCheckStrictly=false),
// 则包含直接分配的菜单及其所有子菜单的ID
SysRoleEntity role = sysRoleDomainService.getById(roleId);
if (role != null && role.getMenuCheckStrictly() != null && role.getMenuCheckStrictly() == 0) {
Set<Long> allCheckedKeys = new HashSet<>(checkedKeys);
List<SysMenuEntity> allMenus = sysMenuDomainService.list();
for (Long menuId : checkedKeys) {
addAllDescendantIds(menuId, allMenus, allCheckedKeys);
}
checkedKeys = new ArrayList<>(allCheckedKeys);
}
Map<String, Object> result = new HashMap<>();
result.put("menus", menus);
result.put("checkedKeys", checkedKeys);
return result;
}
/**
* 递归添加所有子菜单ID
*/
private void addAllDescendantIds(Long parentId, List<SysMenuEntity> allMenus, Set<Long> result) {
for (SysMenuEntity menu : allMenus) {
if (parentId.equals(menu.getParentId())) {
result.add(menu.getMenuId());
addAllDescendantIds(menu.getMenuId(), allMenus, result);
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void add(SysMenuSaveDTO dto) {
SysMenuEntity menu = BeanMapper.mapper().convert(dto, SysMenuEntity.class);
// 检查菜单名称唯一性(同级菜单下名称不能重复)
long nameCount = sysMenuDomainService.count(
new LambdaQueryWrapper<SysMenuEntity>()
.eq(SysMenuEntity::getMenuName, menu.getMenuName())
.eq(menu.getParentId() != null, SysMenuEntity::getParentId, menu.getParentId()));
if (nameCount > 0) {
throw new ServiceException("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
}
menu.setCreateBy(SecurityUtils.getUsername());
menu.setCreateTime(new Date());
sysMenuDomainService.save(menu);
log.info("新增菜单成功: menuName={}", menu.getMenuName());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void edit(SysMenuUpdateDTO dto) {
SysMenuEntity menu = BeanMapper.mapper().convert(dto, SysMenuEntity.class);
// 检查菜单名称唯一性(排除自身)
long nameCount = sysMenuDomainService.count(
new LambdaQueryWrapper<SysMenuEntity>()
.eq(SysMenuEntity::getMenuName, menu.getMenuName())
.eq(menu.getParentId() != null, SysMenuEntity::getParentId, menu.getParentId())
.ne(SysMenuEntity::getMenuId, menu.getMenuId()));
if (nameCount > 0) {
throw new ServiceException("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
}
menu.setUpdateBy(SecurityUtils.getUsername());
menu.setUpdateTime(new Date());
sysMenuDomainService.updateById(menu);
log.info("修改菜单成功: menuId={}", menu.getMenuId());
......@@ -107,6 +156,7 @@ public class SysMenuServiceImpl implements SysMenuService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public void remove(Long menuId) {
// 检查是否有子菜单(controller 层已做检查,此处双重保障)
long childCount = sysMenuDomainService.count(
......@@ -121,8 +171,6 @@ public class SysMenuServiceImpl implements SysMenuService {
log.info("删除菜单成功: menuId={}", menuId);
}
// ==================== 私有辅助方法 ====================
/**
* 构建菜单树选择列表
*/
......
......@@ -11,7 +11,7 @@ import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 密码服务(密码重试锁定,与 custom-back SysPasswordService 对齐
* 密码服务(密码重试锁定)
*
* @author Lizh
*/
......
......@@ -10,12 +10,18 @@ import com.jomalls.custom.app.service.system.SysPostService;
import com.jomalls.custom.app.utils.BeanMapper;
import com.jomalls.custom.app.vo.system.SysPostVO;
import com.jomalls.custom.dal.entity.SysPostEntity;
import com.jomalls.custom.dal.entity.SysUserPostEntity;
import com.jomalls.custom.domain.service.system.SysPostDomainService;
import com.jomalls.custom.domain.service.system.SysUserPostDomainService;
import com.jomalls.custom.security.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
/**
......@@ -29,10 +35,14 @@ import java.util.List;
public class SysPostServiceImpl implements SysPostService {
private final SysPostDomainService sysPostDomainService;
private final SysUserPostDomainService sysUserPostDomainService;
@Override
public List<SysPostVO> listAllPosts() {
List<SysPostEntity> entities = sysPostDomainService.list();
// 与 custom-back selectPostAll 对齐:返回全部岗位(无过滤),按 postSort 排序
List<SysPostEntity> entities = sysPostDomainService.list(
new LambdaQueryWrapper<SysPostEntity>()
.orderByAsc(SysPostEntity::getPostSort));
return BeanMapper.mapper().convertList(entities, SysPostVO.class);
}
......@@ -61,6 +71,7 @@ public class SysPostServiceImpl implements SysPostService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public void add(SysPostSaveDTO dto) {
SysPostEntity post = BeanMapper.mapper().convert(dto, SysPostEntity.class);
// 检查岗位编码唯一性
......@@ -69,6 +80,14 @@ public class SysPostServiceImpl implements SysPostService {
if (count > 0) {
throw new ServiceException("岗位编码已存在");
}
// 检查岗位名称唯一性
long nameCount = sysPostDomainService.count(
new LambdaQueryWrapper<SysPostEntity>().eq(SysPostEntity::getPostName, post.getPostName()));
if (nameCount > 0) {
throw new ServiceException("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在");
}
post.setCreateBy(SecurityUtils.getUsername());
post.setCreateTime(LocalDateTime.now());
boolean saved = sysPostDomainService.save(post);
if (saved) {
log.info("新增岗位成功: postName={}", post.getPostName());
......@@ -76,6 +95,7 @@ public class SysPostServiceImpl implements SysPostService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public void edit(SysPostUpdateDTO dto) {
SysPostEntity post = BeanMapper.mapper().convert(dto, SysPostEntity.class);
// 检查岗位编码唯一性(排除自身)
......@@ -86,13 +106,31 @@ public class SysPostServiceImpl implements SysPostService {
if (count > 0) {
throw new ServiceException("岗位编码已存在");
}
// 检查岗位名称唯一性(排除自身)
long nameCount = sysPostDomainService.count(
new LambdaQueryWrapper<SysPostEntity>()
.eq(SysPostEntity::getPostName, post.getPostName())
.ne(post.getPostId() != null, SysPostEntity::getPostId, post.getPostId() != null ? post.getPostId() : -1L));
if (nameCount > 0) {
throw new ServiceException("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在");
}
post.setUpdateBy(SecurityUtils.getUsername());
post.setUpdateTime(LocalDateTime.now());
sysPostDomainService.updateById(post);
log.info("修改岗位成功: postId={}", post.getPostId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void remove(Long[] postIds) {
for (Long postId : postIds) {
// 检查岗位下是否有用户关联(有用户关联则不允许删除)
long userCount = sysUserPostDomainService.count(
new LambdaQueryWrapper<SysUserPostEntity>().eq(SysUserPostEntity::getPostId, postId));
if (userCount > 0) {
SysPostEntity post = sysPostDomainService.getById(postId);
throw new ServiceException("岗位'" + (post != null ? post.getPostName() : postId) + "'已分配用户,不能删除");
}
sysPostDomainService.removeById(postId);
}
log.info("批量删除岗位成功: count={}", postIds.length);
......
......@@ -3,6 +3,7 @@ package com.jomalls.custom.app.service.system.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jomalls.custom.app.constant.RoleConstants;
import com.jomalls.custom.app.dto.system.SysRoleDataScopeDTO;
import com.jomalls.custom.app.dto.system.SysRoleSaveDTO;
import com.jomalls.custom.app.dto.system.SysRoleStatusDTO;
......@@ -15,9 +16,11 @@ import com.jomalls.custom.app.vo.system.SysRoleVO;
import com.jomalls.custom.app.vo.system.SysUserVO;
import com.jomalls.custom.dal.entity.*;
import com.jomalls.custom.domain.service.system.*;
import com.jomalls.custom.security.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.*;
......@@ -42,7 +45,11 @@ public class SysRoleServiceImpl implements SysRoleService {
@Override
public List<SysRoleVO> listAllRoles() {
List<SysRoleEntity> entities = sysRoleDomainService.list();
// 过滤 delFlag='0',按 roleSort 排序
List<SysRoleEntity> entities = sysRoleDomainService.list(
new LambdaQueryWrapper<SysRoleEntity>()
.eq(SysRoleEntity::getDelFlag, RoleConstants.STATUS_NORMAL)
.orderByAsc(SysRoleEntity::getRoleSort));
return BeanMapper.mapper().convertList(entities, SysRoleVO.class);
}
......@@ -58,7 +65,7 @@ public class SysRoleServiceImpl implements SysRoleService {
if (StringUtils.hasText(role.getStatus())) {
wrapper.eq(SysRoleEntity::getStatus, role.getStatus());
}
wrapper.eq(SysRoleEntity::getDelFlag, "0");
wrapper.eq(SysRoleEntity::getDelFlag, RoleConstants.STATUS_NORMAL);
wrapper.orderByAsc(SysRoleEntity::getRoleSort);
IPage<SysRoleEntity> entityPage = sysRoleDomainService.page(
new Page<>(page.getCurrent(), page.getSize()), wrapper);
......@@ -72,6 +79,7 @@ public class SysRoleServiceImpl implements SysRoleService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public void add(SysRoleSaveDTO dto) {
SysRoleEntity role = BeanMapper.mapper().convert(dto, SysRoleEntity.class);
// 检查角色名称唯一性
......@@ -86,10 +94,14 @@ public class SysRoleServiceImpl implements SysRoleService {
if (countByKey > 0) {
throw new ServiceException("角色权限字符串已存在");
}
role.setCreateBy(SecurityUtils.getUsername());
role.setCreateTime(new Date());
// 保存角色
sysRoleDomainService.save(role);
Long roleId = role.getRoleId();
// 更新角色权限字符串为包含角色ID的格式(始终覆盖)
role.setRoleKey("role_" + roleId);
sysRoleDomainService.updateById(role);
// 保存角色菜单关联
if (dto.getMenuIds() != null) {
for (Long menuId : dto.getMenuIds()) {
......@@ -103,8 +115,14 @@ public class SysRoleServiceImpl implements SysRoleService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public void edit(SysRoleUpdateDTO dto) {
SysRoleEntity role = BeanMapper.mapper().convert(dto, SysRoleEntity.class);
// 检查是否允许操作超级管理员角色
SysRoleEntity existingRole = sysRoleDomainService.getById(role.getRoleId());
if (existingRole != null) {
checkRoleAllowed(existingRole);
}
// 检查角色名称唯一性(排除自身)
long countByName = sysRoleDomainService.count(
new LambdaQueryWrapper<SysRoleEntity>()
......@@ -121,6 +139,7 @@ public class SysRoleServiceImpl implements SysRoleService {
if (countByKey > 0) {
throw new ServiceException("角色权限字符串已存在");
}
role.setUpdateBy(SecurityUtils.getUsername());
role.setUpdateTime(new Date());
// 更新角色
sysRoleDomainService.updateById(role);
......@@ -139,10 +158,18 @@ public class SysRoleServiceImpl implements SysRoleService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public void dataScope(SysRoleDataScopeDTO dto) {
SysRoleEntity role = new SysRoleEntity();
role.setRoleId(dto.getRoleId());
// 检查是否允许操作超级管理员角色
SysRoleEntity existingRole = sysRoleDomainService.getById(dto.getRoleId());
if (existingRole != null) {
checkRoleAllowed(existingRole);
}
role.setDataScope(dto.getDataScope());
role.setUpdateBy(SecurityUtils.getUsername());
role.setUpdateTime(new Date());
// 更新角色信息
sysRoleDomainService.updateById(role);
// 删除旧的部门关联
......@@ -166,23 +193,35 @@ public class SysRoleServiceImpl implements SysRoleService {
if (role == null) {
throw new ServiceException("角色不存在");
}
checkRoleAllowed(role);
role.setStatus(dto.getStatus());
role.setUpdateBy(SecurityUtils.getUsername());
role.setUpdateTime(new Date());
sysRoleDomainService.updateById(role);
log.info("角色状态已修改: roleId={}, status={}", dto.getRoleId(), dto.getStatus());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void remove(Long[] roleIds) {
for (Long roleId : roleIds) {
// 检查是否允许操作超级管理员角色
SysRoleEntity role = sysRoleDomainService.getById(roleId);
if (role != null) {
checkRoleAllowed(role);
}
// 检查是否有用户关联(有用户关联则不允许删除)
long userCount = sysUserRoleDomainService.count(
new LambdaQueryWrapper<SysUserRoleEntity>().eq(SysUserRoleEntity::getRoleId, roleId));
if (userCount > 0) {
throw new ServiceException("角色'" + (role != null ? role.getRoleName() : roleId) + "'已分配用户,不能删除");
}
// 删除角色菜单关联
sysRoleMenuDomainService.remove(
new LambdaQueryWrapper<SysRoleMenuEntity>().eq(SysRoleMenuEntity::getRoleId, roleId));
// 删除角色部门关联
sysRoleDeptDomainService.remove(
new LambdaQueryWrapper<SysRoleDeptEntity>().eq(SysRoleDeptEntity::getRoleId, roleId));
// 删除用户角色关联
sysUserRoleDomainService.remove(
new LambdaQueryWrapper<SysUserRoleEntity>().eq(SysUserRoleEntity::getRoleId, roleId));
// 删除角色
sysRoleDomainService.removeById(roleId);
}
......@@ -274,4 +313,13 @@ public class SysRoleServiceImpl implements SysRoleService {
List<SysDeptEntity> tree = sysDeptDomainService.buildDeptTree(allDepts);
return BeanMapper.mapper().convertList(tree, SysDeptVO.class);
}
/**
* 检查是否允许操作超级管理员角色
*/
private void checkRoleAllowed(SysRoleEntity role) {
if (role.getRoleId() != null && (role.getRoleId() == 1L || "admin".equals(role.getRoleKey()))) {
throw new ServiceException("不允许操作超级管理员角色");
}
}
}
......@@ -86,4 +86,26 @@ public class CharsetKit {
public static String systemCharset() {
return Charset.defaultCharset().name();
}
public static boolean containsChinese(String str) {
if (str == null || str.isEmpty()) {
return false;
}
// 中文字符的 Unicode 范围
// 基本汉字:4E00-9FA5
// 扩展A:3400-4DBF
// 扩展B-F:20000-2EBEF
for (char c : str.toCharArray()) {
// 最常用的汉字范围
if (c >= 0x4E00 && c <= 0x9FA5) {
return true;
}
// 如果需要包含扩展汉字,加上下面的判断
//if ((c >= 0x3400 && c <= 0x4DBF) || (c >= 0x20000 && c <= 0x2EBEF)) {
// return true;
//}
}
return false;
}
}
package com.jomalls.custom.app.utils;
import java.util.UUID;
/**
* @Author: Lizh
* @Date: 2026/7/15 14:36
* @Description: 唯一ID
* @Version: 1.0
*/
public class UniqueId {
private static final int START_INDEX = 1;
/**
* 生成UUID
* @param length
* @return
*/
public static String generateUniqueId(int length) {
return UUID.randomUUID().toString().replace("-", "").substring(START_INDEX, length);
}
}
......@@ -73,4 +73,8 @@ public class SysRoleVO implements Serializable {
@JsonProperty("deptIds")
@Schema(description = "部门ID列表")
private Long[] deptIds;
@JsonProperty("flag")
@Schema(description = "用户是否已分配该角色")
private Boolean flag;
}
......@@ -15,10 +15,9 @@ import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* Token 验证处理服务
......@@ -252,7 +251,7 @@ public class TokenHandle {
redisKey,
JSON.toJSONString(loginUser),
expireTime * 60L,
java.util.concurrent.TimeUnit.SECONDS
TimeUnit.SECONDS
);
}
......@@ -269,19 +268,21 @@ public class TokenHandle {
*/
private String buildJwtCompatibly(String uuid, LoginUser loginUser) {
try {
java.util.Base64.Encoder encoder = java.util.Base64.getUrlEncoder().withoutPadding();
Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
log.info(JSON.toJSONString(loginUser));
// Header: {"alg":"HS512"}
String header = "{\"alg\":\"HS512\"}";
String headerBase64 = encoder.encodeToString(
header.getBytes(java.nio.charset.StandardCharsets.UTF_8));
header.getBytes(StandardCharsets.UTF_8));
// Payload: 仅含 UUID + 基本标识,不含 permissions/deptId(全量信息在 Redis
java.util.LinkedHashMap<String, Object> claims = new java.util.LinkedHashMap<>();
// Payload:基本标识 + deptId + permissions(前端从 JWT 解析 sysUser 信息
LinkedHashMap<String, Object> claims = new LinkedHashMap<>();
claims.put("login_user_key", uuid);
claims.put("sub", loginUser.getUsername());
claims.put("account", loginUser.getUsername());
claims.put("id", loginUser.getUserId());
claims.put("deptId", loginUser.getDeptId());
claims.put("permissions", loginUser.getPermissions());
// JWT 24 小时硬过期(与 custom-back TOKEN_TIMEOUT = 86400000L 一致)
claims.put("exp", (System.currentTimeMillis() + 86400000L) / 1000);
......@@ -343,16 +344,12 @@ public class TokenHandle {
*/
public void refreshToken(LoginUser loginUser) {
if (stringRedisTemplate != null && loginUser != null
&& org.springframework.util.StringUtils.hasText(loginUser.getToken())) {
&& StringUtils.hasText(loginUser.getToken())) {
String redisKey = REDIS_TOKEN_PREFIX + loginUser.getToken();
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(System.currentTimeMillis() + expireTime * 60 * 1000L);
stringRedisTemplate.opsForValue().set(
redisKey,
JSON.toJSONString(loginUser),
expireTime * 60L,
java.util.concurrent.TimeUnit.SECONDS
);
stringRedisTemplate.opsForValue().set(redisKey, JSON.toJSONString(loginUser),
expireTime * 60L, java.util.concurrent.TimeUnit.SECONDS);
}
}
......
......@@ -62,18 +62,19 @@
AND ur.user_id = #{userId}
</select>
<!-- 根据用户ID查询菜单树 -->
<!-- 根据用户ID查询菜单树(与 custom-back 完全对齐:JOIN 方式,含 role.status 过滤) -->
<select id="selectMenuTreeByUserId" resultType="com.jomalls.custom.dal.entity.SysMenuEntity">
SELECT DISTINCT m.* FROM sys_menu m
<if test="userId != 1">
LEFT JOIN sys_role_menu rm ON m.menu_id = rm.menu_id
LEFT JOIN sys_user_role ur ON rm.role_id = ur.role_id
LEFT JOIN sys_role ro ON ur.role_id = ro.role_id
</if>
WHERE m.menu_type IN ('M', 'C')
AND m.status = '0'
<if test="userId != 1">
AND m.menu_id IN (
SELECT rm.menu_id FROM sys_role_menu rm
WHERE rm.role_id IN (
SELECT ur.role_id FROM sys_user_role ur WHERE ur.user_id = #{userId}
)
)
AND ur.user_id = #{userId}
AND ro.status = '0'
</if>
ORDER BY m.parent_id, m.order_num
</select>
......
......@@ -91,8 +91,7 @@ public class CommonExceptionHandlerAdvice {
@ExceptionHandler(ServiceException.class)
public ResponseEntity<R<Object>> handleServiceException(ServiceException e) {
log.debug("[ 业务异常 ] code={}, {}", e.getCode(), e.getMessage(), e);
HttpStatus httpStatus = CodeEnum.MAP.getOrDefault(e.getCode(), HttpStatus.OK);
return ResponseEntity.status(httpStatus).body(R.fail(e.getCode(), e.getMessage()));
return ResponseEntity.status(HttpStatus.OK).body(R.fail(e.getCode(), e.getMessage()));
}
/**
......@@ -101,7 +100,7 @@ public class CommonExceptionHandlerAdvice {
@ExceptionHandler(RemoteServiceException.class)
public ResponseEntity<R<Object>> handleRemoteServiceException(RemoteServiceException e) {
log.debug("[ 远程服务异常 ] status={}, {}", e.getStatusCode(), e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
return ResponseEntity.status(HttpStatus.OK)
.body(R.fail(e.getStatusCode(), e.getMessage()));
}
......@@ -111,7 +110,7 @@ public class CommonExceptionHandlerAdvice {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<R<Object>> handleRuntimeException(Exception e) {
log.debug("[ 运行时异常 ] {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
return ResponseEntity.status(HttpStatus.OK)
.body(R.fail(e.getMessage()));
}
......@@ -121,7 +120,7 @@ public class CommonExceptionHandlerAdvice {
@ExceptionHandler(Exception.class)
public ResponseEntity<R<Object>> handleException(Exception e) {
log.debug("[ 未捕获异常 ] {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
return ResponseEntity.status(HttpStatus.OK)
.body(R.fail(CodeEnum.FAIL.getCode(), e.getMessage()));
}
}
......@@ -3,12 +3,17 @@ package com.jomalls.custom.webapp.controller.system;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jomalls.custom.app.annotation.RequiresPermissions;
import com.jomalls.custom.app.constant.SecurityConstants;
import com.jomalls.custom.app.dto.system.*;
import com.jomalls.custom.app.service.system.SysRoleService;
import com.jomalls.custom.app.vo.system.SysDeptVO;
import com.jomalls.custom.app.vo.system.SysRoleVO;
import com.jomalls.custom.app.vo.system.SysUserVO;
import com.jomalls.custom.dal.entity.SysMenuEntity;
import com.jomalls.custom.domain.service.system.SysMenuDomainService;
import com.jomalls.custom.security.LoginUser;
import com.jomalls.custom.security.SecurityUtils;
import com.jomalls.custom.security.TokenHandle;
import com.jomalls.custom.app.exception.ServiceException;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
......@@ -16,8 +21,10 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 角色管理 Controller
......@@ -32,6 +39,8 @@ import java.util.Map;
public class SysRoleController {
private final SysRoleService sysRoleService;
private final SysMenuDomainService sysMenuDomainService;
private final TokenHandle tokenHandle;
@Operation(summary = "查询角色列表(无权限要求,用于下拉框)")
@GetMapping("/optionselect")
......@@ -69,11 +78,15 @@ public class SysRoleController {
@PutMapping
public void edit(@RequestBody SysRoleUpdateDTO dto) {
sysRoleService.edit(dto);
// 刷新 LoginUser 缓存(非管理员用户的权限可能发生变化)
if (SecurityUtils.getUserId() != null && !SecurityUtils.getUserId().equals(1L)) {
// TODO: 如果当前用户所属角色被修改,需要刷新 token 缓存中的 permissions
// 在 Redis TokenStore 场景下,应清除或刷新对应用户的缓存权限
log.debug("角色更新完成,需要刷新相关用户的LoginUser缓存: roleId={}", dto.getRoleId());
// 角色修改后,刷新缓存用户权限
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser != null && loginUser.getUserId() != null && SecurityConstants.ADMIN_USER_ID.equals(loginUser.getUserId())) {
// 重新查询权限
Set<String> perms = new HashSet<>();
List<String> permList = sysMenuDomainService.selectMenuPermsByUserId(loginUser.getUserId());
if (permList != null) perms.addAll(permList);
loginUser.setPermissions(perms);
tokenHandle.refreshToken(loginUser);
}
}
......
......@@ -3,11 +3,14 @@ package com.jomalls.custom.webapp.controller.system;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jomalls.custom.app.annotation.RequiresPermissions;
import com.jomalls.custom.app.constant.RoleConstants;
import com.jomalls.custom.app.constant.SecurityConstants;
import com.jomalls.custom.app.dto.system.*;
import com.jomalls.custom.app.service.system.SysPostService;
import com.jomalls.custom.app.service.system.SysRoleService;
import com.jomalls.custom.app.service.system.SysUserService;
import com.jomalls.custom.app.vo.system.SysDeptVO;
import com.jomalls.custom.app.vo.system.SysRoleVO;
import com.jomalls.custom.app.vo.system.SysUserVO;
import com.jomalls.custom.security.SecurityUtils;
import com.jomalls.custom.app.exception.ServiceException;
......@@ -19,10 +22,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
/**
* 用户管理 Controller
......@@ -55,15 +55,18 @@ public class SysUserController {
@Operation(summary = "查询用户详情(含角色、岗位)")
@RequiresPermissions("system:user:query")
@GetMapping(value = {"/", "/{userId}"})
public R<Object> getInfo(@PathVariable(value = "userId", required = false) Long userId) {
public R<Object> getInfo(@PathVariable(required = false) Long userId) {
R<Object> result = R.ok();
if (userId != null) {
Map<String, Object> info = sysUserService.getInfo(userId);
result.put("data", info.get("user"));
result.put("postIds", info.get("postIds"));
result.put("roleIds", info.get("roleIds") != null ? info.get("roleIds") : info.get("roles"));
result.put("roleIds", info.get("roleIds"));
}
result.put("roles", sysRoleService.listAllRoles());
// admin 用户看到全部角色,非 admin 不显示 admin 角色
List<SysRoleVO> roles = sysRoleService.listAllRoles();
result.put("roles", (SecurityConstants.ADMIN_USER_ID.equals(userId)) ? roles :
roles.stream().filter(r -> !RoleConstants.ADMIN_USER.equals(r.getRoleKey())).toList());
result.put("posts", sysPostService.listAllPosts());
return result;
}
......@@ -124,21 +127,26 @@ public class SysUserController {
@Operation(summary = "更新当前用户头像")
@PostMapping("/profile/avatar")
public R<Object> updateAvatar(@RequestParam("avatarfile") MultipartFile file) {
public Map<String, String> updateAvatar(@RequestParam("avatarfile") MultipartFile file) {
// TODO: 实际头像上传处理(保存文件到服务器/OSS),此处模拟返回
log.info("上传头像文件: {}", file.getOriginalFilename());
String avatarUrl = "/profile/avatar/" + file.getOriginalFilename();
sysUserService.updateAvatar(avatarUrl);
return R.ok().put("imgUrl", avatarUrl);
Map<String, String> result = new HashMap<>();
result.put("avatarUrl", avatarUrl);
return result;
}
@Operation(summary = "查询用户已分配的角色ID列表")
@Operation(summary = "查询用户已分配的角色(含 user 对象和角色列表)")
@RequiresPermissions("system:user:query")
@GetMapping("/authRole/{userId}")
public Map<String, Object> authRole(@PathVariable Long userId) {
// 返回 user + 全部角色列表(含 flag 标识已分配)
Map<String, Object> result = new java.util.HashMap<>();
result.put("userId", userId);
result.put("roleIds", sysUserService.getAuthRoleList(userId));
SysUserVO user = sysUserService.getById(userId);
result.put("user", user);
// admin 用户看到全部角色,非 admin 不显示 admin 角色
result.put("roles", sysUserService.getAuthRoleList(userId));
return result;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment