微信公众号测试号开发小结

微信公众号踩坑之旅

2019-02-20 15:26:27 By Magina

开发过程中使用的是微信测试公众号

在线申请:

https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

第二步准备工作.获取开发环境的影射外网地址:ngrok百度自行下载,

打开ngrok.exe -> ngrok http 8080  // 此处同样可以指定其他端口.例如8087

此时:外网穿透已经成功.http和https理论上都可以使用.我使用的是http.

下面去测试号 页面查找网页授权域名: 注意.不带http:// 

再设置js接口域名

此时页面的配置基本完成.我们需要接入到    微信接口配置信息:

微信提供一系列的规则.测试号相对简单,只需提供接口url和token,token自定义.需要和我们后台项目设定的一致.url指向后台项目的接口校验.

项目搭建不多做介绍.

官方提供信息:

开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:

signature

微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。

timestamp

时间戳

nonce

随机数

echostr

随机字符串

开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:

1)将token、timestamp、nonce三个参数进行字典序排序

 2)将三个参数字符串拼接成一个字符串进行sha1加密 

3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

项目代码controller:

//    token
private final String token = "cass";

@GetMapping(value = "/checkSignature")
public void testInfo(HttpServletRequest request, HttpServletResponse response) throws Exception {
    log.info("=============================进入Wechat==================================");
    log.info(request.toString());
    System.out.println("开始签名校验");
    String signature = request.getParameter("signature");
    String timestamp = request.getParameter("timestamp");
    String nonce = request.getParameter("nonce");
    String echostr = request.getParameter("echostr");
    //排序
    String sortString = sort(token, timestamp, nonce);//按照微信官方文档提供的规则 组合String
    //加密
    String mytoken = Decript.SHA1(sortString);
    //校验签名
    if (mytoken != null && mytoken != "" && mytoken.equals(signature)) {
        System.out.println("签名校验通过。");
        response.getWriter().print(echostr); //如果检验成功输出echostr,微信服务器接收到此输出,才会确认检验完成。
    }
}
public static String sort(String token, String timestamp, String nonce) {
    String[] strArray = {token, timestamp, nonce};
    Arrays.sort(strArray);
    StringBuilder sbuilder = new StringBuilder();
    for (String str : strArray) {
        sbuilder.append(str);
    }
    return sbuilder.toString();
}

Decript(sha1加密类):

public class Decript {

    public static String SHA1(String decript) {
        try {
            MessageDigest digest = MessageDigest
                    .getInstance("SHA-1");
            digest.update(decript.getBytes());
            byte messageDigest[] = digest.digest();
            // Create Hex String
            StringBuffer hexString = new StringBuffer();
            // 字节数组转换为 十六进制 数
            for (int i = 0; i < messageDigest.length; i++) {
                String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexString.append(0);
                }
                hexString.append(shaHex);
            }
            return hexString.toString();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }
}

很简单理解.微信官方根据你给的token会生成一个签名.然后参数给开发者.开发者也必须生存一个签名.签名equals相等.ok.你即可以成为开发者

项目url:http://1527ade6.ngrok.io/Wechat/checkSignature 

坑: 这个url里面一定不能带端口号.就算用nginx配置过也需要改掉.项目里被坑过

token:cass

此时.开发者账号已经接入成功了.

我们可以先通过微信官方的api接口 测试一些功能.比如自定义菜单跳转后台

接口api:https://mp.weixin.qq.com/debug/cgi-bin/apiinfo?

https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wx00ab3357d8686b43&secret=39d83b4cad24fdcf519bb998c9b9e30f
返回结果:
* 200 OK
* Connection: close
* Date: Wed, 20 Feb 2019 07:36:19 GMT
* Content-Type: application/json; encoding=utf-8
* Content-Length: 173
*
{
    "access_token": "18_NWcKxgaJP6EnY7QO-QuJSZlxIxpAt6ekwIHm-B7xRkHuKJwOwYra6YW2qRQc_9rCrm2d1o7yURT3Yw5H8ClA6gIfg2wSa9pZAcWsx-NEi5am2T6WU_kDKMk9TYoQZEiAGAHQV",
    "expires_in": 7200
}

如果返回非200就是有问题.这步很简单.如有问题百度

https://api.weixin.qq.com/cgi-bin/menu/get?access_token=18_NWcKxgaJP6EnY7QO-QuJSZlxIxpAt6ekwIHm-B7xRkHuKJwOwYra6YW2qRQc_9rCrm2d1o7yURT3Yw5H8ClA6gIfg2wSa9pZAcWsx-NEi5am2T6WU_kDKMk9TYoQZEiAGAHQV
返回结果:
* 200 OK
* Connection: keep-alive
* Date: Wed, 20 Feb 2019 08:10:35 GMT
* Content-Type: application/json; encoding=utf-8
* Content-Length: 408
*
{
    "menu": {
        "button": [
            {
                "type": "view",
                "name": "测试案例",
                "url": "http://87af154f.ngrok.io/Wechat/testData",
                "sub_button": [ ]
            },
            {
                "type": "view",
                "name": "摇珠计划",
                "url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=WWWWWredirect_uri=http://87af154f.ngrok.io/Wechat/tologin/userinforesponse_type=codescope=snsapi_userinfostate=STATE#wechat_redirect",
                "sub_button": [ ]
            }
        ]
    }}
提示:
Request successful

这是我的正常返回值.查看菜单接口

下面来自定义菜单.拼接menu的json:

{
        "button": [
            {
                "type": "view",
                "name": "测试十三",
                "url": "http://1527ade6.ngrok.io/Wechat/testData",
                "sub_button": [ ]
            },
            {
                "type": "view",
                "name": "计划十三",
                "url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx00ab3357d8686b43&redirect_uri=http://1527ade6.ngrok.io/Wechat/tologin/userinfo&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect",
                "sub_button": [ ]
            }
        ]
    }

来说下这边的坑:

    1.json不需要带menu层.只需要button这层

    2:url最好带上http.这里的http只得是本身跳转的url和rediect_uri的url.微信的是https不需要管

    3.下面的url等下讲解

http://1527ade6.ngrok.io/Wechat/testData 对应的是项目接口的localhost:8080/Wechat/testData

代码:

@GetMapping(value = "/testData")
public String testInfo() throws Exception {
    log.info("测试通过了啊");
    return "测试通过了啊";
}

自定义菜单创建成功.ok这时候我们拿自己的微信去扫二维码关注看下结果

这是我手机关注后的菜单显示.

简单的自定义菜单已经完成.

下面说下"计划十三"这个button的url:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx00ab3357d8686b43&redirect_uri=http://1527ade6.ngrok.io/Wechat/tologin/userinfo&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

这个是微信网页授权的url.访问微信的接口,会回调到http://1527ade6.ngrok.io/Wechat/tologin/userinfo我们这个接口.在这个接口里面.我们回得到当前用户的微信基本信息.从授权说起.

官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842

想从微信按钮跳转到我们自定义网页(第三方).需要用户对此网页授权.才能继续.微信提供静默授权和手动授权.很简单.静默就是你点击了按钮.微信后台自动帮你授权了.再跳转第三方网页.手动就会弹出一个授权的页面点击授权才能跳转第三方网页.官方给出2中的url:

scope为snsapi_base静默授权
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx3431726cd6f932f4&redirect_uri=http://pds.movitech.cn:8686/Wechat/tologin/userinfo&response_type=code&scope=snsapi_base&state=123#wechat_redirect
scope为snsapi_userinfo手动授权.
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx3431726cd6f932f4&redirect_uri=http://pds.movitech.cn:8686/Wechat/tologin/userinfo&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
只需要把appid和rediect_uri替换调就可以

接口实例,我的项目例子是这样:前后端分离.此接口既可以给前端调用,也可以给微信调用.调用授权跳转接口.如果此用户不再项目系统里面用手机号码注册过.就返回前端一个false.否则就查找出planList业务数据给前端.

@GetMapping("/tologin/userinfo")
    @ApiOperation(value = "传入code登录微信接口", notes = "前端传入code,调微信接口,获取微信用户信息,返回值:status为true则是此微信号绑定过,返回planList计划列表.status为flase" +
            "微信号尚未绑定手机号码,需要跳转手机验证码页面")
    public Response check(HttpServletRequest request, HttpSession session, Map<String, Object> map) throws BusinessException {
        log.info("=======================进入tologin/userinfo");
        //首先判断一下session中,是否有保存着的当前用户的信息,有的话,就不需要进行重复请求信息
        WeiXinUser weiXinUser = null;
        if (session.getAttribute("currentUser") != null) {
            weiXinUser = (WeiXinUser) session.getAttribute("currentUser");
        } else {
            /**
             * 进行获取openId,必须的一个参数,这个是当进行了授权页面的时候,再重定向了我们自己的一个页面的时候,
             * 会在request页面中,新增这个字段信息,要结合这个ProjectConst.Get_WEIXINPAGE_Code这个常量思考
             */
            String code = request.getParameter("code");
//             code = "021Ct0F42we4lP0ohqI42664F42Ct0Fg";
            try {
                //得到当前用户的信息(具体信息就看weixinUser这个javabean)
                weiXinUser = getTheCode(session, code);
                //将获取到的用户信息,放入到session中
                session.setAttribute("currentUser", weiXinUser);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if(ObjectUtils.isEmpty(weiXinUser)){
            return Response.fail("3999","查找微信用户失败!");
        }
        List planList = wechatService.getPlanListByOpenId(weiXinUser.getOpenId());
        if(CollectionUtils.isEmpty(planList) && null != planList){
            map.put("status",true);
            map.put("planList",planList);
        }else{
            map.put("status",false);
        }
        map.put("weiXinUser", weiXinUser);
        return Response.succeed(map);
    }

看到这里很疑惑.为什么直接从String code = request.getParameter("code");

这个是当进行了授权页面的时候,再重定向了我们自己的一个页面的时候,

微信会在request页面中,新增这个字段信息.不信的话我们可以测试:

 

打开微信开发者工具.1获取token.2.获取button.3复制button的url:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx00ab3357d8686b43&redirect_uri=http://1527ade6.ngrok.io/Wechat/tologin/userinfo&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

粘贴到地址栏enter.演示利用微信工具获取code.最后一个接口的请求参数里面可以看到

看到上图console.回调地址里面其实就是?code=XXXXXXXXXXXXXXXXXXX去请求我们的接口

其他相关代码:

WeiXinUser


/**
* @desc 对于微信用户本身存在的信息的一个javabean,不需要在数据库中进行处理
**/
@Setter
@Getter
public class WeiXinUser {
    // 用户的标识
    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;

    @Override
    public String toString() {
        return "WeiXinUser{" +
                "openId='" + openId + '\'' +
                ", 昵称='" + nickname + '\'' +
                ", 性别=" + (sex == 1? "男" : "女") +
                ", 国籍='" + country + '\'' +
                ", 省份='" + province + '\'' +
                ", 城市='" + city + '\'' +
                '}';
    }
}

AccessToken


@Getter
@Setter
public class AccessToken {

    // 错误code
    private Integer errcode;

    // 错误msg
    private String errmsg;

    // 获取到的凭证
    private String access_token;

    // 凭证有效时间,单位:秒
    private Long expires_in;

    @Override
    public String toString() {
        return "AccessToken{" +
                "errcode='" + errcode + '\'' +
                ", errmsg='" + errmsg + '\'' +
                ", access_token='" + access_token + '\'' +
                ", expires_in=" + expires_in +
                '}';
    }
}

ProjectConst:

/**
* @author Magina
* @create 2019-02-20 17:36:12 By Magina
* @desc 项目相关的静态量
**/
public class ProjectConst {
    /**
     * 用于获取当前与微信公众号交互的用户信息的接口(一般是用第一个接口地址)
     */
    public static final String GET_WEIXIN_USER_URL = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID";
    public final static String GetPageUsersUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";

    /**
     * 用于进行网页授权验证的接口URL,通过这个才可以得到opendID等字段信息
     */
    public final static String GET_WEBAUTH_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";

    /**
     * 用于进行当点击按钮的时候,能够在网页授权之后获取到code,再跳转到自己设定的一个URL路径上的接口,这个主要是为了获取之后于
     * 获取openId的接口相结合
     * 注意:参数:toselfURL  表示的是当授权成功后,跳转到的自己设定的页面,所以这个要根据自己的需要进行修改
     */
    public final static String Get_WEIXINPAGE_Code = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=toselfURL&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect";
    /**
     * 获取access_token的URL
     */
    public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";

    /**
     * SMS服务的参数
     */
//    public static final String SMS_ACCOUNT_SID = "AC83609469e581a0349a3aa499a55938b9";
//    public static final String SMS_AUTH_TOKEN = "f7020503662698bce7e3e5465278c31e";

}

oauth2GetOpenid:

/**
* 进行用户授权,获取到需要的授权字段,比如openId
* @param code 识别得到用户id必须的一个值
* 得到网页授权凭证和用户id
* @return
*/
@Override
public Map<String, String> oauth2GetOpenid(String code) {
    //自己的配置appid(公众号进行查阅)
    String appid = projectAppid;
    //自己的配置APPSECRET;(公众号进行查阅)
    String appsecret = projectAppsecret;
    //拼接用户授权接口信息
    String requestUrl = ProjectConst.GET_WEBAUTH_URL.replace("APPID", appid).replace("SECRET", appsecret).replace("CODE", code);
    //存储获取到的授权字段信息
    Map<String, String> result = new HashMap<String, String>();
    try {
        JSONObject OpenidJSONO = WeiXinUtils.doGetStr(requestUrl);
        //OpenidJSONO可以得到的内容:access_token expires_in  refresh_token openid scope
        String Openid = String.valueOf(OpenidJSONO.get("openid"));
        System.out.println("openId:"+Openid);
        String AccessToken = String.valueOf(OpenidJSONO.get("access_token"));
        //用户保存的作用域
        String Scope = String.valueOf(OpenidJSONO.get("scope"));
        String refresh_token = String.valueOf(OpenidJSONO.get("refresh_token"));
        result.put("Openid", Openid);
        result.put("AccessToken", AccessToken);
        result.put("scope", Scope);
        result.put("refresh_token", refresh_token);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return result;
}

wechatService.getAuthInfo(code);

/**
* 获取到微信用户的唯一的OpendID
* @param code  这是要获取OpendId的必须的一个参数
* @return
*/
@Override
public Map<String , String> getAuthInfo(String code) {
    //进行授权验证,获取到OpenID字段等信息
    Map<String, String> result = oauth2GetOpenid(code);
    // 从这里可以得到用户openid
    String openId = result.get("Openid");
    return result;
}

wechatService.getUserInfo(accessToken, openId):

@Override
public WeiXinUser getUserInfo(String accessToken, String openId) {
    WeiXinUser weixinUserInfo = null;
    // 拼接获取用户信息接口的请求地址
    String requestUrl = ProjectConst.GET_WEIXIN_USER_URL.replace("ACCESS_TOKEN", accessToken).replace(
            "OPENID", openId);
    // 获取用户信息(返回的是Json格式内容)
    JSONObject jsonObject = WeiXinUtils.doGetStr(requestUrl);

    if (null != jsonObject) {
        try {
            //封装获取到的用户信息
            weixinUserInfo = new WeiXinUser();
            // 用户的标识
            weixinUserInfo.setOpenId(jsonObject.getString("openid"));
            // 昵称
            weixinUserInfo.setNickname(jsonObject.getString("nickname"));
            // 用户的性别(1是男性,2是女性,0是未知)
            weixinUserInfo.setSex(jsonObject.getInt("sex"));
            // 用户所在国家
            weixinUserInfo.setCountry(jsonObject.getString("country"));
            // 用户所在省份
            weixinUserInfo.setProvince(jsonObject.getString("province"));
            // 用户所在城市
            weixinUserInfo.setCity(jsonObject.getString("city"));
            // 用户头像
            weixinUserInfo.setHeadImgUrl(jsonObject.getString("headimgurl"));
        } catch (Exception e) {
            if (0 == weixinUserInfo.getSubscribe()) {
                System.out.println("用户并没有关注本公众号");
            } else {
                int errorCode = jsonObject.getInt("errcode");
                String errorMsg = jsonObject.getString("errmsg");
                System.out.println("由于"+errorCode +"错误码;错误信息为:"+errorMsg+";导致获取用户信息失败");
            }
        }
    }
    return weixinUserInfo;
}

WeiXinUtils


/**
* @desc 用户获取access_token,众号调用各接口时都需使用access_token
**/
public class WeiXinUtils {
    /**
     * Get请求,方便到一个url接口来获取结果
     *
     * @param url
     * @return
     */
    public static JSONObject doGetStr(String url) {
        DefaultHttpClient defaultHttpClient = new DefaultHttpClient();
        HttpGet httpGet = new HttpGet(url);
        JSONObject jsonObject = null;
        try {
            HttpResponse response = defaultHttpClient.execute(httpGet);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                String result = EntityUtils.toString(entity, "UTF-8");
                jsonObject = JSONObject.fromObject(result);
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return jsonObject;
    }

    /**
     * 获取access_token
     * @return
     */
    public static AccessToken getAccessToken(String projectAppId,String projectAppsecret){
        AccessToken accessToken = new AccessToken();
        String appid = projectAppId;
        //自己的配置APPSECRET;(公众号进行查阅)
        String appsecret = projectAppsecret;
        String url = ProjectConst.ACCESS_TOKEN_URL.replace("APPID" ,appid).replace("APPSECRET",appsecret);
        JSONObject jsonObject = doGetStr(url);
        if(jsonObject !=null){
            accessToken.setAccess_token(jsonObject.getString("access_token"));
            accessToken.setExpires_in(jsonObject.getLong("expires_in"));
        }
        return accessToken;
    }

    public static String authorizeUrl(String callbackUrl,String projectAppid){
        StringBuilder sb = new StringBuilder();
        try {
            sb.append("https://open.weixin.qq.com/connect/oauth2/authorize?appid=").
                    append(projectAppid).
                    append("&redirect_uri=").
                    append(URLEncoder.encode(callbackUrl,"UTF-8")).
                    append("&response_type=code").
                    //append("&scope=snsapi_base ").
                            append("&scope=snsapi_userinfo").
                    append("&state=STATE").
                    append("#wechat_redirect");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }
}

基本代码都再上面.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值