在企业号工程上新增netty服务&消息分发
This commit is contained in:
@@ -13,10 +13,34 @@ weixin4j-qy-server
|
||||
|
||||
如何使用
|
||||
--------
|
||||
1.正确填写`weixin.properties`中的属性值
|
||||
|
||||
| 属性名 | 说明 |
|
||||
| :---------- | :-------------- |
|
||||
| account | 微信企业号信息 `json格式` |
|
||||
| token_path | 使用FileTokenHolder时token保存的物理路径 |
|
||||
| media_path | 调用媒体接口时保存媒体文件的物理路径 |
|
||||
|
||||
示例(properties中换行用右斜杆\\)
|
||||
|
||||
> account={"id":"corpid","secret":"corpsecret",
|
||||
> "token":"企业号中应用在回调模式下的token",
|
||||
> "encodingAesKey":"企业号中应用在回调模式下AES加密密钥"}
|
||||
> token_path=/tmp/weixin/token </br>
|
||||
> media_path=/tmp/weixin/media </br>
|
||||
|
||||
2.mvn package,得到一个zip的压缩包,解压到启动目录(见`src/main/startup.sh/APP_HOME`)
|
||||
|
||||
3.启动netty服务(`com.foxinmy.weixin4j.mp.startup.WeixinQyServerBootstrap`)
|
||||
|
||||
sh startup.sh start
|
||||
|
||||
更新LOG
|
||||
-------
|
||||
* 2014-11-19
|
||||
|
||||
+ 得到`weixin4j-qy-server`工程
|
||||
+ 得到`weixin4j-qy-server`工程
|
||||
|
||||
* 2014-11-24
|
||||
|
||||
+ 新增netty服务与消息分发
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.foxinmy.weixin4j.qy.action;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.ImageMessage;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 图片消息处理
|
||||
*
|
||||
* @className ImageAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.ImageMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.image)
|
||||
public class ImageAction extends DebugAction<ImageMessage> {
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.foxinmy.weixin4j.qy.action;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.LinkMessage;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 链接消息处理
|
||||
*
|
||||
* @className LinkAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.LinkMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.link)
|
||||
public class LinkAction extends DebugAction<LinkMessage> {
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.foxinmy.weixin4j.qy.action;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.LocationMessage;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 地理位置处理
|
||||
*
|
||||
* @className LocationAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.LocationMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.location)
|
||||
public class LocationAction extends DebugAction<LocationMessage> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
普通消息对应的Action
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.foxinmy.weixin4j.qy.action;
|
||||
|
||||
import com.foxinmy.weixin4j.action.AbstractAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.TextMessage;
|
||||
import com.foxinmy.weixin4j.msg.model.Text;
|
||||
import com.foxinmy.weixin4j.response.ResponseMessage;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 文字消息处理
|
||||
*
|
||||
* @className TextAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.TextMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.text)
|
||||
public class TextAction extends AbstractAction<TextMessage> {
|
||||
|
||||
@Override
|
||||
public ResponseMessage execute(TextMessage inMessage) {
|
||||
return new ResponseMessage(new Text("Hello World!"), inMessage);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.foxinmy.weixin4j.qy.action;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.VideoMessage;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 视频消息处理
|
||||
*
|
||||
* @className VideoAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.VideoMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.video)
|
||||
public class VideoAction extends DebugAction<VideoMessage> {
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.foxinmy.weixin4j.qy.action;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.VoiceMessage;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 语音消息处理
|
||||
*
|
||||
* @className VoiceAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.VoiceMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.voice)
|
||||
public class VoiceAction extends DebugAction<VoiceMessage> {
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.foxinmy.weixin4j.qy.action.event;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.event.LocationEventMessage;
|
||||
import com.foxinmy.weixin4j.type.EventType;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 上报地理位置后触发
|
||||
*
|
||||
* @className LocationAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.event.LocationEventMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.event, eventType = { EventType.location })
|
||||
public class LocationAction extends DebugAction<LocationEventMessage> {
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.foxinmy.weixin4j.qy.action.event;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.event.MassEventMessage;
|
||||
import com.foxinmy.weixin4j.type.EventType;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 群发消息发送动作完成后触发
|
||||
*
|
||||
* @className MassSendAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.event.MassEventMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.event, eventType = { EventType.massendjobfinish })
|
||||
public class MassSendAction extends DebugAction<MassEventMessage> {
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.foxinmy.weixin4j.qy.action.event;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.event.menu.MenuEventMessage;
|
||||
import com.foxinmy.weixin4j.type.EventType;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* click类型菜单点击时触发
|
||||
*
|
||||
* @className MenuClickAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.event.menu.MenuEventMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.event, eventType = { EventType.click })
|
||||
public class MenuClickAction extends DebugAction<MenuEventMessage> {
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.foxinmy.weixin4j.qy.action.event;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.event.menu.MenuLocationEventMessage;
|
||||
import com.foxinmy.weixin4j.type.EventType;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 点击菜单发送地理位置时触发
|
||||
*
|
||||
* @className MenuLocationAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.event.menu.MenuLocationEventMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.event, eventType = { EventType.location_select })
|
||||
public class MenuLocationAction extends DebugAction<MenuLocationEventMessage> {
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.foxinmy.weixin4j.qy.action.event;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.event.menu.MenuPhotoEventMessage;
|
||||
import com.foxinmy.weixin4j.type.EventType;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 点击菜单发送图片时触发
|
||||
*
|
||||
* @className MenuPhotoAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.event.menu.MenuPhotoEventMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.event, eventType = {
|
||||
EventType.pic_photo_or_album, EventType.pic_sysphoto,
|
||||
EventType.pic_weixin })
|
||||
public class MenuPhotoAction extends DebugAction<MenuPhotoEventMessage> {
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.foxinmy.weixin4j.qy.action.event;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.event.menu.MenuScanEventMessage;
|
||||
import com.foxinmy.weixin4j.type.EventType;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 点击菜单扫描时触发
|
||||
*
|
||||
* @className MenuScanAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.event.menu.MenuScanEventMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.event, eventType = { EventType.scancode_push,
|
||||
EventType.scancode_waitmsg })
|
||||
public class MenuScanAction extends DebugAction<MenuScanEventMessage> {
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.foxinmy.weixin4j.qy.action.event;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.event.menu.MenuEventMessage;
|
||||
import com.foxinmy.weixin4j.type.EventType;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* view类型菜单点击时触发
|
||||
*
|
||||
* @className MenuViewAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.event.menu.MenuEventMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.event, eventType = { EventType.view })
|
||||
public class MenuViewAction extends DebugAction<MenuEventMessage> {
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
事件消息对应的Action
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.foxinmy.weixin4j.qy.action.event;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.event.ScanEventMessage;
|
||||
import com.foxinmy.weixin4j.type.EventType;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 扫描事件时触发
|
||||
*
|
||||
* @className ScanAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.event.ScanEventMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.event, eventType = { EventType.scan })
|
||||
public class ScanAction extends DebugAction<ScanEventMessage> {
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.foxinmy.weixin4j.qy.action.event;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.event.ScribeEventMessage;
|
||||
import com.foxinmy.weixin4j.type.EventType;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 关注时触发
|
||||
*
|
||||
* @className SubscribeAction
|
||||
* @author jy
|
||||
* @date 2014年10月9日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.event.ScribeEventMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.event, eventType = { EventType.subscribe })
|
||||
public class SubscribeAction extends DebugAction<ScribeEventMessage> {
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.foxinmy.weixin4j.qy.action.event;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.event.TemplatesendjobfinishMessage;
|
||||
import com.foxinmy.weixin4j.type.EventType;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 模板消息发送动作完成时触发
|
||||
*
|
||||
* @className TemplateSendAction
|
||||
* @author jy
|
||||
* @date 2014年10月10日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.event.TemplatesendjobfinishMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.event, eventType = { EventType.templatesendjobfinish })
|
||||
public class TemplateSendAction extends
|
||||
DebugAction<TemplatesendjobfinishMessage> {
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.foxinmy.weixin4j.qy.action.event;
|
||||
|
||||
import com.foxinmy.weixin4j.action.DebugAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
|
||||
import com.foxinmy.weixin4j.msg.event.ScribeEventMessage;
|
||||
import com.foxinmy.weixin4j.type.EventType;
|
||||
import com.foxinmy.weixin4j.type.MessageType;
|
||||
|
||||
/**
|
||||
* 取消关注时触发
|
||||
*
|
||||
* @className UnsubscribeAction
|
||||
* @author jy
|
||||
* @date 2014年10月10日
|
||||
* @since JDK 1.7
|
||||
* @see com.foxinmy.weixin4j.msg.event.ScribeEventMessage
|
||||
*/
|
||||
@ActionAnnotation(msgType = MessageType.event, eventType = { EventType.unsubscribe })
|
||||
public class UnsubscribeAction extends DebugAction<ScribeEventMessage> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
WeixinMessageDecoder:对微信消息进行解码
|
||||
|
||||
WeixinMessageEncoder:对微信消息进行编码
|
||||
|
||||
WeixinServerHandler:微信请求处理类
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.foxinmy.weixin4j.qy.server;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.Consts;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.foxinmy.weixin4j.model.WeixinQyAccount;
|
||||
import com.foxinmy.weixin4j.response.HttpWeixinMessage;
|
||||
import com.foxinmy.weixin4j.type.EncryptType;
|
||||
import com.foxinmy.weixin4j.util.ConfigUtil;
|
||||
import com.foxinmy.weixin4j.util.MessageUtil;
|
||||
import com.foxinmy.weixin4j.xml.XStream;
|
||||
|
||||
/**
|
||||
* 微信消息解码类
|
||||
*
|
||||
* @className WeixinMessageDecoder
|
||||
* @author jy
|
||||
* @date 2014年11月13日
|
||||
* @since JDK 1.7
|
||||
* @see <a
|
||||
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E5%9B%9E%E8%B0%83%E6%A8%A1%E5%BC%8F">回调模式</a>
|
||||
*/
|
||||
public class WeixinMessageDecoder extends
|
||||
MessageToMessageDecoder<FullHttpRequest> {
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, FullHttpRequest req,
|
||||
List<Object> out) throws Exception {
|
||||
WeixinQyAccount qyAccount = ConfigUtil.getWeixinQyAccount();
|
||||
String xmlContent = req.content().toString(Consts.UTF_8);
|
||||
HttpWeixinMessage message = new HttpWeixinMessage();
|
||||
message.setXmlContent(xmlContent);
|
||||
if (StringUtils.isNotBlank(xmlContent)) {
|
||||
message = XStream.get(xmlContent, HttpWeixinMessage.class);
|
||||
message.setXmlContent(MessageUtil.aesDecrypt(qyAccount.getId(),
|
||||
qyAccount.getEncodingAesKey(), message.getEncryptContent()));
|
||||
}
|
||||
message.setMethod(req.getMethod().name());
|
||||
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.getUri(),
|
||||
true);
|
||||
log.info("\n=================receive request=================");
|
||||
log.info("{}", req.getMethod());
|
||||
log.info("{}", req.getUri());
|
||||
log.info("{}", xmlContent);
|
||||
Map<String, List<String>> parameters = queryDecoder.parameters();
|
||||
String msgSignature = parameters.containsKey("msg_signature") ? parameters
|
||||
.get("msg_signature").get(0) : "";
|
||||
message.setMsgSignature(msgSignature);
|
||||
String echoStr = parameters.containsKey("echostr") ? parameters.get(
|
||||
"echostr").get(0) : "";
|
||||
message.setEchoStr(echoStr);
|
||||
String timeStamp = parameters.containsKey("timestamp") ? parameters
|
||||
.get("timestamp").get(0) : "";
|
||||
message.setTimeStamp(timeStamp);
|
||||
String nonce = parameters.containsKey("nonce") ? parameters
|
||||
.get("nonce").get(0) : "";
|
||||
message.setNonce(nonce);
|
||||
String signature = parameters.containsKey("signature") ? parameters
|
||||
.get("signature").get(0) : "";
|
||||
message.setSignature(signature);
|
||||
|
||||
message.setToken(qyAccount.getToken());
|
||||
message.setEncryptType(EncryptType.AES);
|
||||
out.add(message);
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.foxinmy.weixin4j.qy.server;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.foxinmy.weixin4j.model.WeixinQyAccount;
|
||||
import com.foxinmy.weixin4j.qy.util.HttpUtil;
|
||||
import com.foxinmy.weixin4j.response.ResponseMessage;
|
||||
import com.foxinmy.weixin4j.util.ConfigUtil;
|
||||
import com.foxinmy.weixin4j.util.DateUtil;
|
||||
import com.foxinmy.weixin4j.util.MessageUtil;
|
||||
import com.foxinmy.weixin4j.util.RandomUtil;
|
||||
import com.foxinmy.weixin4j.xml.Map2ObjectConverter;
|
||||
import com.foxinmy.weixin4j.xml.XStream;
|
||||
import com.thoughtworks.xstream.core.ClassLoaderReference;
|
||||
import com.thoughtworks.xstream.mapper.DefaultMapper;
|
||||
|
||||
/**
|
||||
* 微信消息编码类
|
||||
*
|
||||
* @className WeixinMessageEncoder
|
||||
* @author jy
|
||||
* @date 2014年11月13日
|
||||
* @since JDK 1.7
|
||||
* @see <a
|
||||
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E5%8A%A0%E8%A7%A3%E5%AF%86%E6%96%B9%E6%A1%88%E7%9A%84%E8%AF%A6%E7%BB%86%E8%AF%B4%E6%98%8E">加密接入指引</a>
|
||||
*/
|
||||
public class WeixinMessageEncoder extends
|
||||
MessageToMessageEncoder<ResponseMessage> {
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
protected final static XStream mapXstream = XStream.get();
|
||||
static {
|
||||
mapXstream.alias("xml", Map.class);
|
||||
mapXstream.registerConverter(new Map2ObjectConverter(new DefaultMapper(
|
||||
new ClassLoaderReference(XStream.class.getClassLoader()))));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ResponseMessage response,
|
||||
List<Object> out) throws Exception {
|
||||
WeixinQyAccount qyAccount = ConfigUtil.getWeixinQyAccount();
|
||||
String xmlContent = response.toXml();
|
||||
String nonce = RandomUtil.generateString(32);
|
||||
String timestamp = DateUtil.timestamp2string();
|
||||
String encrtypt = MessageUtil.aesEncrypt(qyAccount.getId(),
|
||||
qyAccount.getEncodingAesKey(), xmlContent);
|
||||
String msgSignature = MessageUtil.signature(qyAccount.getToken(),
|
||||
nonce, timestamp, encrtypt);
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put("Encrypt", encrtypt);
|
||||
map.put("MsgSignature", msgSignature);
|
||||
map.put("TimeStamp", timestamp);
|
||||
map.put("Nonce", nonce);
|
||||
|
||||
String content = mapXstream.toXML(map);
|
||||
out.add(HttpUtil.createWeixinMessageResponse(
|
||||
content, null));
|
||||
|
||||
log.info("\n=================aes encrtypt out=================");
|
||||
log.info("{}", content);
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package com.foxinmy.weixin4j.qy.server;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.foxinmy.weixin4j.action.WeixinAction;
|
||||
import com.foxinmy.weixin4j.action.mapping.ActionMapping;
|
||||
import com.foxinmy.weixin4j.qy.util.HttpUtil;
|
||||
import com.foxinmy.weixin4j.response.HttpWeixinMessage;
|
||||
import com.foxinmy.weixin4j.response.ResponseMessage;
|
||||
import com.foxinmy.weixin4j.util.MessageUtil;
|
||||
|
||||
/**
|
||||
* 微信被动消息处理类
|
||||
*
|
||||
* @className WeixinServerHandler
|
||||
* @author jy
|
||||
* @date 2014年11月16日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class WeixinServerHandler extends
|
||||
SimpleChannelInboundHandler<HttpWeixinMessage> {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final ActionMapping actionMapping;
|
||||
|
||||
public WeixinServerHandler(ActionMapping actionMapping) {
|
||||
this.actionMapping = actionMapping;
|
||||
}
|
||||
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx,
|
||||
HttpWeixinMessage httpMessage) throws Exception {
|
||||
log.info("\n=================message in=================\n{}",
|
||||
httpMessage);
|
||||
boolean isGet = httpMessage.getMethod().equals(HttpMethod.GET.name());
|
||||
boolean validate = false;
|
||||
if (isGet) {
|
||||
validate = MessageUtil.signature(httpMessage.getToken(),
|
||||
httpMessage.getTimeStamp(), httpMessage.getNonce()).equals(
|
||||
httpMessage.getSignature());
|
||||
if (validate) {
|
||||
ctx.write(HttpUtil.createWeixinMessageResponse(
|
||||
httpMessage.getEchoStr(), ContentType.TEXT_PLAIN));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
validate = MessageUtil.signature(httpMessage.getToken(),
|
||||
httpMessage.getTimeStamp(), httpMessage.getNonce(),
|
||||
httpMessage.getEncryptContent()).equals(
|
||||
httpMessage.getMsgSignature());
|
||||
}
|
||||
if (!validate) {
|
||||
ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
|
||||
HttpResponseStatus.FORBIDDEN));
|
||||
return;
|
||||
}
|
||||
|
||||
String xmlContent = httpMessage.getXmlContent();
|
||||
WeixinAction action = actionMapping.getAction(xmlContent);
|
||||
if (action == null) {
|
||||
ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
|
||||
HttpResponseStatus.NOT_FOUND));
|
||||
return;
|
||||
}
|
||||
ResponseMessage response = action.execute(xmlContent);
|
||||
log.info("\n=================message out=================\n{}",
|
||||
response);
|
||||
if (response == null) {
|
||||
ctx.write(HttpUtil.createWeixinMessageResponse("",
|
||||
ContentType.TEXT_PLAIN));
|
||||
return;
|
||||
}
|
||||
ctx.write(response);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.foxinmy.weixin4j.qy.server;
|
||||
|
||||
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 com.foxinmy.weixin4j.action.mapping.ActionMapping;
|
||||
import com.foxinmy.weixin4j.action.mapping.AnnotationActionMapping;
|
||||
import com.foxinmy.weixin4j.qy.action.ImageAction;
|
||||
|
||||
public class WeixinServerInitializer extends ChannelInitializer<SocketChannel> {
|
||||
private final ActionMapping actionMapping;
|
||||
|
||||
public WeixinServerInitializer() {
|
||||
this.actionMapping = new AnnotationActionMapping(
|
||||
ImageAction.class.getPackage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initChannel(SocketChannel channel) throws Exception {
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
pipeline.addLast(new HttpServerCodec());
|
||||
pipeline.addLast(new HttpObjectAggregator(65536));
|
||||
pipeline.addLast(new WeixinMessageDecoder());
|
||||
pipeline.addLast(new WeixinMessageEncoder());
|
||||
pipeline.addLast(new WeixinServerHandler(actionMapping));
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.foxinmy.weixin4j.qy.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 java.util.ResourceBundle;
|
||||
|
||||
import com.foxinmy.weixin4j.qy.server.WeixinServerInitializer;
|
||||
|
||||
/**
|
||||
* 微信服务netty启动程序
|
||||
*
|
||||
* @className WeixinServerBootstrap
|
||||
* @author jy
|
||||
* @date 2014年10月12日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public final class WeixinQyServerBootstrap {
|
||||
|
||||
private final static int port;
|
||||
private final static int workerThreads;
|
||||
static {
|
||||
ResourceBundle netty = ResourceBundle.getBundle("netty");
|
||||
port = Integer.parseInt(netty.getString("port"));
|
||||
workerThreads = Integer.parseInt(netty.getString("workerThreads"));
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
|
||||
EventLoopGroup workerGroup = new NioEventLoopGroup(workerThreads);
|
||||
try {
|
||||
ServerBootstrap b = new ServerBootstrap();
|
||||
b.option(ChannelOption.SO_BACKLOG, 1024);
|
||||
b.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.handler(new LoggingHandler())
|
||||
.childHandler(new WeixinServerInitializer());
|
||||
Channel ch = b.bind(port).sync().channel();
|
||||
System.err.println("weixin server startup OK:" + port);
|
||||
ch.closeFuture().sync();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
bossGroup.shutdownGracefully();
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.foxinmy.weixin4j.qy.util;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.DATE;
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.SERVER;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
|
||||
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Values;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.http.Consts;
|
||||
import org.apache.http.entity.ContentType;
|
||||
|
||||
/**
|
||||
* HTTP工具类
|
||||
*
|
||||
* @className HttpUtil
|
||||
* @author jy
|
||||
* @date 2014年11月15日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class HttpUtil {
|
||||
|
||||
public static HttpResponse createWeixinMessageResponse(String content,
|
||||
ContentType contentType) {
|
||||
if (contentType == null) {
|
||||
contentType = ContentType.APPLICATION_XML;
|
||||
}
|
||||
FullHttpResponse httpResponse = new DefaultFullHttpResponse(HTTP_1_1,
|
||||
OK, Unpooled.copiedBuffer(content, Consts.UTF_8));
|
||||
httpResponse.headers().set(
|
||||
CONTENT_TYPE,
|
||||
String.format("%s;encoding=%s", contentType.getMimeType(),
|
||||
Consts.UTF_8.displayName()));
|
||||
|
||||
httpResponse.headers().set(CONTENT_LENGTH, content.getBytes().length);
|
||||
httpResponse.headers().set(CONNECTION, Values.KEEP_ALIVE);
|
||||
httpResponse.headers().set(DATE, new Date());
|
||||
httpResponse.headers().set(SERVER, "netty4");
|
||||
return httpResponse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- For assistance related to logback-translator or configuration -->
|
||||
<!-- files in general, please contact the logback user mailing list -->
|
||||
<!-- at http://www.qos.ch/mailman/listinfo/logback-user -->
|
||||
<!-- -->
|
||||
<!-- For professional support please see -->
|
||||
<!-- http://www.qos.ch/shop/products/professionalSupport -->
|
||||
<!-- -->
|
||||
<configuration>
|
||||
|
||||
<!-- 控制台输出日志 -->
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 文件输出指定项目日志 -->
|
||||
<appender name="file"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!--See http://logback.qos.ch/manual/appenders.html#RollingFileAppender -->
|
||||
<!--and http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy -->
|
||||
<!--for further documentation -->
|
||||
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>/tmp/weixin/log/weixin.qy.%d{yyyy-MM-dd}.log
|
||||
</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 异步输出指定项目日志 -->
|
||||
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
|
||||
<queueSize>512</queueSize>
|
||||
<!-- 添加附加的appender,最多只能添加一个 -->
|
||||
<appender-ref ref="file" />
|
||||
</appender>
|
||||
|
||||
<logger name="org.apache" level="INFO">
|
||||
<appender-ref ref="stdout" />
|
||||
</logger>
|
||||
|
||||
<logger name="org.springframework" level="INFO">
|
||||
<appender-ref ref="stdout" />
|
||||
</logger>
|
||||
|
||||
<logger name="com.foxinmy.weixin4j" level="INFO">
|
||||
<appender-ref ref="async" />
|
||||
</logger>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,2 @@
|
||||
port=8090
|
||||
workerThreads=20
|
||||
@@ -0,0 +1,10 @@
|
||||
# \u4f01\u4e1a\u53f7\u4fe1\u606f
|
||||
account={"id":"corpid","secret":"corpsecret",\
|
||||
"token":"\u4f01\u4e1a\u53f7\u4e2d\u5e94\u7528\u5728\u56de\u8c03\u6a21\u5f0f\u4e0b\u7684token",\
|
||||
"encodingAesKey":"\u4f01\u4e1a\u53f7\u4e2d\u5e94\u7528\u5728\u56de\u8c03\u6a21\u5f0f\u4e0bAES\u52a0\u5bc6\u5bc6\u94a5"\
|
||||
}
|
||||
|
||||
# \u4f7f\u7528FileTokenHolder\u65f6token\u7684\u5b58\u653e\u8def\u5f84
|
||||
token_path=/tmp/weixin/token
|
||||
# \u5a92\u4f53\u6587\u4ef6\u4fdd\u5b58\u8def\u5f84
|
||||
media_path=/tmp/weixin/media
|
||||
@@ -9,7 +9,7 @@ RUNNING_USER=root
|
||||
APP_HOME="/usr/local/weixin/weixin-qy-server"
|
||||
|
||||
#main class
|
||||
APP_MAINCLASS=com.foxinmy.weixin4j.qy.startup.WeixinServerBootstrap
|
||||
APP_MAINCLASS=com.foxinmy.weixin4j.qy.startup.WeixinQyServerBootstrap
|
||||
|
||||
#classpath
|
||||
CLASSPATH=$APP_HOME/classes
|
||||
|
||||
Reference in New Issue
Block a user