Commit c9f598be by Lizh

feat: P0 Step 1.7 — SysLoginService 登录/用户信息/路由构建

- 新增 SysLoginService 接口,定义 login/getInfo/getRouters 三个方法
- 新增 SysLoginServiceImpl 实现:
  - login:查询用户、密码校验(BCrypt)、权限查询、JWT 生成
  - getInfo:获取已登录用户的详细信息、角色和权限
  - getRouters:递归构建前端路由树(含 MetaVO 元数据映射)
- App pom.xml 添加 spring-security-crypto 依赖

Co-Authored-By: Claude <noreply@anthropic.com>
parent 16daf483
......@@ -45,5 +45,9 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aspectj</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
</dependencies>
</project>
package com.jomalls.custom.app.service.system;
import com.jomalls.custom.app.vo.LoginUserInfoVO;
import com.jomalls.custom.app.vo.RouterVO;
import java.util.List;
/**
* 登录服务接口
*
* @author Lizh
*/
public interface SysLoginService {
/**
* 用户登录
*
* @param username 用户名
* @param password 密码
* @return JWT token
*/
String login(String username, String password);
/**
* 获取当前登录用户信息
*
* @return 用户信息 VO(含角色和权限)
*/
LoginUserInfoVO getInfo();
/**
* 获取当前用户的路由菜单
*
* @return 前端路由树
*/
List<RouterVO> getRouters();
}
package com.jomalls.custom.app.service.system.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jomalls.custom.app.exception.ServiceException;
import com.jomalls.custom.app.service.system.SysLoginService;
import com.jomalls.custom.app.utils.BeanMapper;
import com.jomalls.custom.app.vo.*;
import com.jomalls.custom.dal.entity.SysMenuEntity;
import com.jomalls.custom.dal.entity.SysUserEntity;
import com.jomalls.custom.domain.service.system.SysMenuDomainService;
import com.jomalls.custom.domain.service.system.SysRoleDomainService;
import com.jomalls.custom.domain.service.system.SysUserDomainService;
import com.jomalls.custom.security.LoginUser;
import com.jomalls.custom.security.SecurityUtils;
import com.jomalls.custom.security.TokenHandle;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* 登录服务实现
*
* @author Lizh
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SysLoginServiceImpl implements SysLoginService {
private final SysUserDomainService sysUserDomainService;
private final SysRoleDomainService sysRoleDomainService;
private final SysMenuDomainService sysMenuDomainService;
private final TokenHandle tokenHandle;
private final PasswordEncoder passwordEncoder;
@Override
public String login(String username, String password) {
// 1. 查询用户
SysUserEntity user = sysUserDomainService.getOne(
new LambdaQueryWrapper<SysUserEntity>()
.eq(SysUserEntity::getUserName, username)
);
if (user == null) {
throw new ServiceException("用户不存在");
}
if ("1".equals(user.getDelFlag())) {
throw new ServiceException("用户已被删除");
}
if ("1".equals(user.getStatus())) {
throw new ServiceException("用户已被停用");
}
// 2. 验证密码
if (!passwordEncoder.matches(password, user.getPassword())) {
log.warn("用户[{}]密码验证失败", username);
throw new ServiceException("密码错误");
}
// 3. 构建 LoginUser
LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), username);
// 4. 查询权限(返回 List<String>,转为 Set<String>)
List<String> permList = sysMenuDomainService.selectMenuPermsByUserId(user.getUserId());
loginUser.setPermissions(permList != null ? new HashSet<>(permList) : Collections.emptySet());
// 5. 生成 token
String token = tokenHandle.createToken(loginUser);
log.info("用户[{}]登录成功", username);
return token;
}
@Override
public LoginUserInfoVO getInfo() {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser == null) {
throw new ServiceException("未登录");
}
SysUserEntity user = sysUserDomainService.getById(loginUser.getUserId());
if (user == null) {
throw new ServiceException("用户不存在");
}
LoginUserInfoVO result = new LoginUserInfoVO();
// 用户信息 — 使用 BeanMapper 映射
SysUserVO userVO = BeanMapper.mapper().convert(user, SysUserVO.class);
result.setUser(userVO);
// 角色集合
Set<String> roles = sysRoleDomainService.selectRoleKeysByUserId(user.getUserId());
result.setRoles(roles != null ? roles : Collections.emptySet());
// 权限集合
result.setPermissions(loginUser.getPermissions() != null ?
loginUser.getPermissions() : Collections.emptySet());
return result;
}
@Override
public List<RouterVO> getRouters() {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser == null) {
return Collections.emptyList();
}
// 查询用户所有菜单
List<SysMenuEntity> allMenus = sysMenuDomainService.selectMenuTreeByUserId(loginUser.getUserId());
if (allMenus == null || allMenus.isEmpty()) {
return Collections.emptyList();
}
// 构建路由树(从根节点 parentId=0 开始递归)
return buildRouterTree(0L, allMenus);
}
/**
* 递归构建前端路由树
*
* @param parentId 父菜单ID
* @param allMenus 全部菜单列表
* @return 路由树
*/
private List<RouterVO> buildRouterTree(Long parentId, List<SysMenuEntity> allMenus) {
return allMenus.stream()
.filter(menu -> menu.getParentId().equals(parentId))
.filter(menu -> !"F".equals(menu.getMenuType())) // 过滤按钮类型
.sorted(Comparator.comparingInt(m -> m.getOrderNum() != null ? m.getOrderNum() : 0))
.map(menu -> {
RouterVO router = new RouterVO();
// 路由名称:优先用 routeName,无则用 path 驼峰
router.setName(StringUtils.hasText(menu.getRouteName()) ?
menu.getRouteName() : menu.getPath());
router.setPath(menu.getPath());
// 隐藏判断:visible = "1" 表示隐藏
router.setHidden("1".equals(menu.getVisible()));
router.setComponent(menu.getComponent());
// 元数据
MetaVO meta = new MetaVO();
meta.setTitle(menu.getMenuName());
meta.setIcon(menu.getIcon());
// isCache/isFrame 是 Integer 类型:1=否
meta.setIsCache(!Integer.valueOf(1).equals(menu.getIsCache()));
meta.setIsFrame(Integer.valueOf(1).equals(menu.getIsFrame()));
router.setMeta(meta);
// 递归构建子路由
List<RouterVO> children = buildRouterTree(menu.getMenuId(), allMenus);
router.setChildren(children.isEmpty() ? null : children);
// 有子路由时设置 alwaysShow
if (children.isEmpty()) {
router.setAlwaysShow(null);
}
return router;
})
.collect(Collectors.toList());
}
}
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