功能是在详情页面点击按钮,生成二维码。打开微信扫码,扫码之后手机跳转到公众号并发送一条模板消息。点击模板消息,跳转到H5的详情页面。
参考推荐:https://blog.csdn.net/weixin_42720002/category_8977300.html
官方文档:https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html
测试账号:https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
扫码登录获取测试账号的信息。
一,Controller
package com.ikeep.findaccount.web.controller.wechat;
import com.ikeep.api.Response;
import com.ikeep.findaccount.domain.wechat.WechatParam;
import com.ikeep.findaccount.service.wechat.WechatDeveloperModeService;
import com.ikeep.findaccount.service.wechat.impl.WechatDeveloperModeServiceImpl;
import com.ikeep.findaccount.util.WechatCheckoutUtil;
import com.ikeep.findaccount.web.annotation.IgnoreUserToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.ModelMap;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.Charset;
@RestController
@RequestMapping("/wechat")
@IgnoreUserToken
public class WechatBindController {
private final Logger LOGGER = LogManager.getLogger(WechatDeveloperModeServiceImpl.class);
@Autowired
private WechatDeveloperModeService wechatDeveloperModeService;
/**
* 微信验证 微信公众号修改‘接口配置信息’会请求这个接口进行验证,验证成功则配置修改成功
* @param
* @return
*/
@GetMapping("/vatToken")
public String toVatifyToken(HttpServletRequest request, HttpServletResponse response, ModelMap map) {
String signature = request.getParameter("signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机字符串
String echostr = request.getParameter("echostr");
if (signature != null && WechatCheckoutUtil.checkSignature(signature, timestamp, nonce)) {
return echostr;
}
return "";
}
/**
* 消息回复 扫码成功后会触发扫码事件,微信会请求这个接口。对请求事件处理
* @param
* @return
*/
@PostMapping("/vatToken")
public String handleMessage(HttpServletRequest request, HttpServletResponse response, ModelMap map) {
String xmlData;
try {
xmlData = StreamUtils.copyToString(request.getInputStream(), Charset.forName("utf-8"));
} catch (IOException e) {
LOGGER.error("Io系统异常:{}",e.getMessage());
return "";
}
wechatDeveloperModeService.handleMessage(xmlData);
return "";
}
/**
* 获取二维码 前端请求的接口,调微信接口返回二维码链接
* @param
* @return
*/
@PostMapping("/getQrCode")
public Response getYJQrCode(@RequestBody WechatParam wechatParam) {
return wechatDeveloperModeService.getYJQrCode(wechatParam);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
二,service
(1)微信验证工具类
package com.ikeep.findaccount.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* @author zhubin
* @version 0.1
* @Pacakage com.ikeep.findaccount.util
* @Description 微信验证
* @create 2019/11/12 10:28
**/
public class WechatCheckoutUtil {
/**
* 这里写的token要与测试公众号Token 一致
*/
private static String TOKEN = "CY90_TOKEN";
/**
* 验证签名
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) {
String[] arr = new String[]{TOKEN, timestamp, nonce};
// 将token、timestamp、nonce三个参数进行字典序排序
// Arrays.sort(arr);
sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
MessageDigest md = null;
String tmpStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
// 将三个参数字符串拼接成一个字符串进行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
content = null;
// 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
}
/**
* 将字节数组转换为十六进制字符串
* @param byteArray
* @return
*/
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}
/**
* 将字节转换为十六进制字符串
* @param mByte
* @return
*/
private static String byteToHexStr(byte mByte) {
char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
}
public static void sort(String a[]) {
for (int i = 0; i < a.length - 1; i++) {
for (int j = i + 1; j < a.length; j++) {
if (a[j].compareTo(a[i]) < 0) {
String temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
(2)请求t工具类
package com.ikeep.findaccount.util;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
/**
* @author zhubin
* @version 0.1
* @Pacakage com.ikeep.findaccount.util
* @Description
* @create 2019/11/12 12:43
**/
public class HttpClientUtil {
public static String doGet(String url, Map<String, String> param) {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 创建uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build();
// 创建http GET请求
HttpGet httpGet = new HttpGet(uri);
// 执行请求
response = httpclient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doGet(String url) {
return doGet(url, null);
}
public static String doPost(String url, Map<String, String> param) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建参数列表
if (param != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList,"utf-8");
httpPost.setEntity(entity);
}
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return resultString;
}
public static String doPost(String url) {
return doPost(url, null);
}
public static String doPostJson(String url, String json) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建请求内容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return resultString;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
(3)配置类
package com.ikeep.findaccount.service.wechat.utill;
/**
* @author zhubin
* @version 0.1
* @Pacakage com.ikeep.findaccount.service.wechat.utill
* @Description 微信的配置属性
* @create 2019/11/12 22:37
**/
public class WechatConfig {
/**
* 微信获取accessToken接口url
*/
public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appId}&secret={appSecret}";
/**
* 获取ticket
*/
public static final String GET_QRCODE_URL = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={TOKEN}";
/**
* 换取二维码
*/
public static final String QR_CODE_URL = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={TICKET}";
/**
* 推送模板消息的url
*/
public static final String TEMPLATE_URL = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={ACCESS_TOKEN}";
/**
* 创业90商家详情
*/
public static final String CY90_MERCHANT = "http://{cy90url}?merchantCode={merchantCode}&productId={productId}&merchantType={merchantType}&opendId={opendId}";
/**
* 创业90企业详情
*/
public static final String CY90_SHOP = "http://{cy90url}?shopCode={shopCode}&productId={productId}&merchantType={merchantType}&opendId={opendId}";
/**
* 模板消息key
*/
public static final String[] KEY = {"first", "keyword1", "keyword2", "keyword3","remark"};
/**
* 模板消息的值
*/
public static final String[] VALUE = {"你好,欢迎联系商家", "戊吉云-获取商家电话", "", "戊吉云电话", "点击拨打电话"};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
(4)实现类
package com.ikeep.findaccount.service.wechat.impl;
import com.alibaba.fastjson.JSONObject;
import com.ikeep.api.Response;
import com.ikeep.commons.utils.JsonUtils;
import com.ikeep.findaccount.domain.merchant.enums.MerchantTypeEnum;
import com.ikeep.findaccount.domain.wechat.TemplateParam;
import com.ikeep.findaccount.domain.wechat.WechatParam;
import com.ikeep.findaccount.domain.wechat.WechatTemplate;
import com.ikeep.findaccount.service.wechat.WechatBindService;
import com.ikeep.findaccount.service.wechat.WechatDeveloperModeService;
import com.ikeep.findaccount.service.wechat.utill.WechatConfig;
import com.ikeep.findaccount.util.HttpClientUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import redis.clients.jedis.JedisCluster;
import javax.annotation.Resource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author zhubin
* @version 0.1
* @Pacakage com.ikeep.findaccount.service.wechat.impl
* @Description 微信开发者模式
* @create 2019/11/12 10:50
**/
@Service
@Slf4j
public class WechatDeveloperModeServiceImpl implements WechatDeveloperModeService{
//测试环境和正式环境切换需要变得的配置信息写在spring属性文件里
@Value("${wechat.appId}")
private String APPID;
@Value("${wechat.appsecret}")
private String APPSECRET;
@Value("${wechat.templateId}")
private String TEMPLATE_ID;
@Value("${wechat.cy90url}")
private String CY90URL;
DateTimeFormatter DF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private final String REDIS_KEY = "CY90_accesstoken";
@Resource
private JedisCluster jedisCluster;
@Autowired
private WechatBindService wechatBindService;
/**
* 获取微信二维码 scene_str 自定义的场景参数,触发扫码事件时,会携带此参数。由于需求需要多个参数,于是通过
* 下划线拼接
* @return
*/
@Override
public Response getYJQrCode(WechatParam wechatParam) {
log.info("开始获取二维码,参数wechatParam:{}", JsonUtils.toJson(wechatParam));
String merchantCode = wechatParam.getMerchantCode();
String productId = wechatParam.getProductId();
String merchantType = wechatParam.getMerchantType();
String freeOffer = wechatParam.getFreeOffer();
String scene_str = new StringBuilder(merchantCode).append("_").append(productId).append("_").append(merchantType).toString();
if (StringUtils.isNotBlank(freeOffer)){
scene_str = new StringBuilder(scene_str).append("_").append(freeOffer).toString();
}
String accessToken = getAccessToken();
if (StringUtils.isBlank(accessToken)){
return Response.failure("获取微信accessToken失败");
}
JSONObject req=new JSONObject();
JSONObject data=new JSONObject();
JSONObject datare=new JSONObject();
req.put("action_name","QR_LIMIT_STR_SCENE");
datare.put("scene_str",scene_str);
data.put("scene",datare);
req.put("action_info",data);
req.put("expire_seconds","604800");
log.info("获取二维码入参:{}", JsonUtils.toJson(req));
String url = WechatConfig.GET_QRCODE_URL.replace("{TOKEN}", accessToken);
String resut = HttpClientUtil.doPostJson(url, JsonUtils.toJson(req));
Map<String, String> map = JsonUtils.fromJson(resut, Map.class);
String ticket = map.get("ticket");
if (StringUtils.isBlank(ticket)){
return Response.failure(map.get("errmsg"));
}
log.info("获取二维码出参:{}",JsonUtils.toJson(resut));
String ticket1 = map.get("ticket");
log.info("获取ticket出参:{}",ticket1);
String returnUrl = WechatConfig.QR_CODE_URL.replace("{TICKET}", ticket1);
log.info("returnUrl:{}",returnUrl);
return Response.success(returnUrl);
}
/**
* 事件处理
* @param xmlData
* @return
*/
@Override
public Response handleMessage(String xmlData) {
log.info("开始事件处理:{}", xmlData);
Map<String, String> mapData = convertToMap(xmlData);
// 发送方帐号(open_id)
String fromUserName = mapData.get("FromUserName");
//消息类型
String msgType = mapData.get("MsgType");
//场景
String eventKey = mapData.get("EventKey");
// 事件消息
if ("event".equals(msgType)) {
// 区分事件推送
String event = mapData.get("Event");
log.info("****区分事件推送****" + event);
// 订阅事件 或 未关注扫描二维码事件
if ("subscribe".equals(event)) {
Response subscribe = toPostTemplateMessage(fromUserName, eventKey, "subscribe");
if (!subscribe.isSuccess()){
return subscribe;
}
// 已关注扫描二维码事件
} else if ("SCAN".equals(event)) {
Response scan = toPostTemplateMessage(fromUserName, eventKey, "SCAN");
if (!scan.isSuccess()){
return scan;
}
//取关事件
}else if ("unsubscribe".equals(event)){
wechatBindService.removeBinding(fromUserName);
} else if ("LOCATION".equals(event)) {
// todo 处理上报地理位置事件
} else if ("CLICK".equals(event)) {
// todo 处理点击菜单拉取消息时的事件推送事件
} else if ("VIEW".equals(event)) {
// todo 处理点击菜单跳转链接时的事件推送
}
}
return Response.success();
}
/**
* 推送模板消息
* @param opendId
* @param eventKey
*/
private Response toPostTemplateMessage(String opendId, String eventKey, String eventType){
log.info("开始执行推送消息,opendId :{}",opendId);
String freeOfferUrl = "&freeOffer={freeOffer}";
String[] split = eventKey.split("_");
String code;
String productId;
String type;
String freeoffece = null;
//关注扫码
if ("subscribe".equals(eventType)){
code = split[1];
productId = split[2];
type = split[3];
if (split.length == 5){
freeoffece = split[4];
}
}else {
code = split[0];
productId = split[1];
type = split[2];
if (split.length == 4){
freeoffece = split[3];
}
}
String accessToken = getAccessToken();
if (StringUtils.isBlank(accessToken)){
return Response.failure("获取微信accessToken失败");
}
String templateUrl = WechatConfig.TEMPLATE_URL.replace("{ACCESS_TOKEN}", accessToken);
WechatTemplate wechatTemplate =new WechatTemplate();
Map<String, TemplateParam> map = new HashMap<>(5);
TemplateParam templateParam;
String[] key = WechatConfig.KEY;
String[] value = WechatConfig.VALUE;
for(int i =0; i < value.length; i++){
templateParam = new TemplateParam();
if (i != 2){
templateParam.setValue(value[i]);
}else {
templateParam.setValue(LocalDateTime.now(ZoneOffset.of("+8")).format(DF));
}
if(i == 4){
templateParam.setColor("#0000FF");
}
map.put(key[i], templateParam);
}
wechatTemplate.setData(map);
wechatTemplate.setTouser(opendId);
wechatTemplate.setTemplate_id(TEMPLATE_ID);
String cy90DetailUrl;
if (MerchantTypeEnum.SHOP.getCode().toString().equals(type)){
cy90DetailUrl = WechatConfig.CY90_SHOP.replace("{cy90url}", CY90URL).replace("{shopCode}", code).replace("{productId}", productId).replace("{merchantType}", type).replace("{opendId}", opendId);
}else {
cy90DetailUrl = WechatConfig.CY90_MERCHANT.replace("{cy90url}", CY90URL).replace("{merchantCode}", code).replace("{productId}", productId).replace("{merchantType}", type).replace("{opendId}", opendId);
}
if (StringUtils.isNotBlank(freeoffece)){
cy90DetailUrl = new StringBuilder(cy90DetailUrl).append(freeOfferUrl).toString().replace("{freeOffer}", freeoffece);
}
wechatTemplate.setUrl(cy90DetailUrl);
String postStr = JsonUtils.toJson(wechatTemplate);
String result = HttpClientUtil.doPostJson(templateUrl, postStr);
log.info("微信推送消息结果:{}", result);
return Response.success();
}
/**
* xml转map
* @param xml
* @return
*/
private static Map<String,String> convertToMap(String xml){
Map<String, String> map = new LinkedHashMap<>();
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
StringReader sr = new StringReader(xml);
InputSource is = new InputSource(sr);
Document document = db.parse(is);
Element root = document.getDocumentElement();
if(root != null){
NodeList childNodes = root.getChildNodes();
if(childNodes != null && childNodes.getLength()>0){
for(int i = 0;i < childNodes.getLength();i++){
Node node = childNodes.item(i);
if( node != null && node.getNodeType() == Node.ELEMENT_NODE){
map.put(node.getNodeName(), node.getTextContent());
}
}
}
}
} catch (DOMException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
/**
* 获取access_Token
*
* @return
*/
private String getAccessToken() {
log.info("开始获取access_Token");
String accessToken;
try {
accessToken = jedisCluster.get(REDIS_KEY + APPID);
if (StringUtils.isNotBlank(accessToken)){
return accessToken;
}
}catch (Exception e){
log.error(e.getMessage());
}
String url = WechatConfig.ACCESS_TOKEN_URL.replace("{appId}", APPID).replace("{appSecret}", APPSECRET);
String accessTokenJsonStr = HttpClientUtil.doGet(url);
log.info("请求微信accessToken返回值:{}", accessTokenJsonStr);
if (StringUtils.isBlank(accessTokenJsonStr)){
log.error("请求微信返回值为空");
return null;
}
Map<String, Object> accessTokenMap = JsonUtils.fromJson(accessTokenJsonStr, Map.class);
log.info("转换map值:{}", JsonUtils.toJson(accessTokenMap));
if (accessTokenMap.isEmpty()){
log.error("accessTokenMap为空");
return null;
}
accessToken = (String) accessTokenMap.get("access_token");
if (StringUtils.isEmpty(accessToken)) {
Integer errCode = null;
String errMsg = null;
try {
errCode = (Integer)accessTokenMap.get("errcode");
errMsg = (String) accessTokenMap.get("errmsg");
} catch (Exception e) {
log.error("获取微信accessToken异常:{}", JsonUtils.toJson(accessTokenMap));
}
log.error("获取accessToken失败;errCode为:{},errMsg{}", errCode , errMsg);
return null;
}
//存入缓存,官方有效期为2小时
try {
jedisCluster.setex(REDIS_KEY + APPID, 2*60*60 - 5 , accessToken);
}catch (Exception e){
log.error(e.getMessage());
}
log.info("获取access_Token成功,accessToken:", accessToken);
return accessToken;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
三,发布
发布到正式环境,需求修改spring属性文件的配置信息。修改公众号的信息时,需要先部署项目,因为修改公众号配置会调校验的接口
问题
1.关注事件,场景参数EventKey前面会拼接“qrscene”,所以取值的时候注意一下。
2.扫码之后,公众号连续推送三条重复模板消息(三次重复事件),看返回值是否正确。“”或“success”
加粗样式
文章知识点与官方知识档案匹配,可进一步学习相关知识
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_43229729/article/details/103205052