最近公司原本的公众号要发布3.0版本,所以自己申请了一个微信公众号进行测试,第一步就是实现了通过微信接收信息转发至java后台解析并回复的消息的简单功能,经过一番研究终于实现,现在贡献出代码和大家一起分享。
首先百度微信公众平台,进入官网进行公众号的注册,企业可与注册服务号,个人订阅号即可满足大部分功能,申请成功后即可进入到公众平台的相关功能界面
自动回复有两种实现方法,一种是平台自带的回复功能,另一种就是连接后台进行功能实现,如果需要连接后台并在本地调试就需要一款端口映射工具,可以将本地localhost端口映射到外网上,方便进行在公众号服务上的部署,我这里使用的是ngrok,免费下载,使用方便,这个我们后续会讲解如何使用
在进本配置中我们可以看到公众平台给我们的开发者id,需要我们自己配置的:
1.服务器地址URL:这里就是本地服务器部署成功后,通过ngrok映射出的外网地址,当项目部署到发布机上后,就可以改为正式的地址,但必须以http://或https://开头,分别支持80端口和443端口。
2.令牌:令牌是微信端和服务器端配对的基本,令牌只要满足是英文或数字,长度为3-32字符就可以,这里的令牌稍后在服务器端的要相同才可以请求成功
3.秘钥:随机生成就可以
本人使用的是intellij下的springboot框架实现的相关功能,各部分代码如下
Controller
在controller首先进入controller中的/movie/getSpecMovie方法中,此方法即是URL的方法地址,在开头定义我们的Token,和平台上的token对应,通过判断是get方法还是post方法进入不同分支方法,get方位为提交服务器配置时的验证使用,post是接收到公众号内消息进行的逻辑处理
Controller
package com.borya.controller;
private String Token = "123456";
@RestController
@RequestMapping("/movie")
public class movieController {
@RequestMapping(value = "/getSpecMovie" , method = {RequestMethod.GET,RequestMethod.POST})
public void getSpecMovie(HttpServletRequest request , HttpServletResponse response){
response.setContentType("text/html;charset=utf-8");
System.out.println("进入方法");
Boolean isGet = request.getMethod().toLowerCase().equals("get");
if (isGet){
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
System.out.println(signature);
System.out.println(timestamp);
System.out.println(nonce);
System.out.println(echostr);
access(request, response);
}else{
// 进入POST聊天处理
System.out.println("enter post");
try {
// 接收消息并返回消息
acceptMessage(request, response);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 验证url真实性
* @param request
* @param response
* @return
*/
private String access(HttpServletRequest request , HttpServletResponse response){
// 验证URL真实性
System.out.println("进入验证access");
String signature = request.getParameter("signature");// 微信加密签名
String timestamp = request.getParameter("timestamp");// 时间戳
String nonce = request.getParameter("nonce");// 随机数
String echostr = request.getParameter("echostr");// 随机字符串
List<String> params = new ArrayList<String>();
params.add(Token);
params.add(timestamp);
params.add(nonce);
// 1. 将token、timestamp、nonce三个参数进行字典序排序
Collections.sort(params, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
// 2. 将三个参数字符串拼接成一个字符串进行sha1加密
String temp = SHA1.encode(params.get(0) + params.get(1) + params.get(2));
if (temp.equals(signature)) {
try {
response.getWriter().write(echostr);
System.out.println("成功返回 echostr:" + echostr);
return echostr;
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("失败 认证");
return null;
}
private void acceptMessage(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 处理接收消息
ServletInputStream in = request.getInputStream();
// 将POST流转换为XStream对象
XStream xs = SerializeXmlUtil.createXstream();
xs.processAnnotations(wechatInputMessage.class);
xs.processAnnotations(wechatOutputMessage.class);
// 将指定节点下的xml节点数据映射为对象
xs.alias("xml", wechatInputMessage.class);
// 将流转换为字符串
StringBuilder xmlMsg = new StringBuilder();
byte[] b = new byte[4096];
for (int n; (n = in.read(b)) != -1;) {
xmlMsg.append(new String(b, 0, n, "UTF-8"));
}
// 将xml内容转换为InputMessage对象
wechatInputMessage inputMsg = (wechatInputMessage) xs.fromXML(xmlMsg.toString());
String servername = inputMsg.getToUserName();// 服务端
String custermname = inputMsg.getFromUserName();// 客户端
long createTime = inputMsg.getCreateTime();// 接收时间
Long returnTime = Calendar.getInstance().getTimeInMillis() / 1000;// 返回时间
// 取得消息类型
String msgType = inputMsg.getMsgType();
// 根据消息类型获取对应的消息内容
if (msgType.equals(MsgType.Text.toString())) {
// 文本消息
System.out.println("开发者微信号:" + inputMsg.getToUserName());
System.out.println("发送方帐号:" + inputMsg.getFromUserName());
System.out.println("消息创建时间:" + inputMsg.getCreateTime() + new Date(createTime * 1000l));
System.out.println("消息内容:" + inputMsg.getContent());
System.out.println("消息Id:" + inputMsg.getMsgId());
StringBuffer str = new StringBuffer();
str.append("<xml>");
str.append("<ToUserName><![CDATA[" + custermname + "]]></ToUserName>");
str.append("<FromUserName><![CDATA[" + servername + "]]></FromUserName>");
str.append("<CreateTime>" + returnTime + "</CreateTime>");
str.append("<MsgType><![CDATA[" + msgType + "]]></MsgType>");
str.append("<Content><![CDATA[" + 你要回复的内容 + "]]></Content>");
str.append("</xml>");
System.out.println(str.toString());
response.getWriter().write(str.toString());
}
// 获取并返回多图片消息
if (msgType.equals(MsgType.Image.toString())) {
System.out.println("获取多媒体信息");
System.out.println("多媒体文件id:" + inputMsg.getMediaId());
System.out.println("图片链接:" + inputMsg.getPicUrl());
System.out.println("消息id,64位整型:" + inputMsg.getMsgId());
wechatOutputMessage outputMsg = new wechatOutputMessage();
outputMsg.setFromUserName(servername);
outputMsg.setToUserName(custermname);
outputMsg.setCreateTime(returnTime);
outputMsg.setMsgType(msgType);
ImageMessage images = new ImageMessage();
images.setMediaId(inputMsg.getMediaId());
outputMsg.setImage(images);
System.out.println("xml转换:/n" + xs.toXML(outputMsg));
response.getWriter().write(xs.toXML(outputMsg));
}
}
}
输入信息的实体类
package com.borya.bean;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import java.io.Serializable;
@XStreamAlias("xml")
public class wechatInputMessage implements Serializable{
private static final long serialVersionUID = 1L;
@XStreamAlias("ToUserName")
private String ToUserName;
@XStreamAlias("FromUserName")
private String FromUserName;
@XStreamAlias("CreateTime")
private Long CreateTime;
@XStreamAlias("MsgType")
private String MsgType = "text";
@XStreamAlias("MsgId")
private Long MsgId;
// 文本消息
@XStreamAlias("Content")
private String Content;
// 图片消息
@XStreamAlias("PicUrl")
private String PicUrl;
// 位置消息
@XStreamAlias("LocationX")
private String LocationX;
@XStreamAlias("LocationY")
private String LocationY;
@XStreamAlias("Scale")
private Long Scale;
@XStreamAlias("Label")
private String Label;
// 链接消息
@XStreamAlias("Title")
private String Title;
@XStreamAlias("Description")
private String Description;
@XStreamAlias("Url")
private String URL;
// 语音信息
@XStreamAlias("MediaId")
private String MediaId;
@XStreamAlias("Format")
private String Format;
@XStreamAlias("Recognition")
private String Recognition;
// 事件
@XStreamAlias("Event")
private String Event;
@XStreamAlias("EventKey")
private String EventKey;
@XStreamAlias("Ticket")
private String Ticket;
public String getToUserName() {
return ToUserName;
}
public void setToUserName(String toUserName) {
ToUserName = toUserName;
}
public String getFromUserName() {
return FromUserName;
}
public void setFromUserName(String fromUserName) {
FromUserName = fromUserName;
}
public Long getCreateTime() {
return CreateTime;
}
public void setCreateTime(Long createTime) {
CreateTime = createTime;
}
public String getMsgType() {
return MsgType;
}
public void setMsgType(String msgType) {
MsgType = msgType;
}
public Long getMsgId() {
return MsgId;
}
public void setMsgId(Long msgId) {
MsgId = msgId;
}
public String getContent() {
return Content;
}
public void setContent(String content) {
Content = content;
}
public String getPicUrl() {
return PicUrl;
}
public void setPicUrl(String picUrl) {
PicUrl = picUrl;
}
public String getLocationX() {
return LocationX;
}
public void setLocationX(String locationX) {
LocationX = locationX;
}
public String getLocationY() {
return LocationY;
}
public void setLocationY(String locationY) {
LocationY = locationY;
}
public Long getScale() {
return Scale;
}
public void setScale(Long scale) {
Scale = scale;
}
public String getLabel() {
return Label;
}
public void setLabel(String label) {
Label = label;
}
public String getTitle() {
return Title;
}
public void setTitle(String title) {
Title = title;
}
public String getDescription() {
return Description;
}
public void setDescription(String description) {
Description = description;
}
public String getURL() {
return URL;
}
public void setURL(String URL) {
this.URL = URL;
}
public String getMediaId() {
return MediaId;
}
public void setMediaId(String mediaId) {
MediaId = mediaId;
}
public String getFormat() {
return Format;
}
public void setFormat(String format) {
Format = format;
}
public String getRecognition() {
return Recognition;
}
public void setRecognition(String recognition) {
Recognition = recognition;
}
public String getEvent() {
return Event;
}
public void setEvent(String event) {
Event = event;
}
public String getEventKey() {
return EventKey;
}
public void setEventKey(String eventKey) {
EventKey = eventKey;
}
public String getTicket() {
return Ticket;
}
public void setTicket(String ticket) {
Ticket = ticket;
}
}
输出信息的实体类
package com.borya.bean;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@XStreamAlias("xml")
public class wechatOutputMessage {
@XStreamAlias("ToUserName")
@XStreamCDATA
private String ToUserName;
@XStreamAlias("FromUserName")
@XStreamCDATA
private String FromUserName;
@XStreamAlias("CreateTime")
private Long CreateTime;
@XStreamAlias("MsgType")
@XStreamCDATA
private String MsgType = "text";
private ImageMessage Image;
public String getToUserName() {
return ToUserName;
}
public void setToUserName(String toUserName) {
ToUserName = toUserName;
}
public String getFromUserName() {
return FromUserName;
}
public void setFromUserName(String fromUserName) {
FromUserName = fromUserName;
}
public Long getCreateTime() {
return CreateTime;
}
public void setCreateTime(Long createTime) {
CreateTime = createTime;
}
public String getMsgType() {
return MsgType;
}
public void setMsgType(String msgType) {
MsgType = msgType;
}
public ImageMessage getImage() {
return Image;
}
public void setImage(ImageMessage image) {
Image = image;
}
}
图片信息的实体类
package com.borya.bean;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@XStreamAlias("Image")
public class ImageMessage extends MediaIdMessage{
}
多媒体id的实体类
package com.borya.bean;
import com.thoughtworks.xstream.annotations.XStreamAlias;
public class MediaIdMessage {
@XStreamAlias("MediaId")
@XStreamCDATA
private String MediaId;
public String getMediaId() {
return MediaId;
}
public void setMediaId(String mediaId) {
MediaId = mediaId;
}
}
MsgType的枚举类
package com.borya.bean;
public enum MsgType {
Text("text"),
Image("image"),
Music("music"),
Video("video"),
Voice("voice"),
Location("location"),
Link("link");
private String msgType = "";
MsgType(String msgType) {
this.msgType = msgType;
}
/**
* @return the msgType
*/
@Override
public String toString() {
return msgType;
}
}
XML的CDATA验证类
package com.borya.bean;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface XStreamCDATA {
}
SHA1加密类
package com.borya.util;
import java.security.MessageDigest;
public final class SHA1 {
private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
/**
* Takes the raw bytes from the digest and formats them correct.
*
* @param bytes
* the raw bytes from the digest.
* @return the formatted bytes.
*/
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转换工具类
package com.borya.util;
import com.borya.bean.XStreamCDATA;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import java.io.Writer;
import java.lang.reflect.Field;
/**
* xml转换工具
* 2017年12月15日15:33:52
*/
public class SerializeXmlUtil {
public static XStream createXstream() {
return new XStream(new XppDriver() {
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
boolean cdata = false;
Class<?> targetClass = null;
@Override
public void startNode(String name, @SuppressWarnings("rawtypes") Class clazz) {
super.startNode(name, clazz);
// 业务处理,对于用XStreamCDATA标记的Field,需要加上CDATA标签
if (!name.equals("xml")) {
cdata = needCDATA(targetClass, name);
} else {
targetClass = clazz;
}
}
@Override
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
}
private static boolean needCDATA(Class<?> targetClass, String fieldAlias) {
boolean cdata = false;
// first, scan self
cdata = existsCDATA(targetClass, fieldAlias);
if (cdata)
return cdata;
// if cdata is false, scan supperClass until java.lang.Object
Class<?> superClass = targetClass.getSuperclass();
while (!superClass.equals(Object.class)) {
cdata = existsCDATA(superClass, fieldAlias);
if (cdata)
return cdata;
superClass = superClass.getClass().getSuperclass();
}
return false;
}
private static boolean existsCDATA(Class<?> clazz, String fieldAlias) {
if ("MediaId".equals(fieldAlias)) {
return true; // 特例添加 morning99
}
// scan fields
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 1. exists XStreamCDATA
if (field.getAnnotation(XStreamCDATA.class) != null) {
XStreamAlias xStreamAlias = field.getAnnotation(XStreamAlias.class);
// 2. exists XStreamAlias
if (null != xStreamAlias) {
if (fieldAlias.equals(xStreamAlias.value()))// matched
return true;
} else {// not exists XStreamAlias
if (fieldAlias.equals(field.getName()))
return true;
}
}
}
return false;
}
}
项目的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>1</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--xml格式化工具-->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10-java7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
以上部分为功能实现的全部类文件
发布到本地服务器上后,就要开始用到刚才提到的ngrok,这款映射工具,我在刚才的官网上下载后,是个ngrok.exe文件,打开后在dom窗口内输入
ngrok http 8080
8080可以改为你的端口,但微信公众号的URL必须为80端口
然后成功则会进入到映射到的服务器界面,图中所示即为映射出的外网地址,将地址填到公众平台的URL上,加上方法名就可以了,在使用过程中不要关闭窗口,
由于该这个是随机的,每次重启都会变,所以利于我们微信调试,但是ngrok已经给我们了相关办法,就是需要我们注册使用
在官网都注册成功后,会得到一个token
在获得token后重新打开ngrok.exe,输入:ngrok -authtoken token 80其中token换成我们注册后返回的值,回车后,成功启动
但是返回的token太长了,不方便记忆,我们换一个方便记忆的名字,输入ngrok -subdomain leopard 80
回车后,成功启动,下面我们需要验证下输入域名:http://leopard.ngrok.com是否能够通过微信的URL校验步
打开微信公众平台,选择开发者中心,在右边的表单中输入相关内容,URL中输入我们刚设置过的域名
提交成功,说明我们的设置是有效的,此时就可以继续使用啦,有什么问题大家可以在下面留言