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; /** *
 * 微信消息工具类:事件通知、自动回复、
 * 
* * @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 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(eventKey); 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 "" + "" + "" + ""+new Date().getTime()+"" + "" + "1" + "" + "" + "<![CDATA["+title+"]]>" + "" + "" + "" + "" + "" + ""; } /** * 解析微信发来的请求(XML) * * @param request 请求 * @return map * @throws Exception 异常 */ public static Map xmlToMap(HttpServletRequest request) { // 将解析结果存储在HashMap中 Map 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 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 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 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 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 data) throws Exception { Set 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(); } }