class ChannelAction extends Action {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(ChannelAction.class);
    protected WFCMessage.ChannelInfo channelInfo;

    protected interface Callback {
        void onSuccess(byte[] response);
    }

    @Override
    public ErrorCode preAction(Request request, Response response) {
        String nonce = request.getHeader("nonce");
        if (StringUtil.isNullOrEmpty(nonce)) {
            nonce = request.getHeader("Nonce");
        }
        String timestamp = request.getHeader("timestamp");
        if (StringUtil.isNullOrEmpty(timestamp)) {
            timestamp = request.getHeader("Timestamp");
        }
        String sign = request.getHeader("sign");
        if (StringUtil.isNullOrEmpty(sign)) {
            sign = request.getHeader("Sign");
        }
        String cid = request.getHeader("cid");
        if (StringUtil.isNullOrEmpty(cid)) {
            cid = request.getHeader("Cid");
        }

        if (StringUtil.isNullOrEmpty(nonce) || StringUtil.isNullOrEmpty(timestamp) || StringUtil.isNullOrEmpty(sign) || StringUtil.isNullOrEmpty(cid)) {
            return ErrorCode.ERROR_CODE_API_NOT_SIGNED;
        }

        if (!channelLimiter.isGranted(cid)) {
            return ErrorCode.ERROR_CODE_OVER_FREQUENCY;
        }

        Long ts;
        try {
            ts = Long.parseLong(timestamp);
        } catch (Exception e) {
            LOG.error("Channel timestamp:{} invalid", timestamp);
            e.printStackTrace();
            Utility.printExecption(LOG, e, IMExceptionEvent.EventType.CHANNEL_API_Exception);
            return ErrorCode.ERROR_CODE_API_NOT_SIGNED;
        }

        if (System.currentTimeMillis() - ts > 2 * 60 * 60 * 1000) {
            return ErrorCode.ERROR_CODE_SIGN_EXPIRED;
        }

        channelInfo = messagesStore.getChannelInfo(cid);
        if (channelInfo == null) {
            return ErrorCode.ERROR_CODE_CHANNEL_NO_EXIST;
        }
        
        if (StringUtil.isNullOrEmpty(channelInfo.getSecret())) {
            return ErrorCode.ERROR_CODE_CHANNEL_NO_SECRET;
        }

        String str = nonce + "|" + channelInfo.getSecret() + "|" + timestamp;
        String localSign = DigestUtils.sha1Hex(str);
        return localSign.equals(sign) ? ErrorCode.ERROR_CODE_SUCCESS : ErrorCode.ERROR_CODE_AUTH_FAILURE;
    }

    protected void sendResponse(Response response, ErrorCode errorCode, Object data) {
        response.setStatus(HttpResponseStatus.OK);
        if (errorCode == null) {
            errorCode = ErrorCode.ERROR_CODE_SUCCESS;
        }

        RestResult result = RestResult.resultOf(errorCode, errorCode.getMsg(), data);
        response.setContent(gson.toJson(result));
        response.send();
    }

    protected <T> T getRequestBody(HttpRequest request, Class<T> cls) {<FILL_FUNCTION_BODY>}

    protected void sendApiMessage(Response response, String topic, byte[] message, Callback callback) {
        sendApiMessage(response, channelInfo.getOwner(), topic, message, callback);
    }
    protected void sendApiMessage(Response response, String user, String topic, byte[] message, Callback callback) {
        ServerAPIHelper.sendRequest(user, null, topic, message, new ServerAPIHelper.Callback() {
            @Override
            public void onSuccess(byte[] response) {
                callback.onSuccess(response);
            }
            @Override
            public void onError(ErrorCode errorCode) {
                sendResponse(response, errorCode, null);
            }

            @Override
            public void onTimeout() {
                sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null);
            }

            @Override
            public Executor getResponseExecutor() {
                return command -> {
                    ctx.executor().execute(command);
                };
            }
        }, ProtoConstants.RequestSourceType.Request_From_Channel);
    }
}

        if (request instanceof FullHttpRequest) {
            FullHttpRequest fullHttpRequest = (FullHttpRequest) request;
            byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content());
            String content = new String(bytes, StandardCharsets.UTF_8);
            
            T t = gson.fromJson(content, cls);
            return t;
        }
        return null;
    
<methods>public non-sealed void <init>() ,public abstract boolean action(com.xiaoleilu.loServer.handler.Request, com.xiaoleilu.loServer.handler.Response) ,public boolean doAction(com.xiaoleilu.loServer.handler.Request, com.xiaoleilu.loServer.handler.Response) ,public static void init(io.moquette.server.config.IConfig) ,public boolean isTransactionAction() ,public cn.wildfirechat.common.ErrorCode preAction(com.xiaoleilu.loServer.handler.Request, com.xiaoleilu.loServer.handler.Response) <variables>protected static win.liyufan.im.RateLimiter adminLimiter,protected static win.liyufan.im.RateLimiter channelLimiter,protected static boolean closeApiVersion,public ChannelHandlerContext ctx,protected static final com.google.gson.Gson gson,public static io.moquette.spi.IMessagesStore messagesStore,protected static win.liyufan.im.RateLimiter robotLimiter,public static io.moquette.spi.ISessionsStore sessionsStore