2025-07-24 21:10:47 +08:00
|
|
|
|
package com.yupi.springbootinit.service.impl;
|
|
|
|
|
|
|
|
|
|
|
|
import cn.dev33.satoken.stp.StpUtil;
|
|
|
|
|
|
import cn.hutool.core.bean.BeanUtil;
|
|
|
|
|
|
import com.yupi.springbootinit.common.ErrorCode;
|
|
|
|
|
|
import com.yupi.springbootinit.exception.BusinessException;
|
|
|
|
|
|
import com.yupi.springbootinit.model.dto.user.SystemUsersDTO;
|
|
|
|
|
|
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;
|
2025-08-27 21:12:50 +08:00
|
|
|
|
import com.yupi.springbootinit.utils.RedisUtils;
|
2025-07-24 21:10:47 +08:00
|
|
|
|
import lombok.RequiredArgsConstructor;
|
2025-08-27 16:42:53 +08:00
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
|
import org.springframework.amqp.core.*;
|
|
|
|
|
|
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
|
|
|
|
|
import org.springframework.data.redis.core.RedisTemplate;
|
2025-07-24 21:10:47 +08:00
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
2025-08-27 16:42:53 +08:00
|
|
|
|
import javax.annotation.Resource;
|
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
|
|
2025-09-09 15:36:47 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 登录相关业务实现类
|
|
|
|
|
|
*/
|
2025-08-27 16:42:53 +08:00
|
|
|
|
@Slf4j
|
2025-07-24 21:10:47 +08:00
|
|
|
|
@Service
|
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
|
public class LoginService {
|
|
|
|
|
|
|
2025-09-09 15:36:47 +08:00
|
|
|
|
/** 用户业务服务 */
|
2025-07-24 21:10:47 +08:00
|
|
|
|
private final SystemUsersService usersService;
|
|
|
|
|
|
|
2025-09-09 15:36:47 +08:00
|
|
|
|
/** 用于缓存 AI 登录状态的 RedisTemplate(布尔值) */
|
2025-08-27 16:42:53 +08:00
|
|
|
|
@Resource
|
2025-09-09 15:36:47 +08:00
|
|
|
|
private RedisTemplate<String, Boolean> redisTemplate;
|
2025-08-27 16:42:53 +08:00
|
|
|
|
|
2025-09-09 15:36:47 +08:00
|
|
|
|
/** 已创建过 RabbitMQ 队列的租户 ID 集合,防止重复创建 */
|
2025-08-27 16:42:53 +08:00
|
|
|
|
private final Set<String> created = ConcurrentHashMap.newKeySet();
|
2025-09-09 15:36:47 +08:00
|
|
|
|
|
|
|
|
|
|
/** 用户事件使用的 HeadersExchange */
|
2025-08-27 16:42:53 +08:00
|
|
|
|
private final HeadersExchange userHeadersExchange;
|
2025-09-09 15:36:47 +08:00
|
|
|
|
|
|
|
|
|
|
/** RabbitMQ 管理组件 */
|
2025-08-27 16:42:53 +08:00
|
|
|
|
@Resource
|
|
|
|
|
|
private RabbitAdmin rabbitAdmin;
|
|
|
|
|
|
|
2025-09-09 15:36:47 +08:00
|
|
|
|
/** 通用 Redis 工具类 */
|
2025-08-27 21:12:50 +08:00
|
|
|
|
@Resource
|
|
|
|
|
|
private RedisUtils redisUtils;
|
|
|
|
|
|
|
2025-09-09 15:36:47 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 统一登录入口
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param scene 登录场景(HOST / BIG_BROTHER / AI_CHAT)
|
|
|
|
|
|
* @param dto 登录参数(用户名、密码、租户 ID)
|
|
|
|
|
|
* @return 登录成功后的用户信息 + Token
|
|
|
|
|
|
*/
|
2025-07-24 21:10:47 +08:00
|
|
|
|
public SystemUsersVO login(LoginSceneEnum scene, SystemUsersDTO dto) {
|
2025-09-09 15:36:47 +08:00
|
|
|
|
// 1. 校验用户名、密码、状态、租户过期
|
|
|
|
|
|
SystemUsers user = validateUser(dto);
|
|
|
|
|
|
// 2. 按场景校验角色权限
|
|
|
|
|
|
checkRole(scene, user.getId());
|
|
|
|
|
|
|
|
|
|
|
|
// 3. AI_CHAT 场景专属逻辑:缓存登录状态并动态创建 RabbitMQ 队列
|
2025-08-27 16:42:53 +08:00
|
|
|
|
if (scene.equals(LoginSceneEnum.AI_CHAT)) {
|
2025-09-09 15:36:47 +08:00
|
|
|
|
// 记录该用户已登录 AI_CHAT
|
|
|
|
|
|
redisTemplate.opsForValue().set("ai_login:" + user.getTenantId() + ":" + user.getId(), true);
|
|
|
|
|
|
|
2025-08-27 16:42:53 +08:00
|
|
|
|
String queueName = "q.tenant." + user.getTenantId();
|
2025-09-09 15:36:47 +08:00
|
|
|
|
// 若该租户队列尚未创建,则创建队列并绑定到 HeadersExchange
|
2025-08-27 16:42:53 +08:00
|
|
|
|
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)
|
2025-09-09 15:36:47 +08:00
|
|
|
|
.to(userHeadersExchange)
|
2025-08-27 16:42:53 +08:00
|
|
|
|
.whereAll(headers)
|
|
|
|
|
|
.match();
|
|
|
|
|
|
rabbitAdmin.declareBinding(binding);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-09 15:36:47 +08:00
|
|
|
|
|
|
|
|
|
|
// 5. Sa-Token 登录
|
2025-07-24 21:10:47 +08:00
|
|
|
|
StpUtil.login(user.getId(), scene.getSaMode());
|
2025-09-09 15:36:47 +08:00
|
|
|
|
|
|
|
|
|
|
// 6. 封装返回数据
|
2025-07-24 21:10:47 +08:00
|
|
|
|
SystemUsersVO vo = new SystemUsersVO();
|
|
|
|
|
|
BeanUtil.copyProperties(user, vo);
|
|
|
|
|
|
vo.setTokenName(StpUtil.getTokenName());
|
|
|
|
|
|
vo.setTokenValue(StpUtil.getTokenValue());
|
|
|
|
|
|
return vo;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-09 15:36:47 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 校验用户登录信息
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param dto 登录参数
|
|
|
|
|
|
* @return 校验通过的用户实体
|
|
|
|
|
|
* @throws BusinessException 校验失败时抛出
|
|
|
|
|
|
*/
|
2025-07-24 21:10:47 +08:00
|
|
|
|
private SystemUsers validateUser(SystemUsersDTO dto) {
|
|
|
|
|
|
SystemUsers user = usersService.getUserByUserName(dto.getUsername(), dto.getTenantId());
|
2025-09-09 15:36:47 +08:00
|
|
|
|
if (user == null) {
|
|
|
|
|
|
throw new BusinessException(ErrorCode.USERNAME_OR_PASSWORD_ERROR);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!usersService.isPasswordMatch(dto.getPassword(), user.getPassword())) {
|
2025-07-24 21:10:47 +08:00
|
|
|
|
throw new BusinessException(ErrorCode.USERNAME_OR_PASSWORD_ERROR);
|
2025-09-09 15:36:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (CommonStatusEnum.isDisable(Integer.valueOf(user.getStatus()))) {
|
2025-07-24 21:10:47 +08:00
|
|
|
|
throw new BusinessException(ErrorCode.USER_DISABLE);
|
2025-09-09 15:36:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (usersService.isExpired(dto.getTenantId())) {
|
2025-07-24 21:10:47 +08:00
|
|
|
|
throw new BusinessException(ErrorCode.PACKAGE_EXPIRED);
|
2025-09-09 15:36:47 +08:00
|
|
|
|
}
|
2025-07-24 21:10:47 +08:00
|
|
|
|
return user;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-09 15:36:47 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 按登录场景校验角色权限
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param scene 登录场景
|
|
|
|
|
|
* @param userId 用户 ID
|
|
|
|
|
|
* @throws BusinessException 无权限时抛出
|
|
|
|
|
|
*/
|
2025-07-24 21:10:47 +08:00
|
|
|
|
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);
|
|
|
|
|
|
};
|
2025-09-09 15:36:47 +08:00
|
|
|
|
if (!pass) {
|
|
|
|
|
|
throw new BusinessException(ErrorCode.LOGIN_NOT_ALLOWED);
|
|
|
|
|
|
}
|
2025-07-24 21:10:47 +08:00
|
|
|
|
}
|
2025-08-27 16:42:53 +08:00
|
|
|
|
|
2025-09-09 15:36:47 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* AI_CHAT 场景专属登出
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param usersDTO 包含租户 ID 与用户 ID
|
|
|
|
|
|
* @return 固定返回 true
|
|
|
|
|
|
*/
|
2025-08-27 16:42:53 +08:00
|
|
|
|
public Boolean aiChatLogout(SystemUsersDTO usersDTO) {
|
2025-09-09 15:36:47 +08:00
|
|
|
|
// 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 在线用户,则删除队列
|
2025-08-27 21:39:56 +08:00
|
|
|
|
if (!redisUtils.hasKeyByPrefix("ai_login:" + usersDTO.getTenantId())) {
|
2025-08-27 21:12:50 +08:00
|
|
|
|
created.remove(String.valueOf(usersDTO.getTenantId()));
|
|
|
|
|
|
boolean b = rabbitAdmin.deleteQueue("q.tenant." + usersDTO.getTenantId());
|
2025-09-09 15:36:47 +08:00
|
|
|
|
log.info("删除租户:{} 队列删除状态:{}", usersDTO.getTenantId(), b);
|
2025-08-27 21:12:50 +08:00
|
|
|
|
}
|
2025-08-27 16:42:53 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-09-09 15:36:47 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 通用登出(不区分场景)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return 固定返回 true
|
|
|
|
|
|
*/
|
|
|
|
|
|
public Boolean logout() {
|
|
|
|
|
|
String tokenValue = StpUtil.getTokenValue();
|
|
|
|
|
|
Long loginId = (Long) StpUtil.getLoginId();
|
|
|
|
|
|
StpUtil.logoutByTokenValue(tokenValue);
|
|
|
|
|
|
log.info("用户:{} 登出成功", loginId);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|