第一次提交
This commit is contained in:
12
fastbee-server/base-server/pom.xml
Normal file
12
fastbee-server/base-server/pom.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>fastbee-server</artifactId>
|
||||
<groupId>com.fastbee</groupId>
|
||||
<version>3.8.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>base-server</artifactId>
|
||||
</project>
|
@ -0,0 +1,29 @@
|
||||
package com.fastbee.base.codec;
|
||||
|
||||
/**
|
||||
* 分隔符报文处理器
|
||||
* 处理分割符报文,tcp粘包处理
|
||||
* @author bill
|
||||
*/
|
||||
public class Delimiter {
|
||||
|
||||
public final byte[] value;
|
||||
public final boolean strip;
|
||||
|
||||
public Delimiter(byte[] value) {
|
||||
this(value, true);
|
||||
}
|
||||
|
||||
public Delimiter(byte[] value, boolean strip) {
|
||||
this.value = value;
|
||||
this.strip = strip;
|
||||
}
|
||||
|
||||
public byte[] getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean isStrip() {
|
||||
return strip;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.fastbee.base.codec;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkPositive;
|
||||
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
|
||||
|
||||
/**
|
||||
* 固定长度报文处理器
|
||||
* 处理固定长度报文,tcp粘包处理
|
||||
* @author bill
|
||||
*/
|
||||
public class LengthField {
|
||||
|
||||
public final byte[] prefix;
|
||||
/*最大帧长度*/
|
||||
public final int lengthFieldMaxFrameLength;
|
||||
/*偏移量*/
|
||||
public final int lengthFieldOffset;
|
||||
/*字段长度*/
|
||||
public final int lengthFieldLength;
|
||||
/*结尾偏移量*/
|
||||
public final int lengthFieldEndOffset;
|
||||
/*报文调整 默认0,不调整*/
|
||||
public final int lengthAdjustment;
|
||||
/*,默认0*/
|
||||
public final int initialBytesToStrip;
|
||||
|
||||
/**构造固定长度处理器*/
|
||||
public LengthField(byte[] prefix, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
|
||||
this(prefix, maxFrameLength, lengthFieldOffset, lengthFieldLength, 0, 0);
|
||||
}
|
||||
|
||||
public LengthField(byte[] prefix, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
|
||||
checkPositive(maxFrameLength, "maxFrameLength_LengthField");
|
||||
checkPositiveOrZero(lengthFieldOffset, "lengthFieldOffset");
|
||||
checkPositiveOrZero(initialBytesToStrip, "initialBytesToStrip");
|
||||
if (lengthFieldOffset > maxFrameLength - lengthFieldLength) {
|
||||
throw new IllegalArgumentException("maxFrameLength_LengthField (" + maxFrameLength + ") must be equal to or greater than lengthFieldOffset (" + lengthFieldOffset + ") + lengthFieldLength (" + lengthFieldLength + ").");
|
||||
} else {
|
||||
this.prefix = prefix;
|
||||
this.lengthFieldMaxFrameLength = maxFrameLength;
|
||||
this.lengthFieldOffset = lengthFieldOffset;
|
||||
this.lengthFieldLength = lengthFieldLength;
|
||||
this.lengthAdjustment = lengthAdjustment;
|
||||
this.lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength;
|
||||
this.initialBytesToStrip = initialBytesToStrip;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public int getLengthFieldMaxFrameLength() {
|
||||
return lengthFieldMaxFrameLength;
|
||||
}
|
||||
|
||||
public int getLengthFieldOffset() {
|
||||
return lengthFieldOffset;
|
||||
}
|
||||
|
||||
public int getLengthFieldLength() {
|
||||
return lengthFieldLength;
|
||||
}
|
||||
|
||||
public int getLengthFieldEndOffset() {
|
||||
return lengthFieldEndOffset;
|
||||
}
|
||||
|
||||
public int getLengthAdjustment() {
|
||||
return lengthAdjustment;
|
||||
}
|
||||
|
||||
public int getInitialBytesToStrip() {
|
||||
return initialBytesToStrip;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.fastbee.base.codec;
|
||||
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceReport;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* 基础消息解码类
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
public interface MessageDecoder {
|
||||
|
||||
/**
|
||||
* TCP3.进站消息解码方法
|
||||
*/
|
||||
DeviceReport decode(ByteBuf buf, String clientId);
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.fastbee.base.codec;
|
||||
|
||||
import com.fastbee.common.core.protocol.Message;
|
||||
import com.fastbee.base.session.Session;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* 基础消息编码类
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
public interface MessageEncoder{
|
||||
|
||||
ByteBuf encode(Message message, String clientId);
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package com.fastbee.base.core;
|
||||
|
||||
import com.fastbee.base.core.annotation.Async;
|
||||
import com.fastbee.base.core.annotation.AsyncBatch;
|
||||
import com.fastbee.base.core.annotation.PakMapping;
|
||||
import com.fastbee.base.core.hanler.AsyncBatchHandler;
|
||||
import com.fastbee.base.core.hanler.BaseHandler;
|
||||
import com.fastbee.base.core.hanler.SyncHandler;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 消息处理映射
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
public abstract class AbstractHandlerMapping implements HandlerMapping {
|
||||
|
||||
private final Map<Object, BaseHandler> handlerMap = new HashMap<>(64);
|
||||
|
||||
/**
|
||||
* 将node中被@Column标记的方法注册到映射表
|
||||
*/
|
||||
protected synchronized void registerHandlers(Object bean) {
|
||||
Class<?> beanClass = bean.getClass();
|
||||
Method[] methods = beanClass.getDeclaredMethods();
|
||||
|
||||
for (Method method : methods) {
|
||||
PakMapping annotation = method.getAnnotation(PakMapping.class);
|
||||
if (annotation != null) {
|
||||
|
||||
String desc = annotation.desc();
|
||||
int[] types = annotation.types();
|
||||
|
||||
AsyncBatch asyncBatch = method.getAnnotation(AsyncBatch.class);
|
||||
BaseHandler baseHandler;
|
||||
// 异步处理
|
||||
if (asyncBatch != null) {
|
||||
baseHandler = new AsyncBatchHandler(bean, method, desc, asyncBatch.poolSize(), asyncBatch.maxMessageSize(), asyncBatch.maxWaitTime());
|
||||
} else {
|
||||
baseHandler = new SyncHandler(bean, method, desc, method.isAnnotationPresent(Async.class));
|
||||
}
|
||||
|
||||
for (int type : types) {
|
||||
handlerMap.put(type, baseHandler);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据消息类型获取handler
|
||||
*/
|
||||
@Override
|
||||
public BaseHandler getHandler(int messageId) {
|
||||
return handlerMap.get(messageId);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.fastbee.base.core;
|
||||
|
||||
import com.fastbee.base.core.annotation.Node;
|
||||
import com.fastbee.base.util.ClassUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 默认消息映射处理类
|
||||
* @author bill
|
||||
*/
|
||||
public class DefaultHandlerMapping extends AbstractHandlerMapping {
|
||||
|
||||
public DefaultHandlerMapping(String endpointPackage) {
|
||||
List<Class> endpointClasses = ClassUtils.getClassList(endpointPackage, Node.class);
|
||||
|
||||
for (Class endpointClass : endpointClasses) {
|
||||
try {
|
||||
Object bean = endpointClass.getDeclaredConstructor((Class[]) null).newInstance((Object[]) null);
|
||||
super.registerHandlers(bean);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.fastbee.base.core;
|
||||
|
||||
|
||||
import com.fastbee.common.core.protocol.Message;
|
||||
import com.fastbee.base.session.Session;
|
||||
|
||||
/**
|
||||
* 消息拦截器
|
||||
* @author bill
|
||||
*/
|
||||
public interface HandlerInterceptor<T extends Message> {
|
||||
|
||||
/**
|
||||
* 未匹配到对应的Handle(消息处理)
|
||||
*/
|
||||
T notSupported(T request, Session session);
|
||||
|
||||
/**
|
||||
* 调用之前
|
||||
* 处理消息类型匹配
|
||||
*/
|
||||
boolean beforeHandle(T request, Session session);
|
||||
|
||||
/**
|
||||
* 需要应答设备,在这里执行
|
||||
* 调用之后,返回值为void的 */
|
||||
T successful(T request, Session session);
|
||||
|
||||
/** 调用之后,有返回值的 */
|
||||
void afterHandle(T request, T response, Session session);
|
||||
|
||||
/**
|
||||
* 报错应答方法
|
||||
* 调用之后抛出异常的
|
||||
*/
|
||||
T exceptional(T request, Session session, Exception e);
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.fastbee.base.core;
|
||||
|
||||
import com.fastbee.base.core.hanler.BaseHandler;
|
||||
|
||||
/**
|
||||
* 消息处理接口
|
||||
* @author bill
|
||||
*/
|
||||
public interface HandlerMapping {
|
||||
|
||||
BaseHandler getHandler(int messageId);
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.fastbee.base.core;
|
||||
|
||||
import com.fastbee.base.core.annotation.Node;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
public class SpringHandlerMapping extends AbstractHandlerMapping implements ApplicationContextAware {
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
Map<String, Object> endpoints = applicationContext.getBeansWithAnnotation(Node.class);
|
||||
for (Object bean : endpoints.values()) {
|
||||
super.registerHandlers(bean);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.fastbee.base.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 异步处理设备数据
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Async {
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.fastbee.base.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 多线程异步处理设备数据,新建线程组处理
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface AsyncBatch {
|
||||
|
||||
/*批量处理的最大消息数*/
|
||||
int maxMessageSize() default 5000;
|
||||
|
||||
/*线程数*/
|
||||
int poolSize() default 2;
|
||||
|
||||
/*最大等待时间*/
|
||||
int maxWaitTime() default 1000;
|
||||
|
||||
/*最小处理消息数*/
|
||||
int minMessageSize() default 100;
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.fastbee.base.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 消息节点
|
||||
* @author bill
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Node {
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.fastbee.base.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 字段映射
|
||||
* @author bill
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface PakMapping {
|
||||
|
||||
int[] types();
|
||||
|
||||
String desc() default "";
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
package com.fastbee.base.core.hanler;
|
||||
|
||||
import com.fastbee.common.core.protocol.Message;
|
||||
import com.fastbee.base.session.Session;
|
||||
import com.fastbee.base.util.VirtualList;
|
||||
import com.fastbee.common.exception.ServiceException;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 异步批量处理报文
|
||||
* @author bill
|
||||
*/
|
||||
@Slf4j
|
||||
public class AsyncBatchHandler extends BaseHandler{
|
||||
|
||||
|
||||
/*消息处理队列*/
|
||||
private final ConcurrentLinkedQueue<Message> queue;
|
||||
|
||||
/*线程池*/
|
||||
private final ThreadPoolExecutor executor;
|
||||
|
||||
private final int poolSize;
|
||||
|
||||
private final int maxEventSize;
|
||||
|
||||
private final int maxWait;
|
||||
|
||||
private final int warningLines;
|
||||
|
||||
|
||||
public AsyncBatchHandler(Object target, Method targetMethod, String desc, int poolSize, int maxEventSize, int maxWait) {
|
||||
|
||||
super(target, targetMethod, desc);
|
||||
Class<?>[] parameterTypes = targetMethod.getParameterTypes();
|
||||
if (parameterTypes.length >1){
|
||||
throw new ServiceException("参数列表过长");
|
||||
}
|
||||
if (!parameterTypes[0].isAssignableFrom(List.class)){
|
||||
throw new ServiceException("参数不是List类型");
|
||||
}
|
||||
|
||||
this.poolSize = poolSize;
|
||||
this.maxEventSize = maxEventSize;
|
||||
this.maxWait = maxWait;
|
||||
this.warningLines = maxEventSize * poolSize * 50;
|
||||
|
||||
this.queue = new ConcurrentLinkedQueue<>();
|
||||
this.executor = new ThreadPoolExecutor(this.poolSize,this.poolSize,1000L, TimeUnit.MILLISECONDS
|
||||
,new LinkedBlockingQueue<>(500),new DefaultThreadFactory(targetMethod.getName()));
|
||||
|
||||
for (int i = 0; i < poolSize; i++) {
|
||||
boolean start = i == 0;
|
||||
executor.execute(()->{
|
||||
try {
|
||||
startInternal(start);
|
||||
}catch (Exception e){
|
||||
log.error("线程池处理数据出错",e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Message> T invoke(T request, Session session) throws Exception {
|
||||
queue.offer(request);
|
||||
return null;
|
||||
}
|
||||
|
||||
public void startInternal(boolean master) {
|
||||
Message[] array = new Message[maxEventSize];
|
||||
long logtime = 0;
|
||||
long starttime = 0;
|
||||
|
||||
for (; ; ) {
|
||||
Message temp;
|
||||
int i = 0;
|
||||
while ((temp = queue.poll()) != null) {
|
||||
array[i++] = temp;
|
||||
if (i >= maxEventSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
starttime = System.currentTimeMillis();
|
||||
try {
|
||||
targetMethod.invoke(targetObject, new VirtualList<>(array, i));
|
||||
} catch (Exception e) {
|
||||
log.warn(targetMethod.getName(), e);
|
||||
}
|
||||
long time = System.currentTimeMillis() - starttime;
|
||||
if (time > 1000L) {
|
||||
log.warn("线程池处理数据耗时:{}ms,共{}条记录", time, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (i < maxEventSize) {
|
||||
try {
|
||||
for (int j = 0; j < i; j++) {
|
||||
array[j] = null;
|
||||
}
|
||||
Thread.sleep(maxWait);
|
||||
} catch (InterruptedException e) {
|
||||
log.error("sleep error!");
|
||||
}
|
||||
} else if (master) {
|
||||
if (logtime < starttime) {
|
||||
logtime = starttime + 5000L;
|
||||
|
||||
int size = queue.size();
|
||||
if (size > warningLines) {
|
||||
log.warn("线程池队列已满, size:{}", size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package com.fastbee.base.core.hanler;
|
||||
|
||||
import com.fastbee.common.core.protocol.Message;
|
||||
import com.fastbee.base.session.Session;
|
||||
|
||||
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* 基础处理类
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
public abstract class BaseHandler {
|
||||
|
||||
public static final int MESSAGE = 0;
|
||||
public static final int SESSION = 1;
|
||||
|
||||
public final Object targetObject;
|
||||
public final Method targetMethod;
|
||||
public final int[] parameterTypes;
|
||||
public final boolean returnVoid;
|
||||
public final boolean async;
|
||||
public final String desc;
|
||||
|
||||
public BaseHandler(Object target, Method targetMethod, String desc) {
|
||||
this(target, targetMethod, desc, false);
|
||||
}
|
||||
|
||||
public BaseHandler(Object targetObject, Method targetMethod, String desc, boolean async) {
|
||||
this.targetObject = targetObject;
|
||||
this.targetMethod = targetMethod;
|
||||
this.returnVoid = targetMethod.getReturnType().isAssignableFrom(Void.TYPE);
|
||||
this.async = async;
|
||||
if (desc == null || desc.isEmpty())
|
||||
desc = targetMethod.getName();
|
||||
this.desc = desc;
|
||||
|
||||
Type[] types = targetMethod.getGenericParameterTypes();
|
||||
int[] parameterTypes = new int[types.length];
|
||||
try {
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
Type type = types[i];
|
||||
Class<?> clazz;
|
||||
if (type instanceof ParameterizedTypeImpl) {
|
||||
clazz = (Class<?>) ((ParameterizedTypeImpl) type).getActualTypeArguments()[0];
|
||||
} else {
|
||||
clazz = (Class<?>) type;
|
||||
}
|
||||
|
||||
if (Message.class.isAssignableFrom(clazz)) {
|
||||
parameterTypes[i] = MESSAGE;
|
||||
} else if (Session.class.isAssignableFrom(clazz)) {
|
||||
parameterTypes[i] = SESSION;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
this.parameterTypes = parameterTypes;
|
||||
}
|
||||
|
||||
public <T extends Message> T invoke(T request, Session session) throws Exception {
|
||||
Object[] args = new Object[parameterTypes.length];
|
||||
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
int type = parameterTypes[i];
|
||||
switch (type) {
|
||||
case BaseHandler.MESSAGE:
|
||||
args[i] = request;
|
||||
break;
|
||||
case BaseHandler.SESSION:
|
||||
args[i] = session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (T) targetMethod.invoke(targetObject, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return desc;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.fastbee.base.core.hanler;
|
||||
|
||||
import com.fastbee.common.core.protocol.Message;
|
||||
import com.fastbee.base.session.Session;
|
||||
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 同步处理报文
|
||||
* @author bill
|
||||
*/
|
||||
public class SyncHandler extends BaseHandler{
|
||||
|
||||
public SyncHandler(Object target, Method targetMethod, String desc,boolean async) {
|
||||
super(target, targetMethod, desc, async);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Message> T invoke(T request, Session session) throws Exception {
|
||||
return super.invoke(request, session);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.fastbee.base.core.model;
|
||||
|
||||
/**
|
||||
* 消息流水号响应
|
||||
* @author gsb
|
||||
* @date 2022/11/7 10:19
|
||||
*/
|
||||
public interface Response {
|
||||
|
||||
/**应答消息流水号*/
|
||||
int getResponseSerialNo();
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.fastbee.base.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author gsb
|
||||
* @date 2023/3/9 10:07
|
||||
*/
|
||||
@Data
|
||||
public class DeviceMsg {
|
||||
|
||||
protected String clientId;
|
||||
|
||||
protected Long deviceId;
|
||||
|
||||
private int protocolVersion;
|
||||
|
||||
private Long productId;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.fastbee.base.model;
|
||||
|
||||
import com.fastbee.base.session.Session;
|
||||
|
||||
/**
|
||||
* @author gsb
|
||||
* @date 2023/3/9 10:03
|
||||
*/
|
||||
public enum SessionKey {
|
||||
|
||||
DeviceMsg;
|
||||
|
||||
public static DeviceMsg getDeviceMsg(Session session){
|
||||
return (DeviceMsg)session.getAttribute(SessionKey.DeviceMsg);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.fastbee.base.service;
|
||||
|
||||
|
||||
import com.fastbee.base.session.Session;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
/**
|
||||
* 服务会话存储接口
|
||||
* @author gsb
|
||||
* @date 2022/10/14 14:16
|
||||
*/
|
||||
public interface ISessionStore {
|
||||
/**
|
||||
* session 会话存储
|
||||
*
|
||||
* @param clientId: 客户端标识
|
||||
* @param session: session会话
|
||||
|
||||
*/
|
||||
void storeSession(String clientId, Session session);
|
||||
|
||||
/**
|
||||
* 根据客户端标识获取相应会话
|
||||
*
|
||||
* @param clientId: 客户端标识
|
||||
*/
|
||||
Session getSession(String clientId);
|
||||
|
||||
/**
|
||||
* 清除历史会话状态
|
||||
*
|
||||
* @param clientId: 客户端标识
|
||||
*/
|
||||
void cleanSession(String clientId);
|
||||
|
||||
/**
|
||||
* 根据客户端标识查看是否存在该会话
|
||||
*
|
||||
* @param clientId:
|
||||
*/
|
||||
boolean containsKey(String clientId);
|
||||
|
||||
/**
|
||||
* 获取集合
|
||||
* @return MAP
|
||||
*/
|
||||
ConcurrentHashMap<String, Session> getSessionMap();
|
||||
|
||||
/**
|
||||
* map分页(从1开始)
|
||||
*
|
||||
* @param sourceMap 分页数据
|
||||
* @param pageSize 页面大小
|
||||
* @param currentPage 当前页面
|
||||
*/
|
||||
public Map<String, Session> listPage(Map<String, Session> sourceMap, int pageSize, int currentPage);
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package com.fastbee.base.service.impl;
|
||||
|
||||
import com.fastbee.base.service.ISessionStore;
|
||||
import com.fastbee.base.session.Session;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 内存存储服务会话
|
||||
*
|
||||
* @author gsb
|
||||
* @date 2022/10/14 14:18
|
||||
*/
|
||||
@Service
|
||||
public class SessionStoreImpl implements ISessionStore {
|
||||
|
||||
/*session存储集合*/
|
||||
private final ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* MQTT会话存储
|
||||
*
|
||||
* @param clientId: 客户端标识
|
||||
* @param session: MQTT会话
|
||||
*/
|
||||
@Override
|
||||
public void storeSession(String clientId, Session session) {
|
||||
sessionMap.put(clientId, session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据客户端标识获取相应会话
|
||||
*
|
||||
* @param clientId: 客户端标识
|
||||
*/
|
||||
@Override
|
||||
public Session getSession(String clientId) {
|
||||
return sessionMap.get(clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除历史会话状态
|
||||
*
|
||||
* @param clientId: 客户端标识
|
||||
*/
|
||||
@Override
|
||||
public void cleanSession(String clientId) {
|
||||
sessionMap.remove(clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据客户端标识查看是否存在该会话
|
||||
*
|
||||
* @param clientId:
|
||||
*/
|
||||
@Override
|
||||
public boolean containsKey(String clientId) {
|
||||
return sessionMap.containsKey(clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取集合
|
||||
* @return MAP
|
||||
*/
|
||||
@Override
|
||||
public ConcurrentHashMap<String, Session> getSessionMap(){
|
||||
return sessionMap;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* map分页(从1开始)
|
||||
*
|
||||
* @param sourceMap 分页数据
|
||||
* @param pageSize 页面大小
|
||||
* @param currentPage 当前页面
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Session> listPage(Map<String, Session> sourceMap, int pageSize, int currentPage) {
|
||||
Map<String, Session> map = new LinkedHashMap<>();
|
||||
if (sourceMap.size() > 0) {
|
||||
AtomicInteger flag = new AtomicInteger(0);
|
||||
AtomicInteger size = new AtomicInteger(0);
|
||||
int currIdx = (currentPage > 1 ? (currentPage - 1) * pageSize : 0);
|
||||
sourceMap.forEach((ass, list_km) -> {
|
||||
if (flag.get() >= currIdx) {
|
||||
if (size.get() < pageSize) {
|
||||
map.put(ass, list_km);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
size.getAndIncrement();
|
||||
}
|
||||
flag.getAndIncrement();
|
||||
});
|
||||
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package com.fastbee.base.session;
|
||||
|
||||
import com.fastbee.common.core.protocol.Message;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
|
||||
/**
|
||||
* 报文消息的包装
|
||||
* @author bill
|
||||
*/
|
||||
public abstract class Packet {
|
||||
|
||||
/*session*/
|
||||
public final Session session;
|
||||
/*基础消息*/
|
||||
public Message message;
|
||||
/*报文缓存buf*/
|
||||
public ByteBuf byteBuf;
|
||||
|
||||
public static Packet of(Session session, Message message) {
|
||||
if (session.isUdp()) {
|
||||
return new UDP(session, message, null);
|
||||
}
|
||||
return new TCP(session, message, message.getPayload());
|
||||
}
|
||||
|
||||
public static Packet of(Session session, ByteBuf message) {
|
||||
if (session.isUdp()) {
|
||||
return new UDP(session, null, message);
|
||||
}
|
||||
return new TCP(session, null, message);
|
||||
}
|
||||
|
||||
private Packet(Session session, Message message, ByteBuf byteBuf) {
|
||||
this.session = session;
|
||||
this.message = message;
|
||||
this.byteBuf = byteBuf;
|
||||
}
|
||||
|
||||
public Packet replace(Message message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ByteBuf take() {
|
||||
ByteBuf temp = this.byteBuf;
|
||||
this.byteBuf = null;
|
||||
return temp;
|
||||
}
|
||||
|
||||
public abstract Object wrap(ByteBuf byteBuf);
|
||||
|
||||
private static class TCP extends Packet {
|
||||
private TCP(Session session, Message message, ByteBuf byteBuf) {
|
||||
super(session, message, byteBuf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object wrap(ByteBuf byteBuf) {
|
||||
return byteBuf;
|
||||
}
|
||||
}
|
||||
|
||||
private static class UDP extends Packet {
|
||||
private UDP(Session session, Message message, ByteBuf byteBuf) {
|
||||
super(session, message, byteBuf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object wrap(ByteBuf byteBuf) {
|
||||
return new DatagramPacket(byteBuf, session.remoteAddress());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,286 @@
|
||||
package com.fastbee.base.session;
|
||||
|
||||
import com.fastbee.common.core.protocol.Message;
|
||||
import com.fastbee.common.enums.ServerType;
|
||||
import com.fastbee.common.exception.ServiceException;
|
||||
import com.fastbee.common.utils.DateUtils;
|
||||
import com.fastbee.common.utils.gateway.mq.Topics;
|
||||
import com.fastbee.base.core.model.Response;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.mqtt.MqttMessageType;
|
||||
import io.netty.handler.codec.mqtt.MqttVersion;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.MonoSink;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 会话
|
||||
*
|
||||
* @Author guanshubiao
|
||||
* @Date 2022/9/12 20:22
|
||||
*/
|
||||
@Data
|
||||
@Slf4j
|
||||
public class Session {
|
||||
|
||||
private boolean udp;
|
||||
private Function<Session, Boolean> remover;
|
||||
protected Channel channel;
|
||||
/**
|
||||
* 客户端id
|
||||
*/
|
||||
private String clientId;
|
||||
private String productId;
|
||||
private SessionManager sessionManager;
|
||||
private InetSocketAddress remoteAddress;
|
||||
private String remoteAddressStr;
|
||||
|
||||
private long creationTime;
|
||||
private long lastAccessTime;
|
||||
private Map<Object, Object> attributes;
|
||||
|
||||
private String sessionId;
|
||||
//原子计数器,报文没有消息流水号的,充当流水号
|
||||
private AtomicInteger serialNo = new AtomicInteger(0);
|
||||
private int keepAlive = 60;
|
||||
|
||||
|
||||
/*mqtt版本号*/
|
||||
private MqttVersion version;
|
||||
/*是否清楚会话*/
|
||||
private Boolean cleanSession = false;
|
||||
/*mqtt账号*/
|
||||
private String username;
|
||||
/*是否链接*/
|
||||
private Boolean connected = false;
|
||||
/*mqtt消息类型*/
|
||||
private MqttMessageType mqttMessageType;
|
||||
private int keepAliveMax = 120;
|
||||
/*主题*/
|
||||
private String topicName;
|
||||
/*Channel处理类上下文*/
|
||||
private ChannelHandlerContext handlerContext;
|
||||
|
||||
private List<Topics> topics;
|
||||
private Integer topicCount;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date connected_at;
|
||||
private String ip;
|
||||
/*服务协议类型 MQTT,TCP UDP COAP*/
|
||||
private ServerType serverType;
|
||||
|
||||
public Session(){
|
||||
|
||||
}
|
||||
|
||||
public Session(SessionManager sessionManager,
|
||||
Channel channel,
|
||||
InetSocketAddress remoteAddress,
|
||||
Function<Session, Boolean> remover,
|
||||
boolean udp,
|
||||
ServerType serverType) {
|
||||
this.channel = channel;
|
||||
this.creationTime = DateUtils.getTimestamp();
|
||||
this.lastAccessTime = creationTime;
|
||||
this.sessionManager = sessionManager;
|
||||
this.remoteAddress = remoteAddress;
|
||||
this.remoteAddressStr = remoteAddress.toString();
|
||||
this.remover = remover;
|
||||
this.udp = udp;
|
||||
this.serverType = serverType;
|
||||
this.ip = remoteAddress.getHostName();
|
||||
this.connected = true;
|
||||
this.connected_at = DateUtils.getNowDate();
|
||||
if (sessionManager != null && sessionManager.getSessionKeys() != null) {
|
||||
this.attributes = new EnumMap(sessionManager.getSessionKeys());
|
||||
} else {
|
||||
this.attributes = new TreeMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断设备是否已经注册上线
|
||||
*/
|
||||
public boolean isRegistered() {
|
||||
return clientId != null;
|
||||
}
|
||||
|
||||
/*设备端注册*/
|
||||
public void register(Message message) {
|
||||
register(message.getClientId(), message);
|
||||
}
|
||||
|
||||
public void register(String clientId, Message message) {
|
||||
//已经注册,不再发送注册包数据
|
||||
if (isRegistered()){
|
||||
return;
|
||||
}
|
||||
if (clientId == null) {
|
||||
throw new ServiceException("客户端注册clientId不能为空");
|
||||
}
|
||||
this.clientId = clientId.toUpperCase();
|
||||
if (sessionManager != null) {
|
||||
sessionManager.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
public Object getAttribute(Object name) {
|
||||
return attributes.get(name);
|
||||
}
|
||||
|
||||
public void setAttribute(Object name, Object value) {
|
||||
attributes.put(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后上线时间
|
||||
*/
|
||||
public long access() {
|
||||
return lastAccessTime = DateUtils.getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取远程端口
|
||||
*/
|
||||
public InetSocketAddress remoteAddress() {
|
||||
return remoteAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流水号
|
||||
*/
|
||||
public int nextSerialNO() {
|
||||
int current;
|
||||
int next;
|
||||
do {
|
||||
current = serialNo.get();
|
||||
next = current > 0xffff ? 0 : current;
|
||||
} while (!serialNo.compareAndSet(current, next + 1));
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理离线方法
|
||||
*/
|
||||
public void invalidate() {
|
||||
if (isRegistered() && sessionManager != null) {
|
||||
sessionManager.remove(this);
|
||||
}
|
||||
remover.apply(this);
|
||||
}
|
||||
|
||||
public boolean isUdp() {
|
||||
return udp;
|
||||
}
|
||||
|
||||
private final Map<String, MonoSink> topicSubscribers = new HashMap<>();
|
||||
|
||||
private static final Mono rejected = Mono.error(new RejectedExecutionException("设备端暂无响应"));
|
||||
|
||||
/**
|
||||
* 异步发送通知类消息
|
||||
* 同步发送 mono.block()
|
||||
* 订阅回调 mono.doOnSuccess(处理成功).doOnError(处理异常).subscribe(开始订阅)
|
||||
*/
|
||||
public Mono<Void> notify(Message message) {
|
||||
return mono(channel.writeAndFlush(Packet.of(this, message)));
|
||||
}
|
||||
|
||||
public Mono<Void> notify(ByteBuf message) {
|
||||
return mono(channel.writeAndFlush(Packet.of(this, message)));
|
||||
}
|
||||
|
||||
public static Mono<Void> mono(ChannelFuture channelFuture) {
|
||||
return Mono.create(sink -> channelFuture.addListener(future -> {
|
||||
if (future.isSuccess()) {
|
||||
sink.success();
|
||||
} else {
|
||||
sink.error(future.cause());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步发送消息,接收响应
|
||||
* 同步接收 mono.block()
|
||||
* 订阅回调 mono.doOnSuccess(处理成功).doOnError(处理异常).subscribe(开始订阅)
|
||||
*/
|
||||
public <T> Mono<T> request(Message message, Class<T> responseClass) {
|
||||
String key = requestKey(message, responseClass);
|
||||
Mono<T> subscribe = this.subscribe(key);
|
||||
if (subscribe == null) {
|
||||
return rejected;
|
||||
}
|
||||
ChannelFuture channelFuture = channel.writeAndFlush(Packet.of(this, message));
|
||||
return Mono.create(sink -> channelFuture.addListener(future -> {
|
||||
if (future.isSuccess()) {
|
||||
sink.success(future);
|
||||
} else {
|
||||
sink.error(future.cause());
|
||||
}
|
||||
})).then(subscribe).doFinally(signal -> unsubscribe(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息响应
|
||||
*/
|
||||
public boolean response(Message message){
|
||||
MonoSink monoSink = topicSubscribers.get(responseKey(message));
|
||||
if (monoSink != null){
|
||||
monoSink.success(message);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅
|
||||
*/
|
||||
private Mono subscribe(String key) {
|
||||
synchronized (topicSubscribers) {
|
||||
if (!topicSubscribers.containsKey(key)) {
|
||||
return Mono.create(sink -> topicSubscribers.put(key, sink));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订阅
|
||||
*/
|
||||
private void unsubscribe(String key) {
|
||||
topicSubscribers.remove(key);
|
||||
}
|
||||
|
||||
/*生成流水号*/
|
||||
private static String requestKey(Message request, Class responseClass) {
|
||||
String className = responseClass.getName();
|
||||
if (Response.class.isAssignableFrom(responseClass)) {
|
||||
String serNo = request.getSerNo();
|
||||
return new StringBuffer(34).append(className).append('.').append(serNo).toString();
|
||||
}
|
||||
return className;
|
||||
}
|
||||
|
||||
/*返回流水号*/
|
||||
private static String responseKey(Object response) {
|
||||
String className = response.getClass().getName();
|
||||
if (response instanceof Response) {
|
||||
int serialNo = ((Response) response).getResponseSerialNo();
|
||||
return new StringBuffer(34).append(className).append('.').append(serialNo).toString();
|
||||
}
|
||||
return className;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.fastbee.base.session;
|
||||
|
||||
/**
|
||||
* session监听
|
||||
* @author gsb
|
||||
* @date 2022/11/7 8:57
|
||||
*/
|
||||
|
||||
public interface SessionListener {
|
||||
|
||||
/** 客户端建立连接 */
|
||||
default void sessionCreated(Session session) {
|
||||
|
||||
}
|
||||
|
||||
/** 客户端完成注册或鉴权 */
|
||||
default void sessionRegistered(Session session) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端注销或离线
|
||||
*/
|
||||
default void sessionDestroyed(Session session) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
package com.fastbee.base.session;
|
||||
|
||||
import com.fastbee.common.enums.ServerType;
|
||||
import com.fastbee.common.utils.spring.SpringUtils;
|
||||
import com.fastbee.base.service.ISessionStore;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* session管理
|
||||
* @author gsb
|
||||
* @date 2022/11/7 8:55
|
||||
*/
|
||||
@Slf4j
|
||||
public class SessionManager {
|
||||
|
||||
private final Class<? extends Enum> sessionKeys;
|
||||
|
||||
private final SessionListener sessionListener;
|
||||
|
||||
/*Session会话存储*/
|
||||
private static ISessionStore sessionStore = SpringUtils.getBean(ISessionStore.class);
|
||||
|
||||
public SessionManager(){
|
||||
this(null,null);
|
||||
}
|
||||
|
||||
public SessionManager(SessionListener sessionListener){
|
||||
this(null,sessionListener);
|
||||
}
|
||||
|
||||
public SessionManager(Class<? extends Enum> sessionKeys, SessionListener sessionListener){
|
||||
this.sessionKeys = sessionKeys;
|
||||
this.sessionListener = sessionListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Session
|
||||
*/
|
||||
public Session getSession(String clientId) {
|
||||
return sessionStore.getSession(clientId);
|
||||
}
|
||||
|
||||
public boolean containKey(String clientId){
|
||||
return sessionStore.containsKey(clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有session
|
||||
*/
|
||||
public Collection<Session> all(){
|
||||
return sessionStore.getSessionMap().values();
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建session TCP
|
||||
* @return
|
||||
*/
|
||||
public Session newInstance(Channel channel){
|
||||
InetSocketAddress sender = (InetSocketAddress) channel.remoteAddress();
|
||||
Session session = new Session(this, channel, sender, s -> {
|
||||
channel.close();
|
||||
return true;
|
||||
}, false, ServerType.TCP);
|
||||
if (sessionListener != null) {
|
||||
try {
|
||||
sessionListener.sessionCreated(session);
|
||||
} catch (Exception e) {
|
||||
log.error("sessionCreated", e);
|
||||
}
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建session UDP
|
||||
*/
|
||||
public Session newInstance(Channel channel, InetSocketAddress sender, Function<Session, Boolean> remover) {
|
||||
Session session = new Session(this, channel, sender, remover, true,ServerType.UDP);
|
||||
if (sessionListener != null) {
|
||||
try {
|
||||
sessionListener.sessionCreated(session);
|
||||
} catch (Exception e) {
|
||||
log.error("sessionCreated", e);
|
||||
}
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备端离线
|
||||
*/
|
||||
protected void remove(Session session){
|
||||
sessionStore.cleanSession(session.getClientId());
|
||||
if (null != sessionListener){
|
||||
try {
|
||||
//设备状态业务处理
|
||||
sessionListener.sessionDestroyed(session);
|
||||
}catch (Exception e){
|
||||
log.error("设备端离线异常",e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备端上线
|
||||
*/
|
||||
public void add(Session session){
|
||||
sessionStore.storeSession(session.getClientId().toUpperCase(),session);
|
||||
if (null != sessionListener){
|
||||
try {
|
||||
sessionListener.sessionRegistered(session);
|
||||
}catch (Exception e){
|
||||
log.error("设备端注册",e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Class<? extends Enum> getSessionKeys(){
|
||||
return sessionKeys;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.fastbee.base.util;
|
||||
|
||||
|
||||
import com.fastbee.base.session.Session;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
|
||||
/**
|
||||
* channel和pipeline桥梁Attribute信息构建
|
||||
* @author gsb
|
||||
* @date 2022/10/7 18:40
|
||||
*/
|
||||
public class AttributeUtils {
|
||||
|
||||
/*存储客户端连接信息*/
|
||||
private static final AttributeKey<Object> SESSION_KEY = AttributeKey.valueOf("session");
|
||||
/*存储客户端id*/
|
||||
private static final AttributeKey<Object> CLIENT_ID_KEY = AttributeKey.valueOf("clientId");
|
||||
|
||||
|
||||
/*添加session*/
|
||||
public static void setSession(Channel channel, Session session){
|
||||
channel.attr(SESSION_KEY).set(session);
|
||||
}
|
||||
|
||||
/*获取session*/
|
||||
public static Session getSession(Channel channel){
|
||||
return (Session) channel.attr(SESSION_KEY).get();
|
||||
}
|
||||
|
||||
/*添加clientId*/
|
||||
public static void setClientId(Channel channel, String clientId){
|
||||
channel.attr(CLIENT_ID_KEY).set(clientId);
|
||||
}
|
||||
|
||||
/*获取clientId*/
|
||||
public static String getClientId(Channel channel){
|
||||
return (String) channel.attr(CLIENT_ID_KEY).get();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.fastbee.base.util;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* byteBuf操作工具
|
||||
* @author bill
|
||||
*/
|
||||
public class ByteBufUtils {
|
||||
|
||||
/**
|
||||
* 返回报文的readerIndex和报文中找到的第一个指针之间的字节数-如果在报文中找不到针,则返回1
|
||||
*/
|
||||
public static int indexOf(ByteBuf haystack, byte[] needle) {
|
||||
for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i++) {
|
||||
int haystackIndex = i;
|
||||
int needleIndex;
|
||||
for (needleIndex = 0; needleIndex < needle.length; needleIndex++) {
|
||||
if (haystack.getByte(haystackIndex) != needle[needleIndex]) {
|
||||
break;
|
||||
} else {
|
||||
haystackIndex++;
|
||||
if (haystackIndex == haystack.writerIndex() && needleIndex != needle.length - 1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needleIndex == needle.length) {
|
||||
// 找到读取的index
|
||||
return i - haystack.readerIndex();
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static boolean startsWith(ByteBuf haystack, byte[] prefix) {
|
||||
for (int i = 0, j = haystack.readerIndex(); i < prefix.length; )
|
||||
if (prefix[i++] != haystack.getByte(j++))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package com.fastbee.base.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* 类操作工具
|
||||
* @author bill
|
||||
*/
|
||||
public class ClassUtils {
|
||||
public static List<Class> getClassList(String packageName, Class<? extends Annotation> annotationClass) {
|
||||
List<Class> classList = getClassList(packageName);
|
||||
classList.removeIf(next -> !next.isAnnotationPresent(annotationClass));
|
||||
return classList;
|
||||
}
|
||||
|
||||
public static List<Class> getClassList(String packageName) {
|
||||
List<Class> classList = new LinkedList<>();
|
||||
String path = packageName.replace(".", "/");
|
||||
try {
|
||||
Enumeration<URL> urls = ClassUtils.getClassLoader().getResources(path);
|
||||
while (urls.hasMoreElements()) {
|
||||
URL url = urls.nextElement();
|
||||
|
||||
if (url != null) {
|
||||
String protocol = url.getProtocol();
|
||||
|
||||
if (protocol.equals("file")) {
|
||||
addClass(classList, url.toURI().getPath(), packageName);
|
||||
|
||||
} else if (protocol.equals("jar")) {
|
||||
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
|
||||
JarFile jarFile = jarURLConnection.getJarFile();
|
||||
|
||||
Enumeration<JarEntry> jarEntries = jarFile.entries();
|
||||
while (jarEntries.hasMoreElements()) {
|
||||
|
||||
JarEntry jarEntry = jarEntries.nextElement();
|
||||
String entryName = jarEntry.getName();
|
||||
|
||||
if (entryName.startsWith(path) && entryName.endsWith(".class")) {
|
||||
String className = entryName.substring(0, entryName.lastIndexOf(".")).replaceAll("/", ".");
|
||||
addClass(classList, className);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Initial class error!");
|
||||
}
|
||||
return classList;
|
||||
}
|
||||
|
||||
private static void addClass(List<Class> classList, String packagePath, String packageName) {
|
||||
try {
|
||||
File[] files = new File(packagePath).listFiles(file -> (file.isDirectory() || file.getName().endsWith(".class")));
|
||||
if (files != null)
|
||||
for (File file : files) {
|
||||
String fileName = file.getName();
|
||||
if (file.isFile()) {
|
||||
String className = fileName.substring(0, fileName.lastIndexOf("."));
|
||||
if (packageName != null) {
|
||||
className = packageName + "." + className;
|
||||
}
|
||||
addClass(classList, className);
|
||||
} else {
|
||||
String subPackagePath = fileName;
|
||||
if (packageName != null) {
|
||||
subPackagePath = packagePath + "/" + subPackagePath;
|
||||
}
|
||||
String subPackageName = fileName;
|
||||
if (packageName != null) {
|
||||
subPackageName = packageName + "." + subPackageName;
|
||||
}
|
||||
addClass(classList, subPackagePath, subPackageName);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addClass(List<Class> classList, String className) {
|
||||
classList.add(loadClass(className, false));
|
||||
}
|
||||
|
||||
public static Class loadClass(String className, boolean isInitialized) {
|
||||
try {
|
||||
return Class.forName(className, isInitialized, getClassLoader());
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ClassLoader getClassLoader() {
|
||||
return Thread.currentThread().getContextClassLoader();
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package com.fastbee.base.util;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* concurrentMap存储类
|
||||
* @author bill
|
||||
*/
|
||||
@Data
|
||||
public abstract class ConcurrentStorage<K,V> implements Storage<K,V>{
|
||||
|
||||
private volatile ConcurrentHashMap<K,V> map;
|
||||
|
||||
public ConcurrentStorage(){
|
||||
this(new ConcurrentHashMap<K,V>());
|
||||
}
|
||||
|
||||
public ConcurrentStorage(ConcurrentHashMap<K,V> map){
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public V push(K key, V value) {
|
||||
return map.put(key,value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V pop(K key) {
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(K key) {
|
||||
return map.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isContains(Object key) {
|
||||
return map.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getStorage() {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.fastbee.base.util;
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceStatusBo;
|
||||
import com.fastbee.common.enums.DeviceStatus;
|
||||
import com.fastbee.common.utils.DateUtils;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* 设备信息工具类
|
||||
* @author bill
|
||||
*/
|
||||
public class DeviceUtils {
|
||||
|
||||
|
||||
|
||||
|
||||
/*构造返回MQ的设备状态model*/
|
||||
public static DeviceStatusBo buildStatusMsg(Channel channel, String clientId, DeviceStatus status, String ip){
|
||||
InetSocketAddress address = (InetSocketAddress) channel.remoteAddress();
|
||||
return DeviceStatusBo.builder()
|
||||
.serialNumber(clientId)
|
||||
.status(status)
|
||||
.ip(ip)
|
||||
.hostName(address.getHostName())
|
||||
.port(address.getPort())
|
||||
.timestamp(DateUtils.getNowDate()).build();
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.fastbee.base.util;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* @author bill
|
||||
*/
|
||||
public class Stopwatch {
|
||||
private final AtomicInteger count = new AtomicInteger();
|
||||
private final Thread thread;
|
||||
|
||||
public Stopwatch start() {
|
||||
this.thread.start();
|
||||
return this;
|
||||
}
|
||||
|
||||
public int increment() {
|
||||
return count.incrementAndGet();
|
||||
}
|
||||
|
||||
public Stopwatch() {
|
||||
thread = new Thread(() -> {
|
||||
long start;
|
||||
while (true) {
|
||||
if (count.get() > 0) {
|
||||
start = System.currentTimeMillis();
|
||||
break;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(1L);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
try {
|
||||
Thread.sleep(2000L);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
int num = count.get();
|
||||
long time = (System.currentTimeMillis() - start) / 1000;
|
||||
System.out.println(time + "\t" + num + "\t" + num / time);
|
||||
}
|
||||
});
|
||||
thread.setName(Thread.currentThread().getName() + "-c");
|
||||
thread.setPriority(Thread.MIN_PRIORITY);
|
||||
thread.setDaemon(true);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.fastbee.base.util;
|
||||
|
||||
/**
|
||||
* 存储管理
|
||||
* @author bill
|
||||
*/
|
||||
public interface Storage<K,V> {
|
||||
|
||||
V push(K key, V value);
|
||||
|
||||
V pop(K key);
|
||||
|
||||
V remove(K key);
|
||||
|
||||
boolean isContains(K key);
|
||||
|
||||
Object getStorage();
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package com.fastbee.base.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* @author bill
|
||||
*/
|
||||
public class VirtualList<E> extends AbstractList<E> implements RandomAccess, Serializable {
|
||||
|
||||
private final E[] elementData;
|
||||
private final int size;
|
||||
|
||||
public VirtualList(E[] array, int length) {
|
||||
this.elementData = array;
|
||||
this.size = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return elementData.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T[] toArray(T[] a) {
|
||||
if (a.length < size)
|
||||
return Arrays.copyOf(this.elementData, size,
|
||||
(Class<? extends T[]>) a.getClass());
|
||||
System.arraycopy(this.elementData, 0, a, 0, size);
|
||||
if (a.length > size)
|
||||
a[size] = null;
|
||||
return a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E get(int index) {
|
||||
return elementData[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public E set(int index, E element) {
|
||||
E oldValue = elementData[index];
|
||||
elementData[index] = element;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
E[] a = this.elementData;
|
||||
if (o == null) {
|
||||
for (int i = 0; i < size; i++)
|
||||
if (a[i] == null)
|
||||
return i;
|
||||
} else {
|
||||
for (int i = 0; i < size; i++)
|
||||
if (o.equals(a[i]))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return indexOf(o) != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<E> spliterator() {
|
||||
return Spliterators.spliterator(elementData, 0, size, Spliterator.ORDERED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super E> action) {
|
||||
Objects.requireNonNull(action);
|
||||
for (int i = 0; i < size; i++) {
|
||||
action.accept(elementData[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(UnaryOperator<E> operator) {
|
||||
Objects.requireNonNull(operator);
|
||||
E[] a = this.elementData;
|
||||
for (int i = 0; i < size; i++) {
|
||||
a[i] = operator.apply(a[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sort(Comparator<? super E> c) {
|
||||
Arrays.sort(elementData, 0, size, c);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user