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>
<version>1.7.6</version>
</parent>
<packaging>war</packaging>
<artifactId>weixin4j-example</artifactId>
<version>1.0</version>
<name>weixin4j-example</name>

View File

@ -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;
@ -47,20 +44,18 @@ public class Weixin4jServerStartupWithThread implements ApplicationContextAware
*/
private ApplicationContext applicationContext;
private Weixin4jServerStartupWithThread(int port, AesToken aesToken,
String 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 {
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
private ExecutorService executor;
private WeixinServerBootstrap bootstrap;
/**
* 启动函数
@ -68,27 +63,24 @@ public class Weixin4jServerStartupWithThread implements ApplicationContextAware
* @throws WeixinException
*/
public void start() {
executor = Executors.newCachedThreadPool();
executor.execute(new Runnable() {
new Thread(new Runnable() {
@Override
public void run() {
try {
new WeixinServerBootstrap(aesToken) // 指定开发者token信息
bootstrap = new WeixinServerBootstrap(aesToken) // 指定开发者token信息
.handlerPackagesToScan(handlerPackage) // 扫描处理消息的包
.resolveBeanFactory(
new SpringBeanFactory(applicationContext)) // 声明处理消息类由Spring容器去实例化
.resolveBeanFactory(new SpringBeanFactory(applicationContext)) // 声明处理消息类由Spring容器去实例化
.addHandler(DebugMessageHandler.global) // 当没有匹配到消息处理时输出调试信息开发环境打开
.openAlwaysResponse() // 当没有匹配到消息处理时输出空白回复(公众号不会出现该公众号无法提供服务的提示)正式环境打开
.startup(port); // 绑定服务的端口号即对外暴露(微信服务器URL地址)的服务端口
.openAlwaysResponse(); // 当没有匹配到消息处理时输出空白回复(公众号不会出现该公众号无法提供服务的提示)正式环境打开
bootstrap.startup(port); // 绑定服务的端口号即对外暴露(微信服务器URL地址)的服务端口
} catch (WeixinException e) {
InternalLoggerFactory.getInstance(getClass()).error(
"weixin4j server startup:FAIL", e);
InternalLoggerFactory.getInstance(getClass()).error("weixin4j server startup:FAIL", e);
}
}
});
}).start();
}
public void stop() {
executor.shutdown();
bootstrap.shutdown();
}
}

View File

@ -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,6 +13,15 @@ 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;
/**
* 微信消息解码类
*
@ -29,84 +29,60 @@ import com.foxinmy.weixin4j.xml.EncryptMessageHandler;
* @author jinyu(foxinmy@gmail.com)
* @date 2014年11月13日
* @since JDK 1.6
* @see <a
* href="http://mp.weixin.qq.com/wiki/0/61c3a8b9d50ac74f18bdf2e54ddfc4e0.html">加密接入指引</a>
* @see <a href=
* "http://mp.weixin.qq.com/wiki/0/61c3a8b9d50ac74f18bdf2e54ddfc4e0.html">加密接入指引</a>
* @see com.foxinmy.weixin4j.request.WeixinRequest
*/
@ChannelHandler.Sharable
public class WeixinMessageDecoder extends
MessageToMessageDecoder<FullHttpRequest> {
private final InternalLogger logger = InternalLoggerFactory
.getInstance(getClass());
public class WeixinMessageDecoder extends MessageToMessageDecoder<FullHttpRequest> {
private final InternalLogger logger = InternalLoggerFactory.getInstance(getClass());
private Map<String, AesToken> aesTokenMap = new ConcurrentHashMap<String, AesToken>();
public WeixinMessageDecoder(final Map<String, AesToken> aesTokenMap) {
for (Entry<String, AesToken> entry : aesTokenMap.entrySet()) {
this.aesTokenMap.put(entry.getKey() == null ? "" : entry.getKey(),
entry.getValue());
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;
public void addAesToken(final AesToken asetoken) {
aesTokenMap.put(asetoken.getWeixinId(), asetoken);
return 0;
}
@Override
protected void decode(ChannelHandlerContext ctx, FullHttpRequest req,
List<Object> out) throws WeixinException {
protected void decode(ChannelHandlerContext ctx, FullHttpRequest req, List<Object> out) throws WeixinException {
String messageContent = req.content().toString(ServerToolkits.UTF_8);
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.uri(),
true);
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.uri(), true);
HttpMethod method = req.method();
logger.info("decode request:{} use {} method invoking", req.uri(),
method);
logger.info("decode request:{} use {} method invoking", req.uri(), method);
Map<String, List<String>> 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) : "";
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(messageContent) && encryptType == EncryptType.AES) {
if (ServerToolkits.isBlank(aesToken.getAesKey())) {
throw new WeixinException(
"EncodingAESKey not be empty in safety(AES) mode");
throw new WeixinException("EncodingAESKey not be empty in safety(AES) mode");
}
EncryptMessageHandler encryptHandler = EncryptMessageHandler
.parser(messageContent);
EncryptMessageHandler encryptHandler = EncryptMessageHandler.parser(messageContent);
encryptContent = encryptHandler.getEncryptContent();
/**
* 企业号第三方套件 _
*/
if (aesToken.getWeixinId().startsWith("tj")) {
aesToken = new AesToken(encryptHandler.getToUserName(),
aesToken.getToken(), aesToken.getAesKey());
aesToken = new AesToken(encryptHandler.getToUserName(), aesToken.getToken(), aesToken.getAesKey());
}
messageContent = MessageUtil.aesDecrypt(aesToken.getWeixinId(),
aesToken.getAesKey(), encryptContent);
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);
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,16 +1,16 @@
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;
/**
* 微信消息服务器初始化
*
@ -25,14 +25,13 @@ public class WeixinServerInitializer extends ChannelInitializer<SocketChannel> {
private final WeixinMessageDispatcher messageDispatcher;
private final WeixinMessageDecoder messageDecoder;
public WeixinServerInitializer(Map<String, AesToken> aesTokenMap,
WeixinMessageDispatcher messageDispatcher) {
public WeixinServerInitializer(Map<String, AesToken> 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

View File

@ -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,6 +18,18 @@ 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服务启动程序
*
@ -45,8 +45,7 @@ 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的核数
@ -79,7 +78,7 @@ public final class WeixinServerBootstrap {
*/
private final Map<String, AesToken> aesTokenMap;
private WeixinServerInitializer wechatInitializer;
private ServerBootstrap bootstrap;
static {
DEFAULT_BOSSTHREADS = Runtime.getRuntime().availableProcessors();
@ -101,8 +100,8 @@ public final class WeixinServerBootstrap {
/**
* 明文模式 & 兼容模式 & 密文模式
* <dl>
* <font
* color="red">值得注意的是企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font>
* <font color=
* "red">值得注意的是企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font>
* </dl>
*
* @param weixinId
@ -118,13 +117,14 @@ public final class WeixinServerBootstrap {
}
/**
* 多个公众号的支持 <dt>值得注意的是
* 多个公众号的支持
* <dt>值得注意的是
* <dl>
* <font color="red">1).企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font>
* </dl>
* <dl>
* <font
* color="red">2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数</font>
* <font color=
* "red">2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数</font>
* </dl>
*
* @param aesTokens
@ -136,13 +136,14 @@ public final class WeixinServerBootstrap {
}
/**
* 多个公众号的支持 <dt>值得注意的是
* 多个公众号的支持
* <dt>值得注意的是
* <dl>
* <font color="red">1).企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font>
* </dl>
* <dl>
* <font
* color="red">2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数</font>
* <font color=
* "red">2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数</font>
* </dl>
*
* @param messageMatcher
@ -151,8 +152,7 @@ public final class WeixinServerBootstrap {
* 公众号信息
* @return
*/
public WeixinServerBootstrap(WeixinMessageMatcher messageMatcher,
AesToken... aesTokens) {
public WeixinServerBootstrap(WeixinMessageMatcher messageMatcher, AesToken... aesTokens) {
if (messageMatcher == null) {
throw new IllegalArgumentException("MessageMatcher not be null");
}
@ -197,33 +197,22 @@ public final class WeixinServerBootstrap {
* @return
* @throws WeixinException
*/
public void startup(int bossThreads, int workerThreads, final int serverPort)
throws WeixinException {
public void startup(int bossThreads, int workerThreads, final int serverPort) throws WeixinException {
messageDispatcher.setMessageHandlerList(messageHandlerList);
messageDispatcher.setMessageInterceptorList(messageInterceptorList);
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<Void>() {
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 {
public void operationComplete(Future<Void> future) throws Exception {
if (future.isSuccess()) {
logger.info("weixin4j server startup OK:{}",
serverPort);
logger.info("weixin4j server startup OK:{}", serverPort);
} else {
logger.info("weixin4j server startup FAIL:{}",
serverPort);
logger.info("weixin4j server startup FAIL:{}", serverPort);
}
}
}).sync().channel();
@ -231,11 +220,24 @@ public final class WeixinServerBootstrap {
} catch (InterruptedException e) {
throw new WeixinException("netty server startup FAIL", e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
shutdown();
}
}
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;
}
/**
* 添加一个或者多个消息处理器
*
@ -243,8 +245,7 @@ public final class WeixinServerBootstrap {
* 消息处理器
* @return
*/
public WeixinServerBootstrap addHandler(
WeixinMessageHandler... messageHandler) {
public WeixinServerBootstrap addHandler(WeixinMessageHandler... messageHandler) {
if (messageHandler == null) {
throw new IllegalArgumentException("messageHandler not be null");
}
@ -259,8 +260,7 @@ public final class WeixinServerBootstrap {
* 消息拦截器
* @return
*/
public WeixinServerBootstrap addInterceptor(
WeixinMessageInterceptor... messageInterceptor) {
public WeixinServerBootstrap addInterceptor(WeixinMessageInterceptor... messageInterceptor) {
if (messageInterceptor == null) {
throw new IllegalArgumentException("messageInterceptor not be null");
}
@ -275,11 +275,9 @@ public final class WeixinServerBootstrap {
* 消息处理器所在的包名
* @return
*/
public WeixinServerBootstrap handlerPackagesToScan(
String... messageHandlerPackages) {
public WeixinServerBootstrap handlerPackagesToScan(String... messageHandlerPackages) {
if (messageHandlerPackages == null) {
throw new IllegalArgumentException(
"messageHandlerPackages not be null");
throw new IllegalArgumentException("messageHandlerPackages not be null");
}
messageDispatcher.setMessageHandlerPackages(messageHandlerPackages);
return this;
@ -292,14 +290,11 @@ public final class WeixinServerBootstrap {
* 消息拦截器所在的包名
* @return
*/
public WeixinServerBootstrap interceptorPackagesToScan(
String... messageInterceptorPackages) {
public WeixinServerBootstrap interceptorPackagesToScan(String... messageInterceptorPackages) {
if (messageInterceptorPackages == null) {
throw new IllegalArgumentException(
"messageInterceptorPackages not be null");
throw new IllegalArgumentException("messageInterceptorPackages not be null");
}
messageDispatcher
.setMessageInterceptorPackages(messageInterceptorPackages);
messageDispatcher.setMessageInterceptorPackages(messageInterceptorPackages);
return this;
}
@ -324,8 +319,7 @@ public final class WeixinServerBootstrap {
* 消息类
* @return
*/
public WeixinServerBootstrap registMessageClass(
WeixinMessageKey messageKey,
public WeixinServerBootstrap registMessageClass(WeixinMessageKey messageKey,
Class<? extends WeixinMessage> messageClass) {
messageDispatcher.registMessageClass(messageKey, messageClass);
return this;
@ -340,13 +334,18 @@ public final class WeixinServerBootstrap {
}
/**
* aesTokenMap 最好是线程安全的
* 动态添加aesToken
*
* @param aesToken
* @return
*/
public int addAesToken(AesToken aesToken) {
return wechatInitializer.addAesToken(aesToken);
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";