微信公众平台 https://mp.weixin.qq.com/
微信公众平台开发者文档 http://mp.weixin.qq.com/wiki/17/2d4265491f12608cd170a95559800f2d.html
15年元旦的时候,产品要做微信版,便开始研究微信公众平台以及开发者文档,给我的感觉是微信测试很不方便,职能配置一个回调URL,测试和正式通用,还会有回调两次的现象。以及弹出哭脸:请在微信客户端中打开。
半年多过去了,微信开放平台也在不断的完善和优化。自己凭着回忆和代码,做下迟来的总结,作为留念。
项目设计分为两大部分,基础服务搭建和具体业务接入。
基础服务搭建
1. 开发者接入
申请公众号后,微信分配了开发者ID,包括AppID,AppSecret。
2. 菜单·这是重要的基础环节。按照格式,构建菜单。按等级划分: 一级菜单、二级菜单。二级菜单即一级菜单的子菜单。当然一级菜单可以不包含菜单。有种树的感觉:节点,子节点。按按钮事件类型划分: view、 click。view是点击按钮打开url链接。click是点击发送一条消息Message。很简单。关键是做好click类型key与message消息的对应,做到动态配置,可以做管理后台,菜单变更后,立即向微信发送restful请求,刷新菜单。
3. 用户
做用户这块,折腾了好久,难点在于与公司用户体系打通。
4. 用户与公众平台消息交互
用户动作分为消息和事件两种,根据消息EventType为msg或event,分别构建两种策略模式。
微信公众平台开发者文档 http://mp.weixin.qq.com/wiki/17/2d4265491f12608cd170a95559800f2d.html
15年元旦的时候,产品要做微信版,便开始研究微信公众平台以及开发者文档,给我的感觉是微信测试很不方便,职能配置一个回调URL,测试和正式通用,还会有回调两次的现象。以及弹出哭脸:请在微信客户端中打开。
半年多过去了,微信开放平台也在不断的完善和优化。自己凭着回忆和代码,做下迟来的总结,作为留念。
项目设计分为两大部分,基础服务搭建和具体业务接入。
基础服务搭建
1. 开发者接入
申请公众号后,微信分配了开发者ID,包括AppID,AppSecret。
按照微信开发者文档中的接入指南,配置微信交互的url、Token、EncodingAESKey.三者都是自定义的,启用兼容模式,方便调试,项目上线稳定后改为安全模式。url自己写的controller,接受布置在具有外网权限的服务器上。
<pre name="code" class="java"> @RequestMapping(value = {"mp/callback.action"}, method = RequestMethod.POST, produces = "text/xml")
@ResponseBody
public String receive(@RequestBody String xmlbody,
@RequestParam(value = "signature") String signature,
@RequestParam(value = "timestamp") String timestamp,
@RequestParam(value = "nonce") String nonce,
@RequestParam(value = "encrypt_type") String encrypt_type, @RequestParam(
value = "msg_signature") String msg_signature, HttpServletResponse response, Model model) {
// TODO 1. 通过对签名的效验判断此条消息的真实性 2. 解密xmlbody密文 3. 解析消息,交给按照不同的处理策略,得到相应的响应消息加密后返回。
}
2. 菜单·这是重要的基础环节。按照格式,构建菜单。按等级划分: 一级菜单、二级菜单。二级菜单即一级菜单的子菜单。当然一级菜单可以不包含菜单。有种树的感觉:节点,子节点。按按钮事件类型划分: view、 click。view是点击按钮打开url链接。click是点击发送一条消息Message。很简单。关键是做好click类型key与message消息的对应,做到动态配置,可以做管理后台,菜单变更后,立即向微信发送restful请求,刷新菜单。
[{
name: "看点啥",
sub_button: [{
key: "TONIGHT_FOCUS",
name: "今晚看啥",
type: "click"
},
{
name: "小编推荐",
type: "view",
url: "http://"
},
{
key: "COLUMN_SEARCH",
name: "搜索影片",
type: "click"
},
{
name: "会员片库",
type: "view",
url: "http://"
}]
},
{
name: "特色片",
sub_button: [{
name: "8月观影指南",
type: "view",
url: "http://"
},
{
name: "新片推荐",
type: "view",
url: "http://"
},
{
name: "美味周边",
type: "view",
url: "http://"
},
{
name: "免费电影票",
type: "view",
url: "http://"
},
{
name: "更多专题",
type: "view",
url: "http://"
}]
},
{
name: "我的",
sub_button: [{
key: "SERVICE_VIP",
name: "会员状态",
type: "click"
},
{
key: "SERVICE_HIS",
name: "观看记录",
type: "click"
},
{
key: "SERVICE_KF",
name: "专属客服",
type: "click"
},
{
name: "会员中心",
type: "view",
url: "http://"
}]
}]
3. 用户
做用户这块,折腾了好久,难点在于与公司用户体系打通。
/***
* 获取用户基本信息(UnionID机制)
*
* @param openid
* @return
*/
public UserInfo getUserInfo(String openid);
/***
* 通过code换取网页授权access_token
*
* @param code
* @return null if code is blank.
*/
public Oauth2AccessToken oauth2_access_token(String code);
/***
* 拉取用户信息(需scope为 snsapi_userinfo)
*
* @param access_token
* @param openid
* @return
*/
public UserInfo oauth2_userinfo(String access_token, String openid);
4. 用户与公众平台消息交互
用户动作分为消息和事件两种,根据消息EventType为msg或event,分别构建两种策略模式。
public class EventStrategyFactory {
public static IEventStrategy getDealer(String event) {
// 按事件发生概率由高到低排序
switch (event) {
// 自定义菜单 —— 点击菜单拉取消息时的事件
case "CLICK":
return EventStrategy.CLICK;
// 自定义菜单 —— 点击菜单跳转链接时的事件
case "VIEW":
return EventStrategy.VIEW;
// 关注事件 || 扫描带参数二维码, 用户未关注时,进行关注后的事件推送
case "subscribe":
return EventStrategy.subscribe;
// 模版消息发送任务完成后的事件
case "TEMPLATESENDJOBFINISH":
return EventStrategy.TEMPLATESENDJOBFINISH;
// 上报地理位置事件
case "LOCATION":
return EventStrategy.LOCATION;
// 取消关注事件
case "unsubscribe":
return EventStrategy.unsubscribe;
// 扫描带参数二维码, 用户已关注时的事件推送
case "SCAN":
return EventStrategy.SCAN;
// 自定义菜单 —— 扫码推事件的事件
case "scancode_push":
return EventStrategy.scancode_push;
// 自定义菜单 —— 扫码推事件且弹出“消息接收中”提示框的事件
case "scancode_waitmsg":
return EventStrategy.scancode_waitmsg;
// 自定义菜单 —— 弹出系统拍照发图的事件
case "pic_sysphoto":
return EventStrategy.pic_sysphoto;
// 自定义菜单 —— 弹出拍照或者相册发图的事件
case "pic_photo_or_album":
return EventStrategy.pic_photo_or_album;
// 自定义菜单 —— 弹出微信相册发图器的事件
case "pic_weixin":
return EventStrategy.pic_weixin;
// 自定义菜单 —— 弹出地理位置选择器的事件
case "location_select":
return EventStrategy.location_select;
default:
return null;
}
}
}
/***
* 事件枚举策略
*
* @author charles
*
*/
public enum EventStrategy implements IEventStrategy {
// 关注事件 || 扫描带参数二维码, 用户未关注时,进行关注后的事件推送
subscribe {
@Override
public String dealEvent(Element root) {
return SubscribeStrategy.dealEvent(root);
}
},
// 取消关注事件
unsubscribe {
@Override
public String dealEvent(Element root) {
return UnSubscribeStrategy.dealEvent(root);
}
},
// 扫描带参数二维码, 用户已关注时的事件推送
SCAN {
@Override
public String dealEvent(Element root) {
return ScanStrategy.dealEvent(root);
}
},
// 上报地理位置事件
LOCATION {
@Override
public String dealEvent(Element root) {
return LocationStrategy.dealEvent(root);
}
},
// 自定义菜单 —— 点击菜单拉取消息时的事件
CLICK {
@Override
public String dealEvent(Element root) {
return ClickStrategy.dealEvent(root);
}
},
// 自定义菜单 —— 点击菜单跳转链接时的事件
VIEW {
@Override
public String dealEvent(Element root) {
return ViewStrategy.dealEvent(root);
}
},
// 自定义菜单 —— 扫码推事件的事件
scancode_push {
@Override
public String dealEvent(Element root) {
return Scancode_pushStrategy.dealEvent(root);
}
},
// 自定义菜单 —— 扫码推事件且弹出“消息接收中”提示框的事件
scancode_waitmsg {
@Override
public String dealEvent(Element root) {
return Scancode_waitmsgStrategy.dealEvent(root);
}
},
// 自定义菜单 —— 弹出系统拍照发图的事件
pic_sysphoto {
@Override
public String dealEvent(Element root) {
return Pic_sysphotoStrategy.dealEvent(root);
}
},
// 自定义菜单 —— 弹出拍照或者相册发图的事件
pic_photo_or_album {
@Override
public String dealEvent(Element root) {
return Pic_photo_or_albumStrategy.dealEvent(root);
}
},
// 自定义菜单 —— 弹出微信相册发图器的事件
pic_weixin {
@Override
public String dealEvent(Element root) {
return Pic_weixinStrategy.dealEvent(root);
}
},
// 自定义菜单 —— 弹出地理位置选择器的事件
location_select {
@Override
public String dealEvent(Element root) {
return Location_selectStrategy.dealEvent(root);
}
},
// 模版消息发送任务完成后的事件
TEMPLATESENDJOBFINISH {
@Override
public String dealEvent(Element root) {
return TemplateSendStrategy.dealEvent(root);
}
}
}
/***
* 消息策略简单工厂
*
* @author charles
*
*/
public class MessageStrategyFactory {
public static IMessageStrategy getDealer(String msgType) {
switch (msgType) {
// 文本消息
case "text":
return MessageStrategy.text;
// 图片消息
case "image":
return MessageStrategy.image;
// 语音消息
case "voice":
return MessageStrategy.voice;
// 视频消息
case "video":
return MessageStrategy.video;
// 地理位置消息
case "location":
return MessageStrategy.location;
// 链接消息
case "link":
return MessageStrategy.link;
default:
return null;
}
}
}
/***
* 消息枚举策略
*
* @author charles
*
*/
public enum MessageStrategy implements IMessageStrategy {
// 文本消息
text {
@Override
public String dealMsg(Element root) {
return TextStrategy.dealMsg(root);
}
},
// 图片消息
image {
@Override
public String dealMsg(Element root) {
return ImageStrategy.dealMsg(root);
}
},
// 语音消息
voice {
@Override
public String dealMsg(Element root) {
return VoiceStrategy.dealMsg(root);
}
},
// 视频消息
video {
@Override
public String dealMsg(Element root) {
return VideoStrategy.dealMsg(root);
}
},
// 地理位置消息
location {
@Override
public String dealMsg(Element root) {
return LocationStrategy.dealMsg(root);
}
},
// 链接消息
link {
@Override
public String dealMsg(Element root) {
return LinkStrategy.dealMsg(root);
}
}
}