一、介绍

启用并设置消息推送配置后,用户发给小程序的消息以及开发者需要的事件推送,都将被微信转发至该服务器地址中。
开发者也可使用云函数或微信云托管免鉴权接收消息推送
在这里插入图片描述

原本項目需要消息推送,刚写完直接不用了(好消息,省了后面的对接了)
在这里插入图片描述

小程序消息推送:https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html
小程序URL配置验证:https://developers.weixin.qq.com/apiExplorer?type=messagePush

二、代码

这个需要放开接口,如果有token限制记得去SecurityConfig放开

1、controller

// 明文验证
@GetMapping("/wxapp/wxVerify")
public String wxVerify(
        @RequestParam("signature")String signature,
        @RequestParam("timestamp")String timestamp,
        @RequestParam("nonce")String nonce,
        @RequestParam("echostr")String echostr) throws Exception {
    return warningPlanService.wxVerify(signature,timestamp,nonce,echostr);
}

// 明文验证
@PostMapping("/wxapp/wxReceive")
public Map<String, Object> wxReceive(
        @RequestParam("msg_signature")String msg_signature,
        @RequestParam("timestamp")String timestamp,
        @RequestParam("nonce")String nonce,
        @RequestParam("encrypt_type")String encrypt_type,
        @RequestBody String jsonBody) throws Exception {
    return warningPlanService.wxReceive(msg_signature,timestamp,nonce,encrypt_type,jsonBody);
}

2、server

在这里插入图片描述

public String wxVerify(String signature, String timestamp, String nonce, String echostr) throws Exception;
public Map<String, Object> wxReceive(String msg_signature, String timestamp, String nonce, String encrypt_type, String jsonBody) throws Exception;
/*********************************************订阅提醒*********************************************/
@Override
public String wxVerify(String signature, String timestamp, String nonce, String echostr) throws Exception {
		// 替换为自己项目定义的
    String token = "AAAAA";
    String[] arr = {token, timestamp, nonce};
    Arrays.sort(arr);

    String signStr = String.join("", arr);
    // SHA1 加密
    String calcSign = WeChatUtil.getSHA1(signStr);

    if (calcSign.equals(signature)) {
        return echostr;
    } else {
        throw new ServiceException("验签失败");
    }
}

@Override
public Map<String, Object> wxReceive(String msg_signature, String timestamp, String nonce, String encrypt_type, String jsonBody) throws Exception {
		// 替换为自己项目定义的
    String token = "AAAAA";
    // 可以在 小程序URL配置验证网址--请求构造--安全模式 进行生成
    String encodingAESKey = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";

    // 解析加密内容
    ObjectMapper objectMapper = new ObjectMapper();
    JsonNode root = objectMapper.readTree(jsonBody);
    String encrypt = root.get("Encrypt").asText();

    String[] arr = {token, timestamp, nonce, encrypt};
    Arrays.sort(arr);
    String signStr = String.join("", arr);
    // SHA1 加密
    String calcSign = WeChatUtil.getSHA1(signStr);

    if (!calcSign.equals(msg_signature)) {
        throw new RuntimeException("验签失败");
    }
    // AES 解密
    String plainJson = WeChatUtil.decrypt(encrypt, encodingAESKey);

//        JSONObject json = JSONObject.parseObject(plainJson);
//        String toUserName = json.getString("ToUserName");
//        String fromUserName = json.getString("FromUserName");
//        long createTime = json.getLong("CreateTime");
//        String msgType = json.getString("MsgType");
//        String event = json.getString("Event");
//        String debugStr = json.getString("debug_str");

    // 构造加密回包(你要求的 {"demo_resp":"good luck"})
    String respMsg = "{\"demo_resp\":\"测试通过\"}";
    String resEncrypt = WeChatUtil.encrypt(respMsg, encodingAESKey);
    String resTimeStamp = String.valueOf(System.currentTimeMillis() / 1000);

    // 回包签名
    String[] respArr = {token, resTimeStamp, nonce, resEncrypt};
    Arrays.sort(respArr);
    String resMsgSignature = WeChatUtil.getSHA1(String.join("", respArr));

    // 返回加密回包
    Map<String, Object> result = new HashMap<>();
    result.put("Encrypt", resEncrypt);
    result.put("MsgSignature", resMsgSignature);
    result.put("TimeStamp", resTimeStamp);
    result.put("Nonce", nonce);
    return result;
}

3、工具类

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;

public class WeChatUtil {

    // 编码格式
    public static final Charset CHARSET = StandardCharsets.UTF_8;
    // AES加密块大小
    public static final int BLOCK_SIZE = 32;
    // AES算法模式(CBC/PKCS7Padding)
    public static final String AES_ALGORITHM = "AES/CBC/NoPadding";
    public static final String APP_ID = "替换为自己的apid";

    /********************************************* 加解密。。。推送 *********************************************/

    // SHA1
    public static String getSHA1(String str) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        byte[] digest = md.digest(str.getBytes());
        StringBuilder sb = new StringBuilder();
        for (byte b : digest) {
            String hex = Integer.toHexString(b & 0xFF);
            if (hex.length() == 1) sb.append('0');
            sb.append(hex);
        }
        return sb.toString();
    }

    // 对密文进行解密
    public static String decrypt(String encryptBase64,String EncodingAESKey) throws Exception {
        // 密钥处理
        byte[] aesKey = Base64.getDecoder().decode(EncodingAESKey + "=");


        // 设置解密模式为AES的CBC模式
        Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
        SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES");
        IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
        cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);

        // 解密
        byte[] encryptData = Base64.getDecoder().decode(encryptBase64);
        byte[] original = cipher.doFinal(encryptData);

        String xmlContent, from_appid;

        // 去除补位字符
        byte[] bytes = PKCS7Encoder_decode(original);

        // 分离16位随机字符串,网络字节序和AppId
        byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);

        int xmlLength = recoverNetworkBytesOrder(networkOrder);

        xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
        from_appid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET);

        // appid不相同的情况
        if (!from_appid.equals(APP_ID)) {
            throw new RuntimeException("AppID 校验失败");
        }
        return xmlContent;
    }

    // 对明文进行加密
    public static String encrypt(String msg, String EncodingAESKey) throws Exception {
        // 密钥处理
        byte[] aesKey = Base64.getDecoder().decode(EncodingAESKey + "=");

        ByteGroup byteCollector = new ByteGroup();

        String randomStr = getRandomStr();
        byte[] randomStrBytes = randomStr.getBytes(CHARSET);
        byte[] textBytes = msg.getBytes(CHARSET);
        byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length);
        byte[] appidBytes = APP_ID.getBytes(CHARSET);

        // randomStr + networkBytesOrder + text + appid
        byteCollector.addBytes(randomStrBytes);
        byteCollector.addBytes(networkBytesOrder);
        byteCollector.addBytes(textBytes);
        byteCollector.addBytes(appidBytes);


        // ... + pad: 使用自定义的填充方式对明文进行补位填充
        byte[] padBytes = PKCS7Encoder_encode(byteCollector.size());
        byteCollector.addBytes(padBytes);

        // 获得最终的字节流, 未加密
        byte[] unencrypted = byteCollector.toBytes();
        // 设置加密模式为AES的CBC模式
        Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
        SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
        IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);

        // 加密
        byte[] encrypted = cipher.doFinal(unencrypted);

        // 使用BASE64对加密后的字符串进行编码
        String base64Encrypted = Base64.getEncoder().encodeToString(encrypted);

        return base64Encrypted;
    }

    // 生成4个字节的网络字节序
    public static byte[] getNetworkBytesOrder(int sourceNumber) {
        byte[] orderBytes = new byte[4];
        orderBytes[3] = (byte) (sourceNumber & 0xFF);
        orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF);
        orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF);
        orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF);
        return orderBytes;
    }

    // 还原4个字节的网络字节序
    public static int recoverNetworkBytesOrder(byte[] orderBytes) {
        int sourceNumber = 0;
        for (int i = 0; i < 4; i++) {
            sourceNumber <<= 8;
            sourceNumber |= orderBytes[i] & 0xff;
        }
        return sourceNumber;
    }

    // 随机生成16位字符串
    public static String getRandomStr() {
        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 16; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    /********************************************* PKCS7Encoder *********************************************/
    /**
     * 获得对明文进行补位填充的字节.
     *
     * @param count 需要进行填充补位操作的明文字节个数
     * @return 补齐用的字节数组
     */
    public static byte[] PKCS7Encoder_encode(int count) {
        // 计算需要填充的位数
        int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
        if (amountToPad == 0) {
            amountToPad = BLOCK_SIZE;
        }
        // 获得补位所用的字符
        char padChr = PKCS7Encoder_chr(amountToPad);
        String tmp = new String();
        for (int index = 0; index < amountToPad; index++) {
            tmp += padChr;
        }
        return tmp.getBytes(CHARSET);
    }

    /**
     * 删除解密后明文的补位字符
     *
     * @param decrypted 解密后的明文
     * @return 删除补位字符后的明文
     */
    public static byte[] PKCS7Encoder_decode(byte[] decrypted) {
        int pad = (int) decrypted[decrypted.length - 1];
        if (pad < 1 || pad > 32) {
            pad = 0;
        }
        return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
    }

    /**
     * 将数字转化成ASCII码对应的字符,用于对明文进行补码
     *
     * @param a 需要转化的数字
     * @return 转化得到的字符
     */
    public static char PKCS7Encoder_chr(int a) {
        byte target = (byte) (a & 0xFF);
        return (char) target;
    }
    /********************************************* PKCS7Encoder *********************************************/

    public static class ByteGroup {
        ArrayList<Byte> byteContainer = new ArrayList<Byte>();

        public byte[] toBytes() {
            byte[] bytes = new byte[byteContainer.size()];
            for (int i = 0; i < byteContainer.size(); i++) {
                bytes[i] = byteContainer.get(i);
            }
            return bytes;
        }

        public ByteGroup addBytes(byte[] bytes) {
            for (byte b : bytes) {
                byteContainer.add(b);
            }
            return this;
        }

        public int size() {
            return byteContainer.size();
        }
    }
}
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐