245 lines
9.2 KiB
Java
Executable File
245 lines
9.2 KiB
Java
Executable File
/*
|
|
* 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 cn.wildfirechat.common.ErrorCode;
|
|
import win.liyufan.im.RateLimiter;
|
|
import win.liyufan.im.Utility;
|
|
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.Set;
|
|
|
|
import static cn.wildfirechat.common.ErrorCode.ERROR_CODE_OVER_FREQUENCY;
|
|
import static cn.wildfirechat.common.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() == 6) {
|
|
dataCls = method.getParameterTypes()[4];
|
|
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, boolean isAdmin) {
|
|
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, isAdmin, 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;
|
|
}
|
|
}
|
|
|
|
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, boolean isAdmin, 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) {
|
|
Set<String> notifyReceivers = new LinkedHashSet<>();
|
|
|
|
message = m_messagesStore.storeMessage(username, clientID, message);
|
|
int pullType = m_messagesStore.getNotifyReceivers(username, message, notifyReceivers);
|
|
this.publisher.publish2Receivers(message, notifyReceivers, clientID, pullType);
|
|
return message.getMessageId();
|
|
}
|
|
}
|