WeixinServerBootstrap.java

package com.foxinmy.weixin4j.startup;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.foxinmy.weixin4j.dispatcher.BeanFactory;
import com.foxinmy.weixin4j.dispatcher.DefaultMessageMatcher;
import com.foxinmy.weixin4j.dispatcher.WeixinMessageDispatcher;
import com.foxinmy.weixin4j.dispatcher.WeixinMessageKey;
import com.foxinmy.weixin4j.dispatcher.WeixinMessageMatcher;
import com.foxinmy.weixin4j.handler.WeixinMessageHandler;
import com.foxinmy.weixin4j.interceptor.WeixinMessageInterceptor;
import com.foxinmy.weixin4j.request.WeixinMessage;
import com.foxinmy.weixin4j.socket.WeixinServerInitializer;
import com.foxinmy.weixin4j.util.AesToken;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.bootstrap.ServerBootstrapConfig;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

/**
 * 微信netty服务启动程序
 *
 * @className WeixinServerBootstrap
 * @author jinyu(foxinmy@gmail.com)
 * @date 2014年10月12日
 * @since JDK 1.6
 * @see com.foxinmy.weixin4j.dispatcher.WeixinMessageMatcher
 * @see com.foxinmy.weixin4j.handler.WeixinMessageHandler
 * @see com.foxinmy.weixin4j.interceptor.WeixinMessageInterceptor
 * @see com.foxinmy.weixin4j.dispatcher.WeixinMessageDispatcher
 * @see com.foxinmy.weixin4j.dispatcher.BeanFactory
 */
public final class WeixinServerBootstrap {

    private final InternalLogger logger = InternalLoggerFactory.getInstance(getClass());

    /**
     * boss线程数,默认设置为cpu的核数
     */
    public final static int DEFAULT_BOSSTHREADS;
    /**
     * worker线程数,默认设置为DEFAULT_BOSSTHREADS * 2
     */
    public final static int DEFAULT_WORKERTHREADS;
    /**
     * 服务启动的默认端口
     */
    public final static int DEFAULT_SERVERPORT = 30000;
    /**
     * 消息分发器
     */
    private WeixinMessageDispatcher messageDispatcher;
    /**
     * 消息处理器
     */
    private List<WeixinMessageHandler> messageHandlerList;
    /**
     * 消息拦截器
     */
    private List<WeixinMessageInterceptor> messageInterceptorList;

    /**
     * aes and token
     *
     */
    private final Map<String, AesToken> aesTokenMap;

    private ServerBootstrap bootstrap;

    static {
        DEFAULT_BOSSTHREADS = Runtime.getRuntime().availableProcessors();
        DEFAULT_WORKERTHREADS = DEFAULT_BOSSTHREADS * 2;
    }

    /**
     *
     * 明文模式
     *
     * @param token
     *            开发者token
     *
     */
    public WeixinServerBootstrap(String token) {
        this("", token, null);
    }

    /**
     * 明文模式 & 兼容模式 & 密文模式
     * <dl>
     * <font color=
     * "red">值得注意的是:企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font>
     * </dl>
     *
     * @param weixinId
     *            公众号的应用ID(appid/corpid) 密文&兼容模式下需要填写
     *
     * @param token
     *            开发者填写的token 无论哪种模式都需要填写
     * @param aesKey
     *            消息加密的密钥 密文&兼容模式下需要填写
     */
    public WeixinServerBootstrap(String weixinId, String token, String aesKey) {
        this(new AesToken(weixinId, token, aesKey));
    }

    /**
     * 多个公众号的支持
     * <dt>值得注意的是:
     * <dl>
     * <font color="red">1).企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font>
     * </dl>
     * <dl>
     * <font color=
     * "red">2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数</font>
     * </dl>
     *
     * @param aesTokens
     *            多个公众号
     * @return
     */
    public WeixinServerBootstrap(AesToken... aesToken) {
        this(new DefaultMessageMatcher(), aesToken);
    }

    /**
     * 多个公众号的支持
     * <dt>值得注意的是:
     * <dl>
     * <font color="red">1).企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font>
     * </dl>
     * <dl>
     * <font color=
     * "red">2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数</font>
     * </dl>
     *
     * @param messageMatcher
     *            消息匹配器
     * @param aesTokens
     *            公众号信息
     * @return
     */
    public WeixinServerBootstrap(WeixinMessageMatcher messageMatcher, AesToken... aesTokens) {
        if (messageMatcher == null) {
            throw new IllegalArgumentException("MessageMatcher not be null");
        }
        if (aesTokens == null) {
            throw new IllegalArgumentException("AesToken not be null");
        }
        this.aesTokenMap = new HashMap<String, AesToken>();
        for (AesToken aesToken : aesTokens) {
            this.aesTokenMap.put(aesToken.getWeixinId(), aesToken);
        }
        this.aesTokenMap.put("", aesTokens[0]);
        this.messageHandlerList = new ArrayList<WeixinMessageHandler>();
        this.messageInterceptorList = new ArrayList<WeixinMessageInterceptor>();
        this.messageDispatcher = new WeixinMessageDispatcher(messageMatcher);
    }

    /**
     * 默认端口(30000)启动服务
     *
     */
    public void startup() {
        startup(DEFAULT_SERVERPORT);
    }

    /**
     * 指定端口启动服务
     *
     */
    public void startup(int serverPort) {
        startup(DEFAULT_BOSSTHREADS, DEFAULT_WORKERTHREADS, serverPort);
    }

    /**
     * 接受参数启动服务
     *
     * @param bossThreads
     *            boss线程数
     * @param workerThreads
     *            worker线程数
     * @param serverPort
     *            服务启动端口
     * @return
     * @throws WeixinException
     */
    public void startup(int bossThreads, int workerThreads, final int serverPort) {
        messageDispatcher.setMessageHandlerList(messageHandlerList);
        messageDispatcher.setMessageInterceptorList(messageInterceptorList);
        try {
            bootstrap = new ServerBootstrap();
            bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            bootstrap.group(new NioEventLoopGroup(bossThreads), new NioEventLoopGroup(workerThreads))
                    .channel(NioServerSocketChannel.class).handler(new LoggingHandler())
                    .childHandler(new WeixinServerInitializer(aesTokenMap, messageDispatcher));
            Channel ch = bootstrap.bind(serverPort).addListener(new FutureListener<Void>() {
                @Override
                public void operationComplete(Future<Void> future) throws Exception {
                    if (future.isSuccess()) {
                        logger.info("weixin4j server startup OK:{}", serverPort);
                    } else {
                        logger.info("weixin4j server startup FAIL:{}", serverPort);
                    }
                }
            }).sync().channel();
            ch.closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            shutdown(false);
        }
    }

    /**
     * 关闭微信服务
     *
     * @param blocking
     *            阻塞关闭
     * @return
     */
    public boolean shutdown(boolean blocking) {
        if (bootstrap == null) {
            return false;
        }
        ServerBootstrapConfig c = bootstrap.config();
        Future<?> bossF = c.group().shutdownGracefully();
        Future<?> workerF = c.childGroup().shutdownGracefully();
        if (blocking) {
            bossF.awaitUninterruptibly();
            workerF.awaitUninterruptibly();
        }
        messageHandlerList = null;
        messageInterceptorList = null;
        messageDispatcher = null;
        bootstrap = null;
        return true;
    }

    /**
     * 添加一个或者多个消息处理器
     *
     * @param messageHandler
     *            消息处理器
     * @return
     */
    public WeixinServerBootstrap addHandler(WeixinMessageHandler... messageHandler) {
        if (messageHandler == null) {
            throw new IllegalArgumentException("messageHandler not be null");
        }
        messageHandlerList.addAll(Arrays.asList(messageHandler));
        return this;
    }

    /**
     * 插入一个或多个消息拦截器
     *
     * @param messageInterceptor
     *            消息拦截器
     * @return
     */
    public WeixinServerBootstrap addInterceptor(WeixinMessageInterceptor... messageInterceptor) {
        if (messageInterceptor == null) {
            throw new IllegalArgumentException("messageInterceptor not be null");
        }
        messageInterceptorList.addAll(Arrays.asList(messageInterceptor));
        return this;
    }

    /**
     * 按照包名去添加消息处理器
     *
     * @param messageHandlerPackages
     *            消息处理器所在的包名
     * @return
     */
    public WeixinServerBootstrap handlerPackagesToScan(String... messageHandlerPackages) {
        if (messageHandlerPackages == null) {
            throw new IllegalArgumentException("messageHandlerPackages not be null");
        }
        messageDispatcher.setMessageHandlerPackages(messageHandlerPackages);
        return this;
    }

    /**
     * 按照包名去添加消息拦截器
     *
     * @param messageInterceptorPackages
     *            消息拦截器所在的包名
     * @return
     */
    public WeixinServerBootstrap interceptorPackagesToScan(String... messageInterceptorPackages) {
        if (messageInterceptorPackages == null) {
            throw new IllegalArgumentException("messageInterceptorPackages not be null");
        }
        messageDispatcher.setMessageInterceptorPackages(messageInterceptorPackages);
        return this;
    }

    /**
     * 声明处理器跟拦截器类实例化的构造工厂,否则通过Class.newInstance的方式构造
     *
     * @param beanFactory
     *            Bean构造工厂
     * @return
     */
    public WeixinServerBootstrap resolveBeanFactory(BeanFactory beanFactory) {
        messageDispatcher.setBeanFactory(beanFactory);
        return this;
    }

    /**
     * 注册消息类型
     *
     * @param messageKey
     *            消息key
     * @param messageClass
     *            消息类
     * @return
     */
    public WeixinServerBootstrap registMessageClass(WeixinMessageKey messageKey,
            Class<? extends WeixinMessage> messageClass) {
        messageDispatcher.registMessageClass(messageKey, messageClass);
        return this;
    }

    /**
     * 打开总是响应开关,如未匹配到MessageHandler时回复空白消息
     */
    public WeixinServerBootstrap openAlwaysResponse() {
        messageDispatcher.openAlwaysResponse();
        return this;
    }

    /**
     * 动态添加aesToken
     *
     * @param aesToken
     * @return
     */
    public boolean addAesToken(AesToken aesToken) {
        if (bootstrap == null) {
            return false;
        }
        ServerBootstrapConfig c = bootstrap.config();
        ((WeixinServerInitializer) c.childHandler()).addAesToken(aesToken);
        return true;
    }

    public final static String VERSION = "1.1.9";
}