wildfire chat server source initial submit
This commit is contained in:
+251
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* This file is part of the Wildfire Chat package.
|
||||
* (c) Heavyrain2012 <[email protected]>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
package io.moquette.imhandler;
|
||||
|
||||
import cn.wildfirechat.proto.WFCMessage;
|
||||
import cn.wildfirechat.server.ThreadPoolExecutorWrapper;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.protobuf.GeneratedMessage;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.moquette.server.Server;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.impl.MessagesPublisher;
|
||||
import io.moquette.spi.impl.Qos1PublishHandler;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import win.liyufan.im.ErrorCode;
|
||||
import win.liyufan.im.RateLimiter;
|
||||
import win.liyufan.im.Utility;
|
||||
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.Spliterator;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static win.liyufan.im.ErrorCode.ERROR_CODE_OVER_FREQUENCY;
|
||||
import static win.liyufan.im.ErrorCode.ERROR_CODE_SUCCESS;
|
||||
|
||||
/**
|
||||
* 请求处理接口<br>
|
||||
* 当用户请求某个Topic,则调用相应Handler的handle方法
|
||||
*
|
||||
*/
|
||||
|
||||
abstract public class IMHandler<T> {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(IMHandler.class);
|
||||
protected static IMessagesStore m_messagesStore = null;
|
||||
protected static ISessionsStore m_sessionsStore = null;
|
||||
protected static Server mServer = null;
|
||||
protected static MessagesPublisher publisher;
|
||||
private static ThreadPoolExecutorWrapper m_imBusinessExecutor;
|
||||
private static final RateLimiter mLimitCounter = new RateLimiter(5, 100);
|
||||
private Method parseDataMethod;
|
||||
private Class dataCls;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ActionMethod {
|
||||
}
|
||||
|
||||
protected static String actionName;
|
||||
|
||||
|
||||
public IMHandler() {
|
||||
try {
|
||||
if (StringUtil.isNullOrEmpty(actionName)) {
|
||||
Class cls = getClass();
|
||||
while (cls.getSuperclass() != null) {
|
||||
for (Method method : cls.getSuperclass().getDeclaredMethods()) {
|
||||
if (method.getAnnotation(ActionMethod.class) != null) {
|
||||
actionName = method.getName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (StringUtil.isNullOrEmpty(actionName)) {
|
||||
cls = cls.getSuperclass();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (Method method : getClass().getDeclaredMethods()
|
||||
) {
|
||||
|
||||
if (method.getName() == actionName && method.getParameterCount() == 5) {
|
||||
dataCls = method.getParameterTypes()[3];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dataCls.getSuperclass().equals(GeneratedMessage.class)) {
|
||||
parseDataMethod = dataCls.getMethod("parseFrom", byte[].class);
|
||||
} else if (dataCls.isPrimitive()) {
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
Utility.printExecption(LOG, e);
|
||||
}
|
||||
}
|
||||
|
||||
private T getDataObject(byte[] bytes) throws IllegalAccessException, InvocationTargetException {
|
||||
if (parseDataMethod != null) {
|
||||
T object = (T) parseDataMethod.invoke(dataCls, bytes);
|
||||
return object;
|
||||
}
|
||||
|
||||
if (dataCls == String.class) {
|
||||
String str = new String(bytes);
|
||||
return (T)str;
|
||||
}
|
||||
|
||||
if (dataCls == Byte.class) {
|
||||
Byte b = bytes[0];
|
||||
return (T)b;
|
||||
}
|
||||
|
||||
if (dataCls == Void.class) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
//目前还没有需求传int的参数,这里先注释掉
|
||||
//在需要使用时,需要注意大小端的问题。
|
||||
//这里使用的示例代码是小端的,注意一定要与协议中保持一直!!!
|
||||
// if (dataCls == Integer.class) {
|
||||
// int i = bytes[0];
|
||||
// for(int index = 0; index <8; index++) {
|
||||
// i <<= 1;
|
||||
// i += bytes[index];
|
||||
// }
|
||||
// Integer object = i;
|
||||
// return (T)object;
|
||||
// }
|
||||
|
||||
//json ?
|
||||
return (T)(new Gson().fromJson(new String(bytes), dataCls));
|
||||
}
|
||||
|
||||
public static void init(IMessagesStore ms, ISessionsStore ss, MessagesPublisher p, ThreadPoolExecutorWrapper businessExecutor, Server server) {
|
||||
m_messagesStore = ms;
|
||||
m_sessionsStore = ss;
|
||||
publisher = p;
|
||||
m_imBusinessExecutor = businessExecutor;
|
||||
mServer = server;
|
||||
}
|
||||
|
||||
|
||||
public ErrorCode preAction(String clientID, String fromUser, String topic, Qos1PublishHandler.IMCallback callback) {
|
||||
LOG.info("imHandler fromUser={}, clientId={}, topic={}", fromUser, clientID, topic);
|
||||
if(!mLimitCounter.isGranted(clientID + fromUser + topic)) {
|
||||
ByteBuf ackPayload = Unpooled.buffer();
|
||||
ackPayload.ensureWritable(1).writeByte(ERROR_CODE_OVER_FREQUENCY.getCode());
|
||||
try {
|
||||
callback.onIMHandled(ERROR_CODE_OVER_FREQUENCY, ackPayload);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Utility.printExecption(LOG, e);
|
||||
}
|
||||
return ErrorCode.ERROR_CODE_OVER_FREQUENCY;
|
||||
}
|
||||
return ErrorCode.ERROR_CODE_SUCCESS;
|
||||
}
|
||||
|
||||
public void doHandler(String clientID, String fromUser, String topic, byte[] payloadContent, Qos1PublishHandler.IMCallback callback) {
|
||||
m_imBusinessExecutor.execute(() -> {
|
||||
Qos1PublishHandler.IMCallback callbackWrapper = new Qos1PublishHandler.IMCallback() {
|
||||
@Override
|
||||
public void onIMHandled(ErrorCode errorCode, ByteBuf ackPayload) {
|
||||
LOG.debug("execute handler {} with result {}", this.getClass().getName(), errorCode);
|
||||
callback.onIMHandled(errorCode, ackPayload);
|
||||
afterAction(clientID, fromUser, topic, callback);
|
||||
}
|
||||
};
|
||||
|
||||
ErrorCode preActionCode = preAction(clientID, fromUser, topic, callbackWrapper);
|
||||
|
||||
if (preActionCode == ErrorCode.ERROR_CODE_SUCCESS) {
|
||||
ByteBuf ackPayload = Unpooled.buffer(1);
|
||||
ErrorCode errorCode = ERROR_CODE_SUCCESS;
|
||||
ackPayload.ensureWritable(1).writeByte(errorCode.getCode());
|
||||
|
||||
try {
|
||||
LOG.debug("execute handler for topic {}", topic);
|
||||
errorCode = action(ackPayload, clientID, fromUser, getDataObject(payloadContent), callbackWrapper);
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
Utility.printExecption(LOG, e);
|
||||
errorCode = ErrorCode.ERROR_CODE_INVALID_DATA;
|
||||
} catch (InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
Utility.printExecption(LOG, e);
|
||||
errorCode = ErrorCode.ERROR_CODE_INVALID_DATA;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Utility.printExecption(LOG, e);
|
||||
if (e instanceof InvalidProtocolBufferException) {
|
||||
errorCode = ErrorCode.ERROR_CODE_INVALID_DATA;
|
||||
} else {
|
||||
errorCode = ErrorCode.ERROR_CODE_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if(errorCode != ErrorCode.ERROR_CODE_ASYNC_HANDLER) {
|
||||
response(ackPayload, errorCode, callback);
|
||||
}
|
||||
} else {
|
||||
LOG.error("Handler {} preAction failure", this.getClass().getName());
|
||||
ByteBuf ackPayload = Unpooled.buffer(1);
|
||||
ackPayload.ensureWritable(1).writeByte(preActionCode.getCode());
|
||||
response(ackPayload, preActionCode, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void response(ByteBuf ackPayload, ErrorCode errorCode, Qos1PublishHandler.IMCallback callback) {
|
||||
ackPayload.setByte(0, errorCode.getCode());
|
||||
try {
|
||||
callback.onIMHandled(errorCode, ackPayload);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Utility.printExecption(LOG, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ActionMethod
|
||||
abstract public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, T request, Qos1PublishHandler.IMCallback callback) ;
|
||||
|
||||
public void afterAction(String clientID, String fromUser, String topic, Qos1PublishHandler.IMCallback callback) {
|
||||
|
||||
}
|
||||
|
||||
protected long saveAndPublish(String username, String clientID, WFCMessage.Message message, long timestamp) {
|
||||
Set<String> notifyReceivers = new LinkedHashSet<>();
|
||||
|
||||
message = m_messagesStore.storeMessage(username, clientID, message, timestamp);
|
||||
int pullType = m_messagesStore.getNotifyReceivers(username, message, notifyReceivers);
|
||||
this.publisher.publish2Receivers(message, notifyReceivers, clientID, pullType);
|
||||
return message.getMessageId();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user