在企业号工程上新增netty服务&消息分发

This commit is contained in:
jy.hu
2014-11-24 14:32:08 +08:00
parent 2ece136d46
commit 2cf2227822
104 changed files with 1253 additions and 326 deletions
+5 -1
View File
@@ -24,7 +24,7 @@ weixin4j-qy
+ `netty服务器` & `消息分发`
+ 回调`AES`加密、解密
+ 回调`AES`加密、解密
更新LOG
-------
@@ -45,3 +45,7 @@ weixin4j-qy
+ **weixin4j-qy-api**: 新增`发送消息`接口
+ **weixin4j-qy-api**: 新增`菜单管理`接口
* 2014-11-24
+ **weixin4j-qy-server**: 新增netty服务与消息分发
+4 -2
View File
@@ -23,13 +23,15 @@ weixin.properties说明
| :---------- | :-------------- |
| account | 微信企业号信息 `json格式` |
| token_path | 使用FileTokenHolder时token保存的物理路径 |
| media_path | 调用媒体接口时保存媒体文件的物理路径 |
示例(properties中换行用右斜杆\\)
> account={"id":"corpid","secret":"corpsecret",
> "token":"开放者的token 非必须",
> "encodingAesKey":"AES加密密钥"}
> "token":"企业号中应用在回调模式下的token",
> "encodingAesKey":"企业号中应用在回调模式下AES加密密钥"}
> token_path=/tmp/weixin/token </br>
> media_path=/tmp/weixin/media </br>
2.实例化一个`WeixinProxy`对象,调用API
@@ -2,7 +2,6 @@ package com.foxinmy.weixin4j.qy;
import java.util.List;
import com.alibaba.fastjson.JSONArray;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.model.WeixinQyAccount;
@@ -10,6 +9,7 @@ import com.foxinmy.weixin4j.qy.api.DepartApi;
import com.foxinmy.weixin4j.qy.api.TagApi;
import com.foxinmy.weixin4j.qy.api.UserApi;
import com.foxinmy.weixin4j.qy.model.Department;
import com.foxinmy.weixin4j.qy.model.Tag;
import com.foxinmy.weixin4j.qy.model.User;
import com.foxinmy.weixin4j.qy.type.UserStatus;
import com.foxinmy.weixin4j.token.FileTokenHolder;
@@ -251,13 +251,13 @@ public class WeixinProxy {
* @see <a href=
* "http://qydev.weixin.qq.com/wiki/index.php?title=%E7%AE%A1%E7%90%86%E6%A0%87%E7%AD%BE#.E6.9B.B4.E6.96.B0.E6.A0.87.E7.AD.BE.E5.90.8D.E5.AD.97"
* >更新标签说明</a>
* @see com.foxinmy.weixin4j.qy.model.Tag
* @see com.foxinmy.weixin4j.qy.api.TagApi
* @return 处理结果
* @throws WeixinException
*/
public JsonResult updateTag(int tagId, String tagName)
throws WeixinException {
return tagApi.updateTag(tagId, tagName);
public JsonResult updateTag(Tag tag) throws WeixinException {
return tagApi.updateTag(tag);
}
/**
@@ -280,11 +280,12 @@ public class WeixinProxy {
*
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E7%AE%A1%E7%90%86%E6%A0%87%E7%AD%BE#.E8.8E.B7.E5.8F.96.E6.A0.87.E7.AD.BE.E5.88.97.E8.A1.A8">获取标签列表说明</a>
* @see com.foxinmy.weixin4j.qy.model.Tag
* @see com.foxinmy.weixin4j.qy.api.TagApi
* @return 标签列表
* @throws WeixinException
*/
public JSONArray listTag() throws WeixinException {
public List<Tag> listTag() throws WeixinException {
return tagApi.listTag();
}
@@ -3,12 +3,12 @@ package com.foxinmy.weixin4j.qy.api;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.qy.model.Tag;
import com.foxinmy.weixin4j.qy.model.User;
import com.foxinmy.weixin4j.token.TokenHolder;
@@ -59,15 +59,15 @@ public class TagApi extends BaseApi {
* "http://qydev.weixin.qq.com/wiki/index.php?title=%E7%AE%A1%E7%90%86%E6%A0%87%E7%AD%BE#.E6.9B.B4.E6.96.B0.E6.A0.87.E7.AD.BE.E5.90.8D.E5.AD.97"
* >更新标签说明</a>
* @return 处理结果
* @see com.foxinmy.weixin4j.qy.model.Tag
* @throws WeixinException
*/
public JsonResult updateTag(int tagId, String tagName)
throws WeixinException {
public JsonResult updateTag(Tag tag) throws WeixinException {
String tag_update_uri = getRequestUri("tag_update_uri");
Token token = tokenHolder.getToken();
Response response = request.post(String.format(tag_update_uri,
token.getAccessToken()), String.format(
"{\"tagid\":%d,\"tagname\":\"%s\"}", tagId, tagName));
Response response = request.post(
String.format(tag_update_uri, token.getAccessToken()),
JSON.toJSONString(tag));
return response.getAsJsonResult();
}
@@ -95,14 +95,16 @@ public class TagApi extends BaseApi {
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E7%AE%A1%E7%90%86%E6%A0%87%E7%AD%BE#.E8.8E.B7.E5.8F.96.E6.A0.87.E7.AD.BE.E5.88.97.E8.A1.A8">获取标签列表说明</a>
* @return 标签列表
* @see com.foxinmy.weixin4j.qy.model.Tag
* @throws WeixinException
*/
public JSONArray listTag() throws WeixinException {
public List<Tag> listTag() throws WeixinException {
String tag_list_uri = getRequestUri("tag_list_uri");
Token token = tokenHolder.getToken();
Response response = request.post(String.format(tag_list_uri,
token.getAccessToken()));
return response.getAsJson().getJSONArray("taglist");
return JSON.parseArray(response.getAsJson().getString("taglist"),
Tag.class);
}
/**
@@ -100,6 +100,29 @@ public class UserApi extends BaseApi {
return JSON.toJavaObject(obj, User.class);
}
/**
* code获取userid(管理员须拥有agent的使用权限;agentid必须和跳转链接时所在的企业应用ID相同。)
*
* @param code
* 通过员工授权获取到的code,每次员工授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期
* @param agentid
* 跳转链接时所在的企业应用ID
* @return {"UserId":"员工UserID","DeviceId":"手机设备号(由微信在安装时随机生成)"}
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E4%BC%81%E4%B8%9A%E8%8E%B7%E5%8F%96code">企业获取code</a>
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%A0%B9%E6%8D%AEcode%E8%8E%B7%E5%8F%96%E6%88%90%E5%91%98%E4%BF%A1%E6%81%AF">根据code获取成员信息</a>
* @throws WeixinException
*/
public JSONObject getUserid(String code, int agentid)
throws WeixinException {
String user_getid_uri = getRequestUri("user_getid_uri");
Token token = tokenHolder.getToken();
Response response = request.post(String.format(user_getid_uri,
token.getAccessToken(), code, agentid));
return response.getAsJson();
}
/**
* 获取部门成员
*
@@ -156,4 +179,22 @@ public class UserApi extends BaseApi {
token.getAccessToken(), userid));
return response.getAsJsonResult();
}
/**
* 开启二次验证成功时调用(管理员须拥有userid对应员工的管理权限)
*
* @param userid
* 成员ID
* @return 调用结果
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E5%85%B3%E6%B3%A8%E4%B8%8E%E5%8F%96%E6%B6%88%E5%85%B3%E6%B3%A8">二次验证说明</a>
* @throws WeixinException
*/
public JsonResult authsucc(String userid) throws WeixinException {
String user_authsucc_uri = getRequestUri("user_authsucc_uri");
Token token = tokenHolder.getToken();
Response response = request.post(String.format(user_authsucc_uri,
token.getAccessToken(), userid));
return response.getAsJsonResult();
}
}
@@ -15,14 +15,18 @@ department_list_uri={api_base_url}/department/list?access_token=%s
department_delete_uri={api_base_url}/department/delete?access_token=%s&id=%d
# \u521b\u5efa\u6210\u5458
user_create_uri={api_base_url}/user/create?access_token=%s
# \u66f4\u65b0\u6210\u5458
# \u66f4\u65b0\u6210\u5458\u4fe1\u606f
user_update_uri={api_base_url}/user/update?access_token=%s
# \u83b7\u53d6\u6210\u5458
# \u83b7\u53d6\u6210\u5458\u4fe1\u606f
user_get_uri={api_base_url}/user/get?access_token=%s&userid=%s
# code\u83b7\u53d6\u6210\u5458\u4fe1\u606f
user_getid_uri={api_base_url}/getuserinfo?access_token=%s&code=%s&agentid=%d
# \u83b7\u53d6\u90e8\u95e8\u6210\u5458
user_list_uri={api_base_url}/user/simplelist?access_token=%s&department_id=%d&fetch_child=%d&status=%d
# \u5220\u9664\u6210\u5458
user_delete_uri={api_base_url}/user/delete?access_token=%s&userid=%s
# \u6210\u5458\u4e8c\u6b21\u9a8c\u8bc1\u6210\u529f\u65f6\u8c03\u7528
user_authsucc_uri={api_base_url}/user/authsucc?access_token=%s&userid=%s
# \u521b\u5efa\u6807\u7b7e
tag_create_uri={api_base_url}/tag/create?access_token=%s
# \u66f4\u65b0\u6807\u7b7e
@@ -0,0 +1,54 @@
package com.foxinmy.weixin4j.qy.model;
import java.io.Serializable;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 标签对象
*
* @className Tag
* @author jy
* @date 2014年11月24日
* @since JDK 1.7
* @see
*/
public class Tag implements Serializable {
private static final long serialVersionUID = 5204620476267654921L;
@JSONField(name = "tagid")
private int id;
@JSONField(name = "tagname")
private String name;
public Tag() {
}
public Tag(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Tag [id=" + id + ", name=" + name + "]";
}
}
@@ -7,10 +7,10 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import com.alibaba.fastjson.JSONArray;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.qy.api.TagApi;
import com.foxinmy.weixin4j.qy.model.Tag;
import com.foxinmy.weixin4j.qy.model.User;
/**
@@ -38,7 +38,7 @@ public class TagTest extends TokenTest {
@Test
public void update() throws WeixinException {
JsonResult result = tagApi.updateTag(1, "coder456");
JsonResult result = tagApi.updateTag(new Tag(1, "coder456"));
Assert.assertEquals("updated", result.getDesc());
}
@@ -64,7 +64,7 @@ public class TagTest extends TokenTest {
@Test
public void list() throws WeixinException {
JSONArray tags = tagApi.listTag();
List<Tag> tags = tagApi.listTag();
Assert.assertFalse(tags.isEmpty());
System.out.println(tags);
}
+25 -1
View File
@@ -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服务与消息分发
@@ -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> {
}
@@ -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> {
}
@@ -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
@@ -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);
}
}
@@ -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> {
}
@@ -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> {
}
@@ -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> {
}
@@ -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> {
}
@@ -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> {
}
@@ -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> {
}
@@ -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> {
}
@@ -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> {
}
@@ -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> {
}
@@ -0,0 +1 @@
事件消息对应的Action
@@ -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> {
}
@@ -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> {
}
@@ -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> {
}
@@ -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:微信请求处理类
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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));
}
}
@@ -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();
}
}
}
@@ -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