微信公众号介绍:
微信公众号分为服务号、订阅号、企业号,订阅号可以个人申请,服务号和企业号要有企业资质才可以。而我们所说的微信公众号开发是指的是订阅号和服务号,而官方给的两个号的相关权限的解释:
1、服务号:主要偏向于服务交互(功能类似12315,114,银行,提供绑定信息,服务交互),每月可群发4条消息;服务号适用人群:媒体、企业、政府或其他组织。
2、订阅号:主要偏向于为用户传达资讯,(功能类似报纸杂志,为用户提供新闻信息或娱乐趣事),每天可群发1条消息;订阅号适用人群:个人、媒体、企业、政府或其他组织。
而我们程序员如果是初学微信公众号开发的话,订阅号就比较适合,而对于企业方面就偏于开发服务号,服务号的功能也比订阅号开放很多,而且也会显示在主界面,服务号是由统一的聊天窗来管理(类似于群)。
微信公众号开发步骤:
1、注册微信公众号
进入微信公众号注册页面https://mp.weixin.qq.com/
点击公众号右上方的注册按钮,进入注册界面,填写基本信息,选择订阅号, 完成身份认证,这里我是选择了订阅号,注册完即可。
2、注册测试公众号
微信公众平台给我们提供了测试公众账号,测试公众号有很多个人订阅号不具备的权限, 所以我们就先用测试公众号来测试自己写的程序或者功能,然后再进行转移使用,测试公众号的注册地址为:http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
;然后就用自己的微信扫描页面中的二维码进行登录,登录成功后,就可以看到腾讯分配给我们的测试公众号的信息了,如下图所示, 接下来我们就可以搭建环境,进行开发测试了,如图所示:
可以看到测试公众号有很多个人订阅号无法达到的权限功能,所以我们先来进行微信本地调试环境,通过自己写的项目和微信公众号进行一个联系,在开发基于这种微信公众号的应用的时候最大的痛楚就是调试问题,每实现一个功能就要部署到一个公网服务器上进行调试,这就因为用户在给公众号发起请求的时候,微信服务器就会先接受用户的请求,然后再通过公网服务器转发 到我们的tomcat服务器上,但现在我们可以借助第三方软件来进行内网穿透来进行服务器间的交互信息,Ngrok就是一个免费的软件,可以给我们实现内网穿透,也就是说我们可以将自己本地的网络映射到外网来给别人进行访问,这样有助于我们在本地开发环境中调试微信代码提供了便利。https://www.ngrok.cc/user.html,我是在这个平台上用的免费服务器,如果要网速快点的话,可以购买十块钱的服务器来加快内网穿透的网速。
3、微信公众号接入(校验签名)
在这里如果要把自己的代码加载到微信公众号的话就需要进行接口的连接,如图所示:
服务器配置包含服务器地址(URL)、令牌(Token) 和 消息加解密密钥(EncodingAESKey)。
可在开发–>基本配置–>服务器配置中配置
服务器地址即公众号后台提供业务逻辑的入口地址,目前只支持80端口,之后包括接入验证以及任何其它的操作的请求(例如消息的发送、菜单管理、素材管理等)都要从这个地址进入。接入验证和其它请求的区别就是,接入验证时是get请求,其它时候是post请求;
Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性);EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。本例中全部以未加密的明文消息方式,不涉及此配置项。
然后验证服务器地址的有效性,当点击“提交”按钮后,微信服务器将发送一个http的get请求到刚刚填写的服务器地址,并且携带四个参数:
接到请求后,我们需要做以下三步,若确认本次的GET请求来自微信服务器,原样返回echostr参数内容,则接入生效,否则接入失败。这里就需要SHA1算法加密,然后通过对比来返回是否正确
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/**
* signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
timestamp 时间戳
nonce 随机数
echostr 随机字符串
*/
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
/**
* 1)将token、timestamp、nonce三个参数进行字典序排序
* 2)将三个参数字符串拼接成一个字符串进行sha1加密
* 3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
*/
boolean flag =DataUtil.verfiy(timestamp,nonce,signature);
if(flag) {
System.out.println("连接成功");
PrintWriter out = response.getWriter();
out.write(echostr);
out.flush();
out.close();
}else {
System.out.println("连接失败");
}
}
public class DataUtil {
private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5','6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
public static boolean verfiy(String timestamp, String nonce, String signature) {
String token = Config.props.getProperty("token");
// 1)将token、timestamp、nonce三个参数进行字典序排序
String[] temArr = new String[] {token,timestamp,nonce};
Arrays.sort(temArr);
// 2)将三个参数字符串拼接成一个字符串进行sha1加密
StringBuilder sb = new StringBuilder();
for(String s:temArr) {
sb.append(s);
}
String str =encode(sb.toString());
// 3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
return str.equalsIgnoreCase(signature);
}
/**
* SHA1加密
* @param bytes
* @return
*/
private static String getFormattedText(byte[] bytes) {
int len = bytes.length;
StringBuilder buf = new StringBuilder(len * 2);
// 把密文转换成十六进制的字符串形式
for (int j = 0; j < len; j++) {
buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
}
return buf.toString();
}
public static String encode(String str) {
if (str == null) {
return null;
}
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
messageDigest.update(str.getBytes());
return getFormattedText(messageDigest.digest());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
到此,我们的公众号应用已经能够和微信服务器正常通信了,也就是说我们的公众号已经接入到微信公众平台了。如果我们要看到用户发来的是什么消息。这里就需要我们进行XML文件的解析.
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//编码
request.setCharacterEncoding("utf8");
response.setCharacterEncoding("utf8");
ServletInputStream sis = request.getInputStream();
Map<String, String> requestMap = DataUtil.parseXml(sis);
//根据业务需求返回消息
BaseMessage msg = new WeiXiService().getResponse(requestMap);
String xml = DataUtil.beanToXml(msg);
PrintWriter out = response.getWriter();
out.write(xml);
out.flush();
out.close();
}
package com.hotel.wx.entity;
import java.util.Map;
import com.thoughtworks.xstream.annotations.XStreamAlias;
public abstract class BaseMessage {
@XStreamAlias("ToUserName")
private String toUserName;
@XStreamAlias("FromUserName")
private String fromUserName;
@XStreamAlias("CreateTime")
private String createTime;
@XStreamAlias("MsgType")
private String msgType="text";
public String getToUserName() {
return toUserName;
}
public void setToUserName(String toUserName) {
this.toUserName = toUserName;
}
public String getFromUserName() {
return fromUserName;
}
public void setFromUserName(String fromUserName) {
this.fromUserName = fromUserName;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
public String getMsgType() {
return msgType;
}
public void setMsgType(String msgType) {
this.msgType = msgType;
}
public BaseMessage(Map<String, String> requestMap,String msgType) {
super();
this.toUserName = requestMap.get("FromUserName");
this.fromUserName = requestMap.get("ToUserName");
this.createTime = System.currentTimeMillis()/1000+"";
this.msgType = msgType;
}
}
package com.hotel.wx.entity;
import java.util.Map;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@XStreamAlias("xml")
public class TextMessage extends BaseMessage{
@XStreamAlias("Content")
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public TextMessage(Map<String, String> requestMap,String content) {
super(requestMap,"text");
this.content=content;
}
}
这里是工具类的方法
/**
* 微信注解转换
* @param bm
* @return
*/
public static String beanToXml(BaseMessage bm) {
XStream stream = new XStream();
stream.processAnnotations(BaseMessage.class);
stream.processAnnotations(TextMessage.class);
stream.processAnnotations(NewsMessage.class);
String xml = stream.toXML(bm);
return xml;
}
/**
* 根据业务需求回复消息
*/
public BaseMessage getResponse(Map<String, String> requestMap) {
String msgType = requestMap.get("MsgType");
switch (msgType) {
case "text":
return dealTextMsg(requestMap);
case "event":
return deatEvent(requestMap);
default:
String msg = Config.props.getProperty("defaultmsg");
msg = msg.replace("addr", Config.props.getProperty("addr"));
return new TextMessage(requestMap, msg);
}
}
这样我们就可以通过XML架包中的解析方法来解析用户发来的消息。
4、access_token管理
我们的公众号和微信服务器对接成功之后,接下来要做的就是根据我们的业务需求调用微信公众号提供的接口来实现相应的逻辑了。在使用微信公众号接口中都需要一个access_token。
1)access_token介绍
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
总结以上说明,access_token需要做到以下两点:
1.因为access_token有2个小时的时效性,要有一个机制保证最长2个小时重新获取一次。
2.因为接口调用上限每天2000次,所以不能调用太频繁。
2)获取access_token步骤
公众号可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在“微信公众平台-开发-基本配置”页中获得(需要已经成为开发者,且帐号没有异常状态)。调用接口时,请登录“微信公众平台-开发-基本配置”提前将服务器IP地址添加到IP白名单中,点击查看设置方法,否则将无法调用成功。