跳至主要內容

微信公众号开发

微信公众号微信公众号大约 5 分钟约 1643 字全民制作人ikun

微信公众号开发

基本配置

官网:https://mp.weixin.qq.com/open in new window

配置内网穿透

使用ngrok进行内网穿透。(这个方法不行,不要用了,可能是域名里面有-的影响)
错误链接:https://developers.weixin.qq.com/community/develop/doc/000684b1e282e0084f6ff3fcc5d000?highLine=-106open in new window

ngrok http --domain=moving-enhanced-woodcock.ngrok-free.app 8080

使用natapp进行内网穿透:
Nat官网:https://natapp.cn/open in new window
注册好账号,在网站里面配置要映射的端口,然后启动

./natapp -authtoken=你的token

注册一个测试号

接入指南:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.htmlopen in new window
image.png
主要是配置接口信息,这里的URL需要填写刚才内网穿透的地址,并且需要返回微信需要的特定信息,如下:
image.png
开启端口映射:
image.png

这是我提供的后端接口:

/**  
 * @author houyunfei  
 */@RestController  
@RequestMapping("/wx")  
@Slf4j  
public class WxController {  
    @GetMapping(value = "/hello")  
    public String hello() {  
        return "Hello, WeChat!";  
    }  
  
    /**  
     * 微信接入验证  
     *  
     * @param signature 签名  
     * @param timestamp 时间戳  
     * @param nonce     随机数  
     * @param echostr   随机字符串  
     */  
    @GetMapping(value = "/signature")  
    public String signature(String signature, String timestamp, String nonce, String echostr, HttpServletResponse response) {  
        log.info("signature: {}, timestamp: {}, nonce: {}, echostr: {}", signature, timestamp, nonce, echostr);  
        //请讲返回微信服务器的Respose的content-type字段,改成,text/html; charset=utf-8。  
        //怎么改? answer: 在response header中设置content-type为text/html; charset=utf-8  
        response.setHeader("Content-Type", "text/html; charset=utf-8");  
        return echostr;  
    }  
}

此时访问:http://yfjpvb.natappfree.cc/wx/signatureopen in new window 可以访问

配置成功:
image.png

微信消息验证

微信想要的验证方式如下:

开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

校验的工具类:

public class WxUtils {  
  
  
    public static final String token = "123";  
  
  
    /**  
     * 校验签名  
     *  
     * @param signature 签名  
     * @param timestamp 时间戳  
     * @param nonce     随机数  
     * @return 布尔值  
     */  
    public static boolean checkSignature(String signature, String timestamp, String nonce) {  
        String checktext = null;  
        if (null != signature) {  
            // 对ToKen,timestamp,nonce 按字典排序  
            String[] paramArr = new String[]{token, timestamp, nonce};  
            Arrays.sort(paramArr);  
            // 将排序后的结果拼成一个字符串  
            String content = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);  
  
            try {  
                MessageDigest md = MessageDigest.getInstance("SHA-1");  
                // 对接后的字符串进行sha1加密  
                byte[] digest = md.digest(content.toString().getBytes());  
                checktext = byteToStr(digest);  
            } catch (NoSuchAlgorithmException e) {  
                e.printStackTrace();  
            }  
        }  
        // 将加密后的字符串与signature进行对比  
        return checktext != null ? checktext.equals(signature.toUpperCase()) : false;  
    }  
  
    /**  
     * 将字节数组转化我16进制字符串  
     *  
     * @param byteArrays 字符数组  
     * @return 字符串  
     */  
    private static String byteToStr(byte[] byteArrays) {  
        String str = "";  
        for (int i = 0; i < byteArrays.length; i++) {  
            str += byteToHexStr(byteArrays[i]);  
        }  
        return str;  
    }  
  
    /**  
     * 将字节转化为十六进制字符串  
     *  
     * @param myByte 字节  
     * @return 字符串  
     */  
    private static String byteToHexStr(byte myByte) {  
        char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};  
        char[] tampArr = new char[2];  
        tampArr[0] = Digit[(myByte >>> 4) & 0X0F];  
        tampArr[1] = Digit[myByte & 0X0F];  
        String str = new String(tampArr);  
        return str;  
    }  
}

完善验证逻辑:

/**  
 * 微信接入验证  
 *  
 * @param signature 签名  
 * @param timestamp 时间戳  
 * @param nonce     随机数  
 * @param echostr   随机字符串  
 */  
@GetMapping(value = "/signature")  
public String signature(String signature, String timestamp, String nonce, String echostr) {  
    log.info("signature: {}, timestamp: {}, nonce: {}, echostr: {}", signature, timestamp, nonce, echostr);  
    boolean res = WxUtils.checkSignature(signature, timestamp, nonce);  
    return res ? echostr : "error";  
}

接收普通消息

https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.htmlopen in new window

注意:这里发送普通消息他调用的接口是一开始填的,所以我改成了http://tdikru.natappfree.cc/wx/

@PostMapping("/")  
public String post(HttpServletRequest request) {  
    log.info("接收到消息");  
    return "success";  
}

关注测试公众号,随便发送消息:
image.png

接收消息:
dom4j用来转换接收到的消息,xstream用来发送xml类型消息。

<dependency>  
    <groupId>com.thoughtworks.xstream</groupId>  
    <artifactId>xstream</artifactId>  
    <version>1.4.19</version>  
</dependency>  
<dependency>  
    <groupId>org.dom4j</groupId>  
    <artifactId>dom4j</artifactId>  
    <version>2.1.3</version>  
</dependency>

根据消息进行封装:
image.png

封装实体类:

@Data  
@XStreamAlias("xml")  
public class TextMessage {  
    @XStreamAlias("ToUserName")  
    private String toUserName;  
    @XStreamAlias("FromUserName")  
    private String fromUserName;  
    @XStreamAlias("CreateTime")  
    private Long createTime;  
    @XStreamAlias("MsgType")  
    private String ssgType;  
    @XStreamAlias("Content")  
    private String content;  
    @XStreamAlias("MsgId")  
    private Long ssgId;  
    @XStreamAlias("MsgDataId")  
    private String ssgDataId;  
    @XStreamAlias("Idx")  
    private String idx;  
}

具体的响应用户发送消息的代码:

/**  
 * 接收微信用户发送来的消息  
 *  
 * @param request 请求  
 * @return 返回消息  
 * @throws IOException  
 */@PostMapping("/")  
public String receiveMessage(HttpServletRequest request) throws IOException {  
    ServletInputStream inputStream = request.getInputStream();  
    HashMap<String, String> map = new HashMap<>();  
    SAXReader reader = new SAXReader();  
    try {  
        Document document = reader.read(inputStream);  
        Element root = document.getRootElement();  
        List<Element> elements = root.elements();  
        for (Element element : elements) {  
            map.put(element.getName(), element.getStringValue());  
        }  
    } catch (DocumentException e) {  
        throw new RuntimeException(e);  
    }  
    System.out.println(map);  
    log.info("接收到消息");  
    String message = getReplyMessage(map);  
    return message;  
}  
  
/**  
 * 获取回复消息 发送给用户 xml格式  
 *  
 * @param map  
 * @return  
 */  
private String getReplyMessage(HashMap<String, String> map) {  
    TextMessage message = new TextMessage();  
    message.setToUserName(map.get("FromUserName"));  
    message.setFromUserName(map.get("ToUserName"));  
    message.setCreateTime(System.currentTimeMillis());  
    message.setSsgType("text");  
    message.setContent("小黑子,欢迎关注本公众号!");  
  
    // XStream将对象转换为xml字符串  
    XStream xStream = new XStream();  
    xStream.processAnnotations(TextMessage.class);  
    String xml = xStream.toXML(message);  
    return xml;  
}

结果如下:
image.png

开发案例:获取同义词

System.out.println(map);  
log.info("接收到消息");  
String content = map.get("Content");  
if (content.contains("同义词")) {  
    String[] split = content.split(" ");  
    String word = split[1];  
    String res = WordUtils.getWord(word);  
    TextMessage message = new TextMessage();  
    message.setToUserName(map.get("FromUserName"));  
    message.setFromUserName(map.get("ToUserName"));  
    message.setCreateTime(System.currentTimeMillis());  
    message.setSsgType("text");  
    message.setContent(res);  
    // XStream将对象转换为xml字符串  
    XStream xStream = new XStream();  
    xStream.processAnnotations(TextMessage.class);  
    String xml = xStream.toXML(message);  
    return xml;  
}

得到结果:
image.png

图文消息回复

点击链接open in new window

private String getReplyNewsMessage(HashMap<String, String> map) {  
    NewsMessage newsMessage = new NewsMessage();  
    newsMessage.setToUserName(map.get("FromUserName"));  
    newsMessage.setFromUserName(map.get("ToUserName"));  
    newsMessage.setCreateTime(System.currentTimeMillis() / 1000);  
    newsMessage.setMsgType("news");  
    newsMessage.setArticleCount(1);  
    List<Article> articles = new ArrayList<>();  
    Article article = new Article();  
    article.setTitle("跟着ikun学Java,最新教程!");  
    article.setDescription("来自于Ikun学习Java,详细的Java开发教程");  
    article.setPicUrl("http://yunfei.plus/site_logo.png");  
    articles.add(article);  
    newsMessage.setArticles(articles);  
    XStream xStream = new XStream();  
    xStream.processAnnotations(NewsMessage.class);  
    return xStream.toXML(newsMessage);  
}

效果如下:
image.png

微信功能开发

获取Access_token

https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.htmlopen in new window

String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + APPID + "&secret=" + APPSECRET;  
// 发送请求  
String result = HttpUtil.get((url));  
System.out.println(result);  
return result;

报错:原因是因为接口不在白名单
image.png

配置IP白名单,把刚刚报错的IP输入进去:
image.png

拿到结果:
image.png

封装结果:

@Data  
public class AccessToken {  
    private String access_token;  
    private long expires_in;  
  
    public void setExpires_in(long expires_in) {  
        this.expires_in = System.currentTimeMillis() + expires_in * 1000;  
    }  
  
    public boolean isExpired() {  
        return System.currentTimeMillis() > expires_in;  
    }  
}

获取Token的代码,和单例模式非常像,并发问题?

public static AccessToken token = new AccessToken();  
  
public static void getAccessToken() {  
    String APPID = "xx";  
    String APPSECRET = "xx";  
    String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + APPID + "&secret=" + APPSECRET;  
    // 发送请求  
    String result = HttpUtil.get((url));  
    //{"access_token":"xxx","expires_in":7200}  
    // 解析  
    JSONObject jsonObject = JSONUtil.parseObj(result);  
    String accessToken = jsonObject.getStr("access_token");  
    long expiresIn = jsonObject.getLong("expires_in");  
    token.setAccessToken(accessToken);  
    token.setExpiresIn(expiresIn);  
}  
  
public static String getToken() {  
    if (token == null || token.isExpired()) {  
        getAccessToken();  
    }  
    return token.getAccessToken();  
}

自定义菜单

链接:https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.htmlopen in new window

没有认证弄不了

上次编辑于:
贡献者: yunfeidog