目录
前言:
一、公众号中配置
1、获取AppID、AppSecret:
2、配置IP白名单:
问题:
解决:
3、配置JS接口安全域名:
重要:认真阅读系统提示的注意事项:
问题:
解决:
二、前端页面开发:
三、后端服务开发:
* 流程:
代码:
(1)主要代码:
(2)相关代码:
校验工具:
1、appId、secret校验及生成access_token工具:微信公众平台接口调试工具
2、 微信 JS 接口签名校验工具:微信 JS 接口签名校验工具
错误码:
先给大家放一个微信官方文档的链接,要知道,接入第三方,最正确的方式,就是看第三方提供的文档,根据自己的需求在文档里找相关内容已经操作步骤。
微信官方文档连接:微信官方文档 | 微信开放文档
这里给大家放的是微信的相关内容开发的总文档链接,大家根据自己需求在里面点击进入查看自己需要的文档即可。
微信功能有太多,这篇文章以接入微信扫一扫为例说明,好了,话不多说,进入正题~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ps:(1)这个AppID、AppSecret是后面后端服务开发中需要用到的
*(2)这个AppSecret最好找个地方记下来,后面接入其他服务开发时进行复用,尽量不要重置,若重置,会对之前用到这个AppSecret的服务有影响。
这里的ip应该配置部署服务器的网络出口机器的ip,这里配置后,后面才能获取到正确的access_token
调用微信的接口报错 :
{ errcode: 40164, errmsg: 'invalid ip 117.100.47.169 ipv6 ::ffff:117.100.47.169, not in whitelist hint: [39lrcA01394100]' }
这个问题就是因为这里没有加入白名单,将报错信息中的ip加入到这个白名单里即可。
(1)将MP_verify_aZa3PhSE1MBXupsK.txt这个文件下载下来,放到自己的项目路径下
(2)确保可以访问,最好自己先访问确定一下
(3)这个路径最好和自己的页面路径位于同一目录层次下,不然可能会找不到页面路径
(4)配置时,域名签名不要http://或https://,直接域名加路径即可,配置在可访问的文件的上级目录即可,例如:https://wx.qq.com/mp/MP_verify_aZa3PhSE1MBXupsK.txt可以访问到你项目下的这个文件,那么你这里要配置的应该是:wx.qq.com/mp
(5)一个月最多可以保存5次
调用微信的接口报错 : config:invalid url domain
这个错的原因就是这里配置的JS接口安全域名和当前页面的域名不一致导致的,将这里的MP_verify_aZa3PhSE1MBXupsK.txt文件和页面放在同一路径下即可。
博主是做后端开发的,前端在此不做过多阐述,其实就是后端给前端一个接口,提供以下信息,其他页面开发,放一个文档链接,大家自己查看:
微信接入前端JS-SDK说明文档:概述 | 微信开放文档
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名
jsApiList: [] // 必填,需要使用的JS接口列表
});
从上面前端所需内容可以看出,后端需要提供给前端的其实就那几个参数,jsApiList是前端的参数,无需返回,我们可以创建一个对象来返回:
@Data
public class WeChatConfigVo {
//appId
private String appId;
//时间戳
private Long timestamp;
//随机串
private String nonceStr;
//签名
private String signature;
}
这里大概说一下流程:(appId和appSecret之前页面已经拿到了)
1、拿到前端传入的当前页面url,注意这个url指的是当前页面的url,并非当前请求的url,所以应该让前端传入,而并非服务端获取
2、生成随机串、获取当前时间戳
3、根据appId和appSecret获取access_token
4、根据access_token去获取ticket
5、根据随机串、时间戳、ticket和url生成签名
6、将数据返回给前端页面
到了大家最开心的时候了,上代码:
package com.sense.cloud.activateService.service;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.sense.cloud.activateService.utils.HttpRequestUtils;
import com.sense.cloud.activateService.utils.IPUtil;
import com.sense.cloud.activateService.vo.AccessTokenVo;
import com.sense.cloud.activateService.vo.GetWechatConfigParamReq;
import com.sense.cloud.activateService.vo.WeChatConfigVo;
import com.sense.cloud.common.code.ErrorCode;
import com.sense.cloud.common.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
@Service
@Slf4j
public class WeChatConfigService {
@Value("${wechatConfig.appId}")
private String appId;
@Value("${wechatConfig.appSecret}")
private String appSecret;
// 获取ACCESS_TOKEN的url
public final static String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
// 获取ticket的url
public final static String GET_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
/**
* 获取微信公众号认证参数
* @param request
* @return
*/
public WeChatConfigVo getWechatConfigParams(GetWechatConfigParamReq req, HttpServletRequest request){
try {
if (log.isDebugEnabled()) {
log.debug("WeChatConfigService getWechatConfigParams req {}: ", req);
}
String params = request.getQueryString();
if(StringUtils.isEmpty(params)){
params="";
}else{
params="?"+params;
}
//注意这个url指的是当前页面的url,并非当前请求的url,所以应该让前端传入,而并非服务端获取
String url = req.getUrl();
log.debug("Url:{}", url);
//request.getRequestURL().toString();
String ipAddress = IPUtil.getIpAddress(request);
// 时间戳
long timestamp = (new Date().getTime()) / 1000;
// 随机串
String nonceStr = UUID.randomUUID().toString();
// 获取 access_token
AccessTokenVo accessToken = getAccessToken(appId, appSecret);
String access_token = accessToken.getToken();
log.debug("access_token:{}" ,access_token);
// 根据 access_token 获取 ticket
HashMap getTicketParams = new HashMap<>();
getTicketParams.put("access_token", access_token);
getTicketParams.put("type", "jsapi");
String response = HttpRequestUtils.sendGet(GET_TICKET_URL, getTicketParams);
if (StringUtils.isEmpty(response)) {
log.debug("获取微信公众号ticket失败, response == > {}", response);
}
JSONObject jsonObject = JSONObject.parseObject(response);
log.debug("getTicket response:{}" ,JSONObject.toJSONString(jsonObject));
String ticket = jsonObject.getString("ticket");
if(log.isDebugEnabled()) {
log.debug("要加密的参数:"+nonceStr+" "+ticket+" "+timestamp+" "+url);
}
//根据随机串、时间戳、ticket和url生成签名
String signature = getsig(nonceStr,ticket,String.valueOf(timestamp),url);
WeChatConfigVo weChatConfigVo = new WeChatConfigVo();
weChatConfigVo.setAppId(appId);
weChatConfigVo.setTimestamp(timestamp);
weChatConfigVo.setNonceStr(nonceStr);
weChatConfigVo.setSignature(signature);
log.debug(JSONObject.toJSONString(weChatConfigVo));
return weChatConfigVo;
} catch (Exception e) {
log.error("获取微信公众号认证参数失败 :", e);
throw e;
}
}
/**
* 获取access_token
*/
public static AccessTokenVo getAccessToken(String appid, String appsecret) {
AccessTokenVo accessToken = null;
Map paramMap = new HashMap<>();
paramMap.put("grant_type", "client_credential");
paramMap.put("appid", appid);
paramMap.put("secret", appsecret);
String response = HttpRequestUtils.sendGet(GET_ACCESS_TOKEN_URL, paramMap);
if (StringUtils.isEmpty(response)) {
log.debug("获取获取access_token失败");
throw new AppException(ErrorCode.TOKEN_INVALID);
}
if (log.isDebugEnabled()) {
log.debug("resp: {}", response);
}
JSONObject jsonObject = JSONObject.parseObject(response);
if (log.isDebugEnabled()) {
log.debug("respJSONObject : {}", jsonObject.toJSONString());
}
// 如果请求成功
if (null != jsonObject) {
try {
accessToken = new AccessTokenVo();
accessToken.setToken(jsonObject.getString("access_token"));
accessToken.setExpiresIn(jsonObject.getString("expires_in"));
} catch (JSONException e) {
accessToken = null;
// 获取token失败
}
}
return accessToken;
}
/**
* 拼接 signature 签名
* @param noncestr
* @param jsapi_ticket
* @param timestamp
* @param url
* @return
*/
private static String getsig(String noncestr,String jsapi_ticket,String timestamp,String url){
String[] paramArr = new String[] { "jsapi_ticket=" + jsapi_ticket,
"timestamp=" + timestamp, "noncestr=" + noncestr, "url=" + url };
Arrays.sort(paramArr);
// 将排序后的结果拼接成一个字符串
String content = paramArr[0].concat("&"+paramArr[1]).concat("&"+paramArr[2])
.concat("&"+paramArr[3]);
String gensignature = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
// 对拼接后的字符串进行 sha1 加密
System.out.println("拼接加密签名:"+content);
byte[] digest = md.digest(content.getBytes());
gensignature = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 将 sha1 加密后的字符串与 signature 进行对比
if (gensignature != null) {
return gensignature;// 返回signature
} else {
return "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;
}
}
package com.sense.cloud.activateService.controller;
import com.sense.cloud.activateService.service.WeChatConfigService;
import com.sense.cloud.activateService.vo.GetWechatConfigParamReq;
import com.sense.cloud.activateService.vo.WeChatConfigVo;
import com.sense.cloud.common.code.ErrorCode;
import com.sense.cloud.common.exception.AppException;
import com.sense.cloud.common.vo.BaseRes;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @Author: Hu Wentao
* @Date: 2021/11/1 15:55
* @Description: 微信公众号配置
*/
@Slf4j
@CrossOrigin
@RestController
public class WechatConfigController {
@Autowired
private WeChatConfigService weChatConfigService;
/**
* 获取微信公众号参数
* @param request
* @return
*/
@PostMapping("/wechat/getWechatConfigParam")
public BaseRes getWechatConfigParam(@RequestBody GetWechatConfigParamReq req, HttpServletRequest request) {
try {
if (log.isDebugEnabled()) {
log.debug("WechatConfigController getWechatConfigParam : {}", req);
}
if (req == null || StringUtils.isEmpty(req.getUrl())) {
throw new AppException(ErrorCode.ERR_PARA);
}
WeChatConfigVo configVo = weChatConfigService.getWechatConfigParams(req, request);
return BaseRes.ok(configVo);
} catch (Exception e) {
log.error("获取微信公众号参数失败 :", e);
return BaseRes.fail(ErrorCode.ERR_SYS);
}
}
}
package com.sense.cloud.activateService.vo;
import lombok.Data;
/**
* @Author: Hu Wentao
* @Date: 2021/11/2 15:58
* @Description: 获取微信公众号配置参数请求体
*/
@Data
public class GetWechatConfigParamReq {
//url
private String url;
}
这里放些从微信官方扒下来的错误码信息,方便大家查错