diff --git a/weixin4j-example/pom.xml b/weixin4j-example/pom.xml index a8ce7a5d..d3bae503 100644 --- a/weixin4j-example/pom.xml +++ b/weixin4j-example/pom.xml @@ -7,6 +7,7 @@ weixin4j 1.7.6 + war weixin4j-example 1.0 weixin4j-example diff --git a/weixin4j-example/src/main/java/com/foxinmy/weixin4j/example/server/Weixin4jServerStartupWithThread.java b/weixin4j-example/src/main/java/com/foxinmy/weixin4j/example/server/Weixin4jServerStartupWithThread.java index 1838cc80..1bd271cd 100644 --- a/weixin4j-example/src/main/java/com/foxinmy/weixin4j/example/server/Weixin4jServerStartupWithThread.java +++ b/weixin4j-example/src/main/java/com/foxinmy/weixin4j/example/server/Weixin4jServerStartupWithThread.java @@ -1,8 +1,5 @@ package com.foxinmy.weixin4j.example.server; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -19,76 +16,71 @@ import io.netty.util.internal.logging.InternalLoggerFactory; * 微信消息服务:需要另外开启一个线程去启动服务,这里值得注意的时:weixin4j-serve本身是作为一个单独的服务来启动的,可以不依赖Spring容器, * 但考虑到目前都是Spring mvc的架构,这里就需要使用一个独立的线程去启动服务,其实本身没有使用spring mvc的API, * 以后会考虑支持servlet api去集成不同的web框架。 - * + * * @className Weixin4jServerStartupWithThread * @author jinyu(foxinmy@gmail.com) * @date 2015年5月7日 * @since JDK 1.6 */ public class Weixin4jServerStartupWithThread implements ApplicationContextAware { - /** - * 服务监听的端口号,目前微信只支持80端口,可以考虑用nginx做转发到此端口 - */ - private final int port; - /** - * 服务器token信息 - */ - /** - * 明文模式:String aesToken = ""; 密文模式:AesToken aesToken = new - * AesToken("公众号appid", "公众号token","公众号加密/解密消息的密钥"); - */ - private final AesToken aesToken; - /** - * 处理微信消息的全限包名(也可通过addHandler方式一个一个添加) - */ - private final String handlerPackage; - /** - * 用spring去获取bean - */ - private ApplicationContext applicationContext; + /** + * 服务监听的端口号,目前微信只支持80端口,可以考虑用nginx做转发到此端口 + */ + private final int port; + /** + * 服务器token信息 + */ + /** + * 明文模式:String aesToken = ""; 密文模式:AesToken aesToken = new + * AesToken("公众号appid", "公众号token","公众号加密/解密消息的密钥"); + */ + private final AesToken aesToken; + /** + * 处理微信消息的全限包名(也可通过addHandler方式一个一个添加) + */ + private final String handlerPackage; + /** + * 用spring去获取bean + */ + private ApplicationContext applicationContext; - private Weixin4jServerStartupWithThread(int port, AesToken aesToken, - String handlerPackage) { - this.port = port; - this.aesToken = aesToken; - this.handlerPackage = handlerPackage; - } + private Weixin4jServerStartupWithThread(int port, AesToken aesToken, String handlerPackage) { + this.port = port; + this.aesToken = aesToken; + this.handlerPackage = handlerPackage; + } - @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - this.applicationContext = applicationContext; - } + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } - private ExecutorService executor; + private WeixinServerBootstrap bootstrap; - /** - * 启动函数 - * - * @throws WeixinException - */ - public void start() { - executor = Executors.newCachedThreadPool(); - executor.execute(new Runnable() { - @Override - public void run() { - try { - new WeixinServerBootstrap(aesToken) // 指定开发者token信息。 - .handlerPackagesToScan(handlerPackage) // 扫描处理消息的包。 - .resolveBeanFactory( - new SpringBeanFactory(applicationContext)) // 声明处理消息类由Spring容器去实例化。 - .addHandler(DebugMessageHandler.global) // 当没有匹配到消息处理时输出调试信息,开发环境打开。 - .openAlwaysResponse() // 当没有匹配到消息处理时输出空白回复(公众号不会出现「该公众号无法提供服务的提示」),正式环境打开。 - .startup(port); // 绑定服务的端口号,即对外暴露(微信服务器URL地址)的服务端口。 - } catch (WeixinException e) { - InternalLoggerFactory.getInstance(getClass()).error( - "weixin4j server startup:FAIL", e); - } - } - }); - } + /** + * 启动函数 + * + * @throws WeixinException + */ + public void start() { + new Thread(new Runnable() { + @Override + public void run() { + try { + bootstrap = new WeixinServerBootstrap(aesToken) // 指定开发者token信息。 + .handlerPackagesToScan(handlerPackage) // 扫描处理消息的包。 + .resolveBeanFactory(new SpringBeanFactory(applicationContext)) // 声明处理消息类由Spring容器去实例化。 + .addHandler(DebugMessageHandler.global) // 当没有匹配到消息处理时输出调试信息,开发环境打开。 + .openAlwaysResponse(); // 当没有匹配到消息处理时输出空白回复(公众号不会出现「该公众号无法提供服务的提示」),正式环境打开。 + bootstrap.startup(port); // 绑定服务的端口号,即对外暴露(微信服务器URL地址)的服务端口。 + } catch (WeixinException e) { + InternalLoggerFactory.getInstance(getClass()).error("weixin4j server startup:FAIL", e); + } + } + }).start(); + } - public void stop() { - executor.shutdown(); - } + public void stop() { + bootstrap.shutdown(); + } } diff --git a/weixin4j-example/src/main/resources/spring-bean.xml b/weixin4j-example/src/main/resources/spring-bean.xml index d0e809d4..53d6e2a7 100644 --- a/weixin4j-example/src/main/resources/spring-bean.xml +++ b/weixin4j-example/src/main/resources/spring-bean.xml @@ -5,8 +5,8 @@ xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd"> - + - + \ No newline at end of file diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/socket/WeixinMessageDecoder.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/socket/WeixinMessageDecoder.java index 75b04fa1..8d7a2e33 100644 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/socket/WeixinMessageDecoder.java +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/socket/WeixinMessageDecoder.java @@ -1,14 +1,5 @@ package com.foxinmy.weixin4j.socket; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageDecoder; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.QueryStringDecoder; -import io.netty.util.internal.logging.InternalLogger; -import io.netty.util.internal.logging.InternalLoggerFactory; - import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -22,93 +13,78 @@ import com.foxinmy.weixin4j.util.MessageUtil; import com.foxinmy.weixin4j.util.ServerToolkits; import com.foxinmy.weixin4j.xml.EncryptMessageHandler; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.QueryStringDecoder; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + /** * 微信消息解码类 - * + * * @className WeixinMessageDecoder * @author jinyu(foxinmy@gmail.com) * @date 2014年11月13日 * @since JDK 1.6 - * @see 加密接入指引 + * @see 加密接入指引 * @see com.foxinmy.weixin4j.request.WeixinRequest */ @ChannelHandler.Sharable -public class WeixinMessageDecoder extends - MessageToMessageDecoder { - private final InternalLogger logger = InternalLoggerFactory - .getInstance(getClass()); +public class WeixinMessageDecoder extends MessageToMessageDecoder { + private final InternalLogger logger = InternalLoggerFactory.getInstance(getClass()); - private Map aesTokenMap = new ConcurrentHashMap(); + private Map aesTokenMap = new ConcurrentHashMap(); - public WeixinMessageDecoder(final Map aesTokenMap) { - for (Entry entry : aesTokenMap.entrySet()) { - this.aesTokenMap.put(entry.getKey() == null ? "" : entry.getKey(), - entry.getValue()); - } - } + public WeixinMessageDecoder(final Map aesTokenMap) { + for (Entry entry : aesTokenMap.entrySet()) { + this.aesTokenMap.put(entry.getKey() == null ? "" : entry.getKey(), entry.getValue()); + } + } - public int addAesToken(final AesToken asetoken) { - AesToken token = aesTokenMap.get(asetoken.getWeixinId()); - if (token != null) - return -1; - aesTokenMap.put(asetoken.getWeixinId(), asetoken); - return 0; - } + public void addAesToken(final AesToken asetoken) { + aesTokenMap.put(asetoken.getWeixinId(), asetoken); + } - @Override - protected void decode(ChannelHandlerContext ctx, FullHttpRequest req, - List out) throws WeixinException { - String messageContent = req.content().toString(ServerToolkits.UTF_8); - QueryStringDecoder queryDecoder = new QueryStringDecoder(req.uri(), - true); - HttpMethod method = req.method(); - logger.info("decode request:{} use {} method invoking", req.uri(), - method); - Map> parameters = queryDecoder.parameters(); - EncryptType encryptType = parameters.containsKey("encrypt_type") ? EncryptType - .valueOf(parameters.get("encrypt_type").get(0).toUpperCase()) - : EncryptType.RAW; - String echoStr = parameters.containsKey("echostr") ? parameters.get( - "echostr").get(0) : ""; - String timeStamp = parameters.containsKey("timestamp") ? parameters - .get("timestamp").get(0) : ""; - String nonce = parameters.containsKey("nonce") ? parameters - .get("nonce").get(0) : ""; - String signature = parameters.containsKey("signature") ? parameters - .get("signature").get(0) : ""; - String msgSignature = parameters.containsKey("msg_signature") ? parameters - .get("msg_signature").get(0) : ""; - String weixinId = parameters.containsKey("weixin_id") ? parameters.get( - "weixin_id").get(0) : ""; - AesToken aesToken = aesTokenMap.get(weixinId); - String encryptContent = null; - if (!ServerToolkits.isBlank(messageContent) - && encryptType == EncryptType.AES) { - if (ServerToolkits.isBlank(aesToken.getAesKey())) { - throw new WeixinException( - "EncodingAESKey not be empty in safety(AES) mode"); - } - EncryptMessageHandler encryptHandler = EncryptMessageHandler - .parser(messageContent); - encryptContent = encryptHandler.getEncryptContent(); - /** - * 企业号第三方套件 ╮(╯_╰)╭ - */ - if (aesToken.getWeixinId().startsWith("tj")) { - aesToken = new AesToken(encryptHandler.getToUserName(), - aesToken.getToken(), aesToken.getAesKey()); - } - messageContent = MessageUtil.aesDecrypt(aesToken.getWeixinId(), - aesToken.getAesKey(), encryptContent); - } - logger.info("read original message {}", messageContent); - WeixinRequest request = new WeixinRequest(req.headers(), method, - req.uri(), encryptType, echoStr, timeStamp, nonce, - signature, msgSignature, messageContent, encryptContent, - aesToken); - request.setDecoderResult(req.decoderResult()); - request.setProtocolVersion(req.protocolVersion()); - out.add(request); - } + @Override + protected void decode(ChannelHandlerContext ctx, FullHttpRequest req, List out) throws WeixinException { + String messageContent = req.content().toString(ServerToolkits.UTF_8); + QueryStringDecoder queryDecoder = new QueryStringDecoder(req.uri(), true); + HttpMethod method = req.method(); + logger.info("decode request:{} use {} method invoking", req.uri(), method); + Map> parameters = queryDecoder.parameters(); + EncryptType encryptType = parameters.containsKey("encrypt_type") + ? EncryptType.valueOf(parameters.get("encrypt_type").get(0).toUpperCase()) : EncryptType.RAW; + String echoStr = parameters.containsKey("echostr") ? parameters.get("echostr").get(0) : ""; + String timeStamp = parameters.containsKey("timestamp") ? parameters.get("timestamp").get(0) : ""; + String nonce = parameters.containsKey("nonce") ? parameters.get("nonce").get(0) : ""; + String signature = parameters.containsKey("signature") ? parameters.get("signature").get(0) : ""; + String msgSignature = parameters.containsKey("msg_signature") ? parameters.get("msg_signature").get(0) : ""; + String weixinId = parameters.containsKey("weixin_id") ? parameters.get("weixin_id").get(0) : ""; + AesToken aesToken = aesTokenMap.get(weixinId); + String encryptContent = null; + if (!ServerToolkits.isBlank(messageContent) && encryptType == EncryptType.AES) { + if (ServerToolkits.isBlank(aesToken.getAesKey())) { + throw new WeixinException("EncodingAESKey not be empty in safety(AES) mode"); + } + EncryptMessageHandler encryptHandler = EncryptMessageHandler.parser(messageContent); + encryptContent = encryptHandler.getEncryptContent(); + /** + * 企业号第三方套件 ╮(╯_╰)╭ + */ + if (aesToken.getWeixinId().startsWith("tj")) { + aesToken = new AesToken(encryptHandler.getToUserName(), aesToken.getToken(), aesToken.getAesKey()); + } + messageContent = MessageUtil.aesDecrypt(aesToken.getWeixinId(), aesToken.getAesKey(), encryptContent); + } + logger.info("read original message {}", messageContent); + WeixinRequest request = new WeixinRequest(req.headers(), method, req.uri(), encryptType, echoStr, timeStamp, + nonce, signature, msgSignature, messageContent, encryptContent, aesToken); + request.setDecoderResult(req.decoderResult()); + request.setProtocolVersion(req.protocolVersion()); + out.add(request); + } } diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/socket/WeixinServerInitializer.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/socket/WeixinServerInitializer.java index 40bbd742..118d21da 100644 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/socket/WeixinServerInitializer.java +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/socket/WeixinServerInitializer.java @@ -1,19 +1,19 @@ package com.foxinmy.weixin4j.socket; +import java.util.Map; + +import com.foxinmy.weixin4j.dispatcher.WeixinMessageDispatcher; +import com.foxinmy.weixin4j.util.AesToken; + import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; -import java.util.Map; - -import com.foxinmy.weixin4j.dispatcher.WeixinMessageDispatcher; -import com.foxinmy.weixin4j.util.AesToken; - /** * 微信消息服务器初始化 - * + * * @className WeixinServerInitializer * @author jinyu(foxinmy@gmail.com) * @date 2015年5月17日 @@ -22,27 +22,26 @@ import com.foxinmy.weixin4j.util.AesToken; */ public class WeixinServerInitializer extends ChannelInitializer { - private final WeixinMessageDispatcher messageDispatcher; - private final WeixinMessageDecoder messageDecoder; + private final WeixinMessageDispatcher messageDispatcher; + private final WeixinMessageDecoder messageDecoder; - public WeixinServerInitializer(Map aesTokenMap, - WeixinMessageDispatcher messageDispatcher) { - this.messageDispatcher = messageDispatcher; - this.messageDecoder = new WeixinMessageDecoder(aesTokenMap); - } + public WeixinServerInitializer(Map aesTokenMap, WeixinMessageDispatcher messageDispatcher) { + this.messageDispatcher = messageDispatcher; + this.messageDecoder = new WeixinMessageDecoder(aesTokenMap); + } - public int addAesToken(final AesToken asetoken) { - return messageDecoder.addAesToken(asetoken); - } + public void addAesToken(AesToken asetoken) { + messageDecoder.addAesToken(asetoken); + } - @Override - protected void initChannel(SocketChannel channel) { - ChannelPipeline pipeline = channel.pipeline(); - pipeline.addLast(new HttpServerCodec()); - pipeline.addLast(new HttpObjectAggregator(65536)); - pipeline.addLast(messageDecoder); - pipeline.addLast(new WeixinResponseEncoder()); - pipeline.addLast(new SingleResponseEncoder()); - pipeline.addLast(new WeixinRequestHandler(messageDispatcher)); - } + @Override + protected void initChannel(SocketChannel channel) { + ChannelPipeline pipeline = channel.pipeline(); + pipeline.addLast(new HttpServerCodec()); + pipeline.addLast(new HttpObjectAggregator(65536)); + pipeline.addLast(messageDecoder); + pipeline.addLast(new WeixinResponseEncoder()); + pipeline.addLast(new SingleResponseEncoder()); + pipeline.addLast(new WeixinRequestHandler(messageDispatcher)); + } } diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/startup/WeixinServerBootstrap.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/startup/WeixinServerBootstrap.java index ab3e6e74..797c9d76 100644 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/startup/WeixinServerBootstrap.java +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/startup/WeixinServerBootstrap.java @@ -1,17 +1,5 @@ package com.foxinmy.weixin4j.startup; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -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; - import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -30,9 +18,21 @@ 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日 @@ -45,309 +45,308 @@ import com.foxinmy.weixin4j.util.AesToken; */ public final class WeixinServerBootstrap { - private final InternalLogger logger = InternalLoggerFactory - .getInstance(getClass()); + 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 messageHandlerList; - /** - * 消息拦截器 - */ - private List messageInterceptorList; + /** + * 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 messageHandlerList; + /** + * 消息拦截器 + */ + private List messageInterceptorList; - /** - * aes and token - * - */ - private final Map aesTokenMap; + /** + * aes and token + * + */ + private final Map aesTokenMap; - private WeixinServerInitializer wechatInitializer; + private ServerBootstrap bootstrap; - static { - DEFAULT_BOSSTHREADS = Runtime.getRuntime().availableProcessors(); - DEFAULT_WORKERTHREADS = DEFAULT_BOSSTHREADS * 2; - } + static { + DEFAULT_BOSSTHREADS = Runtime.getRuntime().availableProcessors(); + DEFAULT_WORKERTHREADS = DEFAULT_BOSSTHREADS * 2; + } - /** - * - * 明文模式 - * - * @param token - * 开发者token - * - */ - public WeixinServerBootstrap(String token) { - this("", token, null); - } + /** + * + * 明文模式 + * + * @param token + * 开发者token + * + */ + public WeixinServerBootstrap(String token) { + this("", token, null); + } - /** - * 明文模式 & 兼容模式 & 密文模式 - *
- * 值得注意的是:企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数 - *
- * - * @param weixinId - * 公众号的应用ID(appid/corpid) 密文&兼容模式下需要填写 - * - * @param token - * 开发者填写的token 无论哪种模式都需要填写 - * @param aesKey - * 消息加密的密钥 密文&兼容模式下需要填写 - */ - public WeixinServerBootstrap(String weixinId, String token, String aesKey) { - this(new AesToken(weixinId, token, aesKey)); - } + /** + * 明文模式 & 兼容模式 & 密文模式 + *
+ * 值得注意的是:企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数 + *
+ * + * @param weixinId + * 公众号的应用ID(appid/corpid) 密文&兼容模式下需要填写 + * + * @param token + * 开发者填写的token 无论哪种模式都需要填写 + * @param aesKey + * 消息加密的密钥 密文&兼容模式下需要填写 + */ + public WeixinServerBootstrap(String weixinId, String token, String aesKey) { + this(new AesToken(weixinId, token, aesKey)); + } - /** - * 多个公众号的支持
值得注意的是: - *
- * 1).企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数 - *
- *
- * 2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数 - *
- * - * @param aesTokens - * 多个公众号 - * @return - */ - public WeixinServerBootstrap(AesToken... aesToken) { - this(new DefaultMessageMatcher(), aesToken); - } + /** + * 多个公众号的支持 + *
值得注意的是: + *
+ * 1).企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数 + *
+ *
+ * 2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数 + *
+ * + * @param aesTokens + * 多个公众号 + * @return + */ + public WeixinServerBootstrap(AesToken... aesToken) { + this(new DefaultMessageMatcher(), aesToken); + } - /** - * 多个公众号的支持
值得注意的是: - *
- * 1).企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数 - *
- *
- * 2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数 - *
- * - * @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(); - for (AesToken aesToken : aesTokens) { - this.aesTokenMap.put(aesToken.getWeixinId(), aesToken); - } - this.aesTokenMap.put("", aesTokens[0]); - this.messageHandlerList = new ArrayList(); - this.messageInterceptorList = new ArrayList(); - this.messageDispatcher = new WeixinMessageDispatcher(messageMatcher); - } + /** + * 多个公众号的支持 + *
值得注意的是: + *
+ * 1).企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数 + *
+ *
+ * 2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数 + *
+ * + * @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(); + for (AesToken aesToken : aesTokens) { + this.aesTokenMap.put(aesToken.getWeixinId(), aesToken); + } + this.aesTokenMap.put("", aesTokens[0]); + this.messageHandlerList = new ArrayList(); + this.messageInterceptorList = new ArrayList(); + this.messageDispatcher = new WeixinMessageDispatcher(messageMatcher); + } - /** - * 默认端口(30000)启动服务 - * - */ - public void startup() throws WeixinException { - startup(DEFAULT_SERVERPORT); - } + /** + * 默认端口(30000)启动服务 + * + */ + public void startup() throws WeixinException { + startup(DEFAULT_SERVERPORT); + } - /** - * 指定端口启动服务 - * - */ - public void startup(int serverPort) throws WeixinException { - startup(DEFAULT_BOSSTHREADS, DEFAULT_WORKERTHREADS, serverPort); - } + /** + * 指定端口启动服务 + * + */ + public void startup(int serverPort) throws WeixinException { + 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) - throws WeixinException { - messageDispatcher.setMessageHandlerList(messageHandlerList); - messageDispatcher.setMessageInterceptorList(messageInterceptorList); + /** + * 接受参数启动服务 + * + * @param bossThreads + * boss线程数 + * @param workerThreads + * worker线程数 + * @param serverPort + * 服务启动端口 + * @return + * @throws WeixinException + */ + public void startup(int bossThreads, int workerThreads, final int serverPort) throws WeixinException { + 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() { + @Override + public void operationComplete(Future 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 WeixinException("netty server startup FAIL", e); + } finally { + shutdown(); + } + } - EventLoopGroup bossGroup = new NioEventLoopGroup(bossThreads); - EventLoopGroup workerGroup = new NioEventLoopGroup(workerThreads); - try { - wechatInitializer = new WeixinServerInitializer(aesTokenMap, - messageDispatcher); - ServerBootstrap b = new ServerBootstrap(); - b.option(ChannelOption.SO_BACKLOG, 1024); - b.group(bossGroup, workerGroup) - .channel(NioServerSocketChannel.class) - .handler(new LoggingHandler()) - .childHandler(wechatInitializer); - Channel ch = b.bind(serverPort) - .addListener(new FutureListener() { - @Override - public void operationComplete(Future 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 WeixinException("netty server startup FAIL", e); - } finally { - bossGroup.shutdownGracefully(); - workerGroup.shutdownGracefully(); - } - } + public boolean shutdown() { + if (bootstrap == null) { + return false; + } + ServerBootstrapConfig c = bootstrap.config(); + c.group().shutdownGracefully(); + c.childGroup().shutdownGracefully(); + 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 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 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 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; - } + /** + * 按照包名去添加消息拦截器 + * + * @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; - } + /** + * 声明处理器跟拦截器类实例化的构造工厂,否则通过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 messageClass) { - messageDispatcher.registMessageClass(messageKey, messageClass); - return this; - } + /** + * 注册消息类型 + * + * @param messageKey + * 消息key + * @param messageClass + * 消息类 + * @return + */ + public WeixinServerBootstrap registMessageClass(WeixinMessageKey messageKey, + Class messageClass) { + messageDispatcher.registMessageClass(messageKey, messageClass); + return this; + } - /** - * 打开总是响应开关,如未匹配到MessageHandler时回复空白消息 - */ - public WeixinServerBootstrap openAlwaysResponse() { - messageDispatcher.openAlwaysResponse(); - return this; - } + /** + * 打开总是响应开关,如未匹配到MessageHandler时回复空白消息 + */ + public WeixinServerBootstrap openAlwaysResponse() { + messageDispatcher.openAlwaysResponse(); + return this; + } - /** - * aesTokenMap 最好是线程安全的 - * - * @param aesToken - * @return - */ - public int addAesToken(AesToken aesToken) { - return wechatInitializer.addAesToken(aesToken); - } + /** + * 动态添加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.8"; + public final static String VERSION = "1.1.8"; } \ No newline at end of file