View Javadoc
1   package com.foxinmy.weixin4j.startup;
2   
3   import java.util.ArrayList;
4   import java.util.Arrays;
5   import java.util.HashMap;
6   import java.util.List;
7   import java.util.Map;
8   
9   import com.foxinmy.weixin4j.dispatcher.BeanFactory;
10  import com.foxinmy.weixin4j.dispatcher.DefaultMessageMatcher;
11  import com.foxinmy.weixin4j.dispatcher.WeixinMessageDispatcher;
12  import com.foxinmy.weixin4j.dispatcher.WeixinMessageKey;
13  import com.foxinmy.weixin4j.dispatcher.WeixinMessageMatcher;
14  import com.foxinmy.weixin4j.handler.WeixinMessageHandler;
15  import com.foxinmy.weixin4j.interceptor.WeixinMessageInterceptor;
16  import com.foxinmy.weixin4j.request.WeixinMessage;
17  import com.foxinmy.weixin4j.socket.WeixinServerInitializer;
18  import com.foxinmy.weixin4j.util.AesToken;
19  
20  import io.netty.bootstrap.ServerBootstrap;
21  import io.netty.bootstrap.ServerBootstrapConfig;
22  import io.netty.channel.Channel;
23  import io.netty.channel.ChannelOption;
24  import io.netty.channel.nio.NioEventLoopGroup;
25  import io.netty.channel.socket.nio.NioServerSocketChannel;
26  import io.netty.handler.logging.LoggingHandler;
27  import io.netty.util.concurrent.Future;
28  import io.netty.util.concurrent.FutureListener;
29  import io.netty.util.internal.logging.InternalLogger;
30  import io.netty.util.internal.logging.InternalLoggerFactory;
31  
32  /**
33   * 微信netty服务启动程序
34   *
35   * @className WeixinServerBootstrap
36   * @author jinyu(foxinmy@gmail.com)
37   * @date 2014年10月12日
38   * @since JDK 1.6
39   * @see com.foxinmy.weixin4j.dispatcher.WeixinMessageMatcher
40   * @see com.foxinmy.weixin4j.handler.WeixinMessageHandler
41   * @see com.foxinmy.weixin4j.interceptor.WeixinMessageInterceptor
42   * @see com.foxinmy.weixin4j.dispatcher.WeixinMessageDispatcher
43   * @see com.foxinmy.weixin4j.dispatcher.BeanFactory
44   */
45  public final class WeixinServerBootstrap {
46  
47      private final InternalLogger logger = InternalLoggerFactory.getInstance(getClass());
48  
49      /**
50       * boss线程数,默认设置为cpu的核数
51       */
52      public final static int DEFAULT_BOSSTHREADS;
53      /**
54       * worker线程数,默认设置为DEFAULT_BOSSTHREADS * 2
55       */
56      public final static int DEFAULT_WORKERTHREADS;
57      /**
58       * 服务启动的默认端口
59       */
60      public final static int DEFAULT_SERVERPORT = 30000;
61      /**
62       * 消息分发器
63       */
64      private WeixinMessageDispatcher messageDispatcher;
65      /**
66       * 消息处理器
67       */
68      private List<WeixinMessageHandler> messageHandlerList;
69      /**
70       * 消息拦截器
71       */
72      private List<WeixinMessageInterceptor> messageInterceptorList;
73  
74      /**
75       * aes and token
76       *
77       */
78      private final Map<String, AesToken> aesTokenMap;
79  
80      private ServerBootstrap bootstrap;
81  
82      static {
83          DEFAULT_BOSSTHREADS = Runtime.getRuntime().availableProcessors();
84          DEFAULT_WORKERTHREADS = DEFAULT_BOSSTHREADS * 2;
85      }
86  
87      /**
88       *
89       * 明文模式
90       *
91       * @param token
92       *            开发者token
93       *
94       */
95      public WeixinServerBootstrap(String token) {
96          this("", token, null);
97      }
98  
99      /**
100      * 明文模式 & 兼容模式 & 密文模式
101      * <dl>
102      * <font color=
103      * "red">值得注意的是:企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font>
104      * </dl>
105      *
106      * @param weixinId
107      *            公众号的应用ID(appid/corpid) 密文&兼容模式下需要填写
108      *
109      * @param token
110      *            开发者填写的token 无论哪种模式都需要填写
111      * @param aesKey
112      *            消息加密的密钥 密文&兼容模式下需要填写
113      */
114     public WeixinServerBootstrap(String weixinId, String token, String aesKey) {
115         this(new AesToken(weixinId, token, aesKey));
116     }
117 
118     /**
119      * 多个公众号的支持
120      * <dt>值得注意的是:
121      * <dl>
122      * <font color="red">1).企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font>
123      * </dl>
124      * <dl>
125      * <font color=
126      * "red">2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数</font>
127      * </dl>
128      *
129      * @param aesTokens
130      *            多个公众号
131      * @return
132      */
133     public WeixinServerBootstrap(AesToken... aesToken) {
134         this(new DefaultMessageMatcher(), aesToken);
135     }
136 
137     /**
138      * 多个公众号的支持
139      * <dt>值得注意的是:
140      * <dl>
141      * <font color="red">1).企业号服务时需要在服务器URL后面加多一个`encrypt_type=aes`的参数</font>
142      * </dl>
143      * <dl>
144      * <font color=
145      * "red">2).非明文模式下需要在服务器URL后面加多一个`weixin_id=对应的appid/corpid`的参数</font>
146      * </dl>
147      *
148      * @param messageMatcher
149      *            消息匹配器
150      * @param aesTokens
151      *            公众号信息
152      * @return
153      */
154     public WeixinServerBootstrap(WeixinMessageMatcher messageMatcher, AesToken... aesTokens) {
155         if (messageMatcher == null) {
156             throw new IllegalArgumentException("MessageMatcher not be null");
157         }
158         if (aesTokens == null) {
159             throw new IllegalArgumentException("AesToken not be null");
160         }
161         this.aesTokenMap = new HashMap<String, AesToken>();
162         for (AesToken aesToken : aesTokens) {
163             this.aesTokenMap.put(aesToken.getWeixinId(), aesToken);
164         }
165         this.aesTokenMap.put("", aesTokens[0]);
166         this.messageHandlerList = new ArrayList<WeixinMessageHandler>();
167         this.messageInterceptorList = new ArrayList<WeixinMessageInterceptor>();
168         this.messageDispatcher = new WeixinMessageDispatcher(messageMatcher);
169     }
170 
171     /**
172      * 默认端口(30000)启动服务
173      *
174      */
175     public void startup() {
176         startup(DEFAULT_SERVERPORT);
177     }
178 
179     /**
180      * 指定端口启动服务
181      *
182      */
183     public void startup(int serverPort) {
184         startup(DEFAULT_BOSSTHREADS, DEFAULT_WORKERTHREADS, serverPort);
185     }
186 
187     /**
188      * 接受参数启动服务
189      *
190      * @param bossThreads
191      *            boss线程数
192      * @param workerThreads
193      *            worker线程数
194      * @param serverPort
195      *            服务启动端口
196      * @return
197      * @throws WeixinException
198      */
199     public void startup(int bossThreads, int workerThreads, final int serverPort) {
200         messageDispatcher.setMessageHandlerList(messageHandlerList);
201         messageDispatcher.setMessageInterceptorList(messageInterceptorList);
202         try {
203             bootstrap = new ServerBootstrap();
204             bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
205             bootstrap.group(new NioEventLoopGroup(bossThreads), new NioEventLoopGroup(workerThreads))
206                     .channel(NioServerSocketChannel.class).handler(new LoggingHandler())
207                     .childHandler(new WeixinServerInitializer(aesTokenMap, messageDispatcher));
208             Channel ch = bootstrap.bind(serverPort).addListener(new FutureListener<Void>() {
209                 @Override
210                 public void operationComplete(Future<Void> future) throws Exception {
211                     if (future.isSuccess()) {
212                         logger.info("weixin4j server startup OK:{}", serverPort);
213                     } else {
214                         logger.info("weixin4j server startup FAIL:{}", serverPort);
215                     }
216                 }
217             }).sync().channel();
218             ch.closeFuture().sync();
219         } catch (InterruptedException e) {
220             throw new RuntimeException(e);
221         } finally {
222             shutdown(false);
223         }
224     }
225 
226     /**
227      * 关闭微信服务
228      *
229      * @param blocking
230      *            阻塞关闭
231      * @return
232      */
233     public boolean shutdown(boolean blocking) {
234         if (bootstrap == null) {
235             return false;
236         }
237         ServerBootstrapConfig c = bootstrap.config();
238         Future<?> bossF = c.group().shutdownGracefully();
239         Future<?> workerF = c.childGroup().shutdownGracefully();
240         if (blocking) {
241             bossF.awaitUninterruptibly();
242             workerF.awaitUninterruptibly();
243         }
244         messageHandlerList = null;
245         messageInterceptorList = null;
246         messageDispatcher = null;
247         bootstrap = null;
248         return true;
249     }
250 
251     /**
252      * 添加一个或者多个消息处理器
253      *
254      * @param messageHandler
255      *            消息处理器
256      * @return
257      */
258     public WeixinServerBootstrap addHandler(WeixinMessageHandler... messageHandler) {
259         if (messageHandler == null) {
260             throw new IllegalArgumentException("messageHandler not be null");
261         }
262         messageHandlerList.addAll(Arrays.asList(messageHandler));
263         return this;
264     }
265 
266     /**
267      * 插入一个或多个消息拦截器
268      *
269      * @param messageInterceptor
270      *            消息拦截器
271      * @return
272      */
273     public WeixinServerBootstrap addInterceptor(WeixinMessageInterceptor... messageInterceptor) {
274         if (messageInterceptor == null) {
275             throw new IllegalArgumentException("messageInterceptor not be null");
276         }
277         messageInterceptorList.addAll(Arrays.asList(messageInterceptor));
278         return this;
279     }
280 
281     /**
282      * 按照包名去添加消息处理器
283      *
284      * @param messageHandlerPackages
285      *            消息处理器所在的包名
286      * @return
287      */
288     public WeixinServerBootstrap handlerPackagesToScan(String... messageHandlerPackages) {
289         if (messageHandlerPackages == null) {
290             throw new IllegalArgumentException("messageHandlerPackages not be null");
291         }
292         messageDispatcher.setMessageHandlerPackages(messageHandlerPackages);
293         return this;
294     }
295 
296     /**
297      * 按照包名去添加消息拦截器
298      *
299      * @param messageInterceptorPackages
300      *            消息拦截器所在的包名
301      * @return
302      */
303     public WeixinServerBootstrap interceptorPackagesToScan(String... messageInterceptorPackages) {
304         if (messageInterceptorPackages == null) {
305             throw new IllegalArgumentException("messageInterceptorPackages not be null");
306         }
307         messageDispatcher.setMessageInterceptorPackages(messageInterceptorPackages);
308         return this;
309     }
310 
311     /**
312      * 声明处理器跟拦截器类实例化的构造工厂,否则通过Class.newInstance的方式构造
313      *
314      * @param beanFactory
315      *            Bean构造工厂
316      * @return
317      */
318     public WeixinServerBootstrap resolveBeanFactory(BeanFactory beanFactory) {
319         messageDispatcher.setBeanFactory(beanFactory);
320         return this;
321     }
322 
323     /**
324      * 注册消息类型
325      *
326      * @param messageKey
327      *            消息key
328      * @param messageClass
329      *            消息类
330      * @return
331      */
332     public WeixinServerBootstrap registMessageClass(WeixinMessageKey messageKey,
333             Class<? extends WeixinMessage> messageClass) {
334         messageDispatcher.registMessageClass(messageKey, messageClass);
335         return this;
336     }
337 
338     /**
339      * 打开总是响应开关,如未匹配到MessageHandler时回复空白消息
340      */
341     public WeixinServerBootstrap openAlwaysResponse() {
342         messageDispatcher.openAlwaysResponse();
343         return this;
344     }
345 
346     /**
347      * 动态添加aesToken
348      *
349      * @param aesToken
350      * @return
351      */
352     public boolean addAesToken(AesToken aesToken) {
353         if (bootstrap == null) {
354             return false;
355         }
356         ServerBootstrapConfig c = bootstrap.config();
357         ((WeixinServerInitializer) c.childHandler()).addAesToken(aesToken);
358         return true;
359     }
360 
361     public final static String VERSION = "1.1.9";
362 }