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