This commit is contained in:
jinyu 2017-06-23 18:07:31 +08:00
parent 6464ba8842
commit 4c0f96fcfe
6 changed files with 441 additions and 474 deletions

View File

@ -7,6 +7,7 @@
<artifactId>weixin4j</artifactId> <artifactId>weixin4j</artifactId>
<version>1.7.6</version> <version>1.7.6</version>
</parent> </parent>
<packaging>war</packaging>
<artifactId>weixin4j-example</artifactId> <artifactId>weixin4j-example</artifactId>
<version>1.0</version> <version>1.0</version>
<name>weixin4j-example</name> <name>weixin4j-example</name>

View File

@ -1,8 +1,5 @@
package com.foxinmy.weixin4j.example.server; package com.foxinmy.weixin4j.example.server;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
@ -19,76 +16,71 @@ import io.netty.util.internal.logging.InternalLoggerFactory;
* 微信消息服务:需要另外开启一个线程去启动服务,这里值得注意的时weixin4j-serve本身是作为一个单独的服务来启动的可以不依赖Spring容器 * 微信消息服务:需要另外开启一个线程去启动服务,这里值得注意的时weixin4j-serve本身是作为一个单独的服务来启动的可以不依赖Spring容器
* 但考虑到目前都是Spring mvc的架构这里就需要使用一个独立的线程去启动服务其实本身没有使用spring mvc的API * 但考虑到目前都是Spring mvc的架构这里就需要使用一个独立的线程去启动服务其实本身没有使用spring mvc的API
* 以后会考虑支持servlet api去集成不同的web框架 * 以后会考虑支持servlet api去集成不同的web框架
* *
* @className Weixin4jServerStartupWithThread * @className Weixin4jServerStartupWithThread
* @author jinyu(foxinmy@gmail.com) * @author jinyu(foxinmy@gmail.com)
* @date 2015年5月7日 * @date 2015年5月7日
* @since JDK 1.6 * @since JDK 1.6
*/ */
public class Weixin4jServerStartupWithThread implements ApplicationContextAware { public class Weixin4jServerStartupWithThread implements ApplicationContextAware {
/** /**
* 服务监听的端口号,目前微信只支持80端口,可以考虑用nginx做转发到此端口 * 服务监听的端口号,目前微信只支持80端口,可以考虑用nginx做转发到此端口
*/ */
private final int port; private final int port;
/** /**
* 服务器token信息 * 服务器token信息
*/ */
/** /**
* 明文模式:String aesToken = ""; 密文模式:AesToken aesToken = new * 明文模式:String aesToken = ""; 密文模式:AesToken aesToken = new
* AesToken("公众号appid", "公众号token","公众号加密/解密消息的密钥"); * AesToken("公众号appid", "公众号token","公众号加密/解密消息的密钥");
*/ */
private final AesToken aesToken; private final AesToken aesToken;
/** /**
* 处理微信消息的全限包名(也可通过addHandler方式一个一个添加) * 处理微信消息的全限包名(也可通过addHandler方式一个一个添加)
*/ */
private final String handlerPackage; private final String handlerPackage;
/** /**
* 用spring去获取bean * 用spring去获取bean
*/ */
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
private Weixin4jServerStartupWithThread(int port, AesToken aesToken, private Weixin4jServerStartupWithThread(int port, AesToken aesToken, String handlerPackage) {
String handlerPackage) { this.port = port;
this.port = port; this.aesToken = aesToken;
this.aesToken = aesToken; this.handlerPackage = handlerPackage;
this.handlerPackage = handlerPackage; }
}
@Override @Override
public void setApplicationContext(ApplicationContext applicationContext) public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
throws BeansException { this.applicationContext = applicationContext;
this.applicationContext = applicationContext; }
}
private ExecutorService executor; private WeixinServerBootstrap bootstrap;
/** /**
* 启动函数 * 启动函数
* *
* @throws WeixinException * @throws WeixinException
*/ */
public void start() { public void start() {
executor = Executors.newCachedThreadPool(); new Thread(new Runnable() {
executor.execute(new Runnable() { @Override
@Override public void run() {
public void run() { try {
try { bootstrap = new WeixinServerBootstrap(aesToken) // 指定开发者token信息
new WeixinServerBootstrap(aesToken) // 指定开发者token信息 .handlerPackagesToScan(handlerPackage) // 扫描处理消息的包
.handlerPackagesToScan(handlerPackage) // 扫描处理消息的包 .resolveBeanFactory(new SpringBeanFactory(applicationContext)) // 声明处理消息类由Spring容器去实例化
.resolveBeanFactory( .addHandler(DebugMessageHandler.global) // 当没有匹配到消息处理时输出调试信息开发环境打开
new SpringBeanFactory(applicationContext)) // 声明处理消息类由Spring容器去实例化 .openAlwaysResponse(); // 当没有匹配到消息处理时输出空白回复(公众号不会出现该公众号无法提供服务的提示)正式环境打开
.addHandler(DebugMessageHandler.global) // 当没有匹配到消息处理时输出调试信息开发环境打开 bootstrap.startup(port); // 绑定服务的端口号即对外暴露(微信服务器URL地址)的服务端口
.openAlwaysResponse() // 当没有匹配到消息处理时输出空白回复(公众号不会出现该公众号无法提供服务的提示)正式环境打开 } catch (WeixinException e) {
.startup(port); // 绑定服务的端口号即对外暴露(微信服务器URL地址)的服务端口 InternalLoggerFactory.getInstance(getClass()).error("weixin4j server startup:FAIL", e);
} catch (WeixinException e) { }
InternalLoggerFactory.getInstance(getClass()).error( }
"weixin4j server startup:FAIL", e); }).start();
} }
}
});
}
public void stop() { public void stop() {
executor.shutdown(); bootstrap.shutdown();
} }
} }

View File

@ -5,8 +5,8 @@
xsi:schemaLocation=" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd 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"> http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
<import resource="spring-weixin4j-proxy.xml" /> <import resource="spring-weixin4j-proxy.xml" />
<import resource="spring-weixin4j-server.xml" /> <import resource="spring-weixin4j-server.xml" />
</beans> </beans>

View File

@ -1,14 +1,5 @@
package com.foxinmy.weixin4j.socket; 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.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; 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.util.ServerToolkits;
import com.foxinmy.weixin4j.xml.EncryptMessageHandler; 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 * @className WeixinMessageDecoder
* @author jinyu(foxinmy@gmail.com) * @author jinyu(foxinmy@gmail.com)
* @date 2014年11月13日 * @date 2014年11月13日
* @since JDK 1.6 * @since JDK 1.6
* @see <a * @see <a href=
* href="http://mp.weixin.qq.com/wiki/0/61c3a8b9d50ac74f18bdf2e54ddfc4e0.html">加密接入指引</a> * "http://mp.weixin.qq.com/wiki/0/61c3a8b9d50ac74f18bdf2e54ddfc4e0.html">加密接入指引</a>
* @see com.foxinmy.weixin4j.request.WeixinRequest * @see com.foxinmy.weixin4j.request.WeixinRequest
*/ */
@ChannelHandler.Sharable @ChannelHandler.Sharable
public class WeixinMessageDecoder extends public class WeixinMessageDecoder extends MessageToMessageDecoder<FullHttpRequest> {
MessageToMessageDecoder<FullHttpRequest> { private final InternalLogger logger = InternalLoggerFactory.getInstance(getClass());
private final InternalLogger logger = InternalLoggerFactory
.getInstance(getClass());
private Map<String, AesToken> aesTokenMap = new ConcurrentHashMap<String, AesToken>(); private Map<String, AesToken> aesTokenMap = new ConcurrentHashMap<String, AesToken>();
public WeixinMessageDecoder(final Map<String, AesToken> aesTokenMap) { public WeixinMessageDecoder(final Map<String, AesToken> aesTokenMap) {
for (Entry<String, AesToken> entry : aesTokenMap.entrySet()) { for (Entry<String, AesToken> entry : aesTokenMap.entrySet()) {
this.aesTokenMap.put(entry.getKey() == null ? "" : entry.getKey(), this.aesTokenMap.put(entry.getKey() == null ? "" : entry.getKey(), entry.getValue());
entry.getValue()); }
} }
}
public int addAesToken(final AesToken asetoken) { public void addAesToken(final AesToken asetoken) {
AesToken token = aesTokenMap.get(asetoken.getWeixinId()); aesTokenMap.put(asetoken.getWeixinId(), asetoken);
if (token != null) }
return -1;
aesTokenMap.put(asetoken.getWeixinId(), asetoken);
return 0;
}
@Override @Override
protected void decode(ChannelHandlerContext ctx, FullHttpRequest req, protected void decode(ChannelHandlerContext ctx, FullHttpRequest req, List<Object> out) throws WeixinException {
List<Object> out) throws WeixinException { String messageContent = req.content().toString(ServerToolkits.UTF_8);
String messageContent = req.content().toString(ServerToolkits.UTF_8); QueryStringDecoder queryDecoder = new QueryStringDecoder(req.uri(), true);
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.uri(), HttpMethod method = req.method();
true); logger.info("decode request:{} use {} method invoking", req.uri(), method);
HttpMethod method = req.method(); Map<String, List<String>> parameters = queryDecoder.parameters();
logger.info("decode request:{} use {} method invoking", req.uri(), EncryptType encryptType = parameters.containsKey("encrypt_type")
method); ? EncryptType.valueOf(parameters.get("encrypt_type").get(0).toUpperCase()) : EncryptType.RAW;
Map<String, List<String>> parameters = queryDecoder.parameters(); String echoStr = parameters.containsKey("echostr") ? parameters.get("echostr").get(0) : "";
EncryptType encryptType = parameters.containsKey("encrypt_type") ? EncryptType String timeStamp = parameters.containsKey("timestamp") ? parameters.get("timestamp").get(0) : "";
.valueOf(parameters.get("encrypt_type").get(0).toUpperCase()) String nonce = parameters.containsKey("nonce") ? parameters.get("nonce").get(0) : "";
: EncryptType.RAW; String signature = parameters.containsKey("signature") ? parameters.get("signature").get(0) : "";
String echoStr = parameters.containsKey("echostr") ? parameters.get( String msgSignature = parameters.containsKey("msg_signature") ? parameters.get("msg_signature").get(0) : "";
"echostr").get(0) : ""; String weixinId = parameters.containsKey("weixin_id") ? parameters.get("weixin_id").get(0) : "";
String timeStamp = parameters.containsKey("timestamp") ? parameters AesToken aesToken = aesTokenMap.get(weixinId);
.get("timestamp").get(0) : ""; String encryptContent = null;
String nonce = parameters.containsKey("nonce") ? parameters if (!ServerToolkits.isBlank(messageContent) && encryptType == EncryptType.AES) {
.get("nonce").get(0) : ""; if (ServerToolkits.isBlank(aesToken.getAesKey())) {
String signature = parameters.containsKey("signature") ? parameters throw new WeixinException("EncodingAESKey not be empty in safety(AES) mode");
.get("signature").get(0) : ""; }
String msgSignature = parameters.containsKey("msg_signature") ? parameters EncryptMessageHandler encryptHandler = EncryptMessageHandler.parser(messageContent);
.get("msg_signature").get(0) : ""; encryptContent = encryptHandler.getEncryptContent();
String weixinId = parameters.containsKey("weixin_id") ? parameters.get( /**
"weixin_id").get(0) : ""; * 企业号第三方套件 _
AesToken aesToken = aesTokenMap.get(weixinId); */
String encryptContent = null; if (aesToken.getWeixinId().startsWith("tj")) {
if (!ServerToolkits.isBlank(messageContent) aesToken = new AesToken(encryptHandler.getToUserName(), aesToken.getToken(), aesToken.getAesKey());
&& encryptType == EncryptType.AES) { }
if (ServerToolkits.isBlank(aesToken.getAesKey())) { messageContent = MessageUtil.aesDecrypt(aesToken.getWeixinId(), aesToken.getAesKey(), encryptContent);
throw new WeixinException( }
"EncodingAESKey not be empty in safety(AES) mode"); logger.info("read original message {}", messageContent);
} WeixinRequest request = new WeixinRequest(req.headers(), method, req.uri(), encryptType, echoStr, timeStamp,
EncryptMessageHandler encryptHandler = EncryptMessageHandler nonce, signature, msgSignature, messageContent, encryptContent, aesToken);
.parser(messageContent); request.setDecoderResult(req.decoderResult());
encryptContent = encryptHandler.getEncryptContent(); request.setProtocolVersion(req.protocolVersion());
/** out.add(request);
* 企业号第三方套件 _ }
*/
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);
}
} }

View File

@ -1,19 +1,19 @@
package com.foxinmy.weixin4j.socket; 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.ChannelInitializer;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec; 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 * @className WeixinServerInitializer
* @author jinyu(foxinmy@gmail.com) * @author jinyu(foxinmy@gmail.com)
* @date 2015年5月17日 * @date 2015年5月17日
@ -22,27 +22,26 @@ import com.foxinmy.weixin4j.util.AesToken;
*/ */
public class WeixinServerInitializer extends ChannelInitializer<SocketChannel> { public class WeixinServerInitializer extends ChannelInitializer<SocketChannel> {
private final WeixinMessageDispatcher messageDispatcher; private final WeixinMessageDispatcher messageDispatcher;
private final WeixinMessageDecoder messageDecoder; private final WeixinMessageDecoder messageDecoder;
public WeixinServerInitializer(Map<String, AesToken> aesTokenMap, public WeixinServerInitializer(Map<String, AesToken> aesTokenMap, WeixinMessageDispatcher messageDispatcher) {
WeixinMessageDispatcher messageDispatcher) { this.messageDispatcher = messageDispatcher;
this.messageDispatcher = messageDispatcher; this.messageDecoder = new WeixinMessageDecoder(aesTokenMap);
this.messageDecoder = new WeixinMessageDecoder(aesTokenMap); }
}
public int addAesToken(final AesToken asetoken) { public void addAesToken(AesToken asetoken) {
return messageDecoder.addAesToken(asetoken); messageDecoder.addAesToken(asetoken);
} }
@Override @Override
protected void initChannel(SocketChannel channel) { protected void initChannel(SocketChannel channel) {
ChannelPipeline pipeline = channel.pipeline(); ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(65536)); pipeline.addLast(new HttpObjectAggregator(65536));
pipeline.addLast(messageDecoder); pipeline.addLast(messageDecoder);
pipeline.addLast(new WeixinResponseEncoder()); pipeline.addLast(new WeixinResponseEncoder());
pipeline.addLast(new SingleResponseEncoder()); pipeline.addLast(new SingleResponseEncoder());
pipeline.addLast(new WeixinRequestHandler(messageDispatcher)); pipeline.addLast(new WeixinRequestHandler(messageDispatcher));
} }
} }

View File

@ -1,17 +1,5 @@
package com.foxinmy.weixin4j.startup; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -30,9 +18,21 @@ import com.foxinmy.weixin4j.request.WeixinMessage;
import com.foxinmy.weixin4j.socket.WeixinServerInitializer; import com.foxinmy.weixin4j.socket.WeixinServerInitializer;
import com.foxinmy.weixin4j.util.AesToken; 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服务启动程序 * 微信netty服务启动程序
* *
* @className WeixinServerBootstrap * @className WeixinServerBootstrap
* @author jinyu(foxinmy@gmail.com) * @author jinyu(foxinmy@gmail.com)
* @date 2014年10月12日 * @date 2014年10月12日
@ -45,309 +45,308 @@ import com.foxinmy.weixin4j.util.AesToken;
*/ */
public final class WeixinServerBootstrap { public final class WeixinServerBootstrap {
private final InternalLogger logger = InternalLoggerFactory private final InternalLogger logger = InternalLoggerFactory.getInstance(getClass());
.getInstance(getClass());
/** /**
* boss线程数,默认设置为cpu的核数 * boss线程数,默认设置为cpu的核数
*/ */
public final static int DEFAULT_BOSSTHREADS; public final static int DEFAULT_BOSSTHREADS;
/** /**
* worker线程数,默认设置为DEFAULT_BOSSTHREADS * 2 * worker线程数,默认设置为DEFAULT_BOSSTHREADS * 2
*/ */
public final static int DEFAULT_WORKERTHREADS; public final static int DEFAULT_WORKERTHREADS;
/** /**
* 服务启动的默认端口 * 服务启动的默认端口
*/ */
public final static int DEFAULT_SERVERPORT = 30000; public final static int DEFAULT_SERVERPORT = 30000;
/** /**
* 消息分发器 * 消息分发器
*/ */
private WeixinMessageDispatcher messageDispatcher; private WeixinMessageDispatcher messageDispatcher;
/** /**
* 消息处理器 * 消息处理器
*/ */
private List<WeixinMessageHandler> messageHandlerList; private List<WeixinMessageHandler> messageHandlerList;
/** /**
* 消息拦截器 * 消息拦截器
*/ */
private List<WeixinMessageInterceptor> messageInterceptorList; private List<WeixinMessageInterceptor> messageInterceptorList;
/** /**
* aes and token * aes and token
* *
*/ */
private final Map<String, AesToken> aesTokenMap; private final Map<String, AesToken> aesTokenMap;
private WeixinServerInitializer wechatInitializer; private ServerBootstrap bootstrap;
static { static {
DEFAULT_BOSSTHREADS = Runtime.getRuntime().availableProcessors(); DEFAULT_BOSSTHREADS = Runtime.getRuntime().availableProcessors();
DEFAULT_WORKERTHREADS = DEFAULT_BOSSTHREADS * 2; DEFAULT_WORKERTHREADS = DEFAULT_BOSSTHREADS * 2;
} }
/** /**
* *
* 明文模式 * 明文模式
* *
* @param token * @param token
* 开发者token * 开发者token
* *
*/ */
public WeixinServerBootstrap(String token) { public WeixinServerBootstrap(String token) {
this("", token, null); this("", token, null);
} }
/** /**
* 明文模式 & 兼容模式 & 密文模式 * 明文模式 & 兼容模式 & 密文模式
* <dl> * <dl>
* <font * <font color=
* color="red">值得注意的是企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font> * "red">值得注意的是企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font>
* </dl> * </dl>
* *
* @param weixinId * @param weixinId
* 公众号的应用ID(appid/corpid) 密文&兼容模式下需要填写 * 公众号的应用ID(appid/corpid) 密文&兼容模式下需要填写
* *
* @param token * @param token
* 开发者填写的token 无论哪种模式都需要填写 * 开发者填写的token 无论哪种模式都需要填写
* @param aesKey * @param aesKey
* 消息加密的密钥 密文&兼容模式下需要填写 * 消息加密的密钥 密文&兼容模式下需要填写
*/ */
public WeixinServerBootstrap(String weixinId, String token, String aesKey) { public WeixinServerBootstrap(String weixinId, String token, String aesKey) {
this(new AesToken(weixinId, token, aesKey)); this(new AesToken(weixinId, token, aesKey));
} }
/** /**
* 多个公众号的支持 <dt>值得注意的是 * 多个公众号的支持
* <dl> * <dt>值得注意的是
* <font color="red">1).企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font> * <dl>
* </dl> * <font color="red">1).企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font>
* <dl> * </dl>
* <font * <dl>
* color="red">2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数</font> * <font color=
* </dl> * "red">2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数</font>
* * </dl>
* @param aesTokens *
* 多个公众号 * @param aesTokens
* @return * 多个公众号
*/ * @return
public WeixinServerBootstrap(AesToken... aesToken) { */
this(new DefaultMessageMatcher(), aesToken); public WeixinServerBootstrap(AesToken... aesToken) {
} this(new DefaultMessageMatcher(), aesToken);
}
/** /**
* 多个公众号的支持 <dt>值得注意的是 * 多个公众号的支持
* <dl> * <dt>值得注意的是
* <font color="red">1).企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font> * <dl>
* </dl> * <font color="red">1).企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font>
* <dl> * </dl>
* <font * <dl>
* color="red">2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数</font> * <font color=
* </dl> * "red">2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数</font>
* * </dl>
* @param messageMatcher *
* 消息匹配器 * @param messageMatcher
* @param aesTokens * 消息匹配器
* 公众号信息 * @param aesTokens
* @return * 公众号信息
*/ * @return
public WeixinServerBootstrap(WeixinMessageMatcher messageMatcher, */
AesToken... aesTokens) { public WeixinServerBootstrap(WeixinMessageMatcher messageMatcher, AesToken... aesTokens) {
if (messageMatcher == null) { if (messageMatcher == null) {
throw new IllegalArgumentException("MessageMatcher not be null"); throw new IllegalArgumentException("MessageMatcher not be null");
} }
if (aesTokens == null) { if (aesTokens == null) {
throw new IllegalArgumentException("AesToken not be null"); throw new IllegalArgumentException("AesToken not be null");
} }
this.aesTokenMap = new HashMap<String, AesToken>(); this.aesTokenMap = new HashMap<String, AesToken>();
for (AesToken aesToken : aesTokens) { for (AesToken aesToken : aesTokens) {
this.aesTokenMap.put(aesToken.getWeixinId(), aesToken); this.aesTokenMap.put(aesToken.getWeixinId(), aesToken);
} }
this.aesTokenMap.put("", aesTokens[0]); this.aesTokenMap.put("", aesTokens[0]);
this.messageHandlerList = new ArrayList<WeixinMessageHandler>(); this.messageHandlerList = new ArrayList<WeixinMessageHandler>();
this.messageInterceptorList = new ArrayList<WeixinMessageInterceptor>(); this.messageInterceptorList = new ArrayList<WeixinMessageInterceptor>();
this.messageDispatcher = new WeixinMessageDispatcher(messageMatcher); this.messageDispatcher = new WeixinMessageDispatcher(messageMatcher);
} }
/** /**
* 默认端口(30000)启动服务 * 默认端口(30000)启动服务
* *
*/ */
public void startup() throws WeixinException { public void startup() throws WeixinException {
startup(DEFAULT_SERVERPORT); startup(DEFAULT_SERVERPORT);
} }
/** /**
* 指定端口启动服务 * 指定端口启动服务
* *
*/ */
public void startup(int serverPort) throws WeixinException { public void startup(int serverPort) throws WeixinException {
startup(DEFAULT_BOSSTHREADS, DEFAULT_WORKERTHREADS, serverPort); startup(DEFAULT_BOSSTHREADS, DEFAULT_WORKERTHREADS, serverPort);
} }
/** /**
* 接受参数启动服务 * 接受参数启动服务
* *
* @param bossThreads * @param bossThreads
* boss线程数 * boss线程数
* @param workerThreads * @param workerThreads
* worker线程数 * worker线程数
* @param serverPort * @param serverPort
* 服务启动端口 * 服务启动端口
* @return * @return
* @throws WeixinException * @throws WeixinException
*/ */
public void startup(int bossThreads, int workerThreads, final int serverPort) public void startup(int bossThreads, int workerThreads, final int serverPort) throws WeixinException {
throws WeixinException { messageDispatcher.setMessageHandlerList(messageHandlerList);
messageDispatcher.setMessageHandlerList(messageHandlerList); messageDispatcher.setMessageInterceptorList(messageInterceptorList);
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 WeixinException("netty server startup FAIL", e);
} finally {
shutdown();
}
}
EventLoopGroup bossGroup = new NioEventLoopGroup(bossThreads); public boolean shutdown() {
EventLoopGroup workerGroup = new NioEventLoopGroup(workerThreads); if (bootstrap == null) {
try { return false;
wechatInitializer = new WeixinServerInitializer(aesTokenMap, }
messageDispatcher); ServerBootstrapConfig c = bootstrap.config();
ServerBootstrap b = new ServerBootstrap(); c.group().shutdownGracefully();
b.option(ChannelOption.SO_BACKLOG, 1024); c.childGroup().shutdownGracefully();
b.group(bossGroup, workerGroup) messageHandlerList = null;
.channel(NioServerSocketChannel.class) messageInterceptorList = null;
.handler(new LoggingHandler()) messageDispatcher = null;
.childHandler(wechatInitializer); bootstrap = null;
Channel ch = b.bind(serverPort) return true;
.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 WeixinException("netty server startup FAIL", e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
/** /**
* 添加一个或者多个消息处理器 * 添加一个或者多个消息处理器
* *
* @param messageHandler * @param messageHandler
* 消息处理器 * 消息处理器
* @return * @return
*/ */
public WeixinServerBootstrap addHandler( public WeixinServerBootstrap addHandler(WeixinMessageHandler... messageHandler) {
WeixinMessageHandler... messageHandler) { if (messageHandler == null) {
if (messageHandler == null) { throw new IllegalArgumentException("messageHandler not be null");
throw new IllegalArgumentException("messageHandler not be null"); }
} messageHandlerList.addAll(Arrays.asList(messageHandler));
messageHandlerList.addAll(Arrays.asList(messageHandler)); return this;
return this; }
}
/** /**
* 插入一个或多个消息拦截器 * 插入一个或多个消息拦截器
* *
* @param messageInterceptor * @param messageInterceptor
* 消息拦截器 * 消息拦截器
* @return * @return
*/ */
public WeixinServerBootstrap addInterceptor( public WeixinServerBootstrap addInterceptor(WeixinMessageInterceptor... messageInterceptor) {
WeixinMessageInterceptor... messageInterceptor) { if (messageInterceptor == null) {
if (messageInterceptor == null) { throw new IllegalArgumentException("messageInterceptor not be null");
throw new IllegalArgumentException("messageInterceptor not be null"); }
} messageInterceptorList.addAll(Arrays.asList(messageInterceptor));
messageInterceptorList.addAll(Arrays.asList(messageInterceptor)); return this;
return this; }
}
/** /**
* 按照包名去添加消息处理器 * 按照包名去添加消息处理器
* *
* @param messageHandlerPackages * @param messageHandlerPackages
* 消息处理器所在的包名 * 消息处理器所在的包名
* @return * @return
*/ */
public WeixinServerBootstrap handlerPackagesToScan( public WeixinServerBootstrap handlerPackagesToScan(String... messageHandlerPackages) {
String... messageHandlerPackages) { if (messageHandlerPackages == null) {
if (messageHandlerPackages == null) { throw new IllegalArgumentException("messageHandlerPackages not be null");
throw new IllegalArgumentException( }
"messageHandlerPackages not be null"); messageDispatcher.setMessageHandlerPackages(messageHandlerPackages);
} return this;
messageDispatcher.setMessageHandlerPackages(messageHandlerPackages); }
return this;
}
/** /**
* 按照包名去添加消息拦截器 * 按照包名去添加消息拦截器
* *
* @param messageInterceptorPackages * @param messageInterceptorPackages
* 消息拦截器所在的包名 * 消息拦截器所在的包名
* @return * @return
*/ */
public WeixinServerBootstrap interceptorPackagesToScan( public WeixinServerBootstrap interceptorPackagesToScan(String... messageInterceptorPackages) {
String... messageInterceptorPackages) { if (messageInterceptorPackages == null) {
if (messageInterceptorPackages == null) { throw new IllegalArgumentException("messageInterceptorPackages not be null");
throw new IllegalArgumentException( }
"messageInterceptorPackages not be null"); messageDispatcher.setMessageInterceptorPackages(messageInterceptorPackages);
} return this;
messageDispatcher }
.setMessageInterceptorPackages(messageInterceptorPackages);
return this;
}
/** /**
* 声明处理器跟拦截器类实例化的构造工厂,否则通过Class.newInstance的方式构造 * 声明处理器跟拦截器类实例化的构造工厂,否则通过Class.newInstance的方式构造
* *
* @param beanFactory * @param beanFactory
* Bean构造工厂 * Bean构造工厂
* @return * @return
*/ */
public WeixinServerBootstrap resolveBeanFactory(BeanFactory beanFactory) { public WeixinServerBootstrap resolveBeanFactory(BeanFactory beanFactory) {
messageDispatcher.setBeanFactory(beanFactory); messageDispatcher.setBeanFactory(beanFactory);
return this; return this;
} }
/** /**
* 注册消息类型 * 注册消息类型
* *
* @param messageKey * @param messageKey
* 消息key * 消息key
* @param messageClass * @param messageClass
* 消息类 * 消息类
* @return * @return
*/ */
public WeixinServerBootstrap registMessageClass( public WeixinServerBootstrap registMessageClass(WeixinMessageKey messageKey,
WeixinMessageKey messageKey, Class<? extends WeixinMessage> messageClass) {
Class<? extends WeixinMessage> messageClass) { messageDispatcher.registMessageClass(messageKey, messageClass);
messageDispatcher.registMessageClass(messageKey, messageClass); return this;
return this; }
}
/** /**
* 打开总是响应开关,如未匹配到MessageHandler时回复空白消息 * 打开总是响应开关,如未匹配到MessageHandler时回复空白消息
*/ */
public WeixinServerBootstrap openAlwaysResponse() { public WeixinServerBootstrap openAlwaysResponse() {
messageDispatcher.openAlwaysResponse(); messageDispatcher.openAlwaysResponse();
return this; return this;
} }
/** /**
* aesTokenMap 最好是线程安全的 * 动态添加aesToken
* *
* @param aesToken * @param aesToken
* @return * @return
*/ */
public int addAesToken(AesToken aesToken) { public boolean addAesToken(AesToken aesToken) {
return wechatInitializer.addAesToken(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";
} }