intellij下使用springboot实现微信公众号回复功能

来源:互联网 发布:d800微调优化曝光 编辑:程序博客网 时间:2024/05/16 01:10

最近公司原本的公众号要发布3.0版本,所以自己申请了一个微信公众号进行测试,第一步就是实现了通过微信接收信息转发至java后台解析并回复的消息的简单功能,经过一番研究终于实现,现在贡献出代码和大家一起分享。

首先百度微信公众平台,进入官网进行公众号的注册,企业可与注册服务号,个人订阅号即可满足大部分功能,申请成功后即可进入到公众平台的相关功能界面

这里写图片描述

自动回复有两种实现方法,一种是平台自带的回复功能,另一种就是连接后台进行功能实现,如果需要连接后台并在本地调试就需要一款端口映射工具,可以将本地localhost端口映射到外网上,方便进行在公众号服务上的部署,我这里使用的是ngrok,免费下载,使用方便,这个我们后续会讲解如何使用

这里写图片描述

在进本配置中我们可以看到公众平台给我们的开发者id,需要我们自己配置的:

1.服务器地址URL:这里就是本地服务器部署成功后,通过ngrok映射出的外网地址,当项目部署到发布机上后,就可以改为正式的地址,但必须以http://或https://开头,分别支持80端口和443端口。
2.令牌:令牌是微信端和服务器端配对的基本,令牌只要满足是英文或数字,长度为3-32字符就可以,这里的令牌稍后在服务器端的要相同才可以请求成功
3.秘钥:随机生成就可以

本人使用的是intellij下的springboot框架实现的相关功能,各部分代码如下

在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中输入我们刚设置过的域名

提交成功,说明我们的设置是有效的,此时就可以继续使用啦,有什么问题大家可以在下面留言

原创粉丝点击