微信公众号开发学习笔记

微信公众号开发学习笔记

1、创建测试号

登录微信公众平台 https://mp.weixin.qq.com/cgi-bin/loginpage,设置与开发->开发者工具->公众平台测试账户

在这里插入图片描述
在这里插入图片描述

2、接口配置

2.1、文档内容

配置的Url可以选择服务器or内网穿透工具(https://natapp.cn/)

配置时微信会向配置的Url发送一条Get请求,会携带4个参数,分别是signature、timestamp、nonce、echostr。参考文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html

在这里插入图片描述

在这里插入图片描述

2.2、代码实现

package cn.darkiris.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Slf4j
@RestController
@RequestMapping("/api/wechat")
public class WechatController {

    private static final String token = "wxtest";

    @GetMapping("")
    public String token(@RequestParam String signature, @RequestParam String timestamp, @RequestParam String nonce, @RequestParam String echostr) throws NoSuchAlgorithmException {
        List<String> params = Arrays.asList(token, timestamp, nonce);
        Collections.sort(params);
        StringBuilder builder = new StringBuilder();
        for (String param : params) {
            builder.append(param);
        }
        MessageDigest sha1 = MessageDigest.getInstance("sha1");
        byte[] bytes = sha1.digest(builder.toString().getBytes());
        builder.setLength(0);
        for (byte b : bytes) {
            builder.append(Integer.toHexString(b >> 4 & 0x0f));
            builder.append(Integer.toHexString(b & 0x0f));
        }
        if (builder.toString().equals(signature)) {
            return echostr;
        }
        return null;
    }
}

3、消息

3.1、文档内容

当有用户向微信公众号发送消息时,微信微信会向配置的Url发送一条Post请求,消息体是xml,携带用户信息和消息信息。参考文案:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html

在这里插入图片描述

3.2、代码实现

1、消息实体类

这里因为要处理解析和生成时候的额外字符,所以有一个自定义的适配器CustomerXmlAdapter

package cn.darkiris.wechat;

import cn.darkiris.adapter.CustomerXmlAdapter;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;


@Data
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class Message {
    @XmlElement(name = "ToUserName")
    @XmlJavaTypeAdapter(CustomerXmlAdapter.class)
    private String toUserName;

    @XmlElement(name = "FromUserName")
    @XmlJavaTypeAdapter(CustomerXmlAdapter.class)
    private String fromUserName;

    @XmlElement(name = "CreateTime")
    @XmlJavaTypeAdapter(CustomerXmlAdapter.class)
    private String createTime;

    @XmlElement(name = "MsgType")
    @XmlJavaTypeAdapter(CustomerXmlAdapter.class)
    private String msgType;

    @XmlElement(name = "Content")
    @XmlJavaTypeAdapter(CustomerXmlAdapter.class)
    private String content;
}
2、CustomerXmlAdapter
package cn.darkiris.adapter;

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class CustomerXmlAdapter extends XmlAdapter<String, String> {

    @Override
    public String unmarshal(String v) {
        if (v.startsWith("<![CDATA[")) {
            return v.substring(9, v.length() - 3);
        }
        return v;
    }

    @Override
    public String marshal(String v) {
        return "<![CDATA[" + v + "]]>";
    }
}
3、xml解析工具

这里注意使用的不是jdk8自带的jaxb,是额外引入的包jakarta.xml.bind-api@3.0.1,再后面的版本就不是jdk8能用的了

package cn.darkiris.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;
import java.io.StringWriter;

@Slf4j
@Component
public class XmlUtil {

    public Object xmlToObject(String xml, Class clazz) {
        try {
            JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            return unmarshaller.unmarshal(new StringReader(xml));
        } catch (Exception e) {
            log.info("parse xml error: {}", e.getMessage());
            return null;
        }
    }

    public String objectToXml(Object object, Class clazz) {
        try {
            JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
            StringWriter stringWriter = new StringWriter();
            marshaller.marshal(object, stringWriter);
            return stringWriter.toString().replace("&lt;", "<").replace("&gt;", ">");
        } catch (Exception e) {
            log.info("generate xml error: {}", e.getMessage());
        }
        return null;
    }
}
4、SignatureUtil
package cn.darkiris.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Slf4j
@Component
public class SignatureUtil {

    private static final String token = "xxxxxxx";

    public boolean matches(String signature, String timestamp, String nonce) {
        List<String> params = Arrays.asList(token, timestamp, nonce);
        Collections.sort(params);
        StringBuilder builder = new StringBuilder();
        for (String param : params) {
            builder.append(param);
        }
        try {
            MessageDigest sha1 = MessageDigest.getInstance("sha1");
            byte[] bytes = sha1.digest(builder.toString().getBytes());
            builder.setLength(0);
            for (byte b : bytes) {
                builder.append(Integer.toHexString(b >> 4 & 0x0f));
                builder.append(Integer.toHexString(b & 0x0f));
            }
            return builder.toString().equals(signature);
        } catch (NoSuchAlgorithmException e) {
            log.info("no such algorithm: {}", e.getMessage());
        }
        return false;
    }
}
5、WechatController

回复消息统一设置成hello,用户名!

package cn.darkiris.controller;


import cn.darkiris.util.XmlUtil;
import cn.darkiris.wechat.Message;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Slf4j
@RestController
@RequestMapping("/api/wechat")
public class WechatController {
    @Autowired
    private SignatureUtil signatureUtil;

    @Autowired
    private XmlUtil xmlUtil;

    @GetMapping("")
    public String token(@RequestParam String signature, @RequestParam String timestamp, @RequestParam String nonce, @RequestParam String echostr) {
        if (signatureUtil.matches(signature, timestamp, nonce)) {
            return echostr;
        }
        return null;
    }

    @PostMapping("")
    public String message(@RequestParam String signature, @RequestParam String timestamp, @RequestParam String nonce, @RequestBody String xml) {
        System.out.println("=============message=============");
        System.out.println(xml);
        System.out.println("signature: " + signature + ", timestamp: " + timestamp + ", nonce: " + nonce);
        if (!signatureUtil.matches(signature, timestamp, nonce)) {
            return "";
        }
        Message message = (Message) xmlUtil.xmlToObject(xml, Message.class);
        if (message == null) {
            return "";
        }
        message.setOpenid(message.getFromUserName());
        Message reply = new Message(message.getFromUserName(), message.getToUserName(), String.valueOf(System.currentTimeMillis() / 1000), "text", "hello " + message.getFromUserName() + "!");
        return xmlUtil.objectToXml(reply, Message.class);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

眼眸流转

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值