Compare commits

...

3 Commits

Author SHA1 Message Date
a44651dd2f 1.修改判断用户退出状态逻辑 2025-08-27 21:39:56 +08:00
43cbd262ea 1.修改AI退出登录后的逻辑 2025-08-27 21:12:50 +08:00
07a4142818 1.添加 rabbitmq
2.在 AI 登录时 创建消息队列
2025-08-27 16:42:53 +08:00
8 changed files with 196 additions and 19 deletions

View File

@@ -126,6 +126,10 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.dromara.x-file-storage</groupId> <groupId>org.dromara.x-file-storage</groupId>
<artifactId>x-file-storage-spring</artifactId> <artifactId>x-file-storage-spring</artifactId>

View File

@@ -0,0 +1,64 @@
package com.yupi.springbootinit.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.HeadersExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
private static final String QUEUE = "HOST_INFO_QUEUE";
public static final String EXCHANGE_NAME = "user.headers.exchange";
//创建队列
//true:表示持久化
//队列在默认情况下放到内存rabbitmq重启后就丢失了如果希望重启后队列
//数据还能使用,就需要持久化
@Bean
public Queue hostInfoQueue(){
return new Queue(QUEUE,true);
}
//
// @Bean
// public MessageConverter messageConverter(){
// return new Jackson2JsonMessageConverter();
// }
@Bean
public HeadersExchange userHeadersExchange() {
return ExchangeBuilder.headersExchange(EXCHANGE_NAME)
.durable(true)
.build();
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory cf) {
return new RabbitAdmin(cf);
}
@Bean
public MessageConverter messageConverter() {
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
om.registerModule(new JavaTimeModule());
return new Jackson2JsonMessageConverter(om);
}
}

View File

@@ -1,37 +1,49 @@
package com.yupi.springbootinit.config; package com.yupi.springbootinit.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration @Configuration
public class RedisConfig { public class RedisConfig {
@Bean(name="redisTemplate") // @Bean(name="redisTemplate")
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) { // public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>(); // RedisTemplate<String, String> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer(); // RedisSerializer<String> redisSerializer = new StringRedisSerializer();
// template.setConnectionFactory(factory);
// //key序列化方式
// template.setKeySerializer(redisSerializer);
// //value序列化
// template.setValueSerializer(redisSerializer);
// //value hashmap序列化
// template.setHashValueSerializer(redisSerializer);
// //key haspmap序列化
// template.setHashKeySerializer(redisSerializer);
// //
// return template;
// }
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory); template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer); // 使用 JSON 序列化器
//value序列化 GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
template.setValueSerializer(redisSerializer);
//value hashmap序列化 template.setKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(redisSerializer); template.setValueSerializer(jsonSerializer);
//key haspmap序列化 template.setHashKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(redisSerializer); template.setHashValueSerializer(jsonSerializer);
//
template.afterPropertiesSet();
return template; return template;
} }
} }

View File

@@ -47,7 +47,8 @@ public class SaTokenConfigure implements WebMvcConfigurer {
"/tenant/get-id-by-name", "/tenant/get-id-by-name",
"/user/bigbrother-doLogin", "/user/bigbrother-doLogin",
"/user/aiChat-doLogin", "/user/aiChat-doLogin",
"/error" "/user/aiChat-logout",
"/error",
}; };
} }

View File

@@ -57,6 +57,10 @@ public class UserController {
// return ResultUtils.success(systemUsersVO); // return ResultUtils.success(systemUsersVO);
} }
@PostMapping("aiChat-logout")
public BaseResponse<Boolean> aiChatLogout(@RequestBody SystemUsersDTO usersDTO){
return ResultUtils.success(loginService.aiChatLogout(usersDTO));
}
// //
// private SystemUsers getUserByName(@RequestBody SystemUsersDTO usersDTO) { // private SystemUsers getUserByName(@RequestBody SystemUsersDTO usersDTO) {

View File

@@ -29,4 +29,6 @@ public class SystemUsersDTO {
* 租户编号 * 租户编号
*/ */
private Long tenantId; private Long tenantId;
private Long userId;
} }

View File

@@ -10,18 +10,56 @@ import com.yupi.springbootinit.model.enums.CommonStatusEnum;
import com.yupi.springbootinit.model.enums.LoginSceneEnum; import com.yupi.springbootinit.model.enums.LoginSceneEnum;
import com.yupi.springbootinit.model.vo.user.SystemUsersVO; import com.yupi.springbootinit.model.vo.user.SystemUsersVO;
import com.yupi.springbootinit.service.SystemUsersService; import com.yupi.springbootinit.service.SystemUsersService;
import com.yupi.springbootinit.utils.RedisUtils;
import lombok.RequiredArgsConstructor; 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 org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class LoginService { public class LoginService {
private final SystemUsersService usersService; private final SystemUsersService usersService;
@Resource
private RedisTemplate<String,Boolean> redisTemplate;
private final Set<String> created = ConcurrentHashMap.newKeySet();
private final HeadersExchange userHeadersExchange;
@Resource
private RabbitAdmin rabbitAdmin;
@Resource
private RedisUtils redisUtils;
public SystemUsersVO login(LoginSceneEnum scene, SystemUsersDTO dto) { public SystemUsersVO login(LoginSceneEnum scene, SystemUsersDTO dto) {
SystemUsers user = validateUser(dto); // 校验用户名、密码、状态、租户过期 SystemUsers user = validateUser(dto); // 校验用户名、密码、状态、租户过期
checkRole(scene, user.getId()); // 按场景做角色校验 checkRole(scene, user.getId()); // 按场景做角色校验
if (scene.equals(LoginSceneEnum.AI_CHAT)) {
redisTemplate.opsForValue().set("ai_login:"+user.getTenantId()+":"+user.getId(),true);
String queueName = "q.tenant." + user.getTenantId();
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) // ← 传 Exchange 对象
.whereAll(headers)
.match();
rabbitAdmin.declareBinding(binding);
}
}
Long second = usersService.getTenantExpiredTime(dto.getTenantId()); Long second = usersService.getTenantExpiredTime(dto.getTenantId());
// Sa-Token 登录 // Sa-Token 登录
StpUtil.login(user.getId(), scene.getSaMode()); StpUtil.login(user.getId(), scene.getSaMode());
@@ -53,4 +91,16 @@ public class LoginService {
}; };
if (!pass) throw new BusinessException(ErrorCode.LOGIN_NOW_ALLOWED); if (!pass) throw new BusinessException(ErrorCode.LOGIN_NOW_ALLOWED);
} }
public Boolean aiChatLogout(SystemUsersDTO usersDTO) {
Boolean delete = redisTemplate.delete("ai_login:"+usersDTO.getTenantId()+":"+usersDTO.getUserId());
StpUtil.logout(usersDTO.getUserId());
log.info("删除租户:{}登录状态:{}",usersDTO.getTenantId(),delete);
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;
}
} }

View File

@@ -0,0 +1,40 @@
package com.yupi.springbootinit.utils;
/*
* @author: ziin
* @date: 2025/8/27 20:35
*/
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Set;
@Component
public class RedisUtils {
@Resource
private RedisTemplate<String,Object> redisTemplate;
public boolean hasAiLoginKeys(String prefix) {
Set<String> keys = redisTemplate.keys(prefix); // 获取匹配的键集合
return !keys.isEmpty(); // 如果有键匹配返回true否则返回false
}
public boolean hasKeyByPrefix(String prefix) {
return Boolean.TRUE.equals(
redisTemplate.execute((RedisCallback<Boolean>) conn -> {
try (Cursor<byte[]> cursor = conn.scan(
ScanOptions.scanOptions()
.match(prefix + ":*")
.count(100) // 每次返回条数,可调整
.build())) {
return cursor.hasNext(); // 只要存在一条就返回 true
}
})
);
}
}