Files
tkcrawl-client/src/main/java/com/yupi/springbootinit/service/impl/LoginService.java

202 lines
7.4 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.yupi.springbootinit.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.exception.BusinessException;
import com.yupi.springbootinit.mapper.SystemTenantMapper;
import com.yupi.springbootinit.model.dto.user.SystemUsersDTO;
import com.yupi.springbootinit.model.entity.SystemTenant;
import com.yupi.springbootinit.model.entity.SystemUsers;
import com.yupi.springbootinit.model.enums.CommonStatusEnum;
import com.yupi.springbootinit.model.enums.LoginSceneEnum;
import com.yupi.springbootinit.model.vo.user.SystemUsersVO;
import com.yupi.springbootinit.service.SystemUsersService;
import com.yupi.springbootinit.utils.DateUtils;
import com.yupi.springbootinit.utils.RedisUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 登录相关业务实现类
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class LoginService {
/** 用户业务服务 */
private final SystemUsersService usersService;
/** 用于缓存 AI 登录状态的 RedisTemplate布尔值 */
@Resource
private RedisTemplate<String, Boolean> redisTemplate;
/** 已创建过 RabbitMQ 队列的租户 ID 集合,防止重复创建 */
private final Set<String> created = ConcurrentHashMap.newKeySet();
/** 用户事件使用的 HeadersExchange */
private final HeadersExchange userHeadersExchange;
/** RabbitMQ 管理组件 */
@Resource
private RabbitAdmin rabbitAdmin;
/** 通用 Redis 工具类 */
@Resource
private RedisUtils redisUtils;
@Resource
private SystemTenantMapper tenantMapper;
/**
* 统一登录入口
*
* @param scene 登录场景HOST / BIG_BROTHER / AI_CHAT
* @param dto 登录参数(用户名、密码、租户 ID
* @return 登录成功后的用户信息 + Token
*/
public SystemUsersVO login(LoginSceneEnum scene, SystemUsersDTO dto) {
// 1. 校验用户名、密码、状态、租户过期
SystemUsers user = validateUser(dto);
// 2. 按场景校验角色权限
checkRole(scene, user.getId());
// 3. AI_CHAT 场景专属逻辑:缓存登录状态并动态创建 RabbitMQ 队列
if (scene.equals(LoginSceneEnum.AI_CHAT)) {
// 记录该用户已登录 AI_CHAT
redisTemplate.opsForValue().set("ai_login:" + user.getTenantId() + ":" + user.getId(), true);
String queueName = "q.tenant." + user.getTenantId();
// 若该租户队列尚未创建,则创建队列并绑定到 HeadersExchange
if (created.add(String.valueOf(user.getTenantId()))) {
Queue queue = QueueBuilder.durable(queueName).build();
rabbitAdmin.declareQueue(queue);
Map<String, Object> headers = Map.of("tenantId", user.getTenantId(), "x-match", "all");
Binding binding = BindingBuilder
.bind(queue)
.to(userHeadersExchange)
.whereAll(headers)
.match();
rabbitAdmin.declareBinding(binding);
}
}
SystemTenant systemTenant = tenantMapper.selectById(user.getTenantId());
// 5. Sa-Token 登录
StpUtil.login(user.getId(), scene.getSaMode());
switch (scene) {
case AI_CHAT:
StpUtil.renewTimeout(DateUtils.dateBetween(systemTenant.getAiExpireTime(),DateUtil.date()));
break;
case HOST:
StpUtil.renewTimeout(DateUtils.dateBetween(systemTenant.getExpireTime(),DateUtil.date()));
break;
case BIG_BROTHER:
StpUtil.renewTimeout(DateUtils.dateBetween(systemTenant.getBrotherExpireTime(),DateUtil.date()));
break;
}
// 6. 封装返回数据
SystemUsersVO vo = new SystemUsersVO();
BeanUtil.copyProperties(user, vo);
vo.setTokenName(StpUtil.getTokenName());
vo.setTokenValue(StpUtil.getTokenValue());
return vo;
}
/**
* 校验用户登录信息
*
* @param dto 登录参数
* @return 校验通过的用户实体
* @throws BusinessException 校验失败时抛出
*/
private SystemUsers validateUser(SystemUsersDTO dto) {
SystemUsers user = usersService.getUserByUserName(dto.getUsername(), dto.getTenantId());
if (user == null) {
throw new BusinessException(ErrorCode.USERNAME_OR_PASSWORD_ERROR);
}
if (!usersService.isPasswordMatch(dto.getPassword(), user.getPassword())) {
throw new BusinessException(ErrorCode.USERNAME_OR_PASSWORD_ERROR);
}
if (CommonStatusEnum.isDisable(Integer.valueOf(user.getStatus()))) {
throw new BusinessException(ErrorCode.USER_DISABLE);
}
if (usersService.isExpired(dto.getTenantId())) {
throw new BusinessException(ErrorCode.PACKAGE_EXPIRED);
}
return user;
}
/**
* 按登录场景校验角色权限
*
* @param scene 登录场景
* @param userId 用户 ID
* @throws BusinessException 无权限时抛出
*/
private void checkRole(LoginSceneEnum scene, Long userId) {
Boolean pass = switch (scene) {
case HOST -> usersService.checkCrawlRole(userId);
case BIG_BROTHER -> usersService.checkbigBrotherlRole(userId);
case AI_CHAT -> usersService.checkAiCHatLoginRole(userId);
};
if (!pass) {
throw new BusinessException(ErrorCode.LOGIN_NOT_ALLOWED);
}
}
/**
* AI_CHAT 场景专属登出
*
* @param usersDTO 包含租户 ID 与用户 ID
* @return 固定返回 true
*/
public Boolean aiChatLogout(SystemUsersDTO usersDTO) {
// 1. 删除 Redis 中该用户的 AI_CHAT 登录标记
Boolean delete = redisTemplate.delete("ai_login:" + usersDTO.getTenantId() + ":" + usersDTO.getUserId());
// 2. 使当前 Token 失效
String tokenValue = StpUtil.getTokenValue();
StpUtil.logoutByTokenValue(tokenValue);
log.info("删除租户:{} 登录状态:{}", usersDTO.getTenantId(), delete);
// 3. 若该租户下已无 AI_CHAT 在线用户,则删除队列
if (!redisUtils.hasKeyByPrefix("ai_login:" + usersDTO.getTenantId())) {
created.remove(String.valueOf(usersDTO.getTenantId()));
boolean b = rabbitAdmin.deleteQueue("q.tenant." + usersDTO.getTenantId());
log.info("删除租户:{} 队列删除状态:{}", usersDTO.getTenantId(), b);
}
return true;
}
/**
* 通用登出(不区分场景)
*
* @return 固定返回 true
*/
public Boolean logout() {
String tokenValue = StpUtil.getTokenValue();
Long loginId = (Long) StpUtil.getLoginId();
StpUtil.logoutByTokenValue(tokenValue);
log.info("用户:{} 登出成功", loginId);
return true;
}
}