微信公众号开发(Java)

先看微信公众号开发文档

微信公众号填写服务器配置(微信公众号后台–>开发–>基本配置)

验证服务器地址的有效性可以本地测试(可通过FRP内网穿透实现无服务器本地测试开发)

依据接口文档实现业务逻辑

数据发送和返回流程:
  1. 用户发送消息到公众号

  2. 微信服务器接收消息

  3. 微信服务器将消息处理后按一定的格式转发致开发人员配置的请求地址(初次需要验证)

  4. 配置的开发人员服务器接收微信服务器发来的请求,开发人员服务器接收解析数据,根据不同的数据做业务逻辑

  5. 开发人员服务器返回固定的格式数据给微信服务器

  6. 微信服务器发送给用户

使用Ngrok内网穿透数据传递流程:

  1. 就是按照上面的,当微信服务器发送数据的时候而不是开发人员的服务器地址而是Ngrok的服务器给我们开放的地址,然后Ngrok服务器将数据发送到本地电脑的Ngrok客户端上,我们本地的数据也是发送到Ngrok服务器上,然后它再转发给微信服务器

  2. (Idea工具,SpringMVC框架)

  3. 平台接入验证

  4. java通过HttpServlet实现接收数据,发送数据

  5. 首先先进行本地验证

  6. 新建maven项目配置好SpringMVC后编写controller

总体流程:

  1. 用户给微信服务器发送数据,微信服务器发送get请求到我们的服务器,我们解析发过来的数据,按按照给定的方法进行加密,然后再返回回去,进行验证
  • 消息流程:用户发送消息给公众号,微信服务器使用post方法给我们的服务器,我们的服务器接收到请求后,分析数据是什么类型,比如是文本还是图片,根据发送的内容,返回给微信服务器我们的数据。当中一定要按照文档内上数据格式要求来解析数据和发送数据,我们将接收来的xml数据解析成一个map对象,可以从中取节点判断类型也可定向取数据,因为有很多信息类型,解析xml和拼接xml返回有些麻烦,所以将各个信息类型封装成对象,我们只需要关注信息的内容即可,其中用到了很多工具处理xml如:XStream(把对象转化成xml文件)、SAXReader(把inputStream流转化成document对象)具体如何使用,请参考各个工具的文档

以下是代码,详见注释

package controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import service.WxService;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;

/**
 * @Author: nsk
 * @Description:
 * @Date: create in 2021/4/5 17:54
 */
@Controller
public class WxController {
    //开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上
    @RequestMapping(value = "/wx",method = RequestMethod.GET)
    @ResponseBody
    public String verification(HttpServletRequest req, HttpServletResponse resp){
        System.out.println("test");
        //获取四个参数,用来验证
        String signature = req.getParameter("signature");
        String timestamp = req.getParameter("timestamp");
        String nonce = req.getParameter("nonce");
        String echostr = req.getParameter("echostr");
        System.out.println(signature);
        System.out.println(timestamp);
        System.out.println(nonce);
        System.out.println(echostr);
        /*
开发者通过检验signature对请求进行校验(下面有校验方式)。
若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。
加密校验流程如下:
1)将token、timestamp、nonce三个参数进行字典序排序 
2)将三个参数字符串拼接成一个字符串进行sha1加密 
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
 */
               //调用此方法验证是否成功
        if(WxService.check(timestamp,nonce,signature)){
            System.out.println("接入成功!");
            PrintWriter writer = null;
            try {
                    //如果验证成功,就返回一个echostr微信服务器接收成功后,即视为验证成功
                writer = resp.getWriter();
                writer.print(echostr);
                writer.flush();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else {
            System.out.println("接入失败!");
        }

        return echostr;
    }
    //当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
    //post接收消息,相同的url,只是提交方式不一样,实现发送和接收
    //开发文档:https://developers.weixin.qq.com/doc/offiaccount/Message\_Management/Receiving\_standard_messages.html
    //注意:一定要按照开发文档上的格式要求来
    @RequestMapping(value = "/wx",method = RequestMethod.POST)
    @ResponseBody
    public String getMessage(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        //测试是否能接收数据
        System.out.println("GetMessage"); 
        //处理消息和事件推送
        Map<String,String> map  = WxService.parseRequest(req.getInputStream());
//        String str = "<xml>" +
//                " <ToUserName>"+ map.get("FromUserName")+"</ToUserName>\\n" +
//                "<FromUserName>"+map.get("ToUserName")+"</FromUserName>" +
//                "<CreateTime>"+System.currentTimeMillis()/1000+"</CreateTime>" +
//                "<MsgType>text</MsgType>" +
//                "<Content>你好</Content>" +
//                "</xml>";
        //封装数据
        String str  = WxService.getResponse(map);
        System.out.println("responseMap:"+map);
        System.out.println("responseStr:"+str);
        PrintWriter writer = null;
        try {
            //发送数据到微信服务器,然后微信服务器发给用户
            writer = resp.getWriter();
            writer.print(str);
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }
}
package service;

import com.thoughtworks.xstream.XStream;
import entity.*;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.stereotype.Service;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: nsk
 * @Description:
 * @Date: create in 2021/4/5 16:46
 */
@Service
public class WxService {
    private  static final String TOKEN = "nsk666";
    //接入验证
    public static boolean check(String timestamp, String nonce, String signature) {
        //1.进行字典拍讯
        String arg[] = new String[]{TOKEN,timestamp,nonce};
        Arrays.sort(arg);
        //2.进行加密
        String str  = arg[0]+arg[1]+arg[2];
        //3.与signature对比
        String s = sha1(str);
        if (s.equals(signature)){
            System.out.println(s);
            System.out.println(signature);
            return true;
        }
        return false;
    }
    /**
    *description:sha1加密
    *params:
    *return:
    **/
    private static String sha1(String str)  {
        MessageDigest sha = null;
        try {
            sha = MessageDigest.getInstance("SHA");
            byte[] byteArray = str.getBytes("UTF-8");
            byte[] md5Bytes = sha.digest(byteArray);
            StringBuffer hexValue = new StringBuffer();
            for (int i = 0; i < md5Bytes.length; i++) {
                int val = ((int) md5Bytes[i]) & 0xff;
                if (val < 16) {
                    hexValue.append("0");
                }
                hexValue.append(Integer.toHexString(val));
            }
            return hexValue.toString();
        } catch (Exception e) {
            System.out.println(e.toString());
            e.printStackTrace();
            return "";
        }
    }
    /**
    *description:获取servletInpustream转化xml为对象,方便取节点
    *params:
    *return:
    **/
    public static Map<String, String> parseRequest(ServletInputStream inputStream) {
        Map<String,String > map = new HashMap<String, String>();
//        byte[] b = new byte[1024];
//        int len;
//        StringBuilder sb = new StringBuilder();
//        while((len = inputStream.read(b)) != -1){
//            sb.append(new String(b,0,len));
//        }
//        System.out.println(sb.toString());
        //把xml的数据流转化成document对象
        SAXReader reader = new SAXReader();
        try {
            //获取输入流,转化成文档对象
            Document document = reader.read(inputStream);
            //根据文档对象获取跟节点
            Element rootElement = document.getRootElement();
            List<Element> list = rootElement.elements();
            for (Element e: list){
                map.put(e.getName(),e.getStringValue());
            }
            return map;
        } catch (DocumentException e) {
            e.printStackTrace();
        }

        return null;
    }

    //处理所有消息的接收和回复、
    //取出msgType判断是什么类型的消息,对应回复
    public static String getResponse(Map<String, String> map) {
        BaseMessage msg = null;
        String msgType = map.get("MsgType");
        switch (msgType) {
            case "text":
                //是text类型,对应回复
                msg = dealTextMessage(map);
                break;
            case "image":
                break;
            case "voice":
                break;
            case "video":
                break;
            case "shortvideo":
                break;
            case "location":
                break;
            case "link":
                break;
        }
        if (msg != null){
            return beanToXml(msg);
        }
        //把消息对象处理成数据包
        return null;
    }
    //处理文本消息,回复相应的内容
    private static BaseMessage dealTextMessage(Map<String, String> map) {
        //新建对象,设置内容
        TextMessage textMessage = new TextMessage(map,"处理文本消息,回复相应的"+map);
        return textMessage;
    }
    /**
    *description: 将javaBean对象转化成xml字符串
    *params:
    *return:
    **/
    private static String beanToXml(BaseMessage baseMessage){
        XStream xStream = new XStream();
        xStream.processAnnotations(TextMessage.class);
        xStream.processAnnotations(ImageMessage.class);
        xStream.processAnnotations(MusicMessage.class);
        xStream.processAnnotations(NewsMessage.class);
        xStream.processAnnotations(VideoMessage.class);
        String xml = xStream.toXML(baseMessage);
        return xml;
    }
}

封装javaBean方便生成xml数据

package entity;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Map;

/**
 * @Author: nsk
 * @Description:
 * @Date: create in 2021/4/5 19:30
 */
@Data
public class BaseMessage {
    @XStreamAlias("ToUserName")
    private String toUserName;
    @XStreamAlias("FromUserName")
    private String fromUserName;
    @XStreamAlias("CreateTime")
    private String createTime;
    @XStreamAlias("MsgType")
    private String msgType;
    public BaseMessage(Map<String,String> map){
        this.toUserName = map.get("FromUserName");
        this.fromUserName = map.get("ToUserName");
        this.createTime = System.currentTimeMillis()/1000+"";
    }
}
``````java
package entity;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import lombok.Data;

import java.util.Map;

/**
 * @Author: nsk
 * @Description:
 * @Date: create in 2021/4/5 19:33
 */
@Data
@XStreamAlias("xml")
public class TextMessage extends BaseMessage{
    @XStreamAlias("Content")
    private String content;
    public TextMessage(Map<String,String> map,String content){
        super(map);
        this.setMsgType("text");
        this.setContent(content);
    }
}

附一张测试图: