微信小程序消息推送
·
微信小程序消息推送
一、介绍
启用并设置消息推送配置后,用户发给小程序的消息以及开发者需要的事件推送,都将被微信转发至该服务器地址中。
开发者也可使用云函数或微信云托管免鉴权接收消息推送
原本項目需要消息推送,刚写完直接不用了(好消息,省了后面的对接了)
小程序消息推送: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();
}
}
}
更多推荐



所有评论(0)