业务实践系列(9):微信公众号相关开发

最近对接了个微信公众号,access_token,open_id,带参二维码,事件推送,自定义菜单。做个记录。

微信官方文档 | 公众号:开发文档

网页授权

通过code换取网页授权access_token

获取access_token

此 access_token 不是网页授权的 access_token,是访问微信接口必传的 token。

带参二维码

公众号带参二维码

Controller 接口:

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
/**
* @desc: 微信公众号授权
*/
@RestController
@RequestMapping("/wx")
public class WxAuthController {
private static final Logger logger = LogManager.getLogger(WxAuthController.class);

@Autowired
private WxAuthService wxAuthService;
@Autowired
private AuthProperties authProperties;

/**
* @desc: code换取网关授权acces_token,这里返回openId
* @param: [wxWebAuth]
*/
@PostMapping("/openId")
public ResponseModel<?> openId(@RequestBody @Validated WxWebAuth wxWebAuth) {
if (!wxWebAuth.getState().equals(authProperties.getWxState())) {
return ResponseModel.fail("state 值错误");
}
WxAccessToken wxAccessToken = wxAuthService.getWxOpenId(wxWebAuth);
return ResponseModel.success(new WxOpenId(wxAccessToken.getOpenid()));
}

/**
* @desc: 获取微信 accessToken
*/
/*@PostMapping("/accessToken")
public ResponseModel<?> accessToken(String state) {
if (!state.equals(authProperties.getWxState())) {
return ResponseModel.fail("state 值错误");
}
return ResponseModel.success(wxAuthService.getWxAccessToken());
}*/

/**
* @desc: 带参的公众号二维码
*/
@PostMapping("/qrcode/user")
public ResponseModel<WxQrCodeUrl> getUserWxQrCode(@RequestBody @Validated IdVO idVO) {
WxQrCodeUrl wxQrCodeUrl = wxAuthService.getUserWxQrCode(idVO.getId());
return ResponseModel.success(wxQrCodeUrl);
}
}

Service 实现:

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
/**
* @desc: 微信授权
*/
@Service
public class WxAuthServiceImpl implements WxAuthService {
private static final Logger logger = LogManager.getLogger(WxAuthServiceImpl.class);

// 微信用户open_id的接口域名
private static final String WX_WEB_AUTH_DOMAIN = "https://api.weixin.qq.com/sns/oauth2/access_token";
// 微信公众号接口access_token域名
private static final String WX_ACCESS_TOKEN_DOMAIN = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
// 微信获取二维码ticket域名
private static final String WX_QRCODE_TICKET_DOMAIN = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=";

@Autowired
private AuthProperties authProperties;
@Autowired
private RestTemplate restTemplate;


/**
* @desc: code换取网关授权acces_token,这里返回openId
* @param: [wxWebAuth]
*/
@Override
public WxAccessToken getWxOpenId(WxWebAuth wxWebAuth) {
String wxWebAuthUrl = this.getWxWebAuthUrl(wxWebAuth);
ResponseEntity<String> entity = restTemplate.getForEntity(wxWebAuthUrl, String.class, (Object) null);
WxAccessToken wxAccessToken = JSON.parseObject(entity.getBody(), WxAccessToken.class);
logger.info("----->微信公众号网页授权响应:{}", JSON.toJSONString(wxAccessToken));
if (StringUtils.isNotBlank(wxAccessToken.getOpenid())) {
return wxAccessToken;
} else {
logger.error("----->获取access_token异常:{}", wxWebAuthUrl);
throw new BusinessException("获取access_token异常");
}
}

/**
* @desc: 获取微信 accessToken
*/
@Override
public WxAccessToken getWxAccessToken() {
String wxAccessTokenUrl = getWxAccessTokenUrl();
ResponseEntity<String> entity = restTemplate.getForEntity(wxAccessTokenUrl, String.class, (Object) null);
String body = entity.getBody();
WxAccessToken wxAccessToken = JSON.parseObject(body, WxAccessToken.class);
logger.info("----->获取微信accessToken:{}", JSON.toJSONString(wxAccessToken));
if (StringUtils.isNotBlank(wxAccessToken.getAccessToken())) {
return wxAccessToken;
} else {
logger.error("----->获取access_token异常:{}", wxAccessTokenUrl);
throw new BusinessException("获取access_token异常");
}
}

/**
* @desc: 获取带参公众号二维码
* @param: [id]
*/
@Override
public WxQrCodeUrl getUserWxQrCode(Long id) {
String ticketUrl = WX_QRCODE_TICKET_DOMAIN + this.getWxAccessToken().getAccessToken();
WxTicketScene wxTicketScene = new WxTicketScene(id);
WxTicketActionInfo wxTicketActionInfo = new WxTicketActionInfo(wxTicketScene);
WxTicketRequest wxTicketRequest = new WxTicketRequest(wxTicketActionInfo);

HttpHeaders headers = new HttpHeaders();
MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
headers.setContentType(type);
ObjectMapper mapper = new ObjectMapper();
try {
String requestParams = mapper.writeValueAsString(wxTicketRequest);
HttpEntity<String> formEntity = new HttpEntity<>(requestParams, headers);
ResponseEntity<String> entity = restTemplate.postForEntity(ticketUrl, formEntity, String.class);
WxQrCodeUrl wxQrCodeUrl = JSON.parseObject(entity.getBody(), WxQrCodeUrl.class);
logger.info("----->获取公众号二维码,响应:{}", JSON.toJSONString(wxQrCodeUrl));
if (StringUtils.isBlank(wxQrCodeUrl.getUrl())) {
logger.error("----->获取公众号二维码,响应异常:{}", JSON.toJSONString(wxQrCodeUrl));
throw new BusinessException("获取公众号二维码失败,请重试");
}
return wxQrCodeUrl;
} catch (JsonProcessingException e) {
e.printStackTrace();
logger.error("----->获取公众号二维码请求参数序列化错误:{}", JSON.toJSONString(wxTicketRequest));
throw new BusinessException("请求参数序列化错误");
}
}

/**
* @desc: 拼接访问URL
*/
public String getWxWebAuthUrl(WxWebAuth wxWebAuth) {
StringBuilder sb = new StringBuilder(WX_WEB_AUTH_DOMAIN);
sb.append("?appid=")
.append(authProperties.getWxAppid())
.append("&secret=")
.append(authProperties.getWxSecret())
.append("&code=")
.append(wxWebAuth.getCode())
.append("&grant_type=authorization_code");
return sb.toString();
}

/**
* @desc: 获取微信公众号接口access_token域名
*/
public String getWxAccessTokenUrl() {
StringBuilder sb = new StringBuilder(WX_ACCESS_TOKEN_DOMAIN);
sb.append("&appid=")
.append(authProperties.getWxAppid())
.append("&secret=")
.append(authProperties.getWxSecret());
return sb.toString();
}
}

推送消息

下面示例包括未关注扫码推送消息已关注扫码推送消息,取消关注推送消息 的处理。

Controller 接口:

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
/**
* @desc: 微信消息
* @author: gxing
* @date: 2020/9/14
*/
@RestController
@RequestMapping("/wx")
public class WxMsgController {
private static final Logger logger = LogManager.getLogger(WxMsgController.class);

@Autowired
private WxMsgService wxMsgService;
@Autowired
private AuthProperties authProperties;

/**
* 微信消息接收和token验证
*
* @param request
* @param response
* @throws IOException
*/
@GetMapping(value = "/msg/invoke")
public String checkToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 微信加密签名
String signature = request.getParameter("signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机字符串
String echostr = request.getParameter("echostr");
// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
String token = authProperties.getWxVerifyToken();
String[] strArray = {token, timestamp, nonce};
// 1.排序
Arrays.sort(strArray);
// 2.sha1加密
String mySignature = Hex.encodeHexString(DigestUtils.digest(DigestUtils.getSha1Digest(),
(StringUtil.join(strArray, StringUtils.EMPTY)).getBytes(Charsets.UTF_8)));
// 3.字符串校验
if (mySignature.equals(signature)) {
// 如果检验成功原样返回echostr,微信服务器接收到此输出,才会确认检验完成。
return echostr;
} else {
return StringUtil.EMPTY;
}
}

/**
* 接收微信推送的消息
*
* @param request
* @param response
* @throws IOException
*/
@PostMapping(value = "/msg/invoke")
public String pushMsg(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String msg = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
logger.info("----->接收到微信公众号推送的消息:{}", msg);
this.wxMsgService.wxMsgPush(msg);
return "success";
}
}

Service 实现:

下面示例包括未关注扫码推送消息,已关注扫码推送消息,取消关注推送消息

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
/**
* @desc: 微信消息服务
*/
@Service
public class WxMsgServiceImpl implements WxMsgService {
private static final Logger logger = LogManager.getLogger(WxMsgController.class);

// 事件KEY值,qrscene_为前缀,后面为二维码的参数值
private static final String EVENT_PRE = "qrscene_";

@Autowired
private ManagerService managerService;


/**
* @desc: 微信消息推送
* @param: [msg]
*/
@Override
public ResponseModel<?> wxMsgPush(String msg) {
Map<String, String> eventMap = null;
try {
eventMap = WXUtil.xmlToMap(msg);
} catch (Exception e) {
logger.error("----->微信推送消息转Map错误:{}", msg);
return ResponseModel.fail();
}

String msgType = eventMap.get("MsgType");
if (msgType.equals(WxMsgTypeEnums.EVENT.getType())) {
logger.info("----->消息类型:{}", msgType);
String event = eventMap.get("Event");
logger.info("----->事件类型:{}", event);
if (StringUtils.equalsAny(event, WxEventEnums.SUBSCRIBE.getEvent(), WxEventEnums.SCAN.getEvent())) {
// 关注处理
return this.subscribe(eventMap, event);
} else if (event.equals(WxEventEnums.UNSUBSCRIBE.getEvent())) {
// 取消关注处理
return this.unsubscribe(eventMap);
} else {
logger.warn("----->微信推送事件类型不是关注或取消关注类型");
return ResponseModel.fail();
}
} else {
return ResponseModel.fail();
}
}

/**
* @desc: 关注公众号事件
* @param: [eventMap]
*/
private ResponseModel<?> subscribe(Map<String, String> eventMap, String event) {
String eventKey = eventMap.get("EventKey");
Long userId = null;

if (event.equals(WxEventEnums.SUBSCRIBE.getEvent())) {
logger.info("----->员工未关注公众号,扫码关注绑定");
userId = Long.valueOf(eventKey.split("_")[1]);
}
if (event.equals(WxEventEnums.SCAN.getEvent())) {
logger.info("----->员工已关注公众号,扫码关注绑定");
userId = Long.valueOf(eventKey);
}

String openId = eventMap.get("FromUserName");
ResponseModel<?> responseModel = managerService.userSubscribe(new BzStaffOpenIdVO(userId, openId));
logger.info("----->员工绑定公众号:{}", JSON.toJSONString(responseModel));
return responseModel;

}

/**
* @desc: 取消关注公众号事件
* @param: [eventMap]
*/
private ResponseModel<?> unsubscribe(Map<String, String> eventMap) {
String openId = eventMap.get("FromUserName");
ResponseModel<?> responseModel = managerService.userUnsubscribe(new OpenIdVO(openId));
logger.info("----->员工取消关注公众号解绑:{}", JSON.toJSONString(responseModel));
if (responseModel.isSuccess()) {
logger.info("----->员工取消关注公众号解绑成功 openId:{}", openId);
} else {
logger.info("----->员工取消关注公众号解绑失败 openId:{}", openId);
}
return responseModel;
}
}

创建自定义菜单

要接收微信推送的消息,就必须在微信公众号平台 开启 服务器配置,此情况下就不能使用微信的自定义菜单,必须走开发模式调微信接口来创建菜单。

创建菜单接口

Controller 接口:

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
@RestController
@RequestMapping("/wx")
public class WxMenuController {

@Autowired
private WxMenuService wxMenuService;
@Autowired
private AuthProperties authProperties;

/**
* @desc: 创建菜单
* @param: [token]
*/
@PostMapping("/createMenu")
public ResponseModel<?> createMenu(String state) {
if (StringUtils.isBlank(state) || !state.equals(authProperties.getWxState())) {
return ResponseModel.fail("Hello World");
}
boolean createFlag = false;
try {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource("classpath:/wxmenus/baseMenu.json");
InputStream is = resource.getInputStream();
String menuJson = IOUtils.toString(is, Charsets.UTF_8);
menuJson = menuJson.replace("APPID", authProperties.getWxAppid())
.replace("REDIRECT_URI", authProperties.getWxRedirectUri())
.replace("STATE", authProperties.getWxState());
createFlag = wxMenuService.createDiyMenu(menuJson);
} catch (IOException e) {
e.printStackTrace();
throw new BusinessException("----->读取自定义菜单json异常");
}
if (createFlag) {
return ResponseModel.success();
}
return ResponseModel.fail("创建自定义菜单失败");
}
}

Service 实现:

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
public class WxMenuServiceImpl implements WxMenuService {
private static final Logger logger = LogManager.getLogger(WxMsgServiceImpl.class);

/**
* 创建自定义菜单URL
*/
private static final String createDiyMenuUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";

@Autowired
private WxAuthService wxAuthService;
@Autowired
private RestTemplate restTemplate;

/**
* @desc: 创建菜单
* @param: [menuJson]
*/
@Override
public boolean createDiyMenu(String menuJson) {
WxAccessToken wxAccessToken = wxAuthService.getWxAccessToken();
String url = createDiyMenuUrl.replace("ACCESS_TOKEN", wxAccessToken.getAccessToken());
HttpHeaders headers = new HttpHeaders();
MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
headers.setContentType(type);
logger.info("----->创建自定义菜单json:{}", menuJson);
HttpEntity<String> formEntity = new HttpEntity<>(menuJson, headers);
ResponseEntity<String> entity = restTemplate.postForEntity(url, formEntity, String.class);
String body = entity.getBody();
logger.info("----->创建自定义菜返回结果:{}", body);
JSONObject jsonObject = JSON.parseObject(body);
Integer errcode = (Integer) jsonObject.get("errcode");
if (errcode == 0) {
return true;
}
return false;
}
}

菜单json文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"button": [
{
"name": "便利保障",
"sub_button": [
{
"type": "view",
"name": "我的订单",
"url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect"
}
]
}
]
}

相关示例

自定义菜单

菜单JSON

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
{
"button": [
{
"name": "心理测评",
"sub_button": [
{
"type": "view",
"name": "开始测评",
"url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${frontBaseUrl}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect"
},
{
"type": "view",
"name": "我的测评",
"url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${frontBaseUrl}/myEvaluationList&response_type=code&scope=snsapi_userinfo&state=state#wechat_redirect"
}
]
},
{
"name": "我的",
"sub_button": [
{
"type": "view",
"name": "个人信息",
"url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${frontBaseUrl}/userPersonalInfo&response_type=code&scope=snsapi_userinfo&state=state#wechat_redirect"
},
{
"type": "view",
"name": "我的咨询师",
"url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${frontBaseUrl}/counselorList&response_type=code&scope=snsapi_userinfo&state=state#wechat_redirect"
},
{
"type": "view",
"name": "注册咨询师",
"url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${frontBaseUrl}/regEntry&response_type=code&scope=snsapi_userinfo&state=state#wechat_redirect"
},
{
"type": "view",
"name": "邀请加入",
"url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${frontBaseUrl}/teamQRCode?type=1&response_type=code&scope=snsapi_userinfo&state=state#wechat_redirect"
},
{
"type": "view",
"name": "关于",
"url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${frontBaseUrl}/about&response_type=code&scope=snsapi_userinfo&state=state#wechat_redirect"
}
]
}
]
}

Controller 接口:

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
/**
* 微信自定义菜单,个性化菜单Controller
*/
@ApiIgnore
@RestController
@RequestMapping("/wechat/menu")
public class WechatMenuController {

@Autowired
private WechatMenuService wechatMenuService;

@Autowired
private WechatTagService wechatTagService;

@Autowired
private WechatProperties wechatProperties;

/**
* 创建基础(普通用户)菜单
*
* @return
* @throws IOException
*/
@PostMapping("/create-base")
public ResponseModel<?> createBaseMenu(String pwd) throws IOException {
if(!StringUtil.equals(pwd, "Clear1234")) {
return ResponseModel.fail("黄河之水天上来");
}
boolean createFlag = false;
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource("classpath:/vmenus/baseMenu.json");
try (InputStream is = resource.getInputStream()) {
String menuJson = IOUtils.toString(is, Charsets.UTF_8);
menuJson = menuJson.replace("${appid}", wechatProperties.getMp().getAppId()).replace("${frontBaseUrl}",
wechatProperties.getMp().getFrontBaseUrl());
createFlag = wechatMenuService.createDiyMenu(menuJson);
}
return ResponseModel.success(createFlag);
}

/**
* 创建咨询师菜单
*
* @return
* @throws IOException
*/
@PostMapping("/create-counselor")
public ResponseModel<?> createCounselorMenu(String pwd) throws IOException {
if(!StringUtil.equals(pwd, "Clear1234")) {
return ResponseModel.fail("奔流到海不复回");
}
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource("classpath:/vmenus/counselorMenu.json");
String menuJson = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8);
Integer tagId = wechatTagService.getCounselorTag().getTagId();
menuJson = menuJson.replace("${appid}", wechatProperties.getMp().getAppId())
.replace("${frontBaseUrl}", wechatProperties.getMp().getFrontBaseUrl())
.replace("${tagId}", String.valueOf(tagId));
String menuId = wechatMenuService.createIndividuationMenu(menuJson);
Map<String, String> resultMap = new HashMap<>();
resultMap.put("menuId", menuId);
return ResponseModel.success(resultMap);
}
}

Service 接口:

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
/**
* 微信公众平台回调业务分发业务及相关业务实现
*/
@Slf4j
@Service
public class WechatMpInvokeServiceImpl implements WechatMpInvokeService {

@Autowired
private WechatProperties wechatProperties;

@Override
public String validateSign(HttpServletRequest request) {
// 微信加密签名
String signature = HttpUtil.getRequestParameter(request, "signature");
// 时间戳
String timestamp = HttpUtil.getRequestParameter(request, "timestamp");
// 随机数
String nonce = HttpUtil.getRequestParameter(request, "nonce");
// 随机字符串
String echostr = HttpUtil.getRequestParameter(request, "echostr");
if (log.isInfoEnabled()) {
log.info("微信-开始校验签名,收到来自微信的echostr字符串:{}", echostr);
}
// 1.排序
String token = wechatProperties.getMp().getServerVerifyToken();
String[] strArray = { token, timestamp, nonce };
Arrays.sort(strArray);
// 2.sha1加密
String mySignature = Hex.encodeHexString(DigestUtils.digest(DigestUtils.getSha1Digest(),
(StringUtil.join(strArray, StringUtil.EMPTY)).getBytes(Charsets.UTF_8)));
// 3.字符串校验
if (StringUtil.equalsIgnoreCase(mySignature, signature)) {
// 如果检验成功原样返回echostr,微信服务器接收到此输出,才会确认检验完成。
if (log.isInfoEnabled()) {
log.info("微信-签名校验通过,回复给微信的echostr字符串:{}", echostr);
}
return echostr;
} else {
log.error("微信-签名校验失败");
return StringUtil.EMPTY;
}
}

@Override
public String porcessInvokeMessage(String content) {
String responseContent = null;
List<WechatMpInvokeMessageProcessor> processors = WechatMpInvokeMessageProcessorFactory.getProcessors(content);
if (BeanUtil.isNotEmpty(processors)) {
WechatInvokeMessage wechatInvokeMessage = WechatMpInvokeMessageProcessorFactory.getWechatInvokeMessage();
for (WechatMpInvokeMessageProcessor processor : processors) {
String result = processor.process(wechatInvokeMessage);
System.out.println("--------------------------------------\n" + result);
if (StringUtil.isNotEmpty(result)) {
responseContent = result;
}
}
}
WechatMpInvokeMessageProcessorFactory.removeWechatInvokeMessage();
return responseContent;
}
}

微信消息推送

Controller 接口:

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
/**
* 微信公众平台相关Controller
*/
@Slf4j
@ApiIgnore
@RestController
@RequestMapping("/wechat")
public class WechatMpInvokeController {

@Autowired
private WechatMpInvokeService wechatMpInvokeService;

/**
* 微信公众平台开发-基本配置-服务器配置-服务器地址启用校验
*
* @param request
* @param response
* @return
*/
@GetMapping("/invoke")
public String invokeValidate(HttpServletRequest request, HttpServletResponse response) {
return wechatMpInvokeService.validateSign(request);
}

/**
* 接收/处理微信服务器消息
*
* <pre>
* {@code
* 1、用户发送的文本、图片、语音、视频等消息:
* <xml>
* <ToUserName><![CDATA[gh_511a0c58efbd]]></ToUserName>
* <FromUserName><![CDATA[oVH4bweVY1Sgq2zvKuyWBPV3pQaI]]></FromUserName>
* <CreateTime>1564817259</CreateTime>
* <MsgType><![CDATA[text]]></MsgType>
* <Content><![CDATA[?]]></Content>
* <MsgId>22402794709385993</MsgId>
* </xml>
*
* 2、扫码关注公众号通知消息:
* <xml>
* <ToUserName><![CDATA[gh_511a0c58efbd]]></ToUserName>
* <FromUserName><![CDATA[oVH4bwUa8FZeXpXRI2mj_3cDMf00]]></FromUserName>
* <CreateTime>1564817112</CreateTime>
* <MsgType><![CDATA[event]]></MsgType>
* <Event><![CDATA[subscribe]]></Event>
* <EventKey><![CDATA[]]></EventKey>
* </xml>
* }
* </pre>
*
* @param request
* @param response
* @return
* @see https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140453
*/
@PostMapping("/invoke")
public String invoke(HttpServletRequest request, HttpServletResponse response) {
try (ServletInputStream inputStream = request.getInputStream()) {
String content = IOUtils.toString(inputStream, Charsets.UTF_8);
if (log.isInfoEnabled()) {
log.info("接收到微信公众平台消息:\n{}", content);
}
String responseContent = wechatMpInvokeService.porcessInvokeMessage(content);
if (StringUtil.isNotEmpty(responseContent)) {
return responseContent;
}
} catch (Exception ex) {
log.error("接收/处理微信公众平台服务器消息时发生异常", ex);
}
return "success";
}
}

Service 实现:

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
/**
* 微信公众平台回调业务分发业务及相关业务实现
*/
@Slf4j
@Service
public class WechatMpInvokeServiceImpl implements WechatMpInvokeService {

@Autowired
private WechatProperties wechatProperties;

@Override
public String validateSign(HttpServletRequest request) {
// 微信加密签名
String signature = HttpUtil.getRequestParameter(request, "signature");
// 时间戳
String timestamp = HttpUtil.getRequestParameter(request, "timestamp");
// 随机数
String nonce = HttpUtil.getRequestParameter(request, "nonce");
// 随机字符串
String echostr = HttpUtil.getRequestParameter(request, "echostr");
if (log.isInfoEnabled()) {
log.info("微信-开始校验签名,收到来自微信的echostr字符串:{}", echostr);
}
// 1.排序
String token = wechatProperties.getMp().getServerVerifyToken();
String[] strArray = { token, timestamp, nonce };
Arrays.sort(strArray);
// 2.sha1加密
String mySignature = Hex.encodeHexString(DigestUtils.digest(DigestUtils.getSha1Digest(),
(StringUtil.join(strArray, StringUtil.EMPTY)).getBytes(Charsets.UTF_8)));
// 3.字符串校验
if (StringUtil.equalsIgnoreCase(mySignature, signature)) {
// 如果检验成功原样返回echostr,微信服务器接收到此输出,才会确认检验完成。
if (log.isInfoEnabled()) {
log.info("微信-签名校验通过,回复给微信的echostr字符串:{}", echostr);
}
return echostr;
} else {
log.error("微信-签名校验失败");
return StringUtil.EMPTY;
}
}

@Override
public String porcessInvokeMessage(String content) {
String responseContent = null;
List<WechatMpInvokeMessageProcessor> processors = WechatMpInvokeMessageProcessorFactory.getProcessors(content);
if (BeanUtil.isNotEmpty(processors)) {
WechatInvokeMessage wechatInvokeMessage = WechatMpInvokeMessageProcessorFactory.getWechatInvokeMessage();
for (WechatMpInvokeMessageProcessor processor : processors) {
String result = processor.process(wechatInvokeMessage);
System.out.println("--------------------------------------\n" + result);
if (StringUtil.isNotEmpty(result)) {
responseContent = result;
}
}
}
WechatMpInvokeMessageProcessorFactory.removeWechatInvokeMessage();
return responseContent;
}
}

相关参考

  1. 微信公众号开发(三)自定义菜单
  2. 微信公众号开发:自定义菜单

业务实践系列(9):微信公众号相关开发

http://blog.gxitsky.com/2020/09/15/Business-09-wx-public-app/

作者

光星

发布于

2020-09-15

更新于

2023-03-07

许可协议

评论