微信公众号接入开发

目录

前言:

一、公众号中配置

1、获取AppID、AppSecret:

 2、配置IP白名单:

问题:

解决:

3、配置JS接口安全域名:

重要:认真阅读系统提示的注意事项:

问题:

解决:

二、前端页面开发:

 三、后端服务开发:

* 流程:

代码:

(1)主要代码:

(2)相关代码:

校验工具:

1、appId、secret校验及生成access_token工具:微信公众平台接口调试工具

2、 微信 JS 接口签名校验工具:微信 JS 接口签名校验工具

错误码:


前言:

先给大家放一个微信官方文档的链接,要知道,接入第三方,最正确的方式,就是看第三方提供的文档,根据自己的需求在文档里找相关内容已经操作步骤。

微信官方文档连接:微信官方文档 | 微信开放文档

这里给大家放的是微信的相关内容开发的总文档链接,大家根据自己需求在里面点击进入查看自己需要的文档即可。

微信功能有太多,这篇文章以接入微信扫一扫为例说明,好了,话不多说,进入正题~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

一、公众号中配置

1、获取AppID、AppSecret:

ps:(1)这个AppID、AppSecret是后面后端服务开发中需要用到的

      *(2)这个AppSecret最好找个地方记下来,后面接入其他服务开发时进行复用,尽量不要重置,若重置,会对之前用到这个AppSecret的服务有影响。

微信公众号接入开发_第1张图片

 2、配置IP白名单:

这里的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加入到这个白名单里即可。

微信公众号接入开发_第2张图片

3、配置JS接口安全域名:

微信公众号接入开发_第3张图片

重要:认真阅读系统提示的注意事项:

        (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次

微信公众号接入开发_第4张图片

问题:

        调用微信的接口报错 : 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、将数据返回给前端页面

代码:

到了大家最开心的时候了,上代码:

(1)主要代码:

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;
    }
}

(2)相关代码:

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;
}

校验工具:

1、appId、secret校验及生成access_token工具:微信公众平台接口调试工具

2、 微信 JS 接口签名校验工具:微信 JS 接口签名校验工具

错误码:

这里放些从微信官方扒下来的错误码信息,方便大家查错

微信公众号接入开发_第5张图片

微信公众号接入开发_第6张图片

你可能感兴趣的:(Utils,第三方接入,Java学习,微信)