157 changed files with 1387 additions and 5852 deletions
@ -1,234 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client; |
|||
|
|||
import cn.hutool.core.collection.CollectionUtil; |
|||
import cn.hutool.core.util.StrUtil; |
|||
import com.ccsens.ccmq.lowlevel.client.netty.WrapperedChannel; |
|||
import com.ccsens.ccmq.lowlevel.message.MessageHandler; |
|||
import com.ccsens.ccmq.lowlevel.message.client.ClientAuthTimeOutMessage; |
|||
import com.ccsens.ccmq.lowlevel.message.common.*; |
|||
import com.ccsens.ccmq.lowlevel.message.server.ServerAckMessage; |
|||
import com.ccsens.ccmq.lowlevel.client.netty.ChannelManager; |
|||
import com.ccsens.ccmq.lowlevel.client.rabbitmq.QueueManager; |
|||
import com.ccsens.ccmq.lowlevel.client.restful.RestManager; |
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import io.netty.channel.Channel; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Component; |
|||
import wiki.tall.ccmq.common.util.JacksonUtil; |
|||
|
|||
import java.util.Iterator; |
|||
import java.util.Map; |
|||
|
|||
import static com.ccsens.ccmq.lowlevel.message.MessageHandler.handleMessage; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
@Component |
|||
public class ClientManager { |
|||
private static Logger logger = LoggerFactory.getLogger(ClientManager.class); |
|||
/** |
|||
* 未认证连接存活时间(s) |
|||
*/ |
|||
private static final Long UnAuthedChannelMaxAliveTimeInSeconds = 30L; |
|||
|
|||
@Scheduled(cron = "0/30 * * * * *") |
|||
public void closeUnAuthedChannels() throws Exception{ |
|||
logger.debug("Invoke closeUnAuthedChannels({})",UnAuthedChannelMaxAliveTimeInSeconds); |
|||
|
|||
Map<Channel, WrapperedChannel> allChannels = ChannelManager.getCopyOfAllChannels(); |
|||
Iterator<Map.Entry<Channel,WrapperedChannel>> it = allChannels.entrySet().iterator(); |
|||
while(it.hasNext()){ |
|||
Map.Entry<Channel,WrapperedChannel> entry = it.next(); |
|||
WrapperedChannel wrapperedChannel = entry.getValue(); |
|||
if((!wrapperedChannel.isAuthed()) && (wrapperedChannel.getConnectedSeconds() > UnAuthedChannelMaxAliveTimeInSeconds)) { |
|||
//构造AuthTimeOut消息并处理
|
|||
try { |
|||
ChannelManager.setCurrentChannel(wrapperedChannel.getChannel()); |
|||
MessageHandler.handleMessage(InMessage.newToServerMessage(MessageConstant.DomainType.User, |
|||
new ClientAuthTimeOutMessage())); |
|||
//close会触发remove
|
|||
wrapperedChannel.getChannel().close(); |
|||
logger.debug("Remove a unAuthed Channel {}, which has connected {}s,maxOutTime is {}s", |
|||
wrapperedChannel.getId(), wrapperedChannel.getConnectedSeconds(), UnAuthedChannelMaxAliveTimeInSeconds |
|||
); |
|||
}catch(Exception e){ |
|||
e.printStackTrace(); |
|||
logger.error("handle ClientAuthTimeOutMessage error: {}",e.getMessage()); |
|||
}finally { |
|||
ChannelManager.removeCurrentChannel(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Scheduled(cron="*/30 * * * * ?") |
|||
public void showChannels(){ |
|||
logger.debug("----------------- AuthedChannels(" + System.currentTimeMillis()/1000 + ")--------------"); |
|||
ChannelManager.showAuthedChannels(); |
|||
logger.debug("----------------- AllChannels -----------------"); |
|||
ChannelManager.showAllChannels(); |
|||
logger.debug("------------------End -------------------------"); |
|||
} |
|||
|
|||
public static InMessage fillMessageInfo(InMessage inMessage, MessageConstant.DomainType fromDomain){ |
|||
//1. 填充from、toDomain、Rule
|
|||
switch (fromDomain){ |
|||
case User: { |
|||
//1.填充Message from信息
|
|||
inMessage.setFromDomain(MessageConstant.DomainType.User); |
|||
inMessage.setFrom(ChannelManager.getUserIdByChannel(ChannelManager.getCurrentChannel())); |
|||
|
|||
//2.填充Message to信息
|
|||
if (null == inMessage.getToDomain()) { |
|||
inMessage.setToDomain(MessageConstant.DomainType.Queue); |
|||
} |
|||
|
|||
//3.填充规则缺省值
|
|||
if (null == inMessage.getRule()) { |
|||
inMessage.setRule(MessageRule.defaultRule(inMessage.getFromDomain())); |
|||
} |
|||
break; |
|||
} |
|||
case Queue: { |
|||
//1.填充Message from信息
|
|||
inMessage.setFromDomain(MessageConstant.DomainType.Queue); |
|||
inMessage.setFrom(QueueManager.getInQueueName()); |
|||
|
|||
//2.填充Message to信息
|
|||
if(null == inMessage.getToDomain()){ |
|||
inMessage.setToDomain( MessageConstant.DomainType.User); |
|||
} |
|||
|
|||
//3.填充规则缺省值
|
|||
if (null == inMessage.getRule()) { |
|||
inMessage.setRule(MessageRule.defaultRule(inMessage.getFromDomain())); |
|||
} |
|||
break; |
|||
} |
|||
case Rest: { |
|||
//1.填充Message from信息
|
|||
inMessage.setFromDomain(MessageConstant.DomainType.Rest); |
|||
inMessage.setFrom(""); |
|||
|
|||
//2.填充Message to信息
|
|||
if (null == inMessage.getToDomain()) { |
|||
inMessage.setToDomain(MessageConstant.DomainType.User); |
|||
} |
|||
|
|||
//3.填充规则缺省值
|
|||
if (null == inMessage.getRule()) { |
|||
inMessage.setRule(MessageRule.defaultRule(inMessage.getFromDomain())); |
|||
} |
|||
break; |
|||
} |
|||
default: |
|||
break; |
|||
} |
|||
|
|||
//2.填充tos值
|
|||
if (null == inMessage.getTos()) { |
|||
inMessage.setTos(CollectionUtil.newHashSet()); |
|||
} else { |
|||
inMessage.getTos().removeIf(to -> to == null || StrUtil.isEmpty(to)); |
|||
} |
|||
switch (inMessage.getToDomain()) { |
|||
case Queue: { |
|||
if (CollectionUtil.isEmpty(inMessage.getTos())) { |
|||
inMessage.getTos().add(QueueManager.getOutQueueName()); |
|||
} |
|||
break; |
|||
} |
|||
case Rest:{ |
|||
if (CollectionUtil.isEmpty(inMessage.getTos())) { |
|||
inMessage.getTos().add(RestManager.getOutRestName()); |
|||
} |
|||
break; |
|||
} |
|||
default: { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return inMessage; |
|||
} |
|||
|
|||
public static void sendMessageToAuthedClient(MessageConstant.DomainType toDomain, String to, OutMessageSet outMessageSet) throws JsonProcessingException { |
|||
switch (toDomain){ |
|||
case User: |
|||
if(StrUtil.isNotEmpty(to)) { |
|||
ChannelManager.sendTo(to, outMessageSet); |
|||
} |
|||
break; |
|||
case Queue: |
|||
QueueManager.sendTo(outMessageSet); |
|||
break; |
|||
case Rest: |
|||
RestManager.sendTo(outMessageSet); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
|
|||
public static void sendServerMessage(MessageConstant.DomainType toDomain, OutMessageSet outMessageSet) throws JsonProcessingException { |
|||
switch (toDomain){ |
|||
case User: |
|||
ChannelManager.sendTo(ChannelManager.getCurrentChannel(), outMessageSet); |
|||
break; |
|||
case Queue: |
|||
QueueManager.sendTo(outMessageSet); |
|||
break; |
|||
case Rest: |
|||
RestManager.sendTo(outMessageSet); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
|
|||
public static void sendServerAck(InMessage inMessage, MessageConstant.Error error) throws Exception { |
|||
switch (error) { |
|||
case Ok: |
|||
if (inMessage.getRule().getAckRule() != MessageRule.AckRule.ALWAYS) { |
|||
return; |
|||
} |
|||
break; |
|||
default: |
|||
if (inMessage.getRule().getAckRule() == MessageRule.AckRule.NONE) { |
|||
return; |
|||
} |
|||
break; |
|||
} |
|||
OutMessageSet outMessageSet = OutMessageSet.newInstance().ackId(null).add( |
|||
new OutMessage(MessageConstant.DomainType.Server, |
|||
JacksonUtil.beanToJson( |
|||
new ServerAckMessage(inMessage.getId(),inMessage.getUnikey(),error) |
|||
) |
|||
) |
|||
); |
|||
sendServerMessage(inMessage.getFromDomain(),outMessageSet); |
|||
} |
|||
|
|||
public static boolean isSenderAuthed(InMessage inMessage) { |
|||
if (inMessage.getFromDomain() == MessageConstant.DomainType.User) { |
|||
return ChannelManager.channelAuthed(ChannelManager.getCurrentChannel()); |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
public static void closeCurrentSender(InMessage message) { |
|||
if(message.getFromDomain() == MessageConstant.DomainType.User){ |
|||
ChannelManager.getCurrentChannel().close(); |
|||
} |
|||
} |
|||
|
|||
public static boolean isUserOnline(MessageConstant.DomainType domain, String to) { |
|||
if (domain == MessageConstant.DomainType.User) { |
|||
return ChannelManager.isUserOnline(to); |
|||
} |
|||
return true; |
|||
} |
|||
} |
@ -1,366 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty; |
|||
|
|||
import cn.hutool.core.collection.CollectionUtil; |
|||
import com.ccsens.ccmq.lowlevel.message.client.ClientAuthTimeOutMessage; |
|||
import com.ccsens.ccmq.lowlevel.message.common.InMessage; |
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import io.netty.channel.Channel; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import java.util.*; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
|
|||
import static com.ccsens.ccmq.lowlevel.message.MessageHandler.handleMessage; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
public class ChannelManager { |
|||
private static Logger logger = LoggerFactory.getLogger(ChannelManager.class); |
|||
private static ThreadLocal<Channel> threadLocal = new ThreadLocal<>(); |
|||
|
|||
/** |
|||
* UserId,WrappedChannel authed channels; |
|||
*/ |
|||
private static Map<String,Set<WrapperedChannel>> authedChannels; |
|||
/** |
|||
* Channel,WrapperedChannel |
|||
*/ |
|||
private static Map<Channel,WrapperedChannel> rawChannels; |
|||
|
|||
static { |
|||
authedChannels = new ConcurrentHashMap<>(); |
|||
rawChannels = new ConcurrentHashMap<>(); |
|||
} |
|||
|
|||
/** |
|||
* 私有构造,不允许生成该类对象 |
|||
*/ |
|||
private ChannelManager(){} |
|||
|
|||
/** |
|||
* 设置当前正在错误的channel |
|||
* @param channel netty ws/tcp连接 |
|||
*/ |
|||
public static void setCurrentChannel(Channel channel){ |
|||
threadLocal.set(channel); |
|||
} |
|||
|
|||
/** |
|||
* 获取当前线程的channel对象 |
|||
* @return channel |
|||
*/ |
|||
public static Channel getCurrentChannel(){ |
|||
return threadLocal.get(); |
|||
} |
|||
|
|||
/** |
|||
* 删除当前线程的channel对象 |
|||
*/ |
|||
public static void removeCurrentChannel(){ |
|||
threadLocal.remove(); |
|||
} |
|||
|
|||
/** |
|||
* 添加一个新的连接(Channel) |
|||
* @param channel 新的连接 |
|||
* @param serverType 服务器类型 |
|||
*/ |
|||
public static synchronized void addChannel(Channel channel,String serverType){ |
|||
logger.debug("Invoke addChannel({},{})",channel,serverType); |
|||
if(null != channel) { |
|||
rawChannels.put(channel, new WrapperedChannel(channel, serverType)); |
|||
logger.debug("Add a new channel: {},{}",channel.id().asLongText(),serverType); |
|||
}else{ |
|||
logger.error("channel is null"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 用户认证 |
|||
* @param channel 连接 |
|||
* @param userId 用户ID |
|||
* @param major 客户端主版本号 |
|||
* @param minor 客户端次版本号 |
|||
*/ |
|||
public static synchronized void authChannel(Channel channel,String userId,Integer major,Integer minor){ |
|||
logger.debug("Invoke authedChannels({},{},{},{})",channel.id().asLongText(),userId,major,minor); |
|||
major = major != null ? major : 0; |
|||
minor = minor != null ? minor : 0; |
|||
|
|||
WrapperedChannel wrapperedChannel = rawChannels.get(channel); |
|||
if(wrapperedChannel != null){ |
|||
wrapperedChannel.whenAuthed(userId,major,minor); |
|||
Set<WrapperedChannel> authedWchannelSet = authedChannels.computeIfAbsent(userId, k -> new HashSet<>()); |
|||
authedWchannelSet.add(wrapperedChannel); |
|||
logger.debug("Authed channel {} with user {}", channel.id().asLongText(), userId); |
|||
}else{ |
|||
logger.error("Authed channel,but wrappedChannel is null."); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 移除一个连接(Channel) |
|||
* @param channel 要移除的连接 |
|||
*/ |
|||
public static synchronized void removeChannel(Channel channel){ |
|||
logger.debug("Invoke removeChannel({})",channel.id().asLongText()); |
|||
WrapperedChannel wrapperedChannel = rawChannels.get(channel); |
|||
if(wrapperedChannel != null){ |
|||
//从rawChannels集合中删除
|
|||
rawChannels.remove(channel); |
|||
logger.debug("Remove a channel from rawChannels: {}",channel.id().asLongText()); |
|||
if(wrapperedChannel.isAuthed()){ |
|||
Set<WrapperedChannel> authedChannelSet = authedChannels.get(wrapperedChannel.getUserId()); |
|||
//从authedChannel集合的value(set)中删除
|
|||
if(CollectionUtil.isNotEmpty(authedChannelSet)){ |
|||
authedChannelSet.remove(wrapperedChannel); |
|||
logger.debug("Remove a channel from authedChannelSet: {}, {}",wrapperedChannel.getUserId(),channel.id().asLongText()); |
|||
} |
|||
//从authedChannel中删除,此处不用else,因为if中语句执行完毕之后,authedChannelSet也可能变成空集合
|
|||
if(CollectionUtil.isEmpty(authedChannelSet)){ |
|||
authedChannels.remove(wrapperedChannel.getUserId()); |
|||
logger.debug("Remove a user from authedChannels: {}",wrapperedChannel.getUserId()); |
|||
} |
|||
} |
|||
}else{ |
|||
logger.error("Remove channel,but wrappedChannel is null."); |
|||
} |
|||
|
|||
if(channel.isOpen() || channel.isActive()){ |
|||
channel.close(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 移除一个用户 |
|||
* @param userId 要移除的用户 |
|||
*/ |
|||
public static synchronized void removeUser(String userId){ |
|||
logger.debug("Invoke remove user : {}",userId); |
|||
Set<WrapperedChannel> wChannelSet = authedChannels.get(userId); |
|||
if(CollectionUtil.isNotEmpty(wChannelSet)){ |
|||
for(WrapperedChannel wChannel : wChannelSet){ |
|||
//从rawChannel中依次删除
|
|||
rawChannels.remove(wChannel.getChannel()); |
|||
logger.debug("Remove a channel from rawChannels: {}",wChannel.getChannel().id().asLongText()); |
|||
} |
|||
} |
|||
//从authedChannel中删除
|
|||
authedChannels.remove(userId); |
|||
logger.debug("Remove a user from authedChannels: {}",userId); |
|||
} |
|||
|
|||
/** |
|||
* 添加版本号 |
|||
* 只能给已认证的请求添加版本号 |
|||
* @param channel 连接 |
|||
* @param major 主版本号 |
|||
* @param minor 次版本号 |
|||
*/ |
|||
public static synchronized void versionChannel(Channel channel,int major,int minor){ |
|||
logger.debug("Invoke Version channel({},{},{}))",channel.id().asLongText(),major,minor); |
|||
WrapperedChannel wChannel = rawChannels.get(channel); |
|||
if(wChannel != null){ |
|||
wChannel.setVersion(major,minor); |
|||
logger.debug("Version Channel: {},{},{}",channel.id().asLongText(),major,minor); |
|||
}else{ |
|||
logger.error("Remove channel,but wrappedChannel is null."); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 发送广播消息给所有已认证Channel |
|||
* @param message 消息 |
|||
*/ |
|||
public static synchronized void broadCastAuthed(Object message) { |
|||
logger.debug("Invoke broadCastAuthed({})",message); |
|||
for (Map.Entry<Channel,WrapperedChannel> entry : rawChannels.entrySet()) { |
|||
WrapperedChannel wChannel = entry.getValue(); |
|||
if(wChannel.isAuthed()) { |
|||
wChannel.writeAndFlush(message); |
|||
logger.debug("Send Message {} to {},{}",message,wChannel.getUserId(),wChannel.getId()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 发送广播消息 |
|||
* @param message 消息 |
|||
*/ |
|||
public static synchronized void broadCast(Object message) { |
|||
logger.debug("Invoke broadCast({})",message); |
|||
for (Map.Entry<Channel,WrapperedChannel> entry : rawChannels.entrySet()) { |
|||
entry.getValue().writeAndFlush(message); |
|||
logger.debug("Send Message {} to {}",message,entry.getValue().getId()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 发送消息给某个连接 |
|||
* @param channel 连接 |
|||
* @param message 消息 |
|||
*/ |
|||
public static synchronized void sendTo(Channel channel,Object message){ |
|||
logger.debug("Invoke sendTo({},{})",channel.id().asLongText(),message); |
|||
WrapperedChannel wrapperedChannel = rawChannels.get(channel); |
|||
if(wrapperedChannel != null) { |
|||
wrapperedChannel.writeAndFlush(message); |
|||
logger.debug("Write message {} to Channel {}",message,channel); |
|||
}else{ |
|||
logger.error("can't find channel from rawChannels"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 发送消息给某个用户 |
|||
* @param userId 用户ID |
|||
* @param message 消息 |
|||
*/ |
|||
public static synchronized boolean sendTo(String userId,Object message){ |
|||
logger.debug("Invoke sendTo({},{})",userId,message); |
|||
Set<WrapperedChannel> wChannelSet = authedChannels.get(userId); |
|||
if(CollectionUtil.isNotEmpty(wChannelSet)){ |
|||
for(WrapperedChannel wChannel:wChannelSet){ |
|||
wChannel.writeAndFlush(message); |
|||
logger.debug("Send message {} to channel {}",message,userId); |
|||
} |
|||
return true; |
|||
}else{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 刷新最后一次接收数据时间 每次接收数据需要调用该函数 |
|||
* @param channel 接收数据的连接 |
|||
*/ |
|||
public static synchronized void flushReceiveTimestamp(Channel channel){ |
|||
logger.debug("Invoke flushReceiveTimestamp({})",channel.id().asLongText()); |
|||
WrapperedChannel wrapperedChannel = rawChannels.get(channel); |
|||
if(wrapperedChannel != null){ |
|||
wrapperedChannel.whenReceivedData(); |
|||
}else{ |
|||
logger.error("can find channel from rawChannels"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 关闭所有未认证的连接 |
|||
* @param unAuthedChannelsMaxAliveTimeInSeconds 未认证的最大时长(s) |
|||
* @throws Exception |
|||
* @deprecated 已过期,该方法在多线程并发下可能出现问题,建议使用messageService中的同名方法 |
|||
*/ |
|||
@Deprecated |
|||
public static synchronized void closeUnAuthedChannels(Long unAuthedChannelsMaxAliveTimeInSeconds) throws Exception { |
|||
logger.debug("Inovke closeUnAuthedChannels({})",unAuthedChannelsMaxAliveTimeInSeconds); |
|||
Iterator<Map.Entry<Channel,WrapperedChannel>> it = rawChannels.entrySet().iterator(); |
|||
while(it.hasNext()){ |
|||
Map.Entry<Channel,WrapperedChannel> entry = it.next(); |
|||
WrapperedChannel wrapperedChannel = entry.getValue(); |
|||
if((!wrapperedChannel.isAuthed()) && (wrapperedChannel.getConnectedSeconds() > unAuthedChannelsMaxAliveTimeInSeconds)) { |
|||
it.remove(); |
|||
//关闭连接
|
|||
wrapperedChannel.getChannel().close(); |
|||
logger.debug("Remove a unAuthed Channel {}, which has connected {}s,maxOutTime is {}s", wrapperedChannel.getId(),wrapperedChannel.getConnectedSeconds() , |
|||
unAuthedChannelsMaxAliveTimeInSeconds ); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 判断Channel是否存在 |
|||
* @param channel 连接 |
|||
* @return 是否存在 |
|||
*/ |
|||
public static synchronized boolean channelExist(Channel channel){ |
|||
return rawChannels.containsKey(channel); |
|||
} |
|||
|
|||
/** |
|||
* 判断Channel是否已经认证 |
|||
* @param channel 连接 |
|||
* @return 是否认证 |
|||
*/ |
|||
public static synchronized boolean channelAuthed(Channel channel){ |
|||
return rawChannels.containsKey(channel) && rawChannels.get(channel).isAuthed(); |
|||
} |
|||
|
|||
/** |
|||
* 判断用户是否在线 |
|||
* @param userId 用户ID |
|||
* @return 是否认证 |
|||
*/ |
|||
public static synchronized boolean isUserOnline(String userId){ |
|||
return authedChannels.containsKey(userId); |
|||
} |
|||
|
|||
/** |
|||
* 根据Channel获取对应的用户ID |
|||
* @param channel 连接 |
|||
* @return 用户ID |
|||
*/ |
|||
public static synchronized String getUserIdByChannel(Channel channel) { |
|||
return rawChannels.containsKey(channel) ? rawChannels.get(channel).getUserId() : null; |
|||
} |
|||
|
|||
/** |
|||
* 获取所有在线用户 |
|||
* @return 所有用户的集合列表 |
|||
*/ |
|||
public static synchronized Set<String> getOnlineUsers(){ |
|||
return authedChannels.keySet(); |
|||
} |
|||
|
|||
/** |
|||
* 获取某种类型的所有在线用户的连接 |
|||
* @param type 客户端类型(ws/tcp modebus/tcp text) |
|||
* @return 所有在西安channel的集合列表 |
|||
*/ |
|||
public static synchronized Set<Channel> getOnlineChannels(String type){ |
|||
Set<Channel> onLineChannels = CollectionUtil.newHashSet(); |
|||
for(Map.Entry<Channel,WrapperedChannel> entry: rawChannels.entrySet()){ |
|||
WrapperedChannel wrapperedChannel = entry.getValue(); |
|||
if(wrapperedChannel.isAuthed() && wrapperedChannel.getType().equals(type)){ |
|||
onLineChannels.add(entry.getKey()); |
|||
} |
|||
} |
|||
return onLineChannels; |
|||
} |
|||
|
|||
/** |
|||
* 获取一个rawChannel的副本 |
|||
* @return 副本 |
|||
*/ |
|||
public static Map<Channel, WrapperedChannel> getCopyOfAllChannels() { |
|||
Map<Channel,WrapperedChannel> copyMap = new HashMap<>(rawChannels.size()); |
|||
copyMap.putAll(rawChannels); |
|||
return copyMap; |
|||
} |
|||
|
|||
/** |
|||
* 根据Channel获取WrapperedChannel |
|||
* @param channel channel |
|||
* @return 对应的wrapperedChannel |
|||
*/ |
|||
public static WrapperedChannel getWrapperedChannelByChannel(Channel channel) { |
|||
return rawChannels.get(channel); |
|||
} |
|||
|
|||
public static synchronized void showAuthedChannels(){ |
|||
for(Map.Entry<String,Set<WrapperedChannel>> entry : authedChannels.entrySet()){ |
|||
for (WrapperedChannel channel:entry.getValue()){ |
|||
logger.debug("{}-->{}",entry.getKey(),channel.toString()); |
|||
} |
|||
} |
|||
} |
|||
public static synchronized void showAllChannels(){ |
|||
for(Map.Entry<Channel,WrapperedChannel> entry : rawChannels.entrySet()){ |
|||
logger.debug(entry.getValue().toString()); |
|||
} |
|||
} |
|||
|
|||
public static Set<String> getAllOnlineUsers() { |
|||
return authedChannels.keySet(); |
|||
} |
|||
} |
@ -1,200 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty; |
|||
|
|||
import cn.hutool.core.util.ObjectUtil; |
|||
import wiki.tall.ccmq.common.util.DateUtil; |
|||
import io.netty.channel.Channel; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
import java.net.InetSocketAddress; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
public class WrapperedChannel { |
|||
@Getter |
|||
@Setter |
|||
private static class ClientVersion{ |
|||
int major; |
|||
int minor; |
|||
public ClientVersion(int major,int minor){ |
|||
this.major = major; |
|||
this.minor = minor; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Netty's Channel |
|||
*/ |
|||
private Channel channel; |
|||
/** |
|||
* userId |
|||
*/ |
|||
private String userId; |
|||
/** |
|||
* 客户端版本号 |
|||
*/ |
|||
private ClientVersion version; |
|||
|
|||
/** |
|||
* 客户端类型(哪种服务器) |
|||
*/ |
|||
private String type; |
|||
/** |
|||
* 是否认证 |
|||
*/ |
|||
private boolean authed; |
|||
/** |
|||
* 连接建立时间 |
|||
*/ |
|||
private Long createdAtInSeconds; |
|||
/** |
|||
* 认证时间(s) |
|||
*/ |
|||
private Long authedAtInSeconds; |
|||
/** |
|||
* 最后一次接收有效数据时间(包括心跳包) |
|||
*/ |
|||
private Long lastDataReceivedInSeconds; |
|||
/** |
|||
* 最后一次发送有效数据时间(包括心跳包) |
|||
*/ |
|||
private Long lastDataSendInSeconds; |
|||
/** |
|||
* 接收到数据条数(每接收到一条协议加1,包括心跳) |
|||
*/ |
|||
private long dataReceivedCount; |
|||
/** |
|||
* 发送数据条数(每发送到一条协议加1,包括心跳) |
|||
*/ |
|||
private long dataSendCount; |
|||
|
|||
public WrapperedChannel(Channel channel){ |
|||
this.channel = channel; |
|||
this.createdAtInSeconds = DateUtil.currentSeconds(); |
|||
} |
|||
|
|||
public WrapperedChannel(Channel channel,String type){ |
|||
this(channel); |
|||
this.type = type; |
|||
} |
|||
|
|||
public WrapperedChannel(Channel channel,String type,int major,int minor){ |
|||
this(channel,type); |
|||
this.version = new ClientVersion(major,minor); |
|||
} |
|||
|
|||
public String getVersion(){ |
|||
if(ObjectUtil.isNotNull(version)){ |
|||
return "v" + version.getMajor() + "." + version.getMinor(); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
public void setVersion(int major,int minor){ |
|||
if (ObjectUtil.isNull(version)) { |
|||
version = new ClientVersion(major, minor); |
|||
} else { |
|||
version.setMajor(major); |
|||
version.setMinor(minor); |
|||
} |
|||
} |
|||
|
|||
public String getRemoteAddr(){ |
|||
if(ObjectUtil.isNotNull(channel)){ |
|||
InetSocketAddress insocket = (InetSocketAddress) channel.remoteAddress(); |
|||
if(ObjectUtil.isNotNull(insocket)) { |
|||
return insocket.getAddress().getHostAddress(); |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
public Integer getRemotePort(){ |
|||
if(ObjectUtil.isNotNull(channel)){ |
|||
InetSocketAddress insocket = (InetSocketAddress) channel.remoteAddress(); |
|||
if(ObjectUtil.isNotNull(insocket)) { |
|||
return insocket.getPort(); |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
public String getId(){ |
|||
if(ObjectUtil.isNotNull(channel)){ |
|||
return channel.id().asLongText(); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
public void whenReceivedData(){ |
|||
lastDataReceivedInSeconds = DateUtil.currentSeconds(); |
|||
dataReceivedCount++; |
|||
} |
|||
|
|||
public void whenSendData(){ |
|||
lastDataSendInSeconds = DateUtil.currentSeconds(); |
|||
dataSendCount++; |
|||
} |
|||
|
|||
public void whenAuthed(){ |
|||
authed = true; |
|||
authedAtInSeconds = DateUtil.currentSeconds(); |
|||
} |
|||
|
|||
public void whenAuthed(String userId,int major,int minor){ |
|||
whenAuthed(); |
|||
setVersion(major,minor); |
|||
this.userId = userId; |
|||
} |
|||
|
|||
public void writeAndFlush(Object message){ |
|||
if(channel != null && channel.isActive()){ |
|||
channel.writeAndFlush(message); |
|||
whenSendData(); |
|||
} |
|||
} |
|||
|
|||
public Long getConnectedSeconds(){ |
|||
return createdAtInSeconds == null ? null : DateUtil.currentSeconds() - createdAtInSeconds; |
|||
} |
|||
|
|||
public Long getOnlineSeconds(){ |
|||
return authedAtInSeconds == null ? null : DateUtil.currentSeconds() - authedAtInSeconds ; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(Object obj) { |
|||
if(ObjectUtil.isNull(obj)) { |
|||
return false; |
|||
} |
|||
if(this == obj) { |
|||
return true; |
|||
} |
|||
if(ObjectUtil.isNull(channel)) { |
|||
return false; |
|||
} |
|||
if(obj.getClass() == this.getClass()){ |
|||
WrapperedChannel other = (WrapperedChannel)obj; |
|||
return channel.equals(other.channel); |
|||
}else if(obj.getClass() == channel.getClass()){ |
|||
Channel other = (Channel)obj; |
|||
return channel.equals(other); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
if(ObjectUtil.isNotNull(channel)){ |
|||
return channel.hashCode(); |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return String.format("id: %s, type: %s, authed: %b, userId: %s, version: %s",getId(),type,authed,userId,getVersion()); |
|||
} |
|||
} |
@ -1,115 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty.tcphexserver; |
|||
|
|||
public class ModbusConverter { |
|||
|
|||
// public static BaseMessageDto convertCCModbusToMessage(CCModBusEntity ccModBusEntity) {
|
|||
// BaseMessageDto message = null;
|
|||
// int addr = ccModBusEntity.getAddr() & 0xFF;
|
|||
// int oper = ccModBusEntity.getOper() & 0xFF;
|
|||
// WebConstant.Message_Type type = WebConstant.Message_Type.valueOf(addr);
|
|||
// if(type != null) {
|
|||
// switch (type) {
|
|||
// case Heart: {
|
|||
// message = toHeartMessage(addr,oper,ccModBusEntity.getOriginData());
|
|||
// break;
|
|||
// }
|
|||
// case Auth:{
|
|||
// message = toAuthMessage(addr,oper,ccModBusEntity.getOriginData());
|
|||
// }
|
|||
// }
|
|||
// }
|
|||
// return message;
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * OriginaData ==> 0x00
|
|||
// * @param oper
|
|||
// * @return
|
|||
// */
|
|||
// private static BaseMessageDto toHeartMessage(int addr, int oper, byte[] originData){
|
|||
// HeartMessageDto message = new HeartMessageDto();
|
|||
// return message;
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * OriginaData ==> 0x00 0x00 0x00 0x64
|
|||
// * @param oper
|
|||
// * @return
|
|||
// */
|
|||
// private static BaseMessageDto toAuthMessage(int addr, int oper, byte[] originData){
|
|||
// Long userId = 0L;
|
|||
// for(int i=0;i<originData.length;i++){
|
|||
// userId <<= 8;
|
|||
// userId |= originData[i] & 0xFF;
|
|||
// }
|
|||
// AuthMessageDto message = new AuthMessageDto(userId,null);
|
|||
// return message;
|
|||
// }
|
|||
//
|
|||
// public static CCModBusEntity convertCommonProtocolToCCModbus(BaseMessageDto message) {
|
|||
// CCModBusEntity ccModBusEntity = null;
|
|||
// WebConstant.Message_Type type = WebConstant.Message_Type.phaseOf(message.getType());
|
|||
// switch (type){
|
|||
// case Heart:{
|
|||
// ccModBusEntity = fromHeartMessage((HeartMessageDto)message);
|
|||
// break;
|
|||
// }
|
|||
// case Timer:{
|
|||
// WebConstant.Message_Timer_Event event = WebConstant.Message_Timer_Event.phaseOf(message.getEvent());
|
|||
// switch (event){
|
|||
// case CountDown:
|
|||
// ccModBusEntity = fromTimerMessageWithCountDown((TimerMessageWithCountDownDto)message);
|
|||
// break;
|
|||
// case Clock:
|
|||
// ccModBusEntity = fromTimerMessageWithClock((TimerMessageWithClockDto)message);
|
|||
// break;
|
|||
// }
|
|||
// break;
|
|||
// }
|
|||
// }
|
|||
// return ccModBusEntity;
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * OriginalData: 0x00
|
|||
// * @param message
|
|||
// * @return
|
|||
// */
|
|||
// private static CCModBusEntity fromHeartMessage(HeartMessageDto message){
|
|||
// byte []originData = new byte[1];
|
|||
// originData[0] = 0x00;
|
|||
// byte addr = (byte)(WebConstant.Message_Type.phaseOf(message.getType()).value & 0xFF);
|
|||
// byte oper = (byte)(WebConstant.Message_Type.phaseOf(message.getEvent()).value & 0xFF);
|
|||
// CCModBusEntity ccModBusEntity = new CCModBusEntity(addr,oper,originData);
|
|||
// return ccModBusEntity;
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * originaData: a1 xx xx xx crc
|
|||
// * @param message
|
|||
// * @return
|
|||
// */
|
|||
// private static CCModBusEntity fromTimerMessageWithCountDown(TimerMessageWithCountDownDto message){
|
|||
// Long second = message.getData().getSecond();
|
|||
// byte []originData = new byte[5];
|
|||
// originData[0] = (byte)(0xa1 & 0xFF);
|
|||
// originData[1] = (0x48 & 0xFF);
|
|||
// originData[2] = (byte)((second >> 8) & 0xFF);
|
|||
// originData[3] = (byte)(second & 0xFF);
|
|||
// originData[4] = (byte)((originData[0] + originData[1] + originData[2] + originData[3]) & 0xFF);
|
|||
//
|
|||
// byte addr = (byte)(WebConstant.Message_Type.phaseOf(message.getType()).value & 0xFF);
|
|||
// byte oper = (byte)(WebConstant.Message_Timer_Event.phaseOf(message.getEvent()).value & 0xFF);
|
|||
// CCModBusEntity ccModBusEntity = new CCModBusEntity(addr,oper,originData);
|
|||
// return ccModBusEntity;
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * originaData: xxx
|
|||
// * @param message
|
|||
// * @return
|
|||
// */
|
|||
// private static CCModBusEntity fromTimerMessageWithClock(TimerMessageWithClockDto message){
|
|||
// return null;
|
|||
// }
|
|||
} |
@ -1,45 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty.tcphexserver; |
|||
|
|||
import io.netty.buffer.ByteBuf; |
|||
import io.netty.channel.ChannelHandlerContext; |
|||
import io.netty.handler.codec.ByteToMessageDecoder; |
|||
|
|||
import java.util.List; |
|||
|
|||
public class ModbusDecoder extends ByteToMessageDecoder { |
|||
|
|||
private void discardNBytes(ByteBuf in, int length) { |
|||
for (int i = 0; i < length; i++) { |
|||
in.readByte(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> out) throws Exception { |
|||
// if (in.readableBytes() < CCModBusEntity.SIZE_MIN) {
|
|||
// //长度小于协议的最小长度,继续读下一次
|
|||
// return;
|
|||
// }
|
|||
//
|
|||
// CCModBusEntity ccModBusEntity = new CCModBusEntity(in);
|
|||
// CCModBusEntity.Error error = ccModBusEntity.valid();
|
|||
// switch (error) {
|
|||
// case ERROR_FILTER_NOT_MATCH:
|
|||
// case ERROR_LEN_EXCLUEE_MAX:
|
|||
// case ERROR_CRC_INVALID: //丢弃一个字节,继续读取
|
|||
// discardNBytes(in, 1);
|
|||
// return;
|
|||
// case ERROR_NEED_MORE_DATA: //继续读取
|
|||
// return;
|
|||
// case ERROR_NONE:
|
|||
// break;
|
|||
// }
|
|||
//
|
|||
// //交给下个handler处理
|
|||
// discardNBytes(in, ccModBusEntity.getModbusLength());
|
|||
// BaseMessageDto message = ModbusConverter.convertCCModbusToMessage(ccModBusEntity);
|
|||
// if(ObjectUtil.isNotNull(message)) {
|
|||
// out.add(message);
|
|||
// }
|
|||
} |
|||
} |
@ -1,33 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty.tcphexserver; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.OutMessageSet; |
|||
import io.netty.buffer.ByteBuf; |
|||
import io.netty.channel.ChannelHandlerContext; |
|||
import io.netty.handler.codec.MessageToByteEncoder; |
|||
|
|||
public class ModbusEncoder extends MessageToByteEncoder<OutMessageSet> { |
|||
@Override |
|||
protected void encode(ChannelHandlerContext channelHandlerContext, OutMessageSet message, ByteBuf out) throws Exception { |
|||
//
|
|||
// //1.处理Start->Timer[Countdown]
|
|||
// if(message != null && message instanceof SyncMessageWithStartDto){
|
|||
// SyncMessageWithStartDto startMessage = (SyncMessageWithStartDto)message;
|
|||
// if(ObjectUtil.isNotNull(startMessage.getData().getBeginTaskId())){
|
|||
// Long duration = startMessage.getData().getDuration();
|
|||
// message = new TimerMessageWithCountDownDto(duration/1000,null,null);
|
|||
// }
|
|||
// }
|
|||
//
|
|||
// if(message != null){
|
|||
// CCModBusEntity ccModBusEntity = ModbusConverter.convertCommonProtocolToCCModbus(message);
|
|||
// if(ccModBusEntity != null){
|
|||
// byte[] modbusData = ccModBusEntity.getModbusData();
|
|||
// for(int i=0;i<modbusData.length;i++){
|
|||
// System.out.printf("%02x ",modbusData[i]);
|
|||
// }
|
|||
// System.out.println();
|
|||
// out.writeBytes(modbusData);
|
|||
// }
|
|||
// }
|
|||
} |
|||
} |
@ -1,109 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty.tcphexserver; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.InMessage; |
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.client.ClientIdleClosedMessage; |
|||
import com.ccsens.ccmq.lowlevel.message.client.UnExceptedErrorMessage; |
|||
import com.ccsens.ccmq.lowlevel.client.ClientManager; |
|||
import com.ccsens.ccmq.lowlevel.message.MessageHandler; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import wiki.tall.ccmq.common.util.WebConstant; |
|||
import com.ccsens.ccmq.lowlevel.client.netty.ChannelManager; |
|||
import io.netty.channel.ChannelHandler; |
|||
import io.netty.channel.ChannelHandlerContext; |
|||
import io.netty.channel.SimpleChannelInboundHandler; |
|||
import io.netty.handler.timeout.IdleState; |
|||
import io.netty.handler.timeout.IdleStateEvent; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
@ChannelHandler.Sharable |
|||
@Component |
|||
public class ModbusHandler extends SimpleChannelInboundHandler<InMessage> { |
|||
private Logger logger = LoggerFactory.getLogger(ModbusHandler.class); |
|||
|
|||
private static final String TYPE = WebConstant.NETTY_SERVER_TYPE.TCP_MB.phase; |
|||
|
|||
@Override |
|||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception { |
|||
} |
|||
|
|||
@Override |
|||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { |
|||
} |
|||
|
|||
@Override |
|||
public void channelActive(ChannelHandlerContext ctx) throws Exception { |
|||
ChannelManager.addChannel(ctx.channel(),TYPE); |
|||
} |
|||
|
|||
@Override |
|||
public void channelInactive(ChannelHandlerContext ctx) throws Exception { |
|||
ChannelManager.removeChannel(ctx.channel()); |
|||
} |
|||
|
|||
@Override |
|||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { |
|||
try { |
|||
ChannelManager.setCurrentChannel(ctx.channel()); |
|||
MessageHandler.handleMessage( |
|||
InMessage.newToServerMessage(MessageConstant.DomainType.User,new UnExceptedErrorMessage(cause.getMessage()))); |
|||
}catch(Exception e){ |
|||
e.printStackTrace(); |
|||
logger.error("TcpHex exceptionCaught handler error: {}",e.getMessage()); |
|||
}finally { |
|||
ChannelManager.removeCurrentChannel(); |
|||
} |
|||
cause.printStackTrace(); |
|||
ctx.close(); |
|||
logger.error("Channel closed. TcpHex get a exception: {}", cause.getMessage()); |
|||
} |
|||
|
|||
@SuppressWarnings("all") |
|||
@Override |
|||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { |
|||
if (evt instanceof IdleStateEvent) { |
|||
IdleStateEvent idleStateEvent = (IdleStateEvent) evt; |
|||
if (idleStateEvent.state() == IdleState.READER_IDLE) { |
|||
try { |
|||
ChannelManager.setCurrentChannel(ctx.channel()); |
|||
MessageHandler.handleMessage( |
|||
InMessage.newToServerMessage(MessageConstant.DomainType.User,new ClientIdleClosedMessage())); |
|||
}catch(Exception e){ |
|||
e.printStackTrace(); |
|||
logger.error("TcpHex exceptionCaught handler error: {}",e.getMessage()); |
|||
}finally { |
|||
ChannelManager.removeCurrentChannel(); |
|||
} |
|||
ctx.channel().close(); |
|||
logger.error("TcpHex channel idle,closed."); |
|||
} |
|||
} else { |
|||
super.userEventTriggered(ctx, evt); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void channelRead0(ChannelHandlerContext ctx, InMessage inMessage) throws Exception { |
|||
try { |
|||
//0.设置最后一次收数据的时间 和 当前线程channel
|
|||
ChannelManager.flushReceiveTimestamp(ctx.channel()); |
|||
ChannelManager.setCurrentChannel(ctx.channel()); |
|||
|
|||
//1.填充缺省信息
|
|||
ClientManager.fillMessageInfo(inMessage, MessageConstant.DomainType.User); |
|||
|
|||
//2.处理消息
|
|||
MessageHandler.handleMessage(inMessage); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
logger.error("TcpHex Process Message Failed: {},{}", e.getMessage(), inMessage); |
|||
throw e; |
|||
} finally { |
|||
ChannelManager.removeCurrentChannel(); |
|||
} |
|||
} |
|||
} |
@ -1,59 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty.tcphexserver; |
|||
|
|||
import io.netty.bootstrap.ServerBootstrap; |
|||
import io.netty.channel.*; |
|||
import io.netty.channel.nio.NioEventLoopGroup; |
|||
import io.netty.channel.socket.SocketChannel; |
|||
import io.netty.channel.socket.nio.NioServerSocketChannel; |
|||
import io.netty.handler.logging.LogLevel; |
|||
import io.netty.handler.logging.LoggingHandler; |
|||
import io.netty.handler.timeout.IdleStateHandler; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.scheduling.annotation.Async; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
@Component |
|||
public class NettyMBServer { |
|||
@Autowired |
|||
private ModbusHandler modbusHandler; |
|||
private static final short SERVER_PORT = 8195; |
|||
|
|||
@Async |
|||
public void start() { |
|||
// Configure the server.
|
|||
EventLoopGroup bossGroup = new NioEventLoopGroup(1); |
|||
EventLoopGroup workerGroup = new NioEventLoopGroup(); |
|||
try { |
|||
ServerBootstrap b = new ServerBootstrap(); |
|||
b.group(bossGroup, workerGroup) |
|||
.channel(NioServerSocketChannel.class) |
|||
.option(ChannelOption.SO_BACKLOG, 100) |
|||
.handler(new LoggingHandler(LogLevel.INFO)) |
|||
.childHandler(new ChannelInitializer<SocketChannel>() { |
|||
@Override |
|||
public void initChannel(SocketChannel ch) throws Exception { |
|||
ChannelPipeline p = ch.pipeline(); |
|||
p.addLast(new IdleStateHandler(40, 0, 0, TimeUnit.SECONDS)); |
|||
p.addLast(new ModbusDecoder()); |
|||
p.addLast(new ModbusEncoder()); |
|||
p.addLast(modbusHandler); |
|||
} |
|||
}); |
|||
// Start the server.
|
|||
ChannelFuture f = b.bind(SERVER_PORT).sync(); |
|||
// Wait until the server socket is closed.
|
|||
f.channel().closeFuture().sync(); |
|||
} catch(Exception e){ |
|||
e.printStackTrace(); |
|||
}finally { |
|||
// Shut down all event loops to terminate all threads.
|
|||
bossGroup.shutdownGracefully(); |
|||
workerGroup.shutdownGracefully(); |
|||
} |
|||
} |
|||
} |
@ -1,61 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty.tcptextserver; |
|||
|
|||
import io.netty.bootstrap.ServerBootstrap; |
|||
import io.netty.buffer.Unpooled; |
|||
import io.netty.channel.*; |
|||
import io.netty.channel.nio.NioEventLoopGroup; |
|||
import io.netty.channel.socket.SocketChannel; |
|||
import io.netty.channel.socket.nio.NioServerSocketChannel; |
|||
import io.netty.handler.codec.DelimiterBasedFrameDecoder; |
|||
import io.netty.handler.logging.LogLevel; |
|||
import io.netty.handler.logging.LoggingHandler; |
|||
import io.netty.handler.timeout.IdleStateHandler; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.scheduling.annotation.Async; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@Component |
|||
public class NettyTextServer { |
|||
@Autowired |
|||
private TextHandler textHandler; |
|||
@Autowired |
|||
private static final String DELIMITER = "_$$_"; |
|||
private static final short SERVER_PORT = 8199; |
|||
|
|||
@Async |
|||
public void start() { |
|||
// Configure the server.
|
|||
EventLoopGroup bossGroup = new NioEventLoopGroup(1); |
|||
EventLoopGroup workerGroup = new NioEventLoopGroup(); |
|||
try { |
|||
ServerBootstrap b = new ServerBootstrap(); |
|||
b.group(bossGroup, workerGroup) |
|||
.channel(NioServerSocketChannel.class) |
|||
.option(ChannelOption.SO_BACKLOG, 100) |
|||
.handler(new LoggingHandler(LogLevel.INFO)) |
|||
.childHandler(new ChannelInitializer<SocketChannel>() { |
|||
@Override |
|||
public void initChannel(SocketChannel ch) throws Exception { |
|||
ChannelPipeline p = ch.pipeline(); |
|||
p.addLast(new IdleStateHandler(40, 0, 0, TimeUnit.SECONDS)); |
|||
p.addLast(new DelimiterBasedFrameDecoder(2048, Unpooled.copiedBuffer(DELIMITER.getBytes()))); |
|||
p.addLast(new TextDecoder()); |
|||
p.addLast(new TextEncoder(2048,Unpooled.copiedBuffer(DELIMITER.getBytes()))); |
|||
p.addLast(textHandler); |
|||
} |
|||
}); |
|||
// Start the server.
|
|||
ChannelFuture f = b.bind(SERVER_PORT).sync(); |
|||
// Wait until the server socket is closed.
|
|||
f.channel().closeFuture().sync(); |
|||
} catch(Exception e){ |
|||
e.printStackTrace(); |
|||
}finally { |
|||
// Shut down all event loops to terminate all threads.
|
|||
bossGroup.shutdownGracefully(); |
|||
workerGroup.shutdownGracefully(); |
|||
} |
|||
} |
|||
} |
@ -1,32 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty.tcptextserver; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.InMessage; |
|||
import wiki.tall.ccmq.common.util.JacksonUtil; |
|||
import io.netty.buffer.ByteBuf; |
|||
import io.netty.channel.ChannelHandlerContext; |
|||
import io.netty.handler.codec.MessageToMessageDecoder; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import java.io.IOException; |
|||
import java.nio.charset.Charset; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
public class TextDecoder extends MessageToMessageDecoder<ByteBuf> { |
|||
private static Logger logger = LoggerFactory.getLogger(TextDecoder.class); |
|||
|
|||
@Override |
|||
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf msg, List<Object> out) throws Exception { |
|||
String msgString = msg.toString(Charset.defaultCharset()); |
|||
logger.info("TcpText received: {}" + msgString); |
|||
|
|||
try { |
|||
out.add(JacksonUtil.jsonToBean(msgString, InMessage.class)); |
|||
}catch (IOException e){ |
|||
e.printStackTrace(); |
|||
logger.error("TcpText read error: {}",msgString); |
|||
} |
|||
} |
|||
} |
@ -1,49 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty.tcptextserver; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.OutMessageSet; |
|||
import wiki.tall.ccmq.common.util.JacksonUtil; |
|||
import io.netty.buffer.ByteBuf; |
|||
import io.netty.buffer.ByteBufUtil; |
|||
import io.netty.channel.ChannelHandlerContext; |
|||
import io.netty.handler.codec.MessageToMessageEncoder; |
|||
import java.nio.CharBuffer; |
|||
import java.nio.charset.Charset; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
public class TextEncoder extends MessageToMessageEncoder<OutMessageSet> { |
|||
private final Charset charset; |
|||
private int maxLength; |
|||
private ByteBuf delimiter; |
|||
|
|||
public TextEncoder() { |
|||
this(Charset.defaultCharset()); |
|||
} |
|||
|
|||
public TextEncoder(Charset charset) { |
|||
if (charset == null) { |
|||
throw new NullPointerException("charset"); |
|||
} else { |
|||
this.charset = charset; |
|||
} |
|||
} |
|||
|
|||
public TextEncoder(int maxLength, ByteBuf delimiter) { |
|||
this(); |
|||
this.maxLength = maxLength; |
|||
this.delimiter = delimiter; |
|||
} |
|||
|
|||
@Override |
|||
protected void encode(ChannelHandlerContext ctx, OutMessageSet outMessageSet, List<Object> out) throws Exception { |
|||
String delimiterString = delimiter.toString(Charset.defaultCharset()); |
|||
String msgString = JacksonUtil.beanToJson(outMessageSet) + delimiterString; |
|||
|
|||
if (msgString.length() != 0) { |
|||
out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msgString), this.charset)); |
|||
} |
|||
} |
|||
|
|||
} |
@ -1,110 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty.tcptextserver; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.InMessage; |
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.client.ClientIdleClosedMessage; |
|||
import com.ccsens.ccmq.lowlevel.message.client.UnExceptedErrorMessage; |
|||
import com.ccsens.ccmq.lowlevel.client.ClientManager; |
|||
import com.ccsens.ccmq.lowlevel.message.MessageHandler; |
|||
import wiki.tall.ccmq.common.config.SettingProps; |
|||
import com.ccsens.ccmq.lowlevel.client.netty.ChannelManager; |
|||
import io.netty.channel.ChannelHandler; |
|||
import io.netty.channel.ChannelHandlerContext; |
|||
import io.netty.channel.SimpleChannelInboundHandler; |
|||
import io.netty.handler.timeout.IdleState; |
|||
import io.netty.handler.timeout.IdleStateEvent; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
@ChannelHandler.Sharable |
|||
@Component |
|||
public class TextHandler extends SimpleChannelInboundHandler<InMessage> { |
|||
private static Logger logger = LoggerFactory.getLogger(TextHandler.class); |
|||
@Autowired |
|||
private SettingProps settingProps; |
|||
|
|||
@Override |
|||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception { |
|||
} |
|||
|
|||
@Override |
|||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { |
|||
} |
|||
|
|||
@Override |
|||
public void channelActive(ChannelHandlerContext ctx) throws Exception { |
|||
ChannelManager.addChannel(ctx.channel(),settingProps.getNettyTcpTextType()); |
|||
} |
|||
|
|||
@Override |
|||
public void channelInactive(ChannelHandlerContext ctx) throws Exception { |
|||
ChannelManager.removeChannel(ctx.channel()); |
|||
} |
|||
|
|||
@Override |
|||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { |
|||
try { |
|||
ChannelManager.setCurrentChannel(ctx.channel()); |
|||
MessageHandler.handleMessage( |
|||
InMessage.newToServerMessage(MessageConstant.DomainType.User,new UnExceptedErrorMessage(cause.getMessage()))); |
|||
}catch(Exception e){ |
|||
e.printStackTrace(); |
|||
logger.error("TcpText exceptionCaught handler error: {}",e.getMessage()); |
|||
}finally { |
|||
ChannelManager.removeCurrentChannel(); |
|||
} |
|||
cause.printStackTrace(); |
|||
ctx.close(); |
|||
logger.error("Channel closed. TcpText get a exception: {}",cause.getMessage()); |
|||
} |
|||
|
|||
@SuppressWarnings("all") |
|||
@Override |
|||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { |
|||
if (evt instanceof IdleStateEvent) { |
|||
IdleStateEvent idleStateEvent = (IdleStateEvent) evt; |
|||
if (idleStateEvent.state() == IdleState.READER_IDLE) { |
|||
try { |
|||
ChannelManager.setCurrentChannel(ctx.channel()); |
|||
MessageHandler.handleMessage( |
|||
InMessage.newToServerMessage(MessageConstant.DomainType.User,new ClientIdleClosedMessage())); |
|||
}catch(Exception e){ |
|||
e.printStackTrace(); |
|||
logger.error("TcpText exceptionCaught handler error: {}",e.getMessage()); |
|||
}finally { |
|||
ChannelManager.removeCurrentChannel(); |
|||
} |
|||
ctx.channel().close(); |
|||
logger.error("TcpText channel idle,closed."); |
|||
} |
|||
} else { |
|||
super.userEventTriggered(ctx, evt); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void channelRead0(ChannelHandlerContext ctx, InMessage inMessage) throws Exception { |
|||
try { |
|||
//0.设置最后一次收数据的时间 和 当前线程channel
|
|||
ChannelManager.flushReceiveTimestamp(ctx.channel()); |
|||
ChannelManager.setCurrentChannel(ctx.channel()); |
|||
|
|||
//1.填充缺省信息
|
|||
ClientManager.fillMessageInfo(inMessage, MessageConstant.DomainType.User); |
|||
|
|||
//2.处理消息
|
|||
MessageHandler.handleMessage(inMessage); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
logger.error("TcpText Process Message Failed: {},{}", e.getMessage(), inMessage); |
|||
throw e; |
|||
} finally { |
|||
ChannelManager.removeCurrentChannel(); |
|||
} |
|||
} |
|||
} |
@ -1,72 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty.wsserver; |
|||
|
|||
import wiki.tall.ccmq.common.config.SettingProps; |
|||
import io.netty.bootstrap.ServerBootstrap; |
|||
import io.netty.channel.*; |
|||
import io.netty.channel.nio.NioEventLoopGroup; |
|||
import io.netty.channel.socket.SocketChannel; |
|||
import io.netty.channel.socket.nio.NioServerSocketChannel; |
|||
import io.netty.handler.codec.http.HttpObjectAggregator; |
|||
import io.netty.handler.codec.http.HttpServerCodec; |
|||
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; |
|||
import io.netty.handler.logging.LogLevel; |
|||
import io.netty.handler.logging.LoggingHandler; |
|||
import io.netty.handler.stream.ChunkedWriteHandler; |
|||
import io.netty.handler.timeout.IdleStateHandler; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.scheduling.annotation.Async; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Component |
|||
public class NettyWsServer { |
|||
@Autowired |
|||
private SettingProps settingProps; |
|||
@Autowired |
|||
private WebSocketHandler webSocketHandler; |
|||
|
|||
@Async |
|||
public void start() { |
|||
// Configure the server.
|
|||
EventLoopGroup bossGroup = new NioEventLoopGroup(1); |
|||
EventLoopGroup workerGroup = new NioEventLoopGroup(); |
|||
try { |
|||
ServerBootstrap b = new ServerBootstrap(); |
|||
b.group(bossGroup, workerGroup) |
|||
.channel(NioServerSocketChannel.class) |
|||
.option(ChannelOption.SO_BACKLOG, 100) |
|||
.handler(new LoggingHandler(LogLevel.INFO)) |
|||
.childHandler(new ChannelInitializer<SocketChannel>() { |
|||
@Override |
|||
public void initChannel(SocketChannel ch) throws Exception { |
|||
ChannelPipeline p = ch.pipeline(); |
|||
if(settingProps.getKeepAlive().isEnable()) { |
|||
p.addLast(new IdleStateHandler(settingProps.getKeepAlive().getMaxIdleSeconds(), |
|||
0, 0, TimeUnit.SECONDS)); |
|||
} |
|||
p.addLast(new HttpServerCodec()); |
|||
p.addLast(new HttpObjectAggregator(64 * 1024)); |
|||
p.addLast(new ChunkedWriteHandler()); |
|||
p.addLast(new WebSocketServerProtocolHandler(settingProps.getNettyWsUri())); |
|||
p.addLast(new WebSocketDecoder()); |
|||
p.addLast(new WebSocketEncoder()); |
|||
p.addLast(webSocketHandler); |
|||
} |
|||
}); |
|||
// Start the server.
|
|||
ChannelFuture f = b.bind(settingProps.getNettyWsPort()).sync(); |
|||
// Wait until the server socket is closed.
|
|||
f.channel().closeFuture().sync(); |
|||
} catch(Exception e){ |
|||
e.printStackTrace(); |
|||
}finally { |
|||
// Shut down all event loops to terminate all threads.
|
|||
bossGroup.shutdownGracefully(); |
|||
workerGroup.shutdownGracefully(); |
|||
} |
|||
} |
|||
} |
@ -1,33 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty.wsserver; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.InMessage; |
|||
import wiki.tall.ccmq.common.util.JacksonUtil; |
|||
import io.netty.channel.ChannelHandlerContext; |
|||
import io.netty.handler.codec.MessageToMessageDecoder; |
|||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
public class WebSocketDecoder extends MessageToMessageDecoder<TextWebSocketFrame> { |
|||
private static Logger logger = LoggerFactory.getLogger(WebSocketDecoder.class); |
|||
|
|||
@Override |
|||
protected void decode(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame msg, List<Object> out) throws Exception { |
|||
String text = msg.text(); |
|||
logger.info("Websocket received: {}",text); |
|||
|
|||
try { |
|||
out.add(JacksonUtil.jsonToBean(text, InMessage.class)); |
|||
}catch(IOException e){ |
|||
e.printStackTrace(); |
|||
logger.error("Websocket Read Error: {}",text); |
|||
throw e; |
|||
} |
|||
} |
|||
} |
@ -1,25 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty.wsserver; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.OutMessageSet; |
|||
import wiki.tall.ccmq.common.util.JacksonUtil; |
|||
import io.netty.buffer.ByteBuf; |
|||
import io.netty.channel.ChannelHandlerContext; |
|||
import io.netty.handler.codec.MessageToByteEncoder; |
|||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
public class WebSocketEncoder extends MessageToByteEncoder<OutMessageSet> { |
|||
private static Logger logger = LoggerFactory.getLogger(WebSocketEncoder.class); |
|||
|
|||
@Override |
|||
protected void encode(ChannelHandlerContext ctx, OutMessageSet outMessageSet, ByteBuf out) throws Exception { |
|||
String msg = JacksonUtil.beanToJson(outMessageSet); |
|||
ctx.writeAndFlush(new TextWebSocketFrame(msg)); |
|||
|
|||
logger.info("Websocket send: {}",msg); |
|||
} |
|||
} |
@ -1,107 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.netty.wsserver; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.InMessage; |
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.client.ClientIdleClosedMessage; |
|||
import com.ccsens.ccmq.lowlevel.message.client.UnExceptedErrorMessage; |
|||
import com.ccsens.ccmq.lowlevel.client.ClientManager; |
|||
import com.ccsens.ccmq.lowlevel.message.MessageHandler; |
|||
import wiki.tall.ccmq.common.config.SettingProps; |
|||
import com.ccsens.ccmq.lowlevel.client.netty.ChannelManager; |
|||
import io.netty.channel.*; |
|||
import io.netty.handler.timeout.IdleState; |
|||
import io.netty.handler.timeout.IdleStateEvent; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@ChannelHandler.Sharable |
|||
@Component |
|||
public class WebSocketHandler extends SimpleChannelInboundHandler<InMessage> { |
|||
private static Logger logger = LoggerFactory.getLogger(WebSocketHandler.class); |
|||
@Autowired |
|||
private SettingProps settingProps; |
|||
|
|||
@Override |
|||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception { |
|||
} |
|||
|
|||
@Override |
|||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { |
|||
} |
|||
|
|||
@Override |
|||
public void channelActive(ChannelHandlerContext ctx) throws Exception { |
|||
ChannelManager.addChannel(ctx.channel(),settingProps.getNettyWsType()); |
|||
} |
|||
|
|||
@Override |
|||
public void channelInactive(ChannelHandlerContext ctx) throws Exception { |
|||
ChannelManager.removeChannel(ctx.channel()); |
|||
} |
|||
|
|||
@Override |
|||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { |
|||
try { |
|||
ChannelManager.setCurrentChannel(ctx.channel()); |
|||
MessageHandler.handleMessage( |
|||
InMessage.newToServerMessage(MessageConstant.DomainType.User,new UnExceptedErrorMessage(cause.getMessage()))); |
|||
}catch(Exception e){ |
|||
e.printStackTrace(); |
|||
logger.error("Ws exceptionCaught handler error: {}",e.getMessage()); |
|||
}finally { |
|||
ChannelManager.removeCurrentChannel(); |
|||
} |
|||
cause.printStackTrace(); |
|||
ctx.close(); |
|||
logger.error("Channel closed. Ws get a exception: {}", cause.getMessage()); |
|||
} |
|||
|
|||
@Override |
|||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { |
|||
if (evt instanceof IdleStateEvent) { |
|||
IdleStateEvent idleStateEvent = (IdleStateEvent) evt; |
|||
if (idleStateEvent.state() == IdleState.READER_IDLE) { |
|||
try { |
|||
ChannelManager.setCurrentChannel(ctx.channel()); |
|||
MessageHandler.handleMessage( |
|||
InMessage.newToServerMessage(MessageConstant.DomainType.User,new ClientIdleClosedMessage())); |
|||
}catch(Exception e){ |
|||
e.printStackTrace(); |
|||
logger.error("Ws exceptionCaught handler error: {}",e.getMessage()); |
|||
}finally { |
|||
ChannelManager.removeCurrentChannel(); |
|||
} |
|||
ctx.channel().close(); |
|||
logger.error("Ws channel idle,closed."); |
|||
} |
|||
} else { |
|||
super.userEventTriggered(ctx, evt); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void channelRead0(ChannelHandlerContext ctx, InMessage inMessage) throws Exception { |
|||
try { |
|||
//0.设置最后一次收数据的时间 和 当前线程channel
|
|||
ChannelManager.flushReceiveTimestamp(ctx.channel()); |
|||
ChannelManager.setCurrentChannel(ctx.channel()); |
|||
|
|||
//1.填充缺省信息
|
|||
ClientManager.fillMessageInfo(inMessage,MessageConstant.DomainType.User); |
|||
|
|||
//2.处理消息
|
|||
MessageHandler.handleMessage(inMessage); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
logger.error("Websocket Process Message Failed: {},{}", e.getMessage(), inMessage); |
|||
throw e; |
|||
} finally { |
|||
ChannelManager.removeCurrentChannel(); |
|||
} |
|||
} |
|||
} |
@ -1,36 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.rabbitmq; |
|||
|
|||
import wiki.tall.ccmq.common.config.SettingProps; |
|||
import wiki.tall.ccmq.common.util.JacksonUtil; |
|||
import wiki.tall.ccmq.common.util.SpringContextUtils; |
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.amqp.rabbit.core.RabbitTemplate; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
public class QueueManager { |
|||
private static Logger logger = LoggerFactory.getLogger(QueueManager.class); |
|||
|
|||
public static String getInQueueName(){ |
|||
return SpringContextUtils.getBean(SettingProps.class).getMq().getInName(); |
|||
} |
|||
|
|||
public static String getOutQueueName(){ |
|||
return SpringContextUtils.getBean(SettingProps.class).getMq().getOutName(); |
|||
} |
|||
|
|||
public static void sendTo(String queueName,Object obj) throws JsonProcessingException { |
|||
String text = JacksonUtil.beanToJson(obj); |
|||
logger.info("QueueManager SendTo: {}",text); |
|||
SpringContextUtils.getBean(RabbitTemplate.class).convertAndSend(queueName,text); |
|||
} |
|||
|
|||
public static void sendTo(Object obj) throws JsonProcessingException { |
|||
String text = JacksonUtil.beanToJson(obj); |
|||
logger.info("QueueManager SendTo: {}",text); |
|||
SpringContextUtils.getBean(RabbitTemplate.class).convertAndSend(getOutQueueName(),text); |
|||
} |
|||
} |
@ -1,48 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.rabbitmq; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.InMessage; |
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.client.UnExceptedErrorMessage; |
|||
import com.ccsens.ccmq.lowlevel.client.ClientManager; |
|||
import com.ccsens.ccmq.lowlevel.message.MessageHandler; |
|||
import wiki.tall.ccmq.common.util.JacksonUtil; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.amqp.rabbit.annotation.RabbitHandler; |
|||
import org.springframework.amqp.rabbit.annotation.RabbitListener; |
|||
import org.springframework.context.annotation.PropertySource; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Component |
|||
@PropertySource(value = {"classpath:setting-${spring.profiles.active}.properties"}) |
|||
@RabbitListener(queues = "${setting.mq.inName}") |
|||
public class RabbitMqListener { |
|||
private Logger logger = LoggerFactory.getLogger(RabbitMqListener.class); |
|||
|
|||
@RabbitHandler |
|||
public void process(String messageJson) { |
|||
logger.info("Rabbit Received: {}",messageJson); |
|||
|
|||
try { |
|||
InMessage inMessage = JacksonUtil.jsonToBean(messageJson, InMessage.class); |
|||
|
|||
//1.填充缺省字段
|
|||
ClientManager.fillMessageInfo(inMessage,MessageConstant.DomainType.Queue); |
|||
|
|||
//2.处理消息
|
|||
MessageHandler.handleMessage(inMessage); |
|||
}catch (Exception e){ |
|||
e.printStackTrace(); |
|||
try { |
|||
MessageHandler.handleMessage( |
|||
InMessage.newToServerMessage(MessageConstant.DomainType.Queue,new UnExceptedErrorMessage(e.getMessage()))); |
|||
} catch (Exception ex) { |
|||
ex.printStackTrace(); |
|||
} |
|||
logger.error("Rabbit Process Message Failed: {},{}",e.getMessage(),messageJson); |
|||
} |
|||
} |
|||
} |
@ -1,28 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.restful; |
|||
|
|||
import org.springframework.stereotype.Controller; |
|||
import org.springframework.ui.Model; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestMethod; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import wiki.tall.ccmq.common.util.JsonResponse; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
|
|||
@Controller |
|||
public class MessageApi { |
|||
/** |
|||
* Restful Json 请求 |
|||
* @param request |
|||
* @param response |
|||
* @param model |
|||
* @return |
|||
* @throws Exception |
|||
*/ |
|||
@RequestMapping(value = "/json",method = RequestMethod.GET,produces = {"application/json;charset=UTF-8"}) |
|||
@ResponseBody |
|||
public JsonResponse api(HttpServletRequest request, HttpServletResponse response, Model model) throws Exception { |
|||
return JsonResponse.newInstance().ok(); |
|||
} |
|||
} |
@ -1,16 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.client.restful; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.OutMessageSet; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
public class RestManager { |
|||
public static void sendTo(OutMessageSet outMessageSet){ |
|||
|
|||
} |
|||
|
|||
public static String getOutRestName(){ |
|||
return ""; |
|||
} |
|||
} |
@ -1,481 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message; |
|||
|
|||
import cn.hutool.core.collection.CollectionUtil; |
|||
import cn.hutool.core.lang.Console; |
|||
import cn.hutool.core.util.StrUtil; |
|||
import com.ccsens.ccmq.lowlevel.client.ClientManager; |
|||
import com.ccsens.ccmq.lowlevel.client.rabbitmq.QueueManager; |
|||
import com.ccsens.ccmq.lowlevel.message.server.InvertedMessage; |
|||
import com.ccsens.ccmq.lowlevel.persist.IMessageDao; |
|||
import com.ccsens.ccmq.lowlevel.message.client.*; |
|||
import com.ccsens.ccmq.lowlevel.message.common.*; |
|||
import com.ccsens.ccmq.lowlevel.message.server.ChannelStatusMessage; |
|||
import com.ccsens.ccmq.lowlevel.message.server.PongMessage; |
|||
import com.ccsens.ccmq.lowlevel.message.server.ServerAckMessage; |
|||
import com.ccsens.ccmq.lowlevel.client.netty.ChannelManager; |
|||
import com.ccsens.ccmq.lowlevel.client.netty.WrapperedChannel; |
|||
import com.ccsens.ccmq.lowlevel.service.IUserService; |
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.scheduling.annotation.Async; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Component; |
|||
import wiki.tall.ccmq.common.config.SettingProps; |
|||
import wiki.tall.ccmq.common.util.*; |
|||
|
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Set; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
@Component |
|||
public class MessageHandler { |
|||
private Logger logger = LoggerFactory.getLogger(MessageHandler.class); |
|||
/** |
|||
* 发送消息最大等待ack时间 |
|||
*/ |
|||
private static final Integer REDIS_ACK_EXPIRED_SECONDS = 10; |
|||
/** |
|||
* 每次最多查询消息条数 |
|||
*/ |
|||
private static final Integer MAX_MESSAGE_NUM = 50; |
|||
|
|||
private static IMessageDao getMessageDao(){ |
|||
return SpringContextUtils.getBean(IMessageDao.class); |
|||
} |
|||
|
|||
private static SettingProps getSettingProps(){ |
|||
return SpringContextUtils.getBean(SettingProps.class); |
|||
} |
|||
|
|||
private static IUserService getUserService(){ |
|||
return SpringContextUtils.getBean(IUserService.class); |
|||
} |
|||
|
|||
/** |
|||
* 每个20s同步一次有未决消息的用户到pendingClientSet中 |
|||
* @throws Exception |
|||
*/ |
|||
@Scheduled(cron = "0/20 * * * * *") |
|||
public void detectPendingClients() throws Exception{ |
|||
logger.info("schedule detectPendingClients: {}", DateUtil.currentSeconds()); |
|||
//1.获取所有的在线客户端
|
|||
Set<String> onLineUsers = ChannelManager.getAllOnlineUsers(); |
|||
Set<String> onLineClients = new HashSet<>(onLineUsers.size()+1); |
|||
if(CollectionUtil.isNotEmpty(onLineUsers)) { |
|||
for (String onlineUser : onLineUsers) { |
|||
onLineClients.add(CcMessageUtil.getDomainTypeAndUserIdString(MessageConstant.DomainType.User, onlineUser)); |
|||
} |
|||
} |
|||
onLineClients.add(CcMessageUtil.getDomainTypeAndUserIdString(MessageConstant.DomainType.Queue, |
|||
QueueManager.getOutQueueName())); |
|||
|
|||
//2.如果有未ack消息并且不在当前待处理列表中,则添加
|
|||
for(String domainTypeAndUserId: onLineClients){ |
|||
addClientToRedisUnPendingSet(domainTypeAndUserId); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 每隔5分钟将所有的tempSucceed的消息,状态修改为pending |
|||
* @throws Exception |
|||
*/ |
|||
@Scheduled(cron = "0 */5 * * * *") |
|||
public void updateMessageTempStatusToPending() throws Exception{ |
|||
logger.info("schedule updateMessageTempStatusToPending: {}", DateUtil.currentSeconds()); |
|||
getMessageDao().updateMessageStatusToPending(); |
|||
} |
|||
|
|||
/** |
|||
* 每隔5分钟扫描消息过期或发送次数达到上限消息 |
|||
* @throws Exception |
|||
*/ |
|||
@Scheduled(cron = "0 */5 * * * *") |
|||
public void updateMessageExpiredStatus() throws Exception{ |
|||
logger.info("schedule updateMessageExpiredStatus: {}", DateUtil.currentSeconds()); |
|||
getMessageDao().updateMessageToExpired(); |
|||
} |
|||
|
|||
/** |
|||
* 每隔5分钟扫描消息过期或发送次数达到上限消息 |
|||
* @throws Exception |
|||
*/ |
|||
@Scheduled(cron = "0 */5 * * * *") |
|||
public void updateMessageSendTimesUpLimitStatus() throws Exception{ |
|||
logger.info("schedule updateMessageSendTimesUpLimitStatus: {}", DateUtil.currentSeconds()); |
|||
getMessageDao().updateMessageToSendTimesUpLimit(); |
|||
} |
|||
|
|||
@Async("cc-msg-executor") |
|||
public void loopSendMessage() throws Exception { |
|||
String typeAndUserId = null,ackId = null; |
|||
List<Message> messageList = null; |
|||
List<String> sendTimesUpLimitMessageList = null,expiredMessageList = null; |
|||
|
|||
while(true){ |
|||
//从redis中或取第一个待处理用户
|
|||
Object o = RedisUtil.sPop(RedisKeyManager.getPendingClientSetKey()); |
|||
if(o != null && StrUtil.isNotEmpty(typeAndUserId = ackId = (String)o)){ |
|||
Console.log("RedisUtil.sPop: {}",o); |
|||
String []stringArray = CcMessageUtil.splitTypeAndUserId(typeAndUserId); |
|||
MessageConstant.DomainType toDomain = MessageConstant.DomainType.valueOf(stringArray[0]); |
|||
String to = stringArray[1]; |
|||
|
|||
//针对同一用户,在上一次ack还未收到/超时之前,不进行处理
|
|||
Object lockObj = ResourceLock.getLockObj(ackId); |
|||
synchronized (lockObj) { |
|||
//查找该用户是否有正在处理的消息
|
|||
if (RedisUtil.hasKey(RedisKeyManager.getAckSetKey(ackId))) { |
|||
//将当前用户重新放回到待处理列表的最后
|
|||
RedisUtil.sSet(RedisKeyManager.getPendingClientSetKey(), o); |
|||
continue; |
|||
} |
|||
|
|||
//查找所有没有ack的消息
|
|||
messageList = getMessageDao().getClientPendingMessage(toDomain, to, MAX_MESSAGE_NUM); |
|||
if (CollectionUtil.isEmpty(messageList)) { |
|||
continue; |
|||
} |
|||
|
|||
//判断用户是否在线
|
|||
boolean clientOnLine = ClientManager.isUserOnline(toDomain,to); |
|||
|
|||
//发送和处理消息
|
|||
if(clientOnLine){ |
|||
//发送
|
|||
//1.手机所有待发送的messageId,以ackId为key放入redis
|
|||
Set<String> messageIdSet = new HashSet<>(messageList.size()); |
|||
OutMessageSet outMessageSet = OutMessageSet.newInstance(); |
|||
for(Message message : messageList) { |
|||
messageIdSet.add(message.getId()); |
|||
outMessageSet.add(new OutMessage(message)); |
|||
getMessageDao().incrementSendTimes(message.getId(), DateUtil.currentSeconds()); |
|||
} |
|||
RedisUtil.sSetAndTime(RedisKeyManager.getAckSetKey(ackId),REDIS_ACK_EXPIRED_SECONDS,messageIdSet.toArray()); |
|||
|
|||
//2.构造outMessage并且发送
|
|||
outMessageSet.ackId(ackId); |
|||
|
|||
//发送给对应的接收者
|
|||
ClientManager.sendMessageToAuthedClient(toDomain,to,outMessageSet); |
|||
}else{ |
|||
//不发送,根据规则检查所有“offLineDiscard”的消息设置为failed状态
|
|||
for(Message message : messageList){ |
|||
if(message.getRule().getOfflineDiscard() == 1){ |
|||
getMessageDao().updateMessageStatus(message.getId(),MessageConstant.Status.Failed); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
ResourceLock.freeLockObj(ackId); |
|||
} |
|||
try { |
|||
Thread.sleep(10); |
|||
} catch (InterruptedException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static void handleMessage(InMessage inMessage) throws Exception{ |
|||
//1. 处理发送给Server的消息
|
|||
if(inMessage.getToDomain() == MessageConstant.DomainType.Server){ |
|||
String type = null; |
|||
JsonNode typeNode = JacksonUtil.getJsonProperty(inMessage.getData(), "type"); |
|||
//校验错误
|
|||
if (typeNode == null || StrUtil.isEmpty(type = typeNode.textValue())) { |
|||
ClientManager.sendServerAck(inMessage, MessageConstant.Error.MessageNoTypeError); |
|||
} |
|||
//处理消息
|
|||
handlerServerMessage(type,inMessage); |
|||
return; |
|||
} |
|||
|
|||
//2. 处理发送给Client的消息
|
|||
//校验错误
|
|||
if (CollectionUtil.isEmpty(inMessage.getTos())) { |
|||
ClientManager.sendServerAck(inMessage, MessageConstant.Error.MessageNoReceiversError); |
|||
return; |
|||
} |
|||
if(!ClientManager.isSenderAuthed(inMessage)){ |
|||
ClientManager.sendServerAck(inMessage,MessageConstant.Error.UnAuthed); |
|||
ClientManager.closeCurrentSender(inMessage); |
|||
return; |
|||
} |
|||
|
|||
//存储消息(原始消息+按照tos拆分之后的每条消息)
|
|||
saveMessage(inMessage); |
|||
|
|||
//3.发送ack
|
|||
ClientManager.sendServerAck(inMessage, MessageConstant.Error.Ok); |
|||
|
|||
//4.将用户添加到Redis待处理用户集合中
|
|||
RedisUtil.sSet(RedisKeyManager.getPendingClientSetKey(), |
|||
CcMessageUtil.getNameManglingToList(inMessage).toArray() |
|||
); |
|||
} |
|||
|
|||
private static void saveMessage(InMessage inMessage) throws Exception{ |
|||
//1.存储原始消息
|
|||
getMessageDao().saveOrUpdateMessage(inMessage); |
|||
|
|||
//2.按照tos拆分成不同消息
|
|||
for(String to : inMessage.getTos()){ |
|||
Message message = new Message(inMessage,to); |
|||
getMessageDao().saveOrUpdateMessage(message); |
|||
} |
|||
} |
|||
|
|||
private static void handlerServerMessage(String type, InMessage inMessage) throws Exception { |
|||
MessageConstant.ClientMessageType clientMessageType = MessageConstant.ClientMessageType.valueOf(type); |
|||
String data = inMessage.getData(); |
|||
OutMessage outMessage = null; |
|||
|
|||
//1. 处理消息
|
|||
switch(clientMessageType){ |
|||
case Ping: { |
|||
PingMessage inSysData = JacksonUtil.jsonToBean(data, PingMessage.class); |
|||
if (null != inSysData.getData()) { |
|||
ChannelManager.versionChannel(ChannelManager.getCurrentChannel(), inSysData.getData().getMajor(), inSysData.getData().getMinor()); |
|||
} |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson(new PongMessage())); |
|||
break; |
|||
} |
|||
case Auth: { |
|||
boolean authSuccess = false; |
|||
AuthMessage inSysData = JacksonUtil.jsonToBean(data, AuthMessage.class); |
|||
if(null != inSysData.getData()){ |
|||
if(StrUtil.isNotEmpty(inSysData.getData().getToken())) { |
|||
String userId = getUserService().getUserIdByToken(inSysData.getData().getToken()); |
|||
if(StrUtil.isNotEmpty(userId)){ |
|||
ChannelManager.authChannel(ChannelManager.getCurrentChannel(),userId,inSysData.getData().getMajor(),inSysData.getData().getMinor()); |
|||
onClientOnLine(MessageConstant.DomainType.User,userId); |
|||
authSuccess = true; |
|||
} |
|||
} |
|||
} |
|||
if(!authSuccess){ |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson( |
|||
new ChannelStatusMessage(false,0L,MessageConstant.Error.AuthFailed)) |
|||
); |
|||
}else{ |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson( |
|||
new ChannelStatusMessage(true,0L,MessageConstant.Error.Ok)) |
|||
); |
|||
} |
|||
break; |
|||
} |
|||
case GetChannelStatus: { |
|||
WrapperedChannel wrapperedChannel = ChannelManager.getWrapperedChannelByChannel(ChannelManager.getCurrentChannel()); |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson( |
|||
new ChannelStatusMessage(wrapperedChannel.isAuthed(), |
|||
wrapperedChannel.getOnlineSeconds(), |
|||
MessageConstant.Error.Ok)) |
|||
); |
|||
break; |
|||
} |
|||
case Ack: { |
|||
//根据id找到
|
|||
boolean ackSuccess = false; |
|||
AckMessage inSysData = JacksonUtil.jsonToBean(data, AckMessage.class); |
|||
if(null != inSysData.getData()){ |
|||
updateMessageAckStatus(inSysData.getData().getAckId()); |
|||
ackSuccess = true; |
|||
} |
|||
if(!ackSuccess){ |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson( |
|||
new ServerAckMessage(MessageConstant.Error.AckParameterError)) |
|||
); |
|||
} |
|||
//没有错误不回复消息
|
|||
break; |
|||
} |
|||
case HasRead: { |
|||
boolean hasReadSuccess = false; |
|||
HasReadMessage inSysData = JacksonUtil.jsonToBean(data, HasReadMessage.class); |
|||
if(null != inSysData.getData()){ |
|||
updateMessageHasReadStatus( |
|||
inSysData.getData().getFromDomain(),inSysData.getData().getFromUserId(), |
|||
inMessage.getFromDomain().name(),inMessage.getFrom(), inSysData.getData().getTime(),(byte)1 |
|||
); |
|||
hasReadSuccess = true; |
|||
} |
|||
if(!hasReadSuccess){ |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson( |
|||
new ServerAckMessage(MessageConstant.Error.AckParameterError)) |
|||
); |
|||
} |
|||
//没有错误不回复消息
|
|||
break; |
|||
} |
|||
case SetMsgSuccess: { |
|||
boolean setStatusSuccess = false; |
|||
SetSuccessStatusMessage inSysData = JacksonUtil.jsonToBean(data, SetSuccessStatusMessage.class); |
|||
if(null != inSysData.getData() && StrUtil.isNotEmpty(inSysData.getData().getMsgId())){ |
|||
updateMessageStatus(inSysData.getData().getMsgId(), MessageConstant.Status.Succeed); |
|||
setStatusSuccess = true; |
|||
} |
|||
if(!setStatusSuccess){ |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson( |
|||
new ServerAckMessage(MessageConstant.Error.SetStatusParameterError)) |
|||
); |
|||
}else{ |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson( |
|||
new ServerAckMessage(MessageConstant.Error.Ok)) |
|||
); |
|||
} |
|||
break; |
|||
} |
|||
case SetMsgReverted: { |
|||
boolean setStatusSuccess = false; |
|||
SetRevertedStatusMessage inSysData = JacksonUtil.jsonToBean(data, SetRevertedStatusMessage.class); |
|||
if(null != inSysData.getData() && StrUtil.isNotEmpty(inSysData.getData().getMsgId())){ |
|||
updateMessageStatus(inSysData.getData().getMsgId(), MessageConstant.Status.Reverted); |
|||
setStatusSuccess = true; |
|||
} |
|||
if(!setStatusSuccess){ |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson( |
|||
new ServerAckMessage(MessageConstant.Error.SetStatusParameterError)) |
|||
); |
|||
}else{ |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson( |
|||
new ServerAckMessage(MessageConstant.Error.Ok)) |
|||
); |
|||
} |
|||
break; |
|||
} |
|||
case SetMsgDeleted: { |
|||
boolean setStatusSuccess = false; |
|||
SetDeletedStatusMessage inSysData = JacksonUtil.jsonToBean(data, SetDeletedStatusMessage.class); |
|||
if(null != inSysData.getData() && StrUtil.isNotEmpty(inSysData.getData().getMsgId())){ |
|||
updateMessageStatus(inSysData.getData().getMsgId(), MessageConstant.Status.Deleted); |
|||
setStatusSuccess = true; |
|||
} |
|||
if(!setStatusSuccess){ |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson( |
|||
new ServerAckMessage(MessageConstant.Error.SetStatusParameterError)) |
|||
); |
|||
}else{ |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson( |
|||
new ServerAckMessage(MessageConstant.Error.Ok)) |
|||
); |
|||
} |
|||
break; |
|||
} |
|||
case ClientIdleClosed: { |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson( |
|||
new ServerAckMessage( |
|||
MessageConstant.Error.ChannelIdle.joinExtra(String.valueOf(getSettingProps().getKeepAlive().getMaxIdleSeconds())) |
|||
) |
|||
) |
|||
); |
|||
break; |
|||
} |
|||
case UnExceptedError: { |
|||
UnExceptedErrorMessage inSysData = JacksonUtil.jsonToBean(data, UnExceptedErrorMessage.class); |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson( |
|||
new ServerAckMessage( |
|||
MessageConstant.Error.UnExpectedError.joinExtra(inSysData.getData().getErrorString()) |
|||
) |
|||
) |
|||
); |
|||
break; |
|||
} |
|||
case ClientAuthTimeOut:{ |
|||
outMessage = new OutMessage(JacksonUtil.beanToJson( |
|||
new ServerAckMessage(MessageConstant.Error.ChannelAuthTimeOut) |
|||
) |
|||
); |
|||
break; |
|||
} |
|||
default: break; |
|||
} |
|||
|
|||
//2.结果应答
|
|||
if(null != outMessage) { |
|||
ClientManager.sendServerMessage(inMessage.getFromDomain(), |
|||
OutMessageSet.newInstance().ackId(null).add(outMessage) |
|||
); |
|||
} |
|||
} |
|||
|
|||
private static void onClientOnLine(MessageConstant.DomainType domain,String userId){ |
|||
getMessageDao().updateMessageStatusToPending(domain,userId); |
|||
addClientToRedisUnPendingSet(CcMessageUtil.getDomainTypeAndUserIdString(domain,userId)); |
|||
} |
|||
|
|||
private static void updateMessageAckStatus(String ackId) throws Exception { |
|||
String ackKey = RedisKeyManager.getAckSetKey(ackId); |
|||
|
|||
//1.从redis中获取ackId对应的set列表
|
|||
if(!RedisUtil.hasKey(ackKey)) { |
|||
return; |
|||
} |
|||
|
|||
//2.从redis中获取set集合,并删除ackId对应的set列表
|
|||
Set<Object> ackMessageSet = RedisUtil.sGet(ackKey); |
|||
RedisUtil.del(ackKey); |
|||
|
|||
//2.更新msg的ack标志
|
|||
if (CollectionUtil.isNotEmpty(ackMessageSet)) { |
|||
for (Object msgId : ackMessageSet) { |
|||
Message message = getMessageDao().getMessageById((String) msgId); |
|||
if(message.getRule().getAckIsSuccess() == 0) { |
|||
getMessageDao().updateMessageAckAndStatus((String) msgId, (byte)1,MessageConstant.Status.TempSucceed); |
|||
}else{ |
|||
getMessageDao().updateMessageAckAndStatus((String) msgId, (byte)1,MessageConstant.Status.Succeed); |
|||
} |
|||
} |
|||
} |
|||
|
|||
//3.查找该用户是否仍然有未ack消息,
|
|||
addClientToRedisUnPendingSet(ackId); |
|||
} |
|||
|
|||
private static void updateMessageHasReadStatus(String fromDomain, String fromUserId,String toDomain, String toUserId, |
|||
Long time, byte hasRead) throws Exception{ |
|||
getMessageDao().updateMessageReadStatus(fromDomain,fromUserId,toDomain,toUserId,time,hasRead); |
|||
} |
|||
|
|||
private static void updateMessageStatus(String rawId, MessageConstant.Status status) throws Exception { |
|||
getMessageDao().updateMessageStatusByRawId(rawId,status); |
|||
if(status == MessageConstant.Status.Reverted){ |
|||
List<Message> messageList = getMessageDao().getMessageByRawId(rawId); |
|||
if(CollectionUtil.isNotEmpty(messageList)){ |
|||
//ClientManager.sendServerMessage();
|
|||
//为了保证投递到目标客户端,将该消息添加到消息队列中,进行投递。
|
|||
for(Message message : messageList) { |
|||
Message invertedMessage = new Message( |
|||
MessageConstant.DomainType.Server, "", message.getToDomain(),message.getTo(), |
|||
JacksonUtil.beanToJson(new InvertedMessage(message.getId())),MessageRule.defaultRule(MessageConstant.DomainType.Server) |
|||
); |
|||
getMessageDao().saveOrUpdateMessage(invertedMessage); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void addClientToRedisUnPendingSet(String domainTypeAndUserId){ |
|||
//如果有未ack消息并且不在当前待处理列表中,则添加
|
|||
String redisUnPendingClientSetKey = RedisKeyManager.getPendingClientSetKey(); |
|||
String redisWaitAckSetKey = RedisKeyManager.getAckSetKey(domainTypeAndUserId); |
|||
String []stringArray = CcMessageUtil.splitTypeAndUserId(domainTypeAndUserId); |
|||
MessageConstant.DomainType toDomain = MessageConstant.DomainType.valueOf(stringArray[0]); |
|||
String to = stringArray[1]; |
|||
//1.1 当前用户是否在pendingClients列表中
|
|||
if (!RedisUtil.sHas(redisUnPendingClientSetKey,domainTypeAndUserId)) { |
|||
//1.2 当前用户是否在redis的等待ack列表中
|
|||
if(!RedisUtil.hasKey(redisWaitAckSetKey)){ |
|||
//1.3 当前用户是否有pending消息
|
|||
if(getMessageDao().countClientPendingMessage(toDomain,to) > 0){ |
|||
//1.4 将当前用户添加到redisUnPendingClientSet列表中排队处理
|
|||
RedisUtil.sSet(redisUnPendingClientSetKey,domainTypeAndUserId); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -1,31 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.client; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Data |
|||
public class AckMessage extends ServerMessage { |
|||
@Setter |
|||
@Getter |
|||
public static class Data { |
|||
String ackId; |
|||
} |
|||
|
|||
private Data data; |
|||
|
|||
public AckMessage(){ |
|||
setType(MessageConstant.ClientMessageType.Ack.name()); |
|||
} |
|||
|
|||
public AckMessage(String ackId){ |
|||
this(); |
|||
data = new Data(); |
|||
data.setAckId(ackId); |
|||
} |
|||
} |
@ -1,36 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.client; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author wei |
|||
* 客户端认证 (user) |
|||
*/ |
|||
@Data |
|||
public class AuthMessage extends ServerMessage { |
|||
@Setter |
|||
@Getter |
|||
public static class Data{ |
|||
private String token; |
|||
private int major; |
|||
private int minor; |
|||
} |
|||
|
|||
private Data data; |
|||
|
|||
public AuthMessage(){ |
|||
setType(MessageConstant.ClientMessageType.Auth.name()); |
|||
} |
|||
|
|||
public AuthMessage(String token,int major,int minor){ |
|||
this(); |
|||
data = new Data(); |
|||
data.setToken(token); |
|||
data.setMajor(major); |
|||
data.setMinor(minor); |
|||
} |
|||
} |
@ -1,16 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.client; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* @author wei |
|||
* 客户端向服务器请求当前连接状态 |
|||
*/ |
|||
@Data |
|||
public class ClientAuthTimeOutMessage extends ServerMessage { |
|||
public ClientAuthTimeOutMessage(){ |
|||
setType(MessageConstant.ClientMessageType.ClientAuthTimeOut.name()); |
|||
} |
|||
} |
@ -1,16 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.client; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* @author wei |
|||
* 客户端向服务器请求当前连接状态 |
|||
*/ |
|||
@Data |
|||
public class ClientIdleClosedMessage extends ServerMessage { |
|||
public ClientIdleClosedMessage(){ |
|||
setType(MessageConstant.ClientMessageType.ClientIdleClosed.name()); |
|||
} |
|||
} |
@ -1,16 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.client; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* @author wei |
|||
* 客户端向服务器请求当前连接状态 |
|||
*/ |
|||
@Data |
|||
public class GetStatusMessage extends ServerMessage { |
|||
public GetStatusMessage(){ |
|||
setType(MessageConstant.ClientMessageType.GetChannelStatus.name()); |
|||
} |
|||
} |
@ -1,36 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.client; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Data |
|||
public class HasReadMessage extends ServerMessage { |
|||
/** |
|||
* fromDomain域的fromUserId用户 给我发送的所有 时间<lastMsgId时间的消息 都已读。 |
|||
*/ |
|||
@Setter |
|||
@Getter |
|||
public static class Data { |
|||
private Long time; |
|||
private String fromUserId; |
|||
private String fromDomain; |
|||
} |
|||
|
|||
private Data data; |
|||
|
|||
public HasReadMessage(){ |
|||
setType(MessageConstant.ClientMessageType.HasRead.name()); |
|||
} |
|||
|
|||
public HasReadMessage(Long time){ |
|||
this(); |
|||
data = new Data(); |
|||
data.setTime(time); |
|||
} |
|||
} |
@ -1,32 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.client; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Data |
|||
public class PingMessage extends ServerMessage { |
|||
@Setter |
|||
@Getter |
|||
public static class Data{ |
|||
private Integer major; |
|||
private Integer minor; |
|||
} |
|||
private Data data; |
|||
|
|||
public PingMessage(){ |
|||
setType(MessageConstant.ClientMessageType.Ping.name()); |
|||
} |
|||
|
|||
public PingMessage(Integer major,Integer minor){ |
|||
this(); |
|||
data = new Data(); |
|||
data.setMajor(major); |
|||
data.setMinor(minor); |
|||
} |
|||
} |
@ -1,31 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.client; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Data |
|||
public class SetDeletedStatusMessage extends ServerMessage { |
|||
@Setter |
|||
@Getter |
|||
public static class Data { |
|||
String msgId; |
|||
} |
|||
|
|||
private Data data; |
|||
|
|||
public SetDeletedStatusMessage(){ |
|||
setType(MessageConstant.ClientMessageType.SetMsgDeleted.name()); |
|||
} |
|||
|
|||
public SetDeletedStatusMessage(String msgId){ |
|||
this(); |
|||
data = new Data(); |
|||
data.setMsgId(msgId); |
|||
} |
|||
} |
@ -1,31 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.client; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Data |
|||
public class SetRevertedStatusMessage extends ServerMessage { |
|||
@Setter |
|||
@Getter |
|||
public static class Data { |
|||
String msgId; |
|||
} |
|||
|
|||
private Data data; |
|||
|
|||
public SetRevertedStatusMessage(){ |
|||
setType(MessageConstant.ClientMessageType.SetMsgReverted.name()); |
|||
} |
|||
|
|||
public SetRevertedStatusMessage(String msgId){ |
|||
this(); |
|||
data = new Data(); |
|||
data.setMsgId(msgId); |
|||
} |
|||
} |
@ -1,31 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.client; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Data |
|||
public class SetSuccessStatusMessage extends ServerMessage { |
|||
@Setter |
|||
@Getter |
|||
public static class Data { |
|||
String msgId; |
|||
} |
|||
|
|||
private Data data; |
|||
|
|||
public SetSuccessStatusMessage(){ |
|||
setType(MessageConstant.ClientMessageType.SetMsgSuccess.name()); |
|||
} |
|||
|
|||
public SetSuccessStatusMessage(String msgId){ |
|||
this(); |
|||
data = new Data(); |
|||
data.setMsgId(msgId); |
|||
} |
|||
} |
@ -1,31 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.client; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author wei |
|||
* 客户端向服务器请求当前连接状态 |
|||
*/ |
|||
@Data |
|||
public class UnExceptedErrorMessage extends ServerMessage { |
|||
@Getter@Setter |
|||
public static class Data{ |
|||
private String errorString; |
|||
} |
|||
|
|||
private Data data; |
|||
|
|||
public UnExceptedErrorMessage(){ |
|||
setType(MessageConstant.ClientMessageType.UnExceptedError.name()); |
|||
} |
|||
|
|||
public UnExceptedErrorMessage(String errorString){ |
|||
this(); |
|||
data = new Data(); |
|||
data.setErrorString(errorString); |
|||
} |
|||
} |
@ -1,95 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.common; |
|||
|
|||
import cn.hutool.core.date.DateUtil; |
|||
import wiki.tall.ccmq.common.util.JacksonUtil; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import lombok.Data; |
|||
|
|||
import java.util.*; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Data |
|||
public class InMessage { |
|||
/** |
|||
* 消息ID |
|||
*/ |
|||
@JsonProperty(access = JsonProperty.Access.READ_ONLY) |
|||
private String id; |
|||
/** |
|||
* 发送时间(s) |
|||
* Notice: 指的是服务器收到发送请求的时间,不是服务器发出消息的时间 |
|||
*/ |
|||
@JsonProperty(access = JsonProperty.Access.READ_ONLY) |
|||
private Long time; |
|||
/** |
|||
* 消息来自于那个域 |
|||
*/ |
|||
@JsonProperty(access = JsonProperty.Access.READ_ONLY) |
|||
private MessageConstant.DomainType fromDomain; |
|||
/** |
|||
* 发送者信息 |
|||
*/ |
|||
private String from; |
|||
/** |
|||
* 消息要发送到哪个域 |
|||
*/ |
|||
private MessageConstant.DomainType toDomain; |
|||
/** |
|||
* 接受者信息(列表) |
|||
*/ |
|||
private Set<String> tos; |
|||
/** |
|||
* 消息的标示符(通常是由用户传过来的消息ID,该消息在用户系统中的ID) |
|||
*/ |
|||
private String unikey; |
|||
/** |
|||
* 发送规则 |
|||
* DeSerialize but not serialize @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) |
|||
*/ |
|||
private MessageRule rule; |
|||
/** |
|||
* 发送内容 |
|||
* 如果toDomain是Server: 代表消息是发给当前服务器的,必须是MessageSysData的子类型 |
|||
* 其他:(格式不限,由用户指定) |
|||
*/ |
|||
private String data; |
|||
|
|||
public InMessage(){ |
|||
this.time = DateUtil.currentSeconds(); |
|||
} |
|||
public static InMessage newToServerMessage(MessageConstant.DomainType fromDomain,ServerMessage serverMessage) throws JsonProcessingException { |
|||
InMessage inMessage = new InMessage(); |
|||
inMessage.setFromDomain(fromDomain); |
|||
inMessage.setToDomain(MessageConstant.DomainType.Server); |
|||
inMessage.setData(JacksonUtil.beanToJson(serverMessage)); |
|||
return inMessage; |
|||
} |
|||
|
|||
public static InMessage newToQueueMessage(String from, Set<String> tos, String unikey, MessageRule rule, String data) throws JsonProcessingException { |
|||
InMessage inMessage = new InMessage(); |
|||
inMessage.setToDomain(MessageConstant.DomainType.Queue); |
|||
inMessage.setFrom(from); |
|||
inMessage.setTos(tos); |
|||
inMessage.setUnikey(unikey); |
|||
inMessage.setRule(rule); |
|||
inMessage.setData(data); |
|||
return inMessage; |
|||
} |
|||
public static InMessage newToUserMessage(String from, Set<String> tos, String unikey, MessageRule rule, String data) throws JsonProcessingException { |
|||
InMessage inMessage = new InMessage(); |
|||
inMessage.setToDomain(MessageConstant.DomainType.User); |
|||
inMessage.setFrom(from); |
|||
inMessage.setTos(tos); |
|||
inMessage.setUnikey(unikey); |
|||
inMessage.setRule(rule); |
|||
inMessage.setData(data); |
|||
return inMessage; |
|||
} |
|||
|
|||
|
|||
//TODO
|
|||
//添加方便链式调用的构造方法,类似builder
|
|||
} |
@ -1,134 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.common; |
|||
|
|||
import wiki.tall.ccmq.common.util.DateUtil; |
|||
import lombok.Data; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Data |
|||
public class Message { |
|||
/** |
|||
* 消息ID (Message's ID) |
|||
*/ |
|||
private String id; |
|||
|
|||
/** |
|||
* 原始消息ID (InMessage's ID) |
|||
*/ |
|||
private String rawId; |
|||
|
|||
/** |
|||
* 发送时间(s) |
|||
* Notice: 指的是服务器收到发送请求的时间,不是服务器发出消息的时间 |
|||
*/ |
|||
private Long time; |
|||
/** |
|||
* 消息来自于那个域 |
|||
*/ |
|||
private MessageConstant.DomainType fromDomain; |
|||
/** |
|||
* 发送者信息 |
|||
*/ |
|||
private String from; |
|||
/** |
|||
* 消息要发送到哪个域 |
|||
*/ |
|||
private MessageConstant.DomainType toDomain; |
|||
/** |
|||
* 接受者信息(列表) |
|||
*/ |
|||
private String to; |
|||
/** |
|||
* 发送规则 |
|||
* DeSerialize but not serialize @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) |
|||
*/ |
|||
private MessageRule rule; |
|||
/** |
|||
* 发送内容 |
|||
* 如果toDomain是Server: 代表消息是发给当前服务器的,必须是MessageSysData的子类型 |
|||
* 其他:(格式不限,由用户指定) |
|||
*/ |
|||
private String data; |
|||
|
|||
/** |
|||
* 是否收到用户的ack消息 |
|||
*/ |
|||
private byte acked; |
|||
/** |
|||
* 是否收到用户的已读消息 |
|||
*/ |
|||
private byte hasRead; |
|||
/** |
|||
* 消息状态 |
|||
* 发送成功标志:根据配置由三种情况 |
|||
* 1. 配置不需要客户端ack, |
|||
* 发送完毕自动设置 ack = true; |
|||
* rule -> ackIsSuccess = true 发送即代表成功 |
|||
* rule -> ackIsSuccess = false 等待用户手动设置成功标志 |
|||
* 2. 配置需要ack,则发送并受到ack代表成功 |
|||
* rule -> ackIsSuccess = true 发送并收到ack即代表成功 |
|||
* rule -> ackIsSuccess = false 等待用户手动设置成功标志 |
|||
*/ |
|||
private MessageConstant.Status status; |
|||
|
|||
/** |
|||
* 发送次数 |
|||
*/ |
|||
private int sendTimes; |
|||
/** |
|||
* 发送时间 |
|||
*/ |
|||
private List<Long> sendTimeInSecondList; |
|||
|
|||
public Message(){ |
|||
} |
|||
|
|||
public Message(MessageConstant.DomainType fromDomain,String from,MessageConstant.DomainType toDomain,String to,String data,MessageRule rule){ |
|||
this.time = DateUtil.currentSeconds(); |
|||
this.fromDomain = fromDomain; |
|||
this.from = from; |
|||
this.toDomain = toDomain; |
|||
this.to = to; |
|||
this.data = data; |
|||
this.rule = rule; |
|||
} |
|||
|
|||
public Message(InMessage inEntity, String to){ |
|||
this.rawId = inEntity.getId(); |
|||
this.time = inEntity.getTime(); |
|||
this.fromDomain = inEntity.getFromDomain(); |
|||
this.from = inEntity.getFrom(); |
|||
this.toDomain = inEntity.getToDomain(); |
|||
this.to = to; |
|||
this.rule = inEntity.getRule(); |
|||
this.data = inEntity.getData(); |
|||
|
|||
this.acked = 0; |
|||
this.hasRead = 0; |
|||
this.status = MessageConstant.Status.Pending; |
|||
this.sendTimes = 0; |
|||
} |
|||
|
|||
public void incrementSendTimes() { |
|||
this.sendTimes++; |
|||
if(this.sendTimeInSecondList == null){ |
|||
this.sendTimeInSecondList = new ArrayList<>(); |
|||
} |
|||
this.sendTimeInSecondList.add(DateUtil.currentSeconds()); |
|||
} |
|||
|
|||
public boolean isSendTimesUpLimit() { |
|||
if((rule.getNoAckRetryTimes() > 0) && (sendTimes >= rule.getNoAckRetryTimes())){ |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
public boolean isExpired() { |
|||
return DateUtil.currentSeconds() >= rule.getExpireAt(); |
|||
} |
|||
} |
@ -1,217 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.common; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
|
|||
/** |
|||
* @author wei |
|||
* 消息相关常量 |
|||
*/ |
|||
public class MessageConstant { |
|||
public enum ClientMessageType{ |
|||
//客户端心跳
|
|||
Ping(0x00), |
|||
//客户端认证
|
|||
Auth(0x01), |
|||
//客户端收到消息ACK
|
|||
Ack(0x02), |
|||
//客户端收到消息ACK
|
|||
HasRead(0x03), |
|||
//客户端请求连接状态
|
|||
GetChannelStatus(0x04), |
|||
|
|||
//不可预期的错误
|
|||
UnExceptedError(0x21), |
|||
ClientIdleClosed(0x22), |
|||
ClientAuthTimeOut(0x23), |
|||
|
|||
//设置消息状态消息(disable撤销消息,适用于,消息撤回),针对消息本身(eg:消息撤回)
|
|||
SetMsgSuccess(0x52), |
|||
SetMsgReverted(0x53), |
|||
SetMsgDeleted(0x54); |
|||
|
|||
public int value; |
|||
|
|||
ClientMessageType(int value){ |
|||
this.value = value; |
|||
} |
|||
|
|||
/** |
|||
* 从int到enum的转换函数 |
|||
* @param value 枚举int值 |
|||
* @return 对应的枚举,找不到则返回null |
|||
*/ |
|||
public static ClientMessageType valueOf(int value) { |
|||
for(ClientMessageType type : values()){ |
|||
if(type.value == value){ |
|||
return type; |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public enum ServerMessageType{ |
|||
//客户端心跳
|
|||
Pong(0x00), |
|||
//客户端收到消息ACK
|
|||
Ack(0x02), |
|||
//客户端请求连接状态
|
|||
ChannelStatus(0x03); |
|||
//撤销某个消息
|
|||
//DelMessage(0x04),
|
|||
//客户端请求连接状态
|
|||
//QueueStatus(0x05);
|
|||
|
|||
public int value; |
|||
|
|||
ServerMessageType(int value){ |
|||
this.value = value; |
|||
} |
|||
|
|||
/** |
|||
* 从int到enum的转换函数 |
|||
* @param value 枚举int值 |
|||
* @return 对应的枚举,找不到则返回null |
|||
*/ |
|||
public static ServerMessageType valueOf(int value) { |
|||
for(ServerMessageType type : values()){ |
|||
if(type.value == value){ |
|||
return type; |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* JsonFormat是Json Serialize相关配置 |
|||
*/ |
|||
@JsonFormat(shape = JsonFormat.Shape.OBJECT) |
|||
public enum Error{ |
|||
//Ok
|
|||
Ok(200,"Ok"), |
|||
|
|||
/** |
|||
* 通用消息错误 |
|||
*/ |
|||
//消息无接收者错误
|
|||
MessageNoReceiversError(1001,"[用户]消息至少应该有一个接收者"), |
|||
//消息无数据错误
|
|||
MessageNoDataError(1002,"不允许消息内容为空"), |
|||
|
|||
/** |
|||
* server消息错误 |
|||
*/ |
|||
//server消息无type错误
|
|||
MessageNoTypeError(1003,"发送至Server域的消息必须有type字段"), |
|||
//Ack参数错误,没有找到对应的msgId
|
|||
AckParameterError(1103,"Ack参数错误,没有找到对应的msgId"), |
|||
//SetSuccess参数错误,没有找到对应的msgId
|
|||
SetSuccessParameterError(1104,"SetSuccess参数错误,没有找到对应的msgId"), |
|||
//SetStatusParameterError
|
|||
SetStatusParameterError(1105,"SetStatus参数错误,没有找到对应的msgId"), |
|||
//HasRead参数错误,没有找到对应的msgId
|
|||
HasReadParameterError(1106,"HasRead参数错误,没有找到对应的msgId"), |
|||
|
|||
/** |
|||
* 身份认证错误 |
|||
*/ |
|||
//无权限
|
|||
UnAuthed(1021, "未认证的用户"), |
|||
|
|||
/** |
|||
* 业务错误 |
|||
*/ |
|||
//认证失败
|
|||
AuthFailed(1301,"认证失败"), |
|||
//空闲断开连接(连续Ns没有收到数据)
|
|||
ChannelIdle(1302,"连接断开:连续N秒没有从收到客户端收到任何数据"), |
|||
//认证超时断开连接
|
|||
ChannelAuthTimeOut(1303,"连接断开:认证超时"), |
|||
//不可预期错误
|
|||
UnExpectedError(1304,"不可预期异常"); |
|||
|
|||
public int code; |
|||
public String text; |
|||
public String extra; |
|||
|
|||
Error(int code,String text) { |
|||
this.code = code; |
|||
this.text = text; |
|||
} |
|||
Error(int code,String text,String extra) { |
|||
this.code = code; |
|||
this.text = text; |
|||
this.extra = extra; |
|||
} |
|||
public Error joinExtra(String extra){ |
|||
this.extra = extra; |
|||
return this; |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 域(User、Queue、Rest、Server) |
|||
*/ |
|||
public enum DomainType{ |
|||
//Netty Client
|
|||
User(1), |
|||
//Queue Client
|
|||
Queue(2), |
|||
//Rest Client
|
|||
Rest(3), |
|||
//系统
|
|||
Server(4); |
|||
|
|||
public int value; |
|||
|
|||
DomainType(int value){ |
|||
this.value = value; |
|||
} |
|||
|
|||
public static DomainType valueOf(int value) { |
|||
for(DomainType domainType : values()){ |
|||
if(domainType.value == value){ |
|||
return domainType; |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public enum Status{ |
|||
//未决状态(未完成)
|
|||
Pending(0), |
|||
//发送成功(投递成功)
|
|||
Succeed(1), |
|||
//发送失败(投递失败)
|
|||
Failed(2), |
|||
//消息过期
|
|||
Expired(3), |
|||
//消息被撤回
|
|||
Reverted(4), |
|||
//消息被删除
|
|||
Deleted(5), |
|||
//消息达到重试次数上限
|
|||
SendTimesUpLimit(6), |
|||
|
|||
//临时完成状态,当ackIsSuccess==0时,一旦ack设置消息为此状态
|
|||
TempSucceed(10); |
|||
|
|||
public int value; |
|||
|
|||
Status(int value){ |
|||
this.value = value; |
|||
} |
|||
|
|||
public static Status valueOf(int value){ |
|||
for(Status status : values()){ |
|||
if(status.value == value){ |
|||
return status; |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
} |
@ -1,80 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.common; |
|||
|
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* 发送规则实体类 |
|||
* @author wei |
|||
*/ |
|||
@Data |
|||
public class MessageRule { |
|||
public enum AckRule{ |
|||
//不需要ack
|
|||
NONE, |
|||
//产生错误时回复ack
|
|||
ERROR, |
|||
//总是回复ack
|
|||
ALWAYS |
|||
} |
|||
/** |
|||
* 离线丢弃标志 |
|||
*/ |
|||
private byte offlineDiscard; |
|||
/** |
|||
* 消息是否需要ack,如果不需要ack,则发送完毕,即代表成功 |
|||
*/ |
|||
private AckRule ackRule; |
|||
/** |
|||
* 没有收到ack重试次数 |
|||
*/ |
|||
private Integer noAckRetryTimes; |
|||
/** |
|||
* 收到ack代表发送成功,不再发送 |
|||
* 如果该标志为0,则代表每次上线都发送,直到用户手动设置成功。 |
|||
*/ |
|||
private byte ackIsSuccess; |
|||
/** |
|||
* 消息过期时间(s) |
|||
*/ |
|||
private Long expireAt; |
|||
|
|||
public MessageRule(){ |
|||
|
|||
} |
|||
|
|||
public MessageRule(byte offlineDiscard,AckRule ackRule,Integer noAckRetryTimes,byte ackIsSuccess){ |
|||
this.offlineDiscard = offlineDiscard; |
|||
this.ackRule = ackRule; |
|||
this.noAckRetryTimes = noAckRetryTimes; |
|||
this.ackIsSuccess = ackIsSuccess; |
|||
} |
|||
|
|||
public MessageRule(byte offlineDiscard,AckRule ackRule,Integer noAckRetryTimes,byte ackIsSuccess,Long expireAt){ |
|||
this.offlineDiscard = offlineDiscard; |
|||
this.ackRule = ackRule; |
|||
this.noAckRetryTimes = noAckRetryTimes; |
|||
this.ackIsSuccess = ackIsSuccess; |
|||
this.expireAt = expireAt; |
|||
} |
|||
|
|||
public static MessageRule defaultRule(MessageConstant.DomainType fromDomain) { |
|||
MessageRule messageRule = null; |
|||
switch(fromDomain) { |
|||
case User: |
|||
messageRule = new MessageRule((byte) 0, AckRule.ALWAYS, 10, (byte) 1,0L); |
|||
break; |
|||
case Queue: |
|||
messageRule = new MessageRule((byte) 1, AckRule.ALWAYS, 10, (byte) 1,0L); |
|||
break; |
|||
case Rest: |
|||
messageRule = new MessageRule((byte) 1, AckRule.ALWAYS, 10, (byte) 1,0L); |
|||
break; |
|||
case Server: |
|||
messageRule = new MessageRule((byte) 1, AckRule.ALWAYS, 10, (byte) 1,0L); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
return messageRule; |
|||
} |
|||
} |
@ -1,71 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.common; |
|||
|
|||
import cn.hutool.core.date.DateUtil; |
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Data |
|||
public class OutMessage { |
|||
/** |
|||
* 消息ID |
|||
*/ |
|||
private String id; |
|||
/** |
|||
* sender发送消息时间(s) |
|||
* Notice: 指的是服务器收到发送请求的时间,不是服务器发出消息的时间,服务器发出消息的时间客户端很好确定(就是当前时间) |
|||
*/ |
|||
private Long time; |
|||
/** |
|||
* 消息来自于那个域,通常不需要知道toDomain |
|||
*/ |
|||
private MessageConstant.DomainType fromDomain; |
|||
/** |
|||
* 发送者信息 |
|||
*/ |
|||
private String from; |
|||
/** |
|||
* 如果toDomain是Server: 代表消息是发给当前服务器的,必须是MessageSysData的子类型 |
|||
* 其他:(格式不限,由用户指定,消息系统会将data字段原封不动投递到接收方,由接收方进行解析) |
|||
*/ |
|||
private String data; |
|||
|
|||
public OutMessage(){ |
|||
} |
|||
|
|||
public OutMessage(String id, Long time, String from, String data){ |
|||
this.id = id; |
|||
this.time = time; |
|||
this.from = from; |
|||
this.data = data; |
|||
} |
|||
|
|||
public OutMessage(InMessage messageEntity){ |
|||
this.id = messageEntity.getId(); |
|||
this.time = messageEntity.getTime(); |
|||
this.fromDomain = messageEntity.getFromDomain(); |
|||
this.from = messageEntity.getFrom(); |
|||
this.data = messageEntity.getData(); |
|||
} |
|||
|
|||
public OutMessage(String data){ |
|||
this.time = DateUtil.currentSeconds(); |
|||
this.fromDomain = MessageConstant.DomainType.Server; |
|||
this.data = data; |
|||
} |
|||
|
|||
public OutMessage(MessageConstant.DomainType fromDomain, String data) { |
|||
this.time = DateUtil.currentSeconds(); |
|||
this.fromDomain = fromDomain; |
|||
this.data = data; |
|||
} |
|||
|
|||
public OutMessage(Message message){ |
|||
this.id = message.getId(); |
|||
this.time = message.getTime(); |
|||
this.fromDomain = message.getFromDomain(); |
|||
this.from = message.getFrom(); |
|||
this.data = message.getData(); |
|||
} |
|||
} |
@ -1,43 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.common; |
|||
|
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import lombok.ToString; |
|||
|
|||
import java.util.Collection; |
|||
import java.util.HashSet; |
|||
import java.util.Set; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
@Getter @Setter @ToString |
|||
public class OutMessageSet { |
|||
private String ackId; |
|||
private Set<OutMessage> messageSet; |
|||
|
|||
public OutMessageSet ackId(String ackId){ |
|||
this.ackId = ackId; |
|||
return this; |
|||
} |
|||
|
|||
public static OutMessageSet newInstance(){ |
|||
return new OutMessageSet(); |
|||
} |
|||
|
|||
public OutMessageSet add(OutMessage messageOutEntity){ |
|||
if(messageSet == null){ |
|||
messageSet = new HashSet<>(); |
|||
} |
|||
messageSet.add(messageOutEntity); |
|||
return this; |
|||
} |
|||
|
|||
public OutMessageSet addAll(Collection<OutMessage> theMessageSet){ |
|||
if(messageSet == null){ |
|||
messageSet = new HashSet<>(); |
|||
} |
|||
messageSet.addAll(theMessageSet); |
|||
return this; |
|||
} |
|||
} |
@ -1,11 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.common; |
|||
|
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Data |
|||
public class ServerMessage { |
|||
private String type; |
|||
} |
@ -1,37 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.server; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author zhangsan |
|||
*/ |
|||
@Data |
|||
public class ChannelStatusMessage extends ServerMessage { |
|||
@Setter |
|||
@Getter |
|||
public static class Data{ |
|||
//认证状态
|
|||
private Boolean authed; |
|||
//已认证时长(s)
|
|||
private Long onlineSeconds; |
|||
//错误信息
|
|||
private MessageConstant.Error error; |
|||
} |
|||
private Data data; |
|||
|
|||
public ChannelStatusMessage(){ |
|||
setType(MessageConstant.ServerMessageType.ChannelStatus.name()); |
|||
} |
|||
|
|||
public ChannelStatusMessage(Boolean authed,Long onlineSeconds,MessageConstant.Error error){ |
|||
this(); |
|||
data = new Data(); |
|||
data.authed = authed; |
|||
data.onlineSeconds = onlineSeconds; |
|||
data.error = error; |
|||
} |
|||
} |
@ -1,28 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.server; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
public class InvertedMessage extends ServerMessage { |
|||
@Setter |
|||
@Getter |
|||
public static class Data{ |
|||
private String msgId; |
|||
} |
|||
private Data data; |
|||
|
|||
public InvertedMessage(){ |
|||
setType(MessageConstant.ServerMessageType.Pong.name()); |
|||
} |
|||
|
|||
public InvertedMessage(String msgId){ |
|||
this(); |
|||
data = new Data(); |
|||
data.setMsgId(msgId); |
|||
} |
|||
} |
@ -1,15 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.server; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* @author zhangsan |
|||
*/ |
|||
@Data |
|||
public class PongMessage extends ServerMessage { |
|||
public PongMessage(){ |
|||
setType(MessageConstant.ServerMessageType.Pong.name()); |
|||
} |
|||
} |
@ -1,30 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.server; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author zhangsan |
|||
*/ |
|||
@Data |
|||
public class QueueStatusMessage extends ServerMessage { |
|||
@Setter |
|||
@Getter |
|||
public static class Data{ |
|||
private MessageConstant.Error error; |
|||
} |
|||
private Data data; |
|||
|
|||
public QueueStatusMessage(){ |
|||
setType(MessageConstant.ServerMessageType.ChannelStatus.name()); |
|||
} |
|||
|
|||
public QueueStatusMessage(MessageConstant.Error error){ |
|||
this(); |
|||
data = new Data(); |
|||
data.error = error; |
|||
} |
|||
} |
@ -1,49 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.message.server; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.ccsens.ccmq.lowlevel.message.common.ServerMessage; |
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* @author zhangsan |
|||
*/ |
|||
@Data |
|||
public class ServerAckMessage extends ServerMessage { |
|||
@lombok.Data |
|||
public static class Data{ |
|||
/** |
|||
* 消息ID |
|||
*/ |
|||
private String msgId; |
|||
/** |
|||
* 发送者指定的unikey,如果发送者未指定,则为null |
|||
*/ |
|||
private String unikey; |
|||
/** |
|||
* 消息状态 |
|||
*/ |
|||
private MessageConstant.Error error; |
|||
|
|||
public Data(String msgId,String unikey,MessageConstant.Error error){ |
|||
this.msgId = msgId; |
|||
this.unikey = unikey; |
|||
this.error = error; |
|||
} |
|||
} |
|||
|
|||
private Data data; |
|||
|
|||
public ServerAckMessage(){ |
|||
setType(MessageConstant.ServerMessageType.Ack.name()); |
|||
} |
|||
|
|||
public ServerAckMessage(MessageConstant.Error status){ |
|||
this(); |
|||
this.data = new Data(null,null,status); |
|||
} |
|||
|
|||
public ServerAckMessage(String msgId,String unikey,MessageConstant.Error status){ |
|||
this(); |
|||
this.data = new Data(msgId,unikey,status); |
|||
} |
|||
} |
@ -1,118 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.persist; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.InMessage; |
|||
import com.ccsens.ccmq.lowlevel.message.common.Message; |
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
public interface IMessageDao { |
|||
/** |
|||
* 保存原始消息 |
|||
* @param inMessage message to be saved |
|||
* @throws Exception 可能产生的异常 |
|||
*/ |
|||
void saveOrUpdateMessage(InMessage inMessage) throws Exception; |
|||
/** |
|||
* 保存message |
|||
* @param message message to be saved |
|||
* @throws Exception 可能产生的异常 |
|||
*/ |
|||
void saveOrUpdateMessage(Message message) throws Exception; |
|||
|
|||
/** |
|||
* 更新消息的某个接受者的hasRead状态 |
|||
* @param fromDomain 消息fromDomain |
|||
* @param fromUserId 消息from |
|||
* @param toDomain 消息toDomain |
|||
* @param toUserId 接受者ID |
|||
* @param time 该time之前(包括自己)的消息 |
|||
* @param hasRead 是否已读 |
|||
* @throws Exception |
|||
*/ |
|||
void updateMessageReadStatus(String fromDomain, String fromUserId, String toDomain, String toUserId, Long time, byte hasRead)throws Exception; |
|||
|
|||
/** |
|||
* 更新消息的某个接受者的 ack和success状态 |
|||
* @param msgId 消息ID |
|||
* @param acked 是否ack |
|||
* @param status status |
|||
*/ |
|||
void updateMessageAckAndStatus(String msgId, byte acked, MessageConstant.Status status); |
|||
|
|||
/** |
|||
* 根据rawId修改message status |
|||
* @param rawId 原始消息ID |
|||
* @param status 要修改的状态 |
|||
*/ |
|||
void updateMessageStatusByRawId(String rawId, MessageConstant.Status status); |
|||
/** |
|||
* 更新消息状态 |
|||
* @param msgId |
|||
* @param status Normal、Deleted |
|||
*/ |
|||
void updateMessageStatus(String msgId, MessageConstant.Status status); |
|||
|
|||
/** |
|||
* 更新所有的status==TempSucceed的消息为pending |
|||
*/ |
|||
void updateMessageStatusToPending(); |
|||
|
|||
/** |
|||
* 更新所有的status==TempSucceed的消息为pending |
|||
* @param domainType 域 |
|||
* @param userId 用户 |
|||
*/ |
|||
void updateMessageStatusToPending(MessageConstant.DomainType domainType, String userId); |
|||
|
|||
/** |
|||
* 检查pending消息是否过期,如果是,设置为expired状态 |
|||
*/ |
|||
void updateMessageToExpired(); |
|||
|
|||
/** |
|||
* 检查pending消息是否达到发送上限,如果是,设置为SendTimesUpLimit状态 |
|||
*/ |
|||
void updateMessageToSendTimesUpLimit(); |
|||
/** |
|||
* 增加某个消息的发送次数,并添加一条发送记录 |
|||
* @param id 消息ID |
|||
* @param time 发送时间 |
|||
*/ |
|||
void incrementSendTimes(String id, Long time); |
|||
|
|||
/** |
|||
* 根据ID获取MessageEntity |
|||
* @param msgId 消息ID |
|||
* @return 消息实体类 |
|||
*/ |
|||
Message getMessageById(String msgId); |
|||
|
|||
/** |
|||
* 根据ID获取MessageEntity |
|||
* @param rawId 消息ID |
|||
* @return 消息实体类集合 |
|||
*/ |
|||
List<Message> getMessageByRawId(String rawId); |
|||
|
|||
/** |
|||
* 获取某个客户端pending的消息集合,最多获取maxMessageNum个。 |
|||
* @param toDomain 消息的接收域 |
|||
* @param to 消息的接收者Id |
|||
* @param maxMessageNum 最多查询数量 |
|||
* @return 消息集合 |
|||
*/ |
|||
List<Message> getClientPendingMessage(MessageConstant.DomainType toDomain, String to, Integer maxMessageNum); |
|||
|
|||
/** |
|||
* 获取toDomain域、to用户的未ack消息数量 |
|||
* @param toDomain 域 |
|||
* @param to 用户 |
|||
* @return 未ack消息数量 |
|||
*/ |
|||
Long countClientPendingMessage(MessageConstant.DomainType toDomain, String to); |
|||
|
|||
} |
@ -1,180 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.persist; |
|||
|
|||
import com.ccsens.ccmq.lowlevel.message.common.InMessage; |
|||
import com.ccsens.ccmq.lowlevel.message.common.Message; |
|||
import com.ccsens.ccmq.lowlevel.message.common.MessageConstant; |
|||
import com.mongodb.BasicDBObject; |
|||
import com.mongodb.DBObject; |
|||
import org.bson.Document; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.data.domain.Sort; |
|||
import org.springframework.data.mongodb.core.MongoTemplate; |
|||
import org.springframework.data.mongodb.core.query.Criteria; |
|||
import org.springframework.data.mongodb.core.query.Query; |
|||
import org.springframework.data.mongodb.core.query.Update; |
|||
import org.springframework.stereotype.Repository; |
|||
import wiki.tall.ccmq.common.util.DateUtil; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Repository |
|||
public class MessageDao implements IMessageDao { |
|||
private static final String COLLECTION_RAW_MESSAGE = "col_message_raw"; |
|||
private static final String COLLECTION_MESSAGE = "col_message"; |
|||
@Autowired |
|||
private MongoTemplate mongoTemplate; |
|||
|
|||
@Override |
|||
public void saveOrUpdateMessage(InMessage inMessage) throws Exception { |
|||
mongoTemplate.save(inMessage,COLLECTION_RAW_MESSAGE); |
|||
} |
|||
|
|||
@Override |
|||
public void saveOrUpdateMessage(Message message) throws Exception { |
|||
mongoTemplate.save(message,COLLECTION_MESSAGE); |
|||
} |
|||
|
|||
@Override |
|||
public void updateMessageReadStatus(String fromDomain,String fromUserId,String toDomain,String toUserId,Long time,byte hasRead) throws Exception { |
|||
Query query = new Query( |
|||
Criteria.where("fromDomain").is(fromDomain).andOperator( |
|||
Criteria.where("from").is(fromUserId).andOperator( |
|||
Criteria.where("toDomain").is(toDomain).andOperator( |
|||
Criteria.where("to").is(toUserId).andOperator( |
|||
Criteria.where("time").lte(time) |
|||
) |
|||
) |
|||
)) |
|||
); |
|||
Update update = new Update().set("hasRead",hasRead); |
|||
mongoTemplate.updateMulti(query,update, Message.class,COLLECTION_MESSAGE); |
|||
} |
|||
|
|||
@Override |
|||
public void updateMessageAckAndStatus(String msgId, byte acked, MessageConstant.Status status) { |
|||
Query query = new Query(Criteria.where("id").is(msgId)); |
|||
Update update = new Update().set("acked",acked).set("status",status); |
|||
mongoTemplate.updateFirst(query,update, Message.class,COLLECTION_MESSAGE); |
|||
} |
|||
|
|||
@Override |
|||
public void updateMessageStatusByRawId(String rawId, MessageConstant.Status status) { |
|||
Query query = new Query(Criteria.where("rawId").is(rawId)); |
|||
Update update = new Update().set("status",status); |
|||
mongoTemplate.updateFirst(query,update, Message.class,COLLECTION_MESSAGE); |
|||
} |
|||
|
|||
@Override |
|||
public void updateMessageStatus(String msgId, MessageConstant.Status status) { |
|||
Query query = new Query(Criteria.where("id").is(msgId)); |
|||
Update update = new Update().set("status",status); |
|||
mongoTemplate.updateFirst(query,update, Message.class,COLLECTION_MESSAGE); |
|||
} |
|||
|
|||
@Override |
|||
public void updateMessageStatusToPending() { |
|||
Query query = new Query(Criteria.where("status").is(MessageConstant.Status.TempSucceed)); |
|||
Update update = new Update().set("status",MessageConstant.Status.Pending); |
|||
mongoTemplate.updateMulti(query,update, Message.class,COLLECTION_MESSAGE); |
|||
} |
|||
|
|||
@Override |
|||
public void updateMessageStatusToPending(MessageConstant.DomainType domainType, String userId) { |
|||
Query query = new Query() |
|||
.addCriteria(Criteria.where("toDomain").is(domainType)) |
|||
.addCriteria(Criteria.where("to").is(userId)) |
|||
.addCriteria(Criteria.where("status").is(MessageConstant.Status.TempSucceed)); |
|||
Update update = new Update().set("status",MessageConstant.Status.Pending); |
|||
mongoTemplate.updateMulti(query,update, Message.class,COLLECTION_MESSAGE); |
|||
} |
|||
|
|||
@Override |
|||
public void updateMessageToExpired() { |
|||
Query query = new Query() |
|||
.addCriteria(Criteria.where("status").is(MessageConstant.Status.Pending)) |
|||
.addCriteria(Criteria.where("rule.expireAt").gt(0).lt(DateUtil.currentSeconds())); |
|||
Update update = new Update().set("status",MessageConstant.Status.Expired); |
|||
mongoTemplate.updateMulti(query,update, Message.class,COLLECTION_MESSAGE); |
|||
} |
|||
|
|||
@Override |
|||
public void updateMessageToSendTimesUpLimit() { |
|||
Query query = new Query() |
|||
.addCriteria(Criteria.where("status").is(MessageConstant.Status.Pending)) |
|||
.addCriteria(Criteria.where("rule.noAckRetryTimes").gt(0)) |
|||
.addCriteria(new Criteria(){ |
|||
@Override |
|||
public Document getCriteriaObject() { |
|||
Document obj = new Document(); |
|||
obj.put("$where", "this.sendTimes >= this.rule.noAckRetryTimes"); |
|||
return obj; |
|||
} |
|||
}); |
|||
Update update = new Update().set("status",MessageConstant.Status.SendTimesUpLimit); |
|||
mongoTemplate.updateMulti(query,update, Message.class,COLLECTION_MESSAGE); |
|||
} |
|||
|
|||
@Override |
|||
public void incrementSendTimes(String msgId,Long time) { |
|||
Query query = new Query(Criteria.where("id").is(msgId)); |
|||
Update update = new Update(); |
|||
update.inc("sendTimes"); |
|||
update.addToSet("sendTimeInSecondList", time); |
|||
mongoTemplate.updateFirst(query,update, Message.class,COLLECTION_MESSAGE); |
|||
} |
|||
|
|||
@Override |
|||
public Message getMessageById(String msgId) { |
|||
return mongoTemplate.findById(msgId, Message.class,COLLECTION_MESSAGE); |
|||
} |
|||
|
|||
@Override |
|||
public List<Message> getMessageByRawId(String rawId) { |
|||
Query query = new Query().addCriteria(Criteria.where("rawId").is(rawId)); |
|||
return mongoTemplate.find(query,Message.class); |
|||
} |
|||
|
|||
@Override |
|||
public List<Message> getClientPendingMessage(MessageConstant.DomainType toDomain, String to, Integer maxMessageNum) { |
|||
Query query = clientPendingMessageQuery(toDomain,to) |
|||
.with(Sort.by(Sort.Order.asc("time"))); |
|||
if(maxMessageNum == null || maxMessageNum.intValue() == 0) { |
|||
query.limit(maxMessageNum); |
|||
} |
|||
return mongoTemplate.find(query,Message.class,COLLECTION_MESSAGE); |
|||
} |
|||
|
|||
@Override |
|||
public Long countClientPendingMessage(MessageConstant.DomainType toDomain, String to) { |
|||
Query query = clientPendingMessageQuery(toDomain,to); |
|||
return mongoTemplate.count(query,Message.class,COLLECTION_MESSAGE); |
|||
} |
|||
|
|||
private Query clientPendingMessageQuery(MessageConstant.DomainType toDomain, String to){ |
|||
return new Query().addCriteria(Criteria.where("toDomain").is(toDomain)) |
|||
.addCriteria(Criteria.where("to").is(to)) |
|||
//状态为pending
|
|||
.addCriteria(Criteria.where("status").is(MessageConstant.Status.Pending)) |
|||
.addCriteria(new Criteria().andOperator( |
|||
//未过期消息
|
|||
new Criteria().orOperator( |
|||
Criteria.where("rule.expireAt").is(0), |
|||
Criteria.where("rule.expireAt").gt(DateUtil.currentSeconds() |
|||
)), |
|||
//未达到发送次数上限
|
|||
new Criteria().orOperator( |
|||
Criteria.where("rule.noAckRetryTimes").is(0), |
|||
new Criteria() { |
|||
@Override |
|||
public Document getCriteriaObject() { |
|||
Document obj = new Document(); |
|||
obj.put("$where", "this.sendTimes < this.rule.noAckRetryTimes"); |
|||
return obj; |
|||
} |
|||
}) |
|||
)); |
|||
} |
|||
} |
@ -1,9 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.service; |
|||
|
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
|
|||
//@FeignClient(value = "eureka-client-btpro",fallback = TokenSer.class)
|
|||
public interface IUserService { |
|||
//@GetMapping(value="/btpro/v1.0/token")
|
|||
String getUserIdByToken(@RequestParam(value = "token") String token); |
|||
} |
@ -1,15 +0,0 @@ |
|||
package com.ccsens.ccmq.lowlevel.service; |
|||
|
|||
import org.springframework.stereotype.Service; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@Service |
|||
public class UserService implements IUserService{ |
|||
@Override |
|||
public String getUserIdByToken(String token) { |
|||
String userId = "1122331122233"; |
|||
return userId; |
|||
} |
|||
} |
@ -0,0 +1,36 @@ |
|||
package com.ccsens.opensource.wxconfigurer; |
|||
|
|||
import com.ccsens.opensource.wxconfigurer.util.WebConstant; |
|||
import org.mybatis.spring.annotation.MapperScan; |
|||
import org.springframework.boot.CommandLineRunner; |
|||
import org.springframework.boot.SpringApplication; |
|||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
|||
import org.springframework.boot.web.servlet.ServletComponentScan; |
|||
import org.springframework.context.annotation.ComponentScan; |
|||
import org.springframework.scheduling.annotation.EnableAsync; |
|||
import org.springframework.scheduling.annotation.EnableScheduling; |
|||
|
|||
import java.io.File; |
|||
|
|||
/** |
|||
* @author wei |
|||
*/ |
|||
@ComponentScan(basePackages = {"com.ccsens.opensource.wxconfigurer.*"}) |
|||
@MapperScan(basePackages = {"com.ccsens.opensource.wxconfigurer.*"}) |
|||
@ServletComponentScan |
|||
@EnableScheduling |
|||
@EnableAsync |
|||
@SpringBootApplication |
|||
public class WxConfigurerApplication implements CommandLineRunner { |
|||
public static void main(String[] args) { |
|||
createNecessaryDir(); |
|||
SpringApplication.run(WxConfigurerApplication.class, args); |
|||
} |
|||
|
|||
private static void createNecessaryDir(){ |
|||
} |
|||
|
|||
@Override |
|||
public void run(String... args) throws Exception { |
|||
} |
|||
} |
@ -0,0 +1,151 @@ |
|||
package com.ccsens.opensource.wxconfigurer.bean.dto; |
|||
|
|||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; |
|||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; |
|||
import lombok.Data; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
@Data |
|||
@JacksonXmlRootElement(localName = "xml") |
|||
public class WxGzhAction { |
|||
@Data |
|||
private static class ScanCodeInfo{ |
|||
@JacksonXmlProperty(localName = "ScanType") |
|||
private String scanType; |
|||
@JacksonXmlProperty(localName = "ScanResult") |
|||
private String scanResult; |
|||
}; |
|||
|
|||
@Data |
|||
private static class SendPicsInfo{ |
|||
@Data |
|||
private static class Item{ |
|||
@JacksonXmlProperty(localName = "PicMd5Sum") |
|||
private String picMd5Sum; |
|||
} |
|||
@JacksonXmlProperty(localName = "Count") |
|||
private Integer count; |
|||
@JacksonXmlProperty(localName = "PicList") |
|||
private List<Item> picList; |
|||
} |
|||
|
|||
@Data |
|||
private static class SendLocationInfo{ |
|||
@JacksonXmlProperty(localName = "Location_X") |
|||
private String x; |
|||
@JacksonXmlProperty(localName = "Location_Y") |
|||
private String y; |
|||
@JacksonXmlProperty(localName = "Scale") |
|||
private String scale; |
|||
@JacksonXmlProperty(localName = "Label") |
|||
private String label; |
|||
@JacksonXmlProperty(localName = "Poiname") |
|||
private String poiname; |
|||
} |
|||
|
|||
@JacksonXmlProperty(localName = "FromUserName") |
|||
private String openId; |
|||
@JacksonXmlProperty(localName = "ToUserName") |
|||
private String gzhId; |
|||
@JacksonXmlProperty(localName = "CreateTime") |
|||
private Long createTime; |
|||
@JacksonXmlProperty(localName = "MsgType") |
|||
private String msgType; |
|||
|
|||
/** |
|||
* MsgType: Text (Content,MsgId) |
|||
*/ |
|||
@JacksonXmlProperty(localName = "Content") |
|||
private String content; |
|||
@JacksonXmlProperty(localName = "MsgId") |
|||
private String msgId; |
|||
/** |
|||
* MsgType: image (PicUrl,MediaId,MsgId) |
|||
*/ |
|||
@JacksonXmlProperty(localName = "PicUrl") |
|||
private String picUrl; |
|||
@JacksonXmlProperty(localName = "MediaId") |
|||
private String mediaId; |
|||
/** |
|||
* MsgType: voice (MediaId,Format,MsgId) |
|||
* Recognition: 开通语音识别后生效 |
|||
*/ |
|||
@JacksonXmlProperty(localName = "Format") |
|||
private String format; |
|||
@JacksonXmlProperty(localName = "Recognition") |
|||
private String recognition; |
|||
|
|||
/** |
|||
* MsgType: video/shortvideo(MediaId,ThumbMediaId,MsgId) |
|||
*/ |
|||
@JacksonXmlProperty(localName = "ThumbMediaId") |
|||
private String thumbMediaId; |
|||
/** |
|||
* MsgType: location(Location_x,Location_y,Scale,Label,MsgId) |
|||
*/ |
|||
@JacksonXmlProperty(localName = "Location_X") |
|||
private String x; |
|||
@JacksonXmlProperty(localName = "Location_Y") |
|||
private String y; |
|||
@JacksonXmlProperty(localName = "Scale") |
|||
private String scale; |
|||
@JacksonXmlProperty(localName = "Label") |
|||
private String label; |
|||
/** |
|||
* MsgType: link(Title,Description,Url,MsgId) |
|||
*/ |
|||
@JacksonXmlProperty(localName = "Title") |
|||
private String title; |
|||
@JacksonXmlProperty(localName = "Description") |
|||
private String description; |
|||
@JacksonXmlProperty(localName = "Url") |
|||
private String url; |
|||
|
|||
/** |
|||
* MsgType: event |
|||
*/ |
|||
@JacksonXmlProperty(localName = "Event") |
|||
private String event; |
|||
@JacksonXmlProperty(localName = "EventKey") |
|||
private String eventKey; |
|||
|
|||
/** |
|||
* Event: subscribe/SCAN |
|||
*/ |
|||
@JacksonXmlProperty(localName = "Ticket") |
|||
private String ticket; |
|||
/** |
|||
* Event: Location |
|||
*/ |
|||
@JacksonXmlProperty(localName = "Latitude") |
|||
private String latitude; |
|||
@JacksonXmlProperty(localName = "Longitude") |
|||
private String longitude; |
|||
@JacksonXmlProperty(localName = "Precision") |
|||
private String precision; |
|||
|
|||
/** |
|||
* Event: VIEW/view_miniprogram |
|||
*/ |
|||
@JacksonXmlProperty(localName = "MenuId") |
|||
private String menuId; |
|||
/** |
|||
* Event: scancode_push/scancode_waitmsg |
|||
*/ |
|||
@JacksonXmlProperty(localName = "ScanCodeInfo") |
|||
private ScanCodeInfo scanCodeInfo; |
|||
/** |
|||
* Event: pic_sysphoto/pic_photo_or_album/pic_weixin |
|||
*/ |
|||
@JacksonXmlProperty(localName = "SendPicsInfo") |
|||
private SendPicsInfo sendPicsInfo; |
|||
/** |
|||
* Event: location_select |
|||
*/ |
|||
@JacksonXmlProperty(localName = "SendLocationInfo") |
|||
private SendLocationInfo sendLocationInfo; |
|||
} |
@ -0,0 +1,15 @@ |
|||
package com.ccsens.opensource.wxconfigurer.bean.po; |
|||
|
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
@Data |
|||
public class WxAccessToken { |
|||
private String access_token; |
|||
private Long expires_in; |
|||
private Integer errcode; |
|||
private String errmsg; |
|||
private Long createdAt; |
|||
} |
@ -0,0 +1,96 @@ |
|||
package com.ccsens.opensource.wxconfigurer.bean.po; |
|||
|
|||
import cn.hutool.core.collection.CollectionUtil; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import com.fasterxml.jackson.annotation.JsonValue; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
|
|||
import java.util.Collection; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
@Data |
|||
public class WxGzhMenu { |
|||
public enum ButtonType{ |
|||
//点击推事件
|
|||
CLICK("click"), |
|||
//跳转URL
|
|||
VIEW("view"), |
|||
//小程序
|
|||
MINIPROGRAM("miniprogram"), |
|||
//扫码推事件
|
|||
SCANCODE_PUSH("scancode_push"), |
|||
//扫码推事件且弹出“消息接收中”提示框
|
|||
SCANCODE_WAITMSG("scancode_waitmsg"), |
|||
//弹出系统拍照发图
|
|||
PIC_SYSPHOTO("pic_sysphoto"), |
|||
//弹出拍照或者相册发图
|
|||
PIC_PHOTO_OR_ALBUM("pic_photo_or_album"), |
|||
//弹出微信相册发图器
|
|||
PIC_WEIXIN("pic_weixin"), |
|||
//弹出地理位置选择器
|
|||
LOCATION_SELECT("location_select"), |
|||
//下发消息(除文本消息)
|
|||
MEDIA_ID("media_id"), |
|||
//跳转图文消息URL
|
|||
VIEW_LIMITED("view_limited"); |
|||
|
|||
@JsonValue |
|||
private String text; |
|||
|
|||
ButtonType(String text){ |
|||
this.text = text; |
|||
} |
|||
} |
|||
@Builder(toBuilder = true) |
|||
@Getter |
|||
public static class Button{ |
|||
/** |
|||
* 必须 |
|||
*/ |
|||
private ButtonType type; |
|||
/** |
|||
* 必须 |
|||
*/ |
|||
private String name; |
|||
/** |
|||
* click等点击类型必须 |
|||
*/ |
|||
private String key; |
|||
/** |
|||
* view、miniprogram类型必须 |
|||
*/ |
|||
private String url; |
|||
/** |
|||
* media_id类型和view_limited类型必须 |
|||
*/ |
|||
private String media_id; |
|||
/** |
|||
* miniprogram类型必须 |
|||
*/ |
|||
private String appid; |
|||
private String pagepath; |
|||
/** |
|||
* optional |
|||
*/ |
|||
private List<Button> sub_button; |
|||
} |
|||
@JsonProperty("button") |
|||
private List<Button> buttons; |
|||
|
|||
public static WxGzhMenu newInstance(){ |
|||
return new WxGzhMenu(); |
|||
} |
|||
|
|||
public WxGzhMenu add(Button button){ |
|||
if(buttons == null){ |
|||
buttons = CollectionUtil.newArrayList(); |
|||
} |
|||
buttons.add(button); |
|||
return this; |
|||
} |
|||
} |
@ -0,0 +1,52 @@ |
|||
package com.ccsens.opensource.wxconfigurer.bean.po; |
|||
|
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
public enum WxGzhMsgEvent { |
|||
/** |
|||
* 关注/取消关注事件 |
|||
*/ |
|||
SUBSCRIBE("subscribe"),UNSUBSCRIBE("unsubscribe"), |
|||
/** |
|||
* 扫描带参数(公众号)二维码事件 |
|||
* 未关注:产生Event: SUBSCRIBE EventKey: qrscene_xxx |
|||
* 已关注:产生Event: Scan EventKey: scene_value |
|||
*/ |
|||
SCAN("SCAN"), |
|||
/** |
|||
* 上报地理位置事件 |
|||
*/ |
|||
LOCATION("LOCATION"), |
|||
/** |
|||
* 自定义菜单事件 |
|||
*/ |
|||
//点击菜单拉取消息时的事件推送
|
|||
CLICK("CLICK"), |
|||
//点击菜单跳转链接时的事件推送
|
|||
VIEW("VIEW"), |
|||
//扫码推事件的事件推送
|
|||
SCANCODE_PUSH("scancode_push"), |
|||
//扫码推事件且弹出“消息接收中”提示框的事件推送
|
|||
SCANCODE_WAITMSG("scancode_waitmsg"), |
|||
//弹出系统拍照发图的事件推送
|
|||
PIC_SYSPHOTO("pic_sysphoto"), |
|||
//弹出拍照或者相册发图的事件推送
|
|||
PIC_PHOTO_OR_ALBUM("pic_photo_or_album"), |
|||
//弹出微信相册发图器的事件推送
|
|||
PIC_WEIXIN("pic_weixin"), |
|||
//弹出地理位置选择器的事件推送
|
|||
LOCATION_SELECT("location_select"), |
|||
//点击菜单跳转小程序的事件推送
|
|||
VIEW_MINIPROGRAM("view_miniprogram"); |
|||
|
|||
@Getter@Setter |
|||
private String text; |
|||
|
|||
private WxGzhMsgEvent(String text){ |
|||
this.text = text; |
|||
} |
|||
} |
@ -0,0 +1,33 @@ |
|||
package com.ccsens.opensource.wxconfigurer.bean.po; |
|||
|
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
public enum WxGzhMsgType { |
|||
/** |
|||
* 1.普通消息 |
|||
*/ |
|||
//1.1 文本消息
|
|||
TEXT("text"), |
|||
IMAGE("image"), |
|||
VOICE("voice"), |
|||
VIDEO("video"), |
|||
SHORTVIDEO("shortvideo"), |
|||
LOCATION("location"), |
|||
LINK("link"), |
|||
|
|||
/** |
|||
* 2.事件推送 |
|||
*/ |
|||
EVENT("event"); |
|||
|
|||
@Getter @Setter |
|||
private String text; |
|||
|
|||
private WxGzhMsgType(String text){ |
|||
this.text = text; |
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
package com.ccsens.opensource.wxconfigurer.bean.po; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonValue; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
|
|||
public enum WxOperation { |
|||
/** |
|||
* 创建公众号底部菜单 |
|||
*/ |
|||
CREATE_MENU("CreateMenu"); |
|||
|
|||
@JsonValue |
|||
private String text; |
|||
|
|||
WxOperation(String text){ |
|||
this.text = text; |
|||
} |
|||
} |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.config; |
|||
package com.ccsens.opensource.wxconfigurer.config; |
|||
|
|||
import com.jfinal.template.Engine; |
|||
import com.jfinal.template.ext.spring.JFinalViewResolver; |
@ -1,7 +1,7 @@ |
|||
package wiki.tall.ccmq.common.config; |
|||
package com.ccsens.opensource.wxconfigurer.config; |
|||
|
|||
import wiki.tall.ccmq.common.exception.BaseException; |
|||
import wiki.tall.ccmq.common.util.JsonResponse; |
|||
import com.ccsens.opensource.wxconfigurer.exception.BaseException; |
|||
import com.ccsens.opensource.wxconfigurer.util.JsonResponse; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.http.converter.HttpMessageNotReadableException; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.config; |
|||
package com.ccsens.opensource.wxconfigurer.config; |
|||
|
|||
import org.springframework.amqp.core.Queue; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.config; |
|||
package com.ccsens.opensource.wxconfigurer.config; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonAutoDetect; |
|||
import com.fasterxml.jackson.annotation.PropertyAccessor; |
@ -1,6 +1,6 @@ |
|||
package wiki.tall.ccmq.common.config; |
|||
package com.ccsens.opensource.wxconfigurer.config; |
|||
|
|||
import wiki.tall.ccmq.common.util.HttpsClientRequestFactory; |
|||
import com.ccsens.opensource.wxconfigurer.util.HttpsClientRequestFactory; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.autoconfigure.AutoConfigureAfter; |
|||
import org.springframework.boot.web.client.RestTemplateBuilder; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.config; |
|||
package com.ccsens.opensource.wxconfigurer.config; |
|||
|
|||
import com.alibaba.druid.support.http.StatViewServlet; |
|||
import com.alibaba.druid.support.http.WebStatFilter; |
@ -1,9 +1,9 @@ |
|||
package wiki.tall.ccmq.common.config; |
|||
package com.ccsens.opensource.wxconfigurer.config; |
|||
|
|||
|
|||
import cn.hutool.core.lang.Snowflake; |
|||
import cn.hutool.core.util.IdUtil; |
|||
import wiki.tall.ccmq.common.controller.interceptor.TokenInterceptor; |
|||
import com.ccsens.opensource.wxconfigurer.web.interceptor.TokenInterceptor; |
|||
import com.fasterxml.jackson.databind.DeserializationFeature; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import com.fasterxml.jackson.databind.module.SimpleModule; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.config; |
|||
package com.ccsens.opensource.wxconfigurer.config; |
|||
|
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Configuration; |
@ -0,0 +1,21 @@ |
|||
package com.ccsens.opensource.wxconfigurer.exception; |
|||
|
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
@Data |
|||
public class BaseException extends Exception { |
|||
private String operation; |
|||
private Integer code; |
|||
|
|||
public BaseException(){ |
|||
|
|||
} |
|||
|
|||
public BaseException(Integer code,String message){ |
|||
super(message); |
|||
this.code = code; |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
package com.ccsens.opensource.wxconfigurer.exception; |
|||
|
|||
import com.ccsens.opensource.wxconfigurer.bean.po.WxOperation; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
public class BusinessException extends BaseException { |
|||
private String type = "BusinessError"; |
|||
|
|||
public BusinessException(){ |
|||
|
|||
} |
|||
|
|||
public BusinessException(Integer errcode,String errmsg){ |
|||
super(errcode,errmsg); |
|||
} |
|||
} |
@ -0,0 +1,22 @@ |
|||
package com.ccsens.opensource.wxconfigurer.exception; |
|||
|
|||
import com.ccsens.opensource.wxconfigurer.bean.po.WxOperation; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
@Data |
|||
public class WxException extends BaseException{ |
|||
private String type = "BusinessError"; |
|||
|
|||
public WxException(){ |
|||
|
|||
} |
|||
|
|||
public WxException(Integer errcode,String errmsg){ |
|||
super(errcode,errmsg); |
|||
} |
|||
} |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import cn.hutool.core.bean.BeanUtil; |
|||
import cn.hutool.core.collection.CollectionUtil; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
public class CRCUtil { |
|||
public static void crc16(byte crc[], byte data[], int index, int len) { |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import java.util.TimeZone; |
|||
|
@ -0,0 +1,87 @@ |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import cn.hutool.core.lang.Console; |
|||
|
|||
import java.io.FileInputStream; |
|||
import java.io.IOException; |
|||
import java.math.BigInteger; |
|||
import java.security.MessageDigest; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
public class FileUtil { |
|||
public static String getFileMD5(String file) { |
|||
String str = null; |
|||
FileInputStream in = null; |
|||
try { |
|||
in = new FileInputStream(file); |
|||
MessageDigest digest = MessageDigest.getInstance("MD5"); |
|||
byte[] buffer = new byte[1024 * 1024 * 10]; |
|||
|
|||
int len = 0; |
|||
while ((len = in.read(buffer)) > 0) { |
|||
digest.update(buffer, 0, len); |
|||
} |
|||
String md5 = new BigInteger(1, digest.digest()).toString(16); |
|||
int length = 32 - md5.length(); |
|||
if (length > 0) { |
|||
for (int i = 0; i < length; i++) { |
|||
md5 = "0" + md5; |
|||
} |
|||
} |
|||
str = md5; |
|||
} catch (Exception e) { |
|||
System.out.println(e); |
|||
} finally { |
|||
try { |
|||
if (in != null) { |
|||
in.close(); |
|||
} |
|||
} catch (IOException e) { |
|||
System.out.println(e); |
|||
} |
|||
} |
|||
return str; |
|||
} |
|||
|
|||
public static String getFileSha1(String file) { |
|||
String str = null; |
|||
FileInputStream in = null; |
|||
try { |
|||
in = new FileInputStream(file); |
|||
MessageDigest digest = MessageDigest.getInstance("SHA-1"); |
|||
byte[] buffer = new byte[1024 * 1024 * 10]; |
|||
|
|||
int len = 0; |
|||
while ((len = in.read(buffer)) > 0) { |
|||
digest.update(buffer, 0, len); |
|||
} |
|||
String sha1 = new BigInteger(1, digest.digest()).toString(16); |
|||
int length = 40 - sha1.length(); |
|||
if (length > 0) { |
|||
for (int i = 0; i < length; i++) { |
|||
sha1 = "0" + sha1; |
|||
} |
|||
} |
|||
str = sha1; |
|||
} catch (Exception e) { |
|||
System.out.println(e); |
|||
} finally { |
|||
try { |
|||
if (in != null) { |
|||
in.close(); |
|||
} |
|||
} catch (Exception e) { |
|||
System.out.println(e); |
|||
} |
|||
} |
|||
return str; |
|||
} |
|||
|
|||
public static void main(String[] args) { |
|||
String path = "D:\\cygwin64\\Cygwin.ico"; |
|||
Console.log(getFileMD5(path)); |
|||
Console.log(getFileSha1(path)); |
|||
} |
|||
} |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import org.springframework.web.context.request.RequestContextHolder; |
|||
import org.springframework.web.context.request.ServletRequestAttributes; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import org.springframework.http.client.SimpleClientHttpRequestFactory; |
|||
|
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import org.apache.http.HttpEntity; |
|||
import org.apache.http.client.methods.CloseableHttpResponse; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import cn.hutool.core.lang.Snowflake; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import sun.font.FontDesignMetrics; |
|||
|
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import cn.hutool.core.collection.CollectionUtil; |
|||
import cn.hutool.core.util.StrUtil; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import com.jayway.jsonpath.Configuration; |
|||
import com.jayway.jsonpath.Option; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collection; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import cn.hutool.core.codec.Base64; |
|||
import cn.hutool.core.util.StrUtil; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonInclude; |
|||
import com.fasterxml.jackson.core.JsonParser; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
public class NotSupportedFileTypeException extends Exception { |
|||
public NotSupportedFileTypeException() { |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import javax.crypto.SecretKeyFactory; |
|||
import javax.crypto.spec.PBEKeySpec; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
/** |
|||
* @author __zHangSan |
@ -1,6 +1,5 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.data.redis.core.RedisTemplate; |
|||
import org.springframework.util.CollectionUtils; |
|||
import java.util.List; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import java.io.BufferedReader; |
|||
import java.io.InputStream; |
@ -0,0 +1,188 @@ |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import cn.hutool.core.date.DateUtil; |
|||
import cn.hutool.core.io.FileUtil; |
|||
import cn.hutool.core.lang.Console; |
|||
import cn.hutool.core.util.StrUtil; |
|||
import cn.hutool.crypto.SecureUtil; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
import javax.servlet.http.Part; |
|||
import java.io.*; |
|||
import java.net.URL; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* @author __zHangSan |
|||
*/ |
|||
public class ServletUtil extends cn.hutool.extra.servlet.ServletUtil { |
|||
|
|||
public static void responseJson(HttpServletResponse response, String json) throws IOException { |
|||
response.setCharacterEncoding("UTF-8"); |
|||
response.setContentType("application/json;charset=utf-8"); |
|||
PrintWriter out = response.getWriter(); |
|||
out.append(json); |
|||
out.close(); |
|||
} |
|||
|
|||
/** |
|||
* 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址, |
|||
* |
|||
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢? |
|||
* 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。 |
|||
* |
|||
* 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130, |
|||
* 192.168.1.100 |
|||
* |
|||
* 用户真实IP为: 192.168.1.110 |
|||
* |
|||
* @param request |
|||
* @return |
|||
*/ |
|||
public static String getIpAddress(HttpServletRequest request) { |
|||
String ip = request.getHeader("x-forwarded-for"); |
|||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { |
|||
ip = request.getHeader("Proxy-Client-IP"); |
|||
} |
|||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { |
|||
ip = request.getHeader("WL-Proxy-Client-IP"); |
|||
} |
|||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { |
|||
ip = request.getHeader("HTTP_CLIENT_IP"); |
|||
} |
|||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { |
|||
ip = request.getHeader("HTTP_X_FORWARDED_FOR"); |
|||
} |
|||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { |
|||
ip = request.getRemoteAddr(); |
|||
} |
|||
return ip; |
|||
} |
|||
|
|||
public static String getSiteBasePath(HttpServletRequest request){ |
|||
return request.getScheme() + "://" |
|||
+ request.getServerName() + ":" + request.getServerPort(); |
|||
} |
|||
|
|||
public static String getSiteContextPath(HttpServletRequest request){ |
|||
return getSiteBasePath(request) + "/" + request.getContextPath(); |
|||
} |
|||
|
|||
/** |
|||
* Servlet3.0文件上传 (生成uuid.txt文件) |
|||
* @param part Servlet3.0 API ---> Part |
|||
* @param allowedExts 允许的后缀名 |
|||
* @param dir 目录 |
|||
* @return 文件名 |
|||
* @throws IOException |
|||
* @throws NotSupportedFileTypeException |
|||
*/ |
|||
public static String uploadFile(Part part, String allowedExts, String dir) |
|||
throws IOException, NotSupportedFileTypeException { |
|||
String extraPath = DateUtil.format(new Date(), "yyyyMMdd"); |
|||
|
|||
//1.判断是否为空
|
|||
if (part == null) { |
|||
return null; |
|||
} |
|||
|
|||
//2.生成文件名 [uuid.ext]
|
|||
String original = getFileNameByPart(part); |
|||
String ext = FileUtil.extName(original); |
|||
if (StrUtil.isEmpty(ext) || !allowedExts.contains(ext)) { |
|||
throw new NotSupportedFileTypeException("Not Supported File Type: " + ext); |
|||
} |
|||
String path = extraPath + File.separator + SecureUtil.simpleUUID() + "." + ext; |
|||
//3.创建必要的目录
|
|||
File tmpFile = new File(dir); |
|||
if (!tmpFile.exists()) { |
|||
tmpFile.mkdirs(); |
|||
} |
|||
|
|||
//4.写入磁盘
|
|||
String fullPath = dir + File.separator + path; |
|||
FileUtil.writeFromStream(part.getInputStream(),fullPath); |
|||
|
|||
return path; |
|||
} |
|||
|
|||
public static byte[] uploadFile(Part part) |
|||
throws IOException, NotSupportedFileTypeException { |
|||
//1.判断是否为空
|
|||
if (part == null) { |
|||
return null; |
|||
} |
|||
|
|||
//2.读取文件
|
|||
InputStream is = part.getInputStream(); |
|||
byte[] bytes = new byte[is.available()]; |
|||
is.read(bytes); |
|||
|
|||
return bytes; |
|||
} |
|||
|
|||
/** |
|||
* 根据请求头解析出文件名 |
|||
* 请求头的格式:火狐和google浏览器下:form-data; name="file"; filename="snmp4j--api.zip" |
|||
* IE浏览器下:form-data; name="file"; filename="E:\snmp4j--api.zip" |
|||
* @param part 包含请求头 |
|||
* @return 文件名 |
|||
*/ |
|||
public static String getFileNameByPart(Part part) { |
|||
String header = part.getHeader("content-disposition"); |
|||
/** |
|||
* String[] tempArr1 = header.split(";");代码执行完之后,在不同的浏览器下,tempArr1数组里面的内容稍有区别 |
|||
* 火狐或者google浏览器下:tempArr1={form-data,name="file",filename="snmp4j--api.zip"} |
|||
* IE浏览器下:tempArr1={form-data,name="file",filename="E:\snmp4j--api.zip"} |
|||
*/ |
|||
String[] tempArr1 = header.split(";"); |
|||
/** |
|||
*火狐或者google浏览器下:tempArr2={filename,"snmp4j--api.zip"} |
|||
*IE浏览器下:tempArr2={filename,"E:\snmp4j--api.zip"} |
|||
*/ |
|||
String[] tempArr2 = tempArr1[2].split("="); |
|||
//获取文件名,兼容各种浏览器的写法
|
|||
String fileName = tempArr2[1].substring(tempArr2[1].lastIndexOf("\\")+1).replaceAll("\"", ""); |
|||
return fileName; |
|||
} |
|||
|
|||
public static void downLoadFile(HttpServletResponse response, String filePath, String filename, String contentType,boolean isOnLine) throws Exception { |
|||
File f = new File(filePath); |
|||
if (!f.exists()) { |
|||
response.sendError(404, "File not found!"); |
|||
return; |
|||
} |
|||
|
|||
if(StrUtil.isEmpty(filename)){ |
|||
filename = f.getName(); |
|||
} |
|||
if(StrUtil.isEmpty(contentType)){ |
|||
contentType = "application/octet-stream"; |
|||
} |
|||
|
|||
BufferedInputStream br = new BufferedInputStream(new FileInputStream(f)); |
|||
byte[] buf = new byte[1024]; |
|||
int len = 0; |
|||
|
|||
// 非常重要
|
|||
response.reset(); |
|||
if (isOnLine) { |
|||
URL u = new URL("file:///" + filePath); |
|||
response.setContentType(u.openConnection().getContentType()); |
|||
response.setHeader("Content-Disposition", "inline; filename=" + filename); |
|||
// 文件名应该编码成UTF-8
|
|||
} else { |
|||
response.setContentType(contentType); |
|||
response.setHeader("Content-Disposition", "attachment; filename=" + filename); |
|||
} |
|||
|
|||
Console.log("filename: {}",filename); |
|||
OutputStream out = response.getOutputStream(); |
|||
while ((len = br.read(buf)) > 0) { |
|||
out.write(buf, 0, len); |
|||
} |
|||
br.close(); |
|||
out.close(); |
|||
} |
|||
} |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import org.springframework.beans.BeansException; |
|||
import org.springframework.context.ApplicationContext; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import lombok.Data; |
|||
|
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
public class TokenBean1 { |
|||
private String access_token; |
@ -1,4 +1,4 @@ |
|||
package wiki.tall.ccmq.common.util; |
|||
package com.ccsens.opensource.wxconfigurer.util; |
|||
|
|||
import cn.hutool.core.codec.Base64; |
|||
|
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue