第一次提交

This commit is contained in:
wyw
2024-08-08 00:31:26 +08:00
commit c202e2b63d
1819 changed files with 221890 additions and 0 deletions

View 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>

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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 {
}

View File

@ -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;
}

View File

@ -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 {
}

View File

@ -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 "";
}

View File

@ -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);
}
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,12 @@
package com.fastbee.base.core.model;
/**
* 消息流水号响应
* @author gsb
* @date 2022/11/7 10:19
*/
public interface Response {
/**应答消息流水号*/
int getResponseSerialNo();
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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());
}
}
}

View File

@ -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;
/*服务协议类型 MQTTTCP 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;
}
}

View File

@ -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) {
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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);
}
}