package cc.mrbird.febs.pay.util;
|
|
import cc.mrbird.febs.common.utils.AppContants;
|
import cc.mrbird.febs.common.utils.LoginUserUtil;
|
import cc.mrbird.febs.common.utils.MallUtils;
|
import cc.mrbird.febs.common.utils.RedisUtils;
|
import cc.mrbird.febs.mall.entity.MallMember;
|
import cc.mrbird.febs.mall.mapper.MallMemberMapper;
|
import cc.mrbird.febs.rabbit.producter.AgentProducer;
|
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.text.StrFormatter;
|
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.http.HttpRequest;
|
import cn.hutool.http.HttpResponse;
|
import cn.hutool.json.JSONUtil;
|
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSONObject;
|
import org.dom4j.io.SAXReader;
|
|
import javax.servlet.http.HttpServletRequest;
|
import java.io.*;
|
import java.security.MessageDigest;
|
import java.security.NoSuchAlgorithmException;
|
import java.util.Arrays;
|
import java.util.HashMap;
|
import org.dom4j.Document;
|
import org.dom4j.Element;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.client.RestTemplate;
|
|
import java.util.*;
|
import java.util.concurrent.TimeUnit;
|
|
/**
|
* <pre>
|
* 微信消息工具类:事件通知、自动回复、
|
* </pre>
|
*
|
* @author shenshao
|
*/
|
@Service(value="wechatEventUtil")
|
public class WechatEventUtil {
|
|
|
@Autowired
|
private AgentProducer agentProducer;
|
|
|
@Autowired
|
RestTemplate restTemplate;
|
|
|
@Autowired
|
MallMemberMapper mallMemberMapper;
|
|
private static Logger log = LoggerFactory.getLogger(WechatEventUtil.class);
|
|
/**
|
* 公众号《事件通知》服务器校验
|
*
|
* @param req 请求
|
* @return 校验结果
|
*/
|
public String verification(HttpServletRequest req, String wechatToken) {
|
// 接收微信服务器发送请求时传递过来的参数
|
String signature = req.getParameter("signature");
|
String timestamp = req.getParameter("timestamp");
|
String nonce = req.getParameter("nonce");
|
|
// 将token、timestamp、nonce三个参数进行字典序排序,并拼接为一个字符串
|
String sortStr = sort(wechatToken, timestamp, nonce);
|
|
// 字符串进行shal加密
|
String mySignature = shal(sortStr);
|
// 校验微信服务器传递过来的签名 和 加密后的字符串是否一致, 若一致则签名通过
|
if (!"".equals(signature) && !"".equals(mySignature) && signature.equals(mySignature)) {
|
log.info("-----签名校验通过-----");
|
return req.getParameter("echostr");
|
} else {
|
log.error("-----校验签名失败-----");
|
return "";
|
}
|
}
|
|
/**
|
* 消息事件监控:关注、取消关注等事件
|
* @param req
|
* @return
|
*/
|
public String messageEvent(HttpServletRequest req) {
|
String result = null;
|
Map<String, String> map = xmlToMap(req);
|
String fromUserName = map.get("FromUserName");
|
String toUserName = map.get("ToUserName");
|
String msgType = map.get("MsgType");
|
String content = map.get("Content");
|
String eventType = map.get("Event");
|
log.info("事件处理:event:{}、msgType:{}、toUserName:{}、fromUserName:{}", eventType, msgType, toUserName, fromUserName);
|
if ("event".equals(msgType)) {
|
if ("subscribe".equals(eventType)) {
|
result = imgTextMsg(toUserName, fromUserName, "欢迎光临", "欢迎光临【药王谷铺子】,点击左下角【药王谷铺子】-【商城】,进入商城。", "https://excoin.oss-cn-hangzhou.aliyuncs.com/uploadeFile/1690447963307584cd27ac623427f8c07ed1d3bbe4279.png", "http://ywgouth.meiao.biz/");
|
log.info("新增关注事件:toUserName{}、fromUserName{}", toUserName, fromUserName);
|
StringBuffer stringBuffer = new StringBuffer();
|
if(map.containsKey("EventKey") && ObjectUtil.isNotEmpty(map.get("EventKey"))){
|
/**
|
* 扫带参数二维码
|
*/
|
String eventKey = map.get("EventKey");
|
if(StrUtil.isNotEmpty(eventKey)){
|
|
String[] eventKeyStr = StrUtil.split(eventKey, "_");
|
stringBuffer.append(eventKeyStr[1]);
|
stringBuffer.append("@");
|
stringBuffer.append(fromUserName);
|
agentProducer.sendMemberSubScanMsg(stringBuffer.toString());
|
}
|
}else{
|
/**
|
* 普通关注事件
|
*/
|
agentProducer.sendMemberSubMsg(fromUserName);
|
}
|
} else if ("unsubscribe".equals(eventType)) {
|
log.info("取消关注事件:toUserName{}、fromUserName{}", toUserName, fromUserName);
|
//取消关注则退出商城
|
MallMember mallMember = mallMemberMapper.selectMemberByOpenId(fromUserName);
|
mallMember.setSubStatus(MallMember.SUB_STATUS_DISABLED);
|
mallMemberMapper.updateById(mallMember);
|
if(ObjectUtil.isNotEmpty(mallMember)){
|
String redisKey = AppContants.XCX_LOGIN_PREFIX + mallMember.getId();
|
String existToken = redisUtils.getString(redisKey);
|
if (StrUtil.isNotBlank(existToken)) {
|
Object o = redisUtils.get(existToken);
|
if (ObjectUtil.isNotEmpty(o)) {
|
redisUtils.del(existToken);
|
}
|
}
|
redisUtils.del(existToken);
|
redisUtils.del(AppContants.XCX_LOGIN_PREFIX + mallMember.getId());
|
}
|
}else if ("SCAN".equals(eventType)) {
|
log.info("扫码关注事件:toUserName{}、fromUserName{}", toUserName, fromUserName);
|
if(map.containsKey("EventKey")){
|
StringBuffer stringBuffer = new StringBuffer();
|
/**
|
* 扫带参数二维码
|
*/
|
String eventKey = map.get("EventKey");
|
if(StrUtil.isNotEmpty(eventKey)){
|
String[] eventKeyStr = StrUtil.split(eventKey, "_");
|
stringBuffer.append(eventKeyStr[1]);
|
stringBuffer.append("@");
|
stringBuffer.append(fromUserName);
|
agentProducer.sendMemberSubScanMsg(stringBuffer.toString());
|
}
|
}
|
}else if ("VIEW".equals(eventType)) {
|
/**
|
* 普通关注事件
|
*/
|
agentProducer.sendMemberSubMsg(fromUserName);
|
}
|
}
|
return result;
|
}
|
|
/**
|
* 组装图文消息(懒得封装对象转XML方法,直接字符串拼装,大伙勿喷)
|
* @param toUserName 开发者微信号
|
* @param fromUserName 接收人openId
|
* @param title 图文消息上的备注
|
* @param description 图文消息上的备注
|
* @param picUrl 图文消息上的图片
|
* @param url 点击图文消息跳转的路径
|
* @return
|
*/
|
public static String imgTextMsg(String toUserName, String fromUserName, String title, String description, String picUrl, String url){
|
return "<xml>"
|
+ "<ToUserName><![CDATA["+fromUserName+"]]></ToUserName>"
|
+ "<FromUserName><![CDATA["+toUserName+"]]></FromUserName>"
|
+ "<CreateTime>"+new Date().getTime()+"</CreateTime>"
|
+ "<MsgType><![CDATA[news]]></MsgType>"
|
+ "<ArticleCount>1</ArticleCount>"
|
+ "<Articles>"
|
+ "<item>"
|
+ "<Title><![CDATA["+title+"]]></Title>"
|
+ "<Description><![CDATA["+description+"]]></Description>"
|
+ "<PicUrl><![CDATA["+picUrl+"]]></PicUrl>"
|
+ "<Url><![CDATA["+url+"]]></Url>"
|
+ "</item>"
|
+ "</Articles>"
|
+ "</xml>";
|
}
|
|
/**
|
* 解析微信发来的请求(XML)
|
*
|
* @param request 请求
|
* @return map
|
* @throws Exception 异常
|
*/
|
public static Map<String, String> xmlToMap(HttpServletRequest request) {
|
// 将解析结果存储在HashMap中
|
Map<String, String> map = new HashMap<>(16);
|
// 从request中取得输入流
|
try (InputStream inputStream = request.getInputStream()) {
|
System.out.println("获取输入流");
|
// 读取输入流
|
SAXReader reader = new SAXReader();
|
Document document = reader.read(inputStream);
|
// 得到xml根元素
|
Element root = document.getRootElement();
|
// 得到根元素的所有子节点
|
List<Element> elementList = root.elements();
|
|
// 遍历所有子节点
|
for (Element e : elementList) {
|
System.out.println(e.getName() + " | " + e.getText());
|
map.put(e.getName(), e.getText());
|
}
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
return map;
|
}
|
|
/**
|
* 参数排序
|
*
|
* @param token 服务器配置,自定义的Token
|
* @param timestamp timestamp
|
* @param nonce nonce
|
* @return 排序后拼接字符串
|
*/
|
public static String sort(String token, String timestamp, String nonce) {
|
String[] strArray = {token, timestamp, nonce};
|
Arrays.sort(strArray);
|
StringBuilder sb = new StringBuilder();
|
for (String str : strArray) {
|
sb.append(str);
|
}
|
return sb.toString();
|
}
|
|
/**
|
* 字符串进行shal加密
|
*
|
* @param str 字符串
|
* @return 密文
|
*/
|
public static String shal(String str) {
|
try {
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
digest.update(str.getBytes());
|
byte messageDigest[] = digest.digest();
|
|
StringBuilder hexString = new StringBuilder();
|
// 字节数组转换为 十六进制 数
|
for (int i = 0; i < messageDigest.length; i++) {
|
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
|
if (shaHex.length() < 2) {
|
hexString.append(0);
|
}
|
hexString.append(shaHex);
|
}
|
return hexString.toString();
|
|
} catch (NoSuchAlgorithmException e) {
|
e.printStackTrace();
|
}
|
return "";
|
}
|
|
|
@Autowired
|
RedisUtils redisUtils;
|
|
/**
|
* 获取ticket
|
*
|
* @param qrCodeParam
|
* @return
|
*/
|
public String getTicket(String qrCodeParam) {
|
// RestTemplate restTemplate = new RestTemplate();
|
String ticket = null;
|
// 拼接请求地址
|
String CREATE_QRCODE_URL = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN";
|
// String requestUrl = CREATE_QRCODE_URL.replace("TOKEN", "70_ECdQaX-QtNXZP9ugU_JEJGRYEADWrlQZNYrT2I2IRHsx6hx4_O8RY4VasWI97_ixia8vANTdnNRI_cT00toK7CX98513sQI8535eTFw3b-VQChEFZrRqTfNPdSoGYXgAIAVNM");
|
String requestUrl = CREATE_QRCODE_URL.replace("TOKEN", redisUtils.get(WechatConfigure.WX_ACCESS_TOKEN_REDIS_KEY).toString());
|
// 创建临时带参二维码
|
|
JSONObject jsonObject = restTemplate.postForObject(requestUrl,
|
"{\"expire_seconds\": 604800, \"action_name\": \"QR_LIMIT_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": "+qrCodeParam+"}}}",
|
JSONObject.class);
|
// JSONObject jsonObject = HttpsUtil.request(requestUrl, "POST", "{\"expire_seconds\": 604800, \"action_name\": \"QR_SCENE\", \"action_info\": {\"scene\": {\"scene_id\": 123}}}");
|
if (ObjectUtil.isNotEmpty(jsonObject)) {
|
String response = JSON.toJSONString(jsonObject);
|
if (!jsonObject.containsKey("errcode")) {
|
ticket = jsonObject.getString("ticket");
|
System.out.println("临时带参二维码ticket成功" + response);
|
} else {
|
log.error("临时带参二维码ticket失败:" + response);
|
}
|
}
|
return ticket;
|
}
|
|
/**
|
* 通过ticket换取二维码并保存二维码图片
|
*
|
* @param ticket
|
* @param savePath
|
* @return
|
*/
|
//图片上传路径
|
public static final String IMG_UPLOAD_PATH="/home/javaweb/webresource/ywg/h5/wxcode";
|
public String getQRcode(String ticket) {
|
|
String SHOW_QRCODE_URL = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET";
|
String requestUrl = SHOW_QRCODE_URL.replace("TICKET", ticket);
|
|
String randomNum = MallUtils.getRandomNum(5);
|
String imgName="/user_" + randomNum + "_acode_1.jpg";
|
String urlPrefix="https://ywgouth.meiao.biz/wxcode";
|
String imgPath=IMG_UPLOAD_PATH+imgName;
|
if(!FileUtil.exist(imgPath)){
|
try {
|
HttpResponse execute = HttpRequest.get(requestUrl).execute();
|
InputStream inputStream = execute.bodyStream();
|
File file = new File(imgPath);
|
FileUtil.writeFromStream(inputStream, file);
|
long uploadUrl = FileUtil.size(file);
|
//小于10kb重新生成
|
if(uploadUrl<=10240){
|
log.error("生成微信小程序码失败:图片大小异常:{}",uploadUrl);
|
return null;
|
}
|
} catch (Exception e) {
|
log.error("生成微信小程序码失败",e);
|
return null;
|
}
|
}else{
|
|
//判断文件是否正常 不正常 删除
|
File file = new File(imgPath);
|
long uploadUrl = FileUtil.size(file);
|
if(uploadUrl<=10240){
|
FileUtil.del(file);
|
return null;
|
}
|
}
|
System.out.println("根据ticket换取二维码成功,路径:" + imgPath);
|
return urlPrefix+"/"+imgName;
|
|
}
|
//
|
// String timestamp = String.format("%010d", System.currentTimeMillis() / 1000);
|
//
|
public static String createSignature(String nocestr, String ticket, String timestamp, String url) {
|
// 这里参数的顺序要按照 key 值 ASCII 码升序排序
|
String s = "jsapi_ticket=" + ticket + "&noncestr=" + nocestr
|
+ "×tamp=" + timestamp + "&url=" + url;
|
return SHA1(s);
|
}
|
//
|
public static String SHA1(String decript) {
|
try {
|
MessageDigest digest = java.security.MessageDigest.getInstance("SHA-1");
|
digest.update(decript.getBytes());
|
byte messageDigest[] = digest.digest();
|
// Create Hex String
|
StringBuffer hexString = new StringBuffer();
|
// 字节数组转换为 十六进制 数
|
for (int i = 0; i < messageDigest.length; i++) {
|
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
|
if (shaHex.length() < 2) {
|
hexString.append(0);
|
}
|
hexString.append(shaHex);
|
}
|
return hexString.toString();
|
|
} catch (NoSuchAlgorithmException e) {
|
e.printStackTrace();
|
}
|
return "";
|
}
|
|
public static void main(String[] args) {
|
}
|
|
//代码示例
|
public Map<String, Object> ticket(String url) {
|
String ticket = null;
|
//从redis内获取 如果空说明第一次获取或已过期
|
if(ObjectUtil.isEmpty(redisUtils.get("ticket"))){
|
ticket = getTicket();
|
}
|
//生成10位时间戳
|
String timestamp = String.format("%010d", System.currentTimeMillis() / 1000);
|
String nonceStr = createNonceStr();
|
String signature = createSignature(nonceStr, ticket, timestamp, url);
|
|
Map<String, Object> map = new HashMap<>(16);
|
map.put("timestamp", timestamp);
|
map.put("nonceStr", nonceStr);
|
map.put("signature", signature);
|
return map;
|
}
|
|
|
|
private static final String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
public static String createNonceStr() {
|
String nonceStr = "";
|
for (int i = 0; i < 16; i++) {
|
int beginIndex = (int) Math.round(Math.random() * 10);
|
nonceStr += str.substring(beginIndex, beginIndex + 1);
|
}
|
return nonceStr;
|
}
|
|
|
/**
|
*
|
*/
|
private String getTicket() {
|
//从redis 获取access_token
|
String accessToken = redisUtils.get(WechatConfigure.WX_ACCESS_TOKEN_REDIS_KEY).toString();
|
if (StrUtil.isBlank(accessToken)) {
|
//获取access_token 自行实现
|
return null;
|
}
|
//请求接口
|
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
|
Map<String, String> param = new HashMap<>(16);
|
param.put("access_token", accessToken);
|
param.put("type", "jsapi");
|
String getAccessTokenUrl = url
|
+ "?access_token=" + accessToken
|
+ "&type=jsapi";
|
JSONObject jsonObject = restTemplate.getForObject(getAccessTokenUrl, JSONObject.class);
|
|
int errcode = jsonObject.getInteger("errcode");
|
String errmsg = jsonObject.getString("errmsg");
|
if (errcode == 0 && "ok".equals(errmsg)) {
|
String ticket = jsonObject.getString("ticket");
|
Integer expiresIn = jsonObject.getInteger("expires_in");
|
redisUtils.set("ticket", ticket, expiresIn);
|
return ticket;
|
}
|
return null;
|
}
|
|
|
|
/**
|
*SHA1签名算法
|
*/
|
// public static String SHA1(String decript) {
|
// try {
|
// MessageDigest digest = java.security.MessageDigest.getInstance("SHA-1");
|
// digest.update(decript.getBytes());
|
// byte messageDigest[] = digest.digest();
|
// // Create Hex String
|
// StringBuffer hexString = new StringBuffer();
|
// // 字节数组转换为 十六进制 数
|
// for (int i = 0; i < messageDigest.length; i++) {
|
// String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
|
// if (shaHex.length() < 2) {
|
// hexString.append(0);
|
// }
|
// hexString.append(shaHex);
|
// }
|
// return hexString.toString();
|
//
|
// } catch (NoSuchAlgorithmException e) {
|
// e.printStackTrace();
|
// }
|
// return "";
|
// }
|
|
|
/**
|
* 生成签名
|
*
|
* @param data 待签名数据
|
* @return 签名
|
*/
|
public static String generateSignature(final Map<String, String> data) throws Exception {
|
Set<String> keySet = data.keySet();
|
String[] keyArray = keySet.toArray(new String[keySet.size()]);
|
Arrays.sort(keyArray);
|
StringBuilder sb = new StringBuilder();
|
for (String k : keyArray) {
|
String value = data.get(k).toString();
|
if (k.equals("sign")) {
|
continue;
|
}
|
if (value.trim().length() > 0) // 参数值为空,则不参与签名
|
{
|
sb.append(k).append("=").append(value.trim()).append("&");
|
}
|
}
|
return sb.toString();
|
}
|
|
}
|