15 changed files with 292 additions and 179 deletions
@ -0,0 +1,25 @@
|
||||
package net.sopod.soim.common.util; |
||||
|
||||
/** |
||||
* Converter |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-30 22:23 |
||||
*/ |
||||
public class Converter { |
||||
|
||||
public static String toString(Object data) { |
||||
return data == null ? null : data.toString(); |
||||
} |
||||
|
||||
/** |
||||
* @exception NumberFormatException if the string cannot be parsed as an integer. |
||||
*/ |
||||
public static int toInt(Object value) { |
||||
if (value instanceof Number) { |
||||
return ((Number)value).intValue(); |
||||
} |
||||
return Integer.parseInt(String.valueOf(value)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,56 @@
|
||||
package net.sopod.soim.das.user.api.config; |
||||
|
||||
import net.sopod.soim.das.user.api.mq.ChatMQMessageConverter; |
||||
import net.sopod.soim.das.user.api.mq.ChatQueueType; |
||||
import org.springframework.amqp.core.Queue; |
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; |
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate; |
||||
import org.springframework.amqp.support.converter.MessageConverter; |
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry; |
||||
import org.springframework.beans.factory.support.RootBeanDefinition; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.context.annotation.Import; |
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; |
||||
import org.springframework.core.type.AnnotationMetadata; |
||||
|
||||
/** |
||||
* ChatRabbitMQAutoConfiguration |
||||
* RabbitMQ的配置类,用来配置队列、交换器、路由等高级信息 |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-30 23:14 |
||||
*/ |
||||
@Import(ChatRabbitMQAutoConfiguration.class) |
||||
@Configuration |
||||
public class ChatRabbitMQAutoConfiguration implements ImportBeanDefinitionRegistrar { |
||||
|
||||
/** |
||||
* direct直连模式, 按照routingkey分发到指定队列 |
||||
*/ |
||||
@Override |
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { |
||||
// 消息队列Queue绑定
|
||||
for (ChatQueueType chatQueueType : ChatQueueType.values()) { |
||||
Queue queue = new Queue(chatQueueType.getQueueName(), true); |
||||
RootBeanDefinition queueDef = new RootBeanDefinition(); |
||||
queueDef.setInstanceSupplier(() -> queue); |
||||
queueDef.setBeanClass(Queue.class); |
||||
registry.registerBeanDefinition(queue.getName(), queueDef); |
||||
} |
||||
} |
||||
|
||||
@Bean |
||||
public MessageConverter rabbitMessageConverter() { |
||||
return new ChatMQMessageConverter(); |
||||
} |
||||
|
||||
@Bean |
||||
public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory, |
||||
MessageConverter messageConverter) { |
||||
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); |
||||
rabbitTemplate.setMessageConverter(messageConverter); |
||||
return rabbitTemplate; |
||||
} |
||||
|
||||
} |
||||
@ -1,15 +0,0 @@
|
||||
package net.sopod.soim.das.user.api.mq; |
||||
|
||||
/** |
||||
* ChatMQ |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-30 17:43 |
||||
*/ |
||||
public class ChatMQ { |
||||
|
||||
private MQType mqType; |
||||
|
||||
private byte[] body; |
||||
|
||||
} |
||||
@ -0,0 +1,76 @@
|
||||
package net.sopod.soim.das.user.api.mq; |
||||
|
||||
import net.sopod.soim.common.constant.Consts; |
||||
import net.sopod.soim.common.util.Converter; |
||||
import net.sopod.soim.common.util.ImClock; |
||||
import net.sopod.soim.common.util.Jackson; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.amqp.core.Message; |
||||
import org.springframework.amqp.core.MessageProperties; |
||||
import org.springframework.amqp.support.converter.AbstractMessageConverter; |
||||
import org.springframework.amqp.support.converter.MessageConversionException; |
||||
import org.xerial.snappy.Snappy; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
/** |
||||
* ChatMessageConverter |
||||
* 聊天消息到 MQ 生产和消费序列化 {@link ChatQueueType} |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-30 18:07 |
||||
*/ |
||||
public class ChatMQMessageConverter extends AbstractMessageConverter { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ChatMQMessageConverter.class); |
||||
|
||||
public static final String MQ_MSG_TYPE = "MQ_MSG_TYPE"; |
||||
|
||||
public static final String MQ_MSG_IS_SNAPPY = "MQ_MSG_IS_SNAPPY"; |
||||
|
||||
@Override |
||||
protected Message createMessage(Object msg, MessageProperties messageProperties) { |
||||
if (msg instanceof Message) { |
||||
return (Message) msg; |
||||
} |
||||
ChatQueueType chatQueueType = ChatQueueType.getMQType(msg.getClass(), |
||||
type -> new MessageConversionException(String.format("不支持%s消息类型,在MQType添加", type.getName()))); |
||||
messageProperties.setHeader(MQ_MSG_TYPE, chatQueueType.ordinal()); |
||||
messageProperties.setMessageId(chatQueueType.getMsgId(msg)); |
||||
// 消息序列化成字节
|
||||
byte[] bytes = Jackson.msgpack().serializeBytes(msg); |
||||
if (bytes.length > Consts.KB) { |
||||
// snappy 压缩
|
||||
try { |
||||
long start = ImClock.millis(); |
||||
byte[] compressed = Snappy.compress(bytes); |
||||
logger.info("snappy compress msg bytes {} to {}, use {} ms.", bytes.length, compressed.length, ImClock.millis() - start); |
||||
bytes = compressed; |
||||
messageProperties.setHeader(MQ_MSG_IS_SNAPPY, true); |
||||
} catch (IOException e) { |
||||
logger.error("snappy compress error: bytes len {}", bytes.length, e); |
||||
} |
||||
} |
||||
return new Message(bytes, messageProperties); |
||||
} |
||||
|
||||
@Override |
||||
public Object fromMessage(Message message) throws MessageConversionException { |
||||
MessageProperties messageProperties = message.getMessageProperties(); |
||||
Object ordinal = messageProperties.getHeaders().get(MQ_MSG_TYPE); |
||||
Object isSnappy = messageProperties.getHeaders().get(MQ_MSG_IS_SNAPPY); |
||||
ChatQueueType chatQueueType = ChatQueueType.getMQType(Converter.toInt(ordinal), |
||||
ori -> new MessageConversionException(String.format("没有ordinal为%d的消息类型,在MQType添加", ori))); |
||||
byte[] body = message.getBody(); |
||||
if (Boolean.TRUE.equals(isSnappy)) { |
||||
try { |
||||
body = Snappy.uncompress(body); |
||||
} catch (IOException e) { |
||||
throw new MessageConversionException(String.format("消息设置为snappy压缩但解压失败,消息长度 %d bytes", body.length), e); |
||||
} |
||||
} |
||||
return Jackson.msgpack().deserializeBytes(body, chatQueueType.getMsgType()); |
||||
} |
||||
|
||||
} |
||||
@ -1,39 +0,0 @@
|
||||
package net.sopod.soim.das.user.api.mq; |
||||
|
||||
import net.sopod.soim.common.constant.Consts; |
||||
import net.sopod.soim.common.util.Jackson; |
||||
import net.sopod.soim.das.user.api.model.entity.ImMessage; |
||||
import org.springframework.amqp.core.Message; |
||||
import org.springframework.amqp.core.MessageProperties; |
||||
import org.springframework.amqp.support.converter.AbstractMessageConverter; |
||||
import org.springframework.amqp.support.converter.MessageConversionException; |
||||
|
||||
/** |
||||
* ChatMessageConverter |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-30 18:07 |
||||
*/ |
||||
public class ChatMessageConverter extends AbstractMessageConverter { |
||||
|
||||
@Override |
||||
protected Message createMessage(Object object, MessageProperties messageProperties) { |
||||
if (object instanceof Message) { |
||||
return (Message) object; |
||||
} |
||||
byte[] bytes = Jackson.msgpack().serializeBytes(object); |
||||
if (bytes.length > 100 * Consts.KB) { |
||||
// TODO 压缩
|
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public Object fromMessage(Message message) throws MessageConversionException { |
||||
byte[] body = message.getBody(); |
||||
Jackson.msgpack().deserializeBytes(body, ImMessage.class); |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
@ -1,27 +0,0 @@
|
||||
package net.sopod.soim.das.user.api.mq; |
||||
|
||||
import net.sopod.soim.common.util.Jackson; |
||||
import net.sopod.soim.common.util.StringUtil; |
||||
import net.sopod.soim.das.user.api.model.entity.ImMessage; |
||||
import org.springframework.amqp.core.Message; |
||||
import org.springframework.amqp.core.MessageProperties; |
||||
|
||||
/** |
||||
* ChatMsg |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-30 15:03 |
||||
*/ |
||||
public class ChatMsg extends Message { |
||||
|
||||
public ChatMsg(ImMessage imMessage) { |
||||
super(Jackson.msgpack().serializeBytes(imMessage), getMessageProperties(imMessage)); |
||||
} |
||||
|
||||
private static MessageProperties getMessageProperties(ImMessage imMessage) { |
||||
MessageProperties messageProperties = new MessageProperties(); |
||||
messageProperties.setMessageId(StringUtil.toString(imMessage.getId())); |
||||
return messageProperties; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,14 @@
|
||||
package net.sopod.soim.das.user.api.mq; |
||||
|
||||
/** |
||||
* ChatQueue |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-31 00:05 |
||||
*/ |
||||
public class ChatQueue { |
||||
|
||||
public static final String IM_MESSAGE_PERSISTENT = "IM_MESSAGE_PERSISTENT"; |
||||
|
||||
public static final String IM_GROUP_MESSAGE_PERSISTENT = "IM_GROUP_MESSAGE_PERSISTENT"; |
||||
} |
||||
@ -0,0 +1,73 @@
|
||||
package net.sopod.soim.das.user.api.mq; |
||||
|
||||
import net.sopod.soim.common.util.Converter; |
||||
import net.sopod.soim.common.util.StringUtil; |
||||
import net.sopod.soim.das.user.api.model.entity.ImGroupMessage; |
||||
import net.sopod.soim.das.user.api.model.entity.ImMessage; |
||||
|
||||
import java.util.function.Function; |
||||
|
||||
/** |
||||
* MQType |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-30 17:43 |
||||
*/ |
||||
public enum ChatQueueType { |
||||
|
||||
/** 单聊消息 */ |
||||
IM_MESSAGE(ChatQueue.IM_MESSAGE_PERSISTENT, |
||||
ImMessage.class, |
||||
msg -> Converter.toString(((ImMessage)msg).getId())), |
||||
|
||||
/** 群聊消息 */ |
||||
IM_GROUP_MESSAGE(ChatQueue.IM_GROUP_MESSAGE_PERSISTENT, |
||||
ImGroupMessage.class, |
||||
msg -> Converter.toString(((ImGroupMessage)msg).getId())); |
||||
|
||||
private final Class<?> msgType; |
||||
|
||||
private final String queueName; |
||||
|
||||
private final Function<Object, String> msgIdConverter; |
||||
|
||||
public static final String TOPIC_CHAT_PERSISTENT = "topic.chat_persistent"; |
||||
|
||||
ChatQueueType(String queueName, Class<?> msgType, Function<Object, String> msgIdConverter) { |
||||
this.queueName = queueName; |
||||
this.msgType = msgType; |
||||
this.msgIdConverter = msgIdConverter; |
||||
} |
||||
|
||||
public Class<?> getMsgType() { |
||||
return msgType; |
||||
} |
||||
|
||||
public String getQueueName() { |
||||
return queueName; |
||||
} |
||||
|
||||
public String getMsgId(Object msg) { |
||||
String msgId = msgIdConverter.apply(msg); |
||||
return msgId != null ? msgId : StringUtil.randomUUID(); |
||||
} |
||||
|
||||
public static ChatQueueType getMQType(Class<?> msgType, Function<Class<?>, RuntimeException> exceptionProvider) { |
||||
ChatQueueType[] values = values(); |
||||
for (ChatQueueType type : values) { |
||||
if (type.msgType == msgType) { |
||||
return type; |
||||
} |
||||
} |
||||
throw exceptionProvider.apply(msgType); |
||||
} |
||||
|
||||
public static ChatQueueType getMQType(int ordinal, Function<Integer, RuntimeException> exceptionProvider) { |
||||
ChatQueueType[] values = values(); |
||||
if (ordinal < values.length) { |
||||
return values[ordinal]; |
||||
} |
||||
throw exceptionProvider.apply(ordinal); |
||||
} |
||||
|
||||
} |
||||
@ -1,47 +0,0 @@
|
||||
package net.sopod.soim.das.user.api.mq; |
||||
|
||||
import net.sopod.soim.das.user.api.model.entity.ImGroupMessage; |
||||
import net.sopod.soim.das.user.api.model.entity.ImMessage; |
||||
|
||||
/** |
||||
* MQType |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-30 17:43 |
||||
*/ |
||||
public enum MQType { |
||||
|
||||
/** 单聊消息 */ |
||||
IM_MESSAGE("IM_MESSAGE", ImMessage.class), |
||||
|
||||
/** 群聊消息 */ |
||||
IM_GROUP_MESSAGE("IM_GROUP_MESSAGE", ImGroupMessage.class); |
||||
|
||||
private final Class<?> beanClazz; |
||||
|
||||
private final String queueName; |
||||
|
||||
MQType(String queueName, Class<?> beanClazz) { |
||||
this.queueName = queueName; |
||||
this.beanClazz = beanClazz; |
||||
} |
||||
|
||||
public Class<?> getBeanClazz() { |
||||
return beanClazz; |
||||
} |
||||
|
||||
public String getQueueName() { |
||||
return queueName; |
||||
} |
||||
|
||||
public static MQType getMQType(Class<?> beanClazz) { |
||||
MQType[] values = values(); |
||||
for (MQType type : values) { |
||||
if (type.beanClazz == beanClazz) { |
||||
return type; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,3 @@
|
||||
|
||||
@org.springframework.lang.NonNullApi |
||||
package net.sopod.soim.das.user.api.mq; |
||||
@ -0,0 +1 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=net.sopod.soim.logic.api.segmentid.config.SegmentIdAutoConfiguration |
||||
@ -1,30 +0,0 @@
|
||||
package net.sopod.soim.das.user.config; |
||||
|
||||
import org.springframework.amqp.core.Queue; |
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
/** |
||||
* RabbitConfig |
||||
* RabbitMQ的配置类,用来配置队列、交换器、路由等高级信息 |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-28 16:31 |
||||
*/ |
||||
@Configuration |
||||
public class RabbitConfig { |
||||
|
||||
@Bean |
||||
public Queue helloQueue() { |
||||
return new Queue("hello"); |
||||
} |
||||
|
||||
@Bean |
||||
public RabbitTemplate rabbitTemplate() { |
||||
RabbitTemplate rabbitTemplate = new RabbitTemplate(); |
||||
rabbitTemplate.setMessageConverter(null); |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue