微信公共号开发教程java版——微信网页授权(八)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wangyuanjun008/article/details/79233288

一:微信网页授权介绍

官网详细介绍:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140839
如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。

关于网页授权回调域名的说明

1、在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;

2、授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.htmlhttp://www.qq.com/login.html 都可以进行OAuth2.0鉴权。但http://pay.qq.comhttp://music.qq.comhttp://qq.com无法进行OAuth2.0鉴权

3、如果公众号登录授权给了第三方开发者来进行管理,则不必做任何设置,由第三方代替公众号实现网页授权即可

关于网页授权的两种scope的区别说明

1、以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)

2、以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。

3、用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注后事件推送后,才能根据用户OpenID来获取用户基本信息。这个接口,包括其他微信接口,都是需要该用户(即openid)关注了公众号后,才能调用成功的。

关于网页授权access_token和普通access_token的区别

1、微信网页授权是通过OAuth2.0机制实现的,在用户授权给公众号后,公众号可以获取到一个网页授权特有的接口调用凭证(网页授权access_token),通过网页授权access_token可以进行授权后接口调用,如获取用户基本信息;

2、其他微信接口,需要通过基础支持中的“获取access_token”接口来获取到的普通access_token调用。

关于UnionID机制

1、请注意,网页授权获取用户基本信息也遵循UnionID机制。即如果开发者有在多个公众号,或在公众号、移动应用之间统一用户帐号的需求,需要前往微信开放平台(open.weixin.qq.com)绑定公众号后,才可利用UnionID机制来满足上述需求。

2、UnionID机制的作用说明:如果开发者拥有多个移动应用、网站应用和公众帐号,可通过获取用户基本信息中的unionid来区分用户的唯一性,因为同一用户,对同一个微信开放平台下的不同应用(移动应用、网站应用和公众帐号),unionid是相同的。

关于特殊场景下的静默授权

1、上面已经提到,对于以snsapi_base为scope的网页授权,就静默授权的,用户无感知;

2、对于已关注公众号的用户,如果用户从公众号的会话或者自定义菜单进入本公众号的网页授权页,即使是scope为snsapi_userinfo,也是静默授权,用户无感知。

具体而言,网页授权流程分为四步:

1、引导用户进入授权页面同意授权,获取code

2、通过code换取网页授权access_token(与基础支持中的access_token不同)

3、如果需要,开发者可以刷新网页授权access_token,避免过期

4、通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)

二:网页授权的实现

1.用户同意授权,获取code

在确保微信公众账号拥有授权作用域(scope参数)的权限的前提下(服务号获得高级接口后,默认拥有scope参数中的snsapi_base和snsapi_userinfo),引导关注者打开如下页面:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect 若提示“该链接无法访问”,请检查参数是否填写错误,是否拥有scope参数对应的授权作用域权限。

尤其注意:由于授权操作安全等级较高,所以在发起授权请求时,微信会对授权链接做正则强匹配校验,如果链接的参数顺序不对,授权页面将无法正常访问

参考链接(请在微信客户端中打开此链接体验):
scope为snsapi_base
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx520c15f417810387&redirect_uri=https%3A%2F%2Fchong.qq.com%2Fphp%2Findex.php%3Fd%3D%26c%3DwxAdapter%26m%3DmobileDeal%26showwxpaytitle%3D1%26vb2ctag%3D4_2030_5_1194_60&response_type=code&scope=snsapi_base&state=123#wechat_redirect
scope为snsapi_userinfo
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

尤其注意:跳转回调redirect_uri,应当使用https链接来确保授权code的安全性。

参数说明

参数 是否必须 说明
appid 公众号的唯一标识
redirect_uri 授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理
response_type 返回类型,请填写code
scope 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )
state 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
wechat_redirect 无论直接打开还是做页面302重定向时候,必须带此参数

下图为scope等于snsapi_userinfo时的授权页面:
这里写图片描述

用户同意授权后
如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。

code说明 : code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。

错误返回码说明如下:

返回码 说明
10003 redirect_uri域名与后台配置不一致
10004 此公众号被封禁
10005 此公众号并没有这些scope的权限
10006 必须关注此测试号
10009 操作太频繁了,请稍后重试
10010 scope不能为空
10011 redirect_uri不能为空
10012 appid不能为空
10013 state不能为空
10015 公众号未授权第三方平台,请检查授权状态
10016 不支持微信开放平台的Appid,请使用公众号Appid

2. 通过网页授权获取的用户信息

用户信息类:SNSUserInfo类

package com.wyj.wechart.pojo;

import java.util.List;

/**
 * 通过网页授权获取的用户信息
 * 
 * 
 * @author:WangYuanJun
 * @date:2018年1月24日 下午3:09:02
 */
public class SNSUserInfo {

    // 用户标识
    private String openId;

    // 用户昵称
    private String nickname;

    // 性别(1是男性,2是女性,0是未知)
    private int sex;

    // 国家
    private String country;

    // 省份
    private String province;

    // 城市
    private String city;

    // 用户头像链接
    private String headImgUrl;

    // 用户特权信息
    private List<String> privilegeList;

    public String getOpenId() {
        return openId;
    }

    public void setOpenId(String openId) {
        this.openId = openId;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getHeadImgUrl() {
        return headImgUrl;
    }

    public void setHeadImgUrl(String headImgUrl) {
        this.headImgUrl = headImgUrl;
    }

    public List<String> getPrivilegeList() {
        return privilegeList;
    }

    public void setPrivilegeList(List<String> privilegeList) {
        this.privilegeList = privilegeList;
    }

}

3.凭证实体类

package com.wyj.wechart.pojo;

/**
 * 
 * 凭证
 * 
 * @author:WangYuanJun
 * @date:2018年1月23日 下午3:19:14
 */
public class Token {

    // 接口访问凭证
    private String accessToken;

    // 凭证有效期,单位:秒
    private int expiresIn;

    public String getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public int getExpiresIn() {
        return expiresIn;
    }

    public void setExpiresIn(int expiresIn) {
        this.expiresIn = expiresIn;
    }
}

4.网页授权信息 WeixinOauth2Token类

package com.wyj.wechart.pojo;

/**
 * 网页授权信息
 * 
 * 
 * @author:WangYuanJun
 * @date:2018年1月24日 下午3:10:03
 */
public class WeixinOauth2Token {

    // 网页授权接口调用凭证
    private String accessToken;

    // 凭证有效时长
    private int expiresIn;

    // 用于刷新凭证
    private String refreshToken;

    // 用户标识
    private String openId;

    // 用户授权作用域
    private String scope;

    public String getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public int getExpiresIn() {
        return expiresIn;
    }

    public void setExpiresIn(int expiresIn) {
        this.expiresIn = expiresIn;
    }

    public String getRefreshToken() {
        return refreshToken;
    }

    public void setRefreshToken(String refreshToken) {
        this.refreshToken = refreshToken;
    }

    public String getOpenId() {
        return openId;
    }

    public void setOpenId(String openId) {
        this.openId = openId;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

}

5.微信用户的基本信息WeixinUserInfo类

package com.wyj.wechart.pojo;

/**
 * 微信用户的基本信息
 * 
 * 
 * @author:WangYuanJun
 * @date:2018年1月24日 上午10:05:42
 */
public class WeixinUserInfo {

    // 用户的标识
    private String openId;

    // 关注状态(1是关注,0是未关注),未关注时获取不到其余信息
    private int subscribe;

    // 用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间
    private String subscribeTime;

    // 昵称
    private String nickname;

    // 用户的性别(1是男性,2是女性,0是未知)
    private int sex;

    // 用户所在国家
    private String country;

    // 用户所在省份
    private String province;

    // 用户所在城市
    private String city;

    // 用户的语言,简体中文为zh_CN
    private String language;

    // 用户头像
    private String headImgUrl;

    public String getOpenId() {
        return openId;
    }

    public void setOpenId(String openId) {
        this.openId = openId;
    }

    public int getSubscribe() {
        return subscribe;
    }

    public void setSubscribe(int subscribe) {
        this.subscribe = subscribe;
    }

    public String getSubscribeTime() {
        return subscribeTime;
    }

    public void setSubscribeTime(String subscribeTime) {
        this.subscribeTime = subscribeTime;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getLanguage() {
        return language;
    }

    public void setLanguage(String language) {
        this.language = language;
    }

    public String getHeadImgUrl() {
        return headImgUrl;
    }

    public void setHeadImgUrl(String headImgUrl) {
        this.headImgUrl = headImgUrl;
    }
}

6.获取网页授权凭证及获取用户信息

package com.wyj.wechart.utils;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.wyj.wechart.pojo.SNSUserInfo;
import com.wyj.wechart.pojo.WeixinOauth2Token;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
/**
 * 
 * 
 * 
 * @author:WangYuanJun
 * @date:2018年1月24日 下午3:19:44
 */
public class AdvancedUtil {

    private static Logger log = LoggerFactory.getLogger(AdvancedUtil.class);

    /**
     * 获取网页授权凭证
     * 
     * @param appId
     *            公众账号的唯一标识
     * @param appSecret
     *            公众账号的密钥
     * @param code
     * @return WeixinAouth2Token
     */
    public static WeixinOauth2Token getOauth2AccessToken(String appId, String appSecret, String code) {
        WeixinOauth2Token wat = null;
        // 拼接请求地址
        String requestUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
        requestUrl = requestUrl.replace("APPID", appId);
        requestUrl = requestUrl.replace("SECRET", appSecret);
        requestUrl = requestUrl.replace("CODE", code);
        // 获取网页授权凭证
        JSONObject jsonObject = CommonUtil.httpsRequest(requestUrl, "GET", null);
        if (null != jsonObject) {
            try {
                wat = new WeixinOauth2Token();
                wat.setAccessToken(jsonObject.getString("access_token"));
                wat.setExpiresIn(jsonObject.getInt("expires_in"));
                wat.setRefreshToken(jsonObject.getString("refresh_token"));
                wat.setOpenId(jsonObject.getString("openid"));
                wat.setScope(jsonObject.getString("scope"));
            } catch (Exception e) {
                wat = null;
                int errorCode = jsonObject.getInt("errcode");
                String errorMsg = jsonObject.getString("errmsg");
                log.error("获取网页授权凭证失败 errcode:{} errmsg:{}", errorCode, errorMsg);
            }
        }
        return wat;
    }

    /**
     * 通过网页授权获取用户信息
     * 
     * @param accessToken
     *            网页授权接口调用凭证
     * @param openId
     *            用户标识
     * @return SNSUserInfo
     */
    @SuppressWarnings({ "deprecation", "unchecked" })
    public static SNSUserInfo getSNSUserInfo(String accessToken, String openId) {
        SNSUserInfo snsUserInfo = null;
        // 拼接请求地址
        String requestUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID";
        requestUrl = requestUrl.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId);
        // 通过网页授权获取用户信息
        JSONObject jsonObject = CommonUtil.httpsRequest(requestUrl, "GET", null);

        if (null != jsonObject) {
            try {
                snsUserInfo = new SNSUserInfo();
                // 用户的标识
                snsUserInfo.setOpenId(jsonObject.getString("openid"));
                // 昵称
                snsUserInfo.setNickname(jsonObject.getString("nickname"));
                // 性别(1是男性,2是女性,0是未知)
                snsUserInfo.setSex(jsonObject.getInt("sex"));
                // 用户所在国家
                snsUserInfo.setCountry(jsonObject.getString("country"));
                // 用户所在省份
                snsUserInfo.setProvince(jsonObject.getString("province"));
                // 用户所在城市
                snsUserInfo.setCity(jsonObject.getString("city"));
                // 用户头像
                snsUserInfo.setHeadImgUrl(jsonObject.getString("headimgurl"));
                // 用户特权信息
                snsUserInfo.setPrivilegeList(JSONArray.toList(jsonObject.getJSONArray("privilege"), List.class));
            } catch (Exception e) {
                snsUserInfo = null;
                int errorCode = jsonObject.getInt("errcode");
                String errorMsg = jsonObject.getString("errmsg");
                log.error("获取用户信息失败 errcode:{} errmsg:{}", errorCode, errorMsg);
            }
        }
        return snsUserInfo;
    }

}

7.封装https请求类 CommonUtil 类

https请求的工具

package com.wyj.wechart.utils;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.X509TrustManager;

/**
 * 证书信任管理器(用于https请求)
 * 这个证书管理器的作用就是让它信任我们指定的证书,下面的代码意味着信任所有证书,不管是否权威机构颁发。
 * 
 * @author:WangYuanJun
 * @date:2018年1月23日 下午3:22:19
 */
public class MyX509TrustManager implements X509TrustManager {

    // 检查客户端证书
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }

    // 检查服务器端证书
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }

    // 返回受信任的X509证书数组
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
}
    /**
     * 发送https请求
     * 
     * @param requestUrl
     *            请求地址
     * @param requestMethod
     *            请求方式(GET、POST)
     * @param outputStr
     *            提交的数据
     * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
     */
    public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) {
        JSONObject jsonObject = null;
        try {
            // 创建SSLContext对象,并使用我们指定的信任管理器初始化
            TrustManager[] tm = { new MyX509TrustManager() };
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new java.security.SecureRandom());
            // 从上述SSLContext对象中得到SSLSocketFactory对象
            SSLSocketFactory ssf = sslContext.getSocketFactory();

            URL url = new URL(requestUrl);
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            conn.setSSLSocketFactory(ssf);

            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            // 设置请求方式(GET/POST)
            conn.setRequestMethod(requestMethod);

            // 当outputStr不为null时向输出流写数据
            if (null != outputStr) {
                OutputStream outputStream = conn.getOutputStream();
                // 注意编码格式
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }

            // 从输入流读取返回内容
            InputStream inputStream = conn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }

            // 释放资源
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
            inputStream = null;
            conn.disconnect();
            jsonObject = JSONObject.fromObject(buffer.toString());
        } catch (ConnectException ce) {
            log.error("连接超时:{}", ce);
        } catch (Exception e) {
            log.error("https请求异常:{}", e);
        }
        return jsonObject;
    }

8.写授权类:

替换成自己的appid 和 密钥

package com.wyj.wechart.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.wyj.wechart.pojo.SNSUserInfo;
import com.wyj.wechart.pojo.WeixinOauth2Token;
import com.wyj.wechart.utils.AdvancedUtil;
/**
 * 授权后的回调请求处理
 * 
 * 
 * @author:WangYuanJun
 * @date:2018年1月27日 下午5:31:09
 */
@Controller
@RequestMapping("/oauth")
public class OAuthController {

    @RequestMapping
    public ModelAndView index(String code,String state){
        ModelAndView mv = new ModelAndView("/index");

        // 用户同意授权
        if (!"authdeny".equals(code)) {
            // 获取网页授权access_token
            WeixinOauth2Token weixinOauth2Token = AdvancedUtil.getOauth2AccessToken("wx17fdedc3d6d0b68e", "c3b3d919d65a781ba7db58d9d8dfb515", code);
            // 网页授权接口访问凭证
            String accessToken = weixinOauth2Token.getAccessToken();
            // 用户标识
            String openId = weixinOauth2Token.getOpenId();
            // 获取用户信息
            SNSUserInfo snsUserInfo = AdvancedUtil.getSNSUserInfo(accessToken, openId);
            // 设置要传递的参数
            mv.addObject("snsUserInfo", snsUserInfo);
            mv.addObject("state", state);
        }
        return mv;
    }

}

9.授权后,显示信息的页面

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>OAuth2.0网页授权</title>
</head>
<body>

<#if snsUserInfo??>
    <table width="100%" cellspacing="0" cellpadding="0">
        <tr><td width="20%">属性</td><td width="80%"></td></tr>
        <tr><td>OpenID</td><td>${snsUserInfo.openId}</td></tr>
        <tr><td>昵称</td><td>${snsUserInfo.nickname}</td></tr>
        <tr><td>性别</td><td>${snsUserInfo.sex}</td></tr>
        <tr><td>国家</td><td>${snsUserInfo.country}</td></tr>
        <tr><td>省份</td><td>${snsUserInfo.province}</td></tr>
        <tr><td>城市</td><td>${snsUserInfo.city}</td></tr>
        <tr><td>头像</td><td>${snsUserInfo.headImgUrl}</td></tr>
<!--         <tr><td>特权</td><td>${snsUserInfo.privilegeList}</td></tr> -->
        <tr><td>state:</td><td>${state}</td></tr>
    </table>

    <#else>
    <p>用户不同意授权,未获取到用户信息!</p>
</#if>



</body>
</html>

10.application.properties配置

server.port=80

spring.freemarker.cache=false
spring.freemarker.charset=UTF-8
spring.freemarker.check-template-location=true
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=true
spring.freemarker.expose-session-attributes=true
spring.freemarker.request-context-attribute=request
spring.freemarker.template-loader-path=classpath:/templates
spring.freemarker.suffix=.html
spring.mvc.static-path-pattern=/static/**

11.替换官方的链接成我们的方法路径:

官方的请求链接:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

需要修改的地方:

(1)替换自己的AppID

(2)将redirect_url换成自己的授权请求链接URL。注意这个连接需要经过UTF-8编码。

(3)需要修改scope。需要弹出页面则要修改为snsapi_userinfo 。

scope参数的解释:

1、以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)

2、以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。

URL转码

    /**
     * URL编码(utf-8)
     * 
     * @param source
     * @return
     */
    public static String urlEncodeUTF8(String source) {
        String result = source;
        try {
            result = java.net.URLEncoder.encode(source, "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return result;
    }
package com.wyj.wechart.test;

import com.wyj.wechart.utils.CommonUtil;
/**
 * URL转码
 * 
 * 
 * @author:WangYuanJun
 * @date:2018年1月27日 下午5:35:02
 */
public class TransCodeUrlTest {
    /**
     * 生成URL编码
     * 
     * @param args
     */
    public static void main(String[] args) {
        String source = "http://6400cc45.ngrok.io/oauth";
        System.out.println(CommonUtil.urlEncodeUTF8(source));
    }
}

也可以直接在线url编码: http://tool.chinaz.com/Tools/URLEncode.aspx

12.修改网页授权获取用户基本信息

微信公共平台->测试号管理->体验接口权限表->网页服务->网页帐号->修改
修改完成后需要重新关注
这里写图片描述
这里写图片描述

13.测试效果:

复制上面替换好的链接,然后丢进浏览器,然后用微信来扫一扫。
这里写图片描述
这里写图片描述

注:github项目地址:微信公共号开发用例

展开阅读全文

没有更多推荐了,返回首页