24 changed files with 430 additions and 186 deletions
@ -1,31 +0,0 @@ |
|||
package com.bx.imclient.config; |
|||
|
|||
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer; |
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Configuration; |
|||
import org.springframework.data.redis.connection.RedisConnectionFactory; |
|||
import org.springframework.data.redis.core.RedisTemplate; |
|||
import org.springframework.data.redis.serializer.StringRedisSerializer; |
|||
|
|||
@Configuration("IMRedisConfig") |
|||
public class RedisConfig { |
|||
|
|||
@Bean("IMRedisTemplate") |
|||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { |
|||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate(); |
|||
redisTemplate.setConnectionFactory(redisConnectionFactory); |
|||
// 设置值(value)的序列化采用FastJsonRedisSerializer
|
|||
redisTemplate.setValueSerializer(fastJsonRedisSerializer()); |
|||
redisTemplate.setHashValueSerializer(fastJsonRedisSerializer()); |
|||
// 设置键(key)的序列化采用StringRedisSerializer。
|
|||
redisTemplate.setKeySerializer(new StringRedisSerializer()); |
|||
redisTemplate.setHashKeySerializer(new StringRedisSerializer()); |
|||
redisTemplate.afterPropertiesSet(); |
|||
return redisTemplate; |
|||
} |
|||
|
|||
public FastJsonRedisSerializer fastJsonRedisSerializer(){ |
|||
return new FastJsonRedisSerializer<>(Object.class); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
package com.bx.imcommon.mq; |
|||
|
|||
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer; |
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Configuration; |
|||
import org.springframework.data.redis.connection.RedisConnectionFactory; |
|||
import org.springframework.data.redis.core.RedisTemplate; |
|||
import org.springframework.data.redis.serializer.StringRedisSerializer; |
|||
|
|||
|
|||
@Configuration |
|||
public class RedisMQConfig { |
|||
|
|||
@Bean |
|||
public RedisMQTemplate redisMQTemplate(RedisConnectionFactory redisConnectionFactory) { |
|||
RedisMQTemplate redisMQTemplate = new RedisMQTemplate(); |
|||
redisMQTemplate.setConnectionFactory(redisConnectionFactory); |
|||
// 设置值(value)的序列化采用FastJsonRedisSerializer
|
|||
redisMQTemplate.setValueSerializer(fastJsonRedisSerializer()); |
|||
redisMQTemplate.setHashValueSerializer(fastJsonRedisSerializer()); |
|||
// 设置键(key)的序列化采用StringRedisSerializer。
|
|||
redisMQTemplate.setKeySerializer(new StringRedisSerializer()); |
|||
redisMQTemplate.setHashKeySerializer(new StringRedisSerializer()); |
|||
redisMQTemplate.afterPropertiesSet(); |
|||
return redisMQTemplate; |
|||
} |
|||
|
|||
@Bean |
|||
public FastJsonRedisSerializer fastJsonRedisSerializer(){ |
|||
return new FastJsonRedisSerializer<>(Object.class); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
package com.bx.imcommon.mq; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* redis 队列消费者抽象类 |
|||
*/ |
|||
public abstract class RedisMQConsumer<T> { |
|||
|
|||
public void onMessage(T data){} |
|||
|
|||
public void onMessage(List<T> datas){} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
package com.bx.imcommon.mq; |
|||
|
|||
import java.lang.annotation.ElementType; |
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.RetentionPolicy; |
|||
import java.lang.annotation.Target; |
|||
|
|||
/** |
|||
* redis 队列消费监听注解 |
|||
*/ |
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
@Target(ElementType.TYPE) |
|||
public @interface RedisMQListener { |
|||
|
|||
/** |
|||
* 队列,也是redis的key |
|||
*/ |
|||
String queue(); |
|||
|
|||
/** |
|||
* 一次性拉取的数据数量 |
|||
*/ |
|||
int batchSize() default 1; |
|||
|
|||
/** |
|||
* 拉取间隔周期,单位:ms |
|||
*/ |
|||
int period() default 100; |
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
package com.bx.imcommon.mq; |
|||
|
|||
import com.alibaba.fastjson.JSONObject; |
|||
|
|||
import com.bx.imcommon.util.ThreadPoolExecutorFactory; |
|||
import lombok.SneakyThrows; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.CommandLineRunner; |
|||
import org.springframework.data.redis.connection.RedisConnection; |
|||
import org.springframework.data.redis.core.RedisTemplate; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import javax.annotation.PreDestroy; |
|||
import javax.annotation.Resource; |
|||
import java.lang.reflect.ParameterizedType; |
|||
import java.lang.reflect.Type; |
|||
import java.util.*; |
|||
import java.util.concurrent.ScheduledThreadPoolExecutor; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
/** |
|||
* reids 队列拉取定时任务 |
|||
* |
|||
* @author: Blue |
|||
* @date: 2024-07-15 |
|||
* @version: 1.0 |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
public class RedisMQPullTask implements CommandLineRunner { |
|||
|
|||
private static final ScheduledThreadPoolExecutor EXECUTOR_SERVICE = |
|||
ThreadPoolExecutorFactory.getThreadPoolExecutor(); |
|||
|
|||
@Autowired(required = false) |
|||
private List<RedisMQConsumer> consumers = Collections.emptyList(); |
|||
|
|||
@Autowired |
|||
private RedisMQTemplate redisMQTemplate; |
|||
|
|||
@Override |
|||
public void run(String... args) { |
|||
consumers.forEach((consumer -> { |
|||
// 注解参数
|
|||
RedisMQListener annotation = consumer.getClass().getAnnotation(RedisMQListener.class); |
|||
String key = annotation.queue(); |
|||
int batchSize = annotation.batchSize(); |
|||
int period = annotation.period(); |
|||
// 获取泛型类型
|
|||
Type superClass = consumer.getClass().getGenericSuperclass(); |
|||
Type type = ((ParameterizedType)superClass).getActualTypeArguments()[0]; |
|||
EXECUTOR_SERVICE.execute(new Runnable() { |
|||
@Override |
|||
public void run() { |
|||
List<Object> datas = new LinkedList<>(); |
|||
try { |
|||
// 拉取一个批次的数据
|
|||
List<Object> objects = pullBatch(key, batchSize); |
|||
for (Object obj : objects) { |
|||
if (obj instanceof JSONObject) { |
|||
JSONObject jsonObject = (JSONObject)obj; |
|||
Object data = jsonObject.toJavaObject(type); |
|||
consumer.onMessage(data); |
|||
datas.add(data); |
|||
} |
|||
} |
|||
if(!datas.isEmpty()){ |
|||
consumer.onMessage(datas); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("数据消费异常,队列:{}", key, e); |
|||
} |
|||
// 继续消费数据
|
|||
if (!EXECUTOR_SERVICE.isShutdown()) { |
|||
if (datas.size() < batchSize) { |
|||
// 数据已经消费完,等待下一个周期继续拉取
|
|||
EXECUTOR_SERVICE.schedule(this, period, TimeUnit.MICROSECONDS); |
|||
} else { |
|||
// 数据没有消费完,直接开启下一个消费周期
|
|||
EXECUTOR_SERVICE.execute(this); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
})); |
|||
} |
|||
|
|||
private List<Object> pullBatch(String key, Integer batchSize) { |
|||
List<Object> objects = new LinkedList<>(); |
|||
if (redisMQTemplate.isSupportBatchPull()) { |
|||
// 版本大于6.2,支持批量拉取
|
|||
objects = redisMQTemplate.opsForList().leftPop(key, 100); |
|||
} else { |
|||
// 版本小于6.2,只能逐条拉取
|
|||
Object obj = redisMQTemplate.opsForList().leftPop(key); |
|||
while (!Objects.isNull(obj) && objects.size() < batchSize) { |
|||
objects.add(obj); |
|||
obj = redisMQTemplate.opsForList().leftPop(key); |
|||
} |
|||
} |
|||
return objects; |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void destory(){ |
|||
ThreadPoolExecutorFactory.shutDown(); |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
package com.bx.imcommon.mq; |
|||
|
|||
import cn.hutool.core.util.StrUtil; |
|||
import org.apache.logging.log4j.util.Strings; |
|||
import org.springframework.data.redis.connection.RedisConnection; |
|||
import org.springframework.data.redis.core.RedisTemplate; |
|||
|
|||
import java.util.Properties; |
|||
|
|||
/** |
|||
* @author: 谢绍许 |
|||
* @date: 2024-07-16 |
|||
* @version: 1.0 |
|||
*/ |
|||
public class RedisMQTemplate extends RedisTemplate<String, Object> { |
|||
|
|||
private String version = Strings.EMPTY; |
|||
|
|||
public String getVersion() { |
|||
if (version.isEmpty()) { |
|||
RedisConnection redisConnection = this.getConnectionFactory().getConnection(); |
|||
Properties properties = redisConnection.info(); |
|||
version = properties.getProperty("redis_version"); |
|||
} |
|||
return version; |
|||
} |
|||
|
|||
/** |
|||
* 是否支持批量拉取,redis版本大于6.2支持批量拉取 |
|||
* @return |
|||
*/ |
|||
Boolean isSupportBatchPull() { |
|||
String version = getVersion(); |
|||
String[] arr = version.split("\\."); |
|||
if (arr.length < 2) { |
|||
return false; |
|||
} |
|||
Integer firVersion = Integer.valueOf(arr[0]); |
|||
Integer secVersion = Integer.valueOf(arr[1]); |
|||
return firVersion > 6 || (firVersion == 6 && secVersion >= 2); |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
package com.bx.implatform.dto; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* @author: Blue |
|||
* @date: 2024-07-14 |
|||
* @version: 1.0 |
|||
*/ |
|||
@Data |
|||
@ApiModel(description = "群组封禁") |
|||
public class GroupBanDTO { |
|||
|
|||
@ApiModelProperty(value = "群组id") |
|||
private Long id; |
|||
|
|||
@ApiModelProperty(value = "封禁原因") |
|||
private String reason; |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
package com.bx.implatform.dto; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* @author: Blue |
|||
* @date: 2024-07-14 |
|||
* @version: 1.0 |
|||
*/ |
|||
@Data |
|||
@ApiModel(description = "群组解锁") |
|||
public class GroupUnbanDTO { |
|||
|
|||
@ApiModelProperty(value = "群组id") |
|||
private Long id; |
|||
|
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
package com.bx.implatform.dto; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* @author: Blue |
|||
* @date: 2024-07-14 |
|||
* @version: 1.0 |
|||
*/ |
|||
@Data |
|||
@ApiModel("用户锁定DTO") |
|||
public class UserBanDTO { |
|||
|
|||
@ApiModelProperty(value = "用户id") |
|||
private Long id; |
|||
|
|||
@ApiModelProperty(value = "锁定原因") |
|||
private String reason; |
|||
|
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
package com.bx.implatform.task; |
|||
|
|||
import com.bx.imclient.IMClient; |
|||
import com.bx.imcommon.enums.IMTerminalType; |
|||
import com.bx.imcommon.model.IMPrivateMessage; |
|||
import com.bx.imcommon.model.IMUserInfo; |
|||
import com.bx.imcommon.mq.RedisMQConsumer; |
|||
import com.bx.imcommon.mq.RedisMQListener; |
|||
import com.bx.implatform.contant.Constant; |
|||
import com.bx.implatform.contant.RedisKey; |
|||
import com.bx.implatform.dto.UserBanDTO; |
|||
import com.bx.implatform.service.IUserService; |
|||
import com.bx.implatform.util.BeanUtils; |
|||
import com.bx.implatform.vo.PrivateMessageVO; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.security.core.parameters.P; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
/** |
|||
* @author: 谢绍许 |
|||
* @date: 2024-07-15 |
|||
* @version: 1.0 |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
@RequiredArgsConstructor |
|||
@RedisMQListener(queue = RedisKey.IM_QUEUE_USER_BANNED) |
|||
public class UserBannedConsumerTask extends RedisMQConsumer<UserBanDTO> { |
|||
|
|||
private final IMClient imClient; |
|||
@Override |
|||
public void onMessage(UserBanDTO dto) { |
|||
|
|||
log.info("用户被封禁处理,userId:{},原因:{}",dto.getId(),dto.getReason()); |
|||
// 推送消息
|
|||
|
|||
PrivateMessageVO msgInfo = new PrivateMessageVO(); |
|||
msgInfo.setRecvId(dto.getId()); |
|||
msgInfo.setSendId(Constant.SYS_USER_ID); |
|||
msgInfo.setContent(dto.getReason()); |
|||
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>(); |
|||
sendMessage.setSender(new IMUserInfo(Constant.SYS_USER_ID, IMTerminalType.WEB.code())); |
|||
sendMessage.setRecvId(dto.getId()); |
|||
sendMessage.setSendToSelf(false); |
|||
sendMessage.setData(msgInfo); |
|||
sendMessage.setSendResult(true); |
|||
imClient.sendPrivateMessage(sendMessage); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue