2016-12-08 17:40:07 nideshijian 阅读数 10050

【微信开发】-HTML5页面直接调用百度地图API,获取当前位置,直接导航目的地

HTML5页面直接调用百度地图API,获取当前位置,直接导航目的地 
我是应用在微信中,自定义菜单,菜单直接链接到这个HTML5页面,获取当前位置后,页面中定好目的地,这样打开页面后直接进入导航页面 
可以省下先发送位置信息后,点确定再出导航,省一步,
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0">
    <title>HTML5页面直接调用百度地图API,获取当前位置,直接导航目的地</title>
    <script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=wDYEcxgRRheZwyC9jpN1Tt7fzr2zjosZ"></script>  
    <script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script><!--调用jQuery-->

    <style type="text/css">
        body, html,#allmap {width: 100%;height: 100%;overflow: hidden;margin:0;font-family:"微软雅黑";}
    </style> 

</head>

<body>  
   <div id="allmap"></div>
</body>  
</html>  
<script type="text/javascript">  

    var map = new BMap.Map("allmap");  
    var point = new BMap.Point(116.709684,39.89778);
    map.centerAndZoom(point, 16);  
    map.enableScrollWheelZoom(); 

    var myIcon = new BMap.Icon("myicon.png",new BMap.Size(30,30),{
        anchor: new BMap.Size(10,10)    
    });

    var marker=new BMap.Marker(point,{icon: myIcon});  
    map.addOverlay(marker);  

    var geolocation = new BMap.Geolocation();
    geolocation.getCurrentPosition(function(r){
        if(this.getStatus() == BMAP_STATUS_SUCCESS){
            var mk = new BMap.Marker(r.point);
            map.addOverlay(mk);
            //map.panTo(r.point);//地图中心点移到当前位置
            var latCurrent = r.point.lat;
            var lngCurrent = r.point.lng;
            //alert('我的位置:'+ latCurrent + ',' + lngCurrent);

            location.href="http://api.map.baidu.com/direction?origin="+latCurrent+","+lngCurrent+"&destination=39.89778,116.709684&mode=driving&region=北京&output=html";

        }
        else {
            alert('failed'+this.getStatus());
        }        
    },{enableHighAccuracy: true})


    map.addOverlay(marker);  
    var licontent="<b>健龙森羽毛球馆</b><br>";  
        licontent+="<span><strong>地址:</strong>北京市通州区滨河中路108号</span><br>";  
        licontent+="<span><strong>电话:</strong>(010)81556565 / 6969</span><br>";          
    var opts = { 
        width : 200,
        height: 80,
    };         
    var  infoWindow = new BMap.InfoWindow(licontent, opts);  
    marker.openInfoWindow(infoWindow);  
    marker.addEventListener('click',function(){
        marker.openInfoWindow(infoWindow);
    });  

</script>


2018-11-07 15:02:10 qq_31122833 阅读数 3857

在上一篇的基础上,有一个地方是需要去微信公众平台设置的(如果你这个url需要获取用户openid的话!如果不需要获取可以无视)

登录微信公众平台-->公众号设置-->选择功能设置-->设置网页授权域名(为微信认为安全的地址,不用精确到方法,但是你需要将一个文本密钥放入对应地址下,微信会在这个地址校验密钥。密钥微信会给你下载)

我放在这里(这是我op项目下的resources/static/open)对应op/open:

这些做完之后,点击保存,微信会给你返回结果。我这边是成功了!如果失败:会有错误信息,一般是找不到那个密钥,看看你的位置是否放正确了!

这些事情做完之后,我们回到上一篇:

组装菜单数据方法getMenu()那:

String redirect_uri = "http://www.你的服务器回调地址.com/op/open/openArticle";

这个地址:BaseAPI.OPEN_URI+"/connect/oauth2/authorize?appid="+ WeiXinUtil.APPID+"&redirect_uri="+redirect_uri+"?category=1&response_type=code&scope=snsapi_base&state=1#wechat_redirect"
它的完整地址是:
https://open.weixin.qq.com/connect/oauth2/authorize?appid="+ WeiXinUtil.APPID+"&redirect_uri="+redirect_uri+"?category=1&response_type=code&scope=snsapi_base&state=1#wechat_redirect
其中appid不用我说了吧。别的东西都不用管,你先看redirect_uri
这个地址能否调用你的项目接口
例如我的项目接口有这个方法:
/**
  * 公共文章界面
  * @param map
  * @return
  */
 @RequestMapping(value = "/open/openArticle")
 public String openArticle(ModelMap map,HttpServletRequest request) throws Exception{
     String code = request.getParameter("code");//页面授权获取的code用以交换 openid
     String openId = OAuthGetOpenid(code);//OAuth获取用户openid区分是哪个用户请求登录W3
     map.put("title", "公共文章界面");
     map.put("path", path);
     map.put("openId",openId);
     map.put("category",category);
    
     return "openArticle";
    }

/**
  * 通过页面OAuth授权,用code获取openid
  *
  * @param code
  * @return
  */
 public String OAuthGetOpenid(String code) throws Exception{
     JSONObject json = new JSONObject();
     String requestUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+WeiXinUtil.APPID+"&secret="+WeiXinUtil.APPSECRET+"&code=" + code + "&grant_type=authorization_code";
     String outputStr = "";
     json = WeiXinUtil.doPostStr(requestUrl, outputStr);
     String openid = json.getString("openid");
     logger.info(json+"---------json--------openid-----------"+openid);
     return openid;
 }

到这就完成了。点击菜单按钮之后,我这边可以成功获取到用户的openid,并且成功跳转到指定url页面

2019-12-24 11:26:41 csdn_fpf 阅读数 111

说明:

       微信公众号开发只作参考,不作限制,条条大路通罗马,本文只是实现当前业务的一种方式,需要优化的地方还有很多,共勉。

       应系统业务拓展,加入微信公众号模块。微信公众号开发流程如下,本文共分六个部分:

       1)微信后台与项目对接;(开启开发者模式)

       2)获取AccessToken;

       3)自定义菜单;

       4)获取用户openid(用户在微信注册的基本信息)

       5)页面跳转控制;

       6)接收事件推送

一、微信后台与项目对接

       通过阅读官方文档,可以得知若要接入微信公众号平台开发,开发者需要按照以下三步流程:

       1、填写服务器配置

        

        解释:

               1)服务器地址(URL):域名指向的IP端口必须是80端口和443端口

               2)令牌(Token):须与后台配置的一致

               3)消息加解密密钥(EncodingAESKey):随机生成即可

               4)消息加解密方式:我选择的是明文模式,选加密模式也可以,看项目的私密性而定

               5)最重要的一点:配服务器信息之后,需要添加白名单(域名指向的IP)

        2、验证服务器地址的有效性

             点击上图的提交,验证服务器地址等相关信息的有效性

             验证通过后选择启用,则进入开发者模式,通过微信后台自定义事件失去效果

             注意:

                    我们的应用服务器要接受微信服务器的get请求,其中包括四个参数(signature、timestamp、nonce、echostr),开                 发者通过检验signature对请求进行校验。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生               效,成为开发者成功,否则接入失败。

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

             2) 加密:将三个参数字符串拼接成一个字符串进行SHA1加密,开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

    // 此处TOKEN即我们刚刚所填的token,须与微信后台配置的token保持一致
    private static final String TOKEN = "obdkurongshenji";


    @GetMapping(value = "/login")
    public String checkName(@RequestParam(name = "signature") String signature,
            @RequestParam(name = "timestamp") String timestamp, @RequestParam(name = "nonce") String nonce,
            @RequestParam(name = "echostr") String echostr) throws UnsupportedEncodingException {
        // 排序
        String sortString = sort(TOKEN, timestamp, nonce);
        // 加密
        String myString = sha1(sortString);
        // 校验
        if (myString != null && myString != "" && myString.equals(signature)) {
            // 如果检验成功原样返回echostr,微信服务器接收到此输出,才会确认检验完成。
            logger.debug("-----------------------签名校验通过------------------------");
            // 首次接入打开,成功创建之后请注释掉(生成自定义菜单)
            Long id = tbWxAccesstokenService.selectMaxId();// 获取最后一个(未过期)token
            String access_token = tbWxAccesstokenService.selectAccessTokeById(id);
            logger.debug("-----------后端服务器地址:" + backUrl);
            if (null != access_token) {
                Menu menu = WebchatMenuUtil.initMenu(backUrl, appId);
                WebchatMenuUtil.createMenu(access_token, JSON.toJSONString(menu));
            } else {
                logger.debug("-----------access_token获取失败------------");
            }
            return echostr;
        } else {
            logger.debug("-----------------------签名校验失败------------------------");
            return null;
        }
    }

       解释:

              代码中涉及到的排序,加密的代码示例如下:  
    // 排序方法
    public String sort(String token, String timestamp, String nonce) {
        String[] strArray = { token, timestamp, nonce };
        Arrays.sort(strArray);
        StringBuilder sb = new StringBuilder();
        for (String str : strArray) {
            sb.append(str);
        }
        return sb.toString();
    }

    /**
     * 将字符串进行sha1加密
     * @param str 需要加密的字符串
     * @return 加密后的内容
     */
    public String sha1(String str) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            digest.update(str.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 null;
    }

二、获取AccessToken

       1、定义:access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。

       2、在库融审计项目中,用的是access_token持久化,每隔一个小时获取到新的access_token,则保存到数据库中,调用微信接口需要access_token时,从数据库中取出最新的access_token即可。

       3、接口请求方式:GET

       4、接口:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

       5、接口详情:

            

三、自定义菜单

        1、自定义菜单能够帮助公众号丰富界面,让用户更好更快的理解公众号的功能。开启自定义菜单后,公众号界面如图所示:

              

注意:

       1)自定义菜单最多包含3个一级菜单,每个菜单最多包含5个二级菜单

       2)一级菜单最多四个汉字,二级菜单最多七个汉字,多出来的部分将会以”…”代替

       3)创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。

2、接口请求方式:POST

3、接口:https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN

4、菜单示例:

      private final static String CREATE_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";

    private static Logger logger = LoggerFactory.getLogger(WebchatMenuUtil.class);

    public static int createMenu(String token, String menu) {
        int result = 0;
        String url = CREATE_MENU_URL.replace("ACCESS_TOKEN", token);
        JSONObject jsonObject = WebchatHttpsUtil.httpsRequest(url, "POST", menu);
        if (null != jsonObject) {
            result = jsonObject.getInteger("errcode");
        }
        logger.debug("----------------公众号菜单初始化成功-------------------");
        return result;
    }

    // 默认自定义菜单初始化
    public static Menu initMenu(String backUrl, String appId) throws UnsupportedEncodingException {
        Menu menu = new Menu();
        ViewButton button11 = new ViewButton();
        button11.setName("车辆列表");
        button11.setType("view");
        button11.setUrl(backUrl + "/freeter-api/wechatredirect/auth?state=carListPage");

        ViewButton button12 = new ViewButton();
        button12.setName("报警列表");
        button12.setType("view");
        button12.setUrl(backUrl + "/freeter-api/wechatredirect/auth?state=stockListPage");

        Button button1 = new Button();
        button1.setName("数据列表"); // 将button11/button12两个button作为二级菜单封装第一个一级菜单
        button1.setSub_button(new Button[] { button11, button12 });

        // 创建公众号跳转到小程序的菜单
        MiniprogramButton miniprogramButton = new MiniprogramButton();
        miniprogramButton.setName("上线验证");
        miniprogramButton.setType("miniprogram");
        miniprogramButton.setAppid("appid");
        miniprogramButton.setUrl("http://mp.weixin.qq.com");
        miniprogramButton.setPagepath("pages/index/index");

        ViewButton button3 = new ViewButton();
        button3.setName("个人中心");
        button3.setType("view");
        button3.setUrl(backUrl + "/freeter-api/wechatredirect/auth?state=userInfoPage");

        menu.setButton(new Button[] { button1, miniprogramButton, button3 });// 将31Button直接作为一级菜单
        return menu;
    }

     

四、获取用户openid

      1、作用:

      开发者可通过OpenID来获取用户基本信息。特别需要注意的是,如果开发者拥有多个移动应用、网站应用和公众帐号,可通过获取用户基本信息中的unionid来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号,用户的unionid是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid是相同的。

      2、在本项目中,采用的是通过OAuth2的方式获取用户对于当前公众号的openId。步骤如下:

           1)配置回调域名

                 

                     注意:须将MP_verify_wtfJFVgIjlkfwy2I.txt文件放在项目的根目录下

             2)构造请求url如下:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx8888888888888888&redirect_uri=http://mascot.duapp.com/oauth2.php&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect

                     注意:1)redirect_uri表示回调地址

                                2)snsapi_userinfo表示应用授权作用域为请求用户信息,scope=snsapi_base表示不需要用户授权

                                3)state表示我们在后面需要的参数

           3)通过调用第二步的url,微信服务器会携带code和state参数将请求转发到回调地址,也就是我们在后台定义的接口,再使用code调用微信提供的接口,获取用户的openid,url如下:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=APPSECRET&code=CODE&grant_type=authorization_code

           通过接口调用,返回详情如下:

           此时就获取到用户的openId了

五、页面跳转控制

       1、流程说明:

        在项目中,页面跳转控制是在获取到用户openid之后,根据openid判断用户是否绑定到项目,如果没有,则页面统一跳转到用户绑定页;如果已经绑定,再根据获取code时,传的state参数值,判断当前用户点击的菜单是去哪个页面,再通过重定向的方式,携带必要的参数重定向到指定的页面。

      2、示例:

    @Value("${wechat.frontUrl}")
    private String frontUrl;
    @Value("${wechat.backUrl}")
    private String backUrl;
    @Value("${wechat.appId}")
    private String appId;
    @Value("${wechat.appSecret}")
    private String appSecret;

    // 用户同意授权,获取code
    private final static String GET_CODE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPIDVALUE"
            + "&redirect_uri=REDIRECTURIVALUE&response_type=code&scope=snsapi_base&state=STATEVALUE#wechat_redirect";
    
    // 获取openId地址
    private final static String GET_OPENID_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPIDVALUE"
            + "&secret=SECRETVALUE&code=CODEVALUE&grant_type=authorization_code";
    
    @Autowired
    private TbWxUserbindingService tbWxUserbindingService;
    
    @ResponseBody
       @RequestMapping(value = "/auth", method = RequestMethod.GET)
       public void GuideServlet(HttpServletRequest request, HttpServletResponse response, @RequestParam("state") String state) throws Exception {
        System.out.println("------state:" + state);
        // 设置编码
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        /**
         * 第一步:用户同意授权,获取code
         */
        String redirect_uri = URLEncoder.encode("后端接口url", "UTF-8");// 授权后重定向的回调链接地址,请使用urlencode对链接进行处理(文档要求)
        // 按照文档要求拼接访问地址
        String url = GET_CODE_URL.replace("APPIDVALUE", appId).replace("REDIRECTURIVALUE", redirect_uri).replace("STATEVALUE", state);
        System.out.println("------url:" + url);
        response.sendRedirect(url);// 跳转到要访问的地址
    }
    
    @RequestMapping("/distribute")
    @ApiOperation(value = "微信端页面跳转中央控制器")
    public String distribute(HttpServletRequest request, @RequestParam("code") String code, @RequestParam("state") String state) throws UnsupportedEncodingException {
        System.out.println("----------获取到的code:" + code + "----------state:" + state);
        String openId = null;
        String returnUrl = null;
        // 第三步:同意授权,通过code换取网页授权access_token
        if (code != null) {
            // 拼接请求地址
            String url = GET_OPENID_URL.replace("APPIDVALUE", appId).replace("SECRETVALUE", appSecret).replace("CODEVALUE", code);
            System.out.println(url);
            JSONObject jsonObject = HttpUtil.httpGet(url);
            openId = jsonObject.getString("openid");
            System.out.println("返回信息:"+ jsonObject);
        }
        if (null != openId) {
            // 判断用户是否绑定
            TbWxUserbindingEntity user = tbWxUserbindingService.isAlreadyBinding(openId);
            // 用户已绑定,根据返回的state判断需要跳转的页面
            if (null != user) {
                switch (state) {
                case "carListPage":// 车辆列表
                    returnUrl = "url" + user.getUserid();
                    break;
                case "stockListPage":// 报警列表
                    returnUrl = "url" + user.getUserid();
                    break;
                case "userInfoPage":// 用户个人中心页面
                    returnUrl = "url" + user.getUserid() + "&openId=" + openId;
                    break;
                default:
                    break;
                }
            } else {
                // 用户未绑定,页面跳转到用户绑定页面
                returnUrl = "url";
            }
            System.out.println("------跳转地址为:-----" + returnUrl);
        } else {
            return "openId获取失败";
        }
        return "redirect:" + returnUrl;
    }

 

六、接收事件推送

       1、官方解读

            在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信息。其中,某些事件推送在发生后,是允许开发者回复用户的,某些则不允许,详细内容如下:

     1)关注/取消关注事件

     2)扫描带参数二维码事件

     3)上报地理位置事件

     4)自定义菜单事件

     5)点击菜单拉取消息时的事件推送

     6)点击菜单跳转链接时的事件推送

     2、在我的项目中,目前涉及到的事件包括:

          消关注事件:用户取消关注,自动解除该用户与库融审计项目的绑定关系自定义菜单事件:用户点击菜单,请求统一指向同一个接口,获取用户的openid,判断用户是否绑定库融审计项目,如果未绑定,则页面统一跳转到用户绑定页,绑定则跳转到该菜单对应的页面。

接收普通消息:统一回复文本消息

        1)文本消息

        2)图片消息

        3)语音消息

        4)视频消息

        5)地理位置消息

        6)链接消息

2017-04-11 21:56:00 weixin_33752045 阅读数 7
原文:微信公众号开发及时获取当前用户Openid及注意事项

 

目录

(一)微信公众号开发之VS远程调试
(二)微信公众号开发之基础梳理
(三)微信公众号开发之自动消息回复和自定义菜单
(四)微信公众号开发之网页授权获取用户基本信息
(五)微信公众号开发之网页中及时获取当前用户Openid及注意事项

前言

这篇主要是承接上篇的网页授权获取用户基本信息的后文,也是对第一种静默授权之后,用户点击公众号内链接时,如何再次取得当前用户的OpenId的大致讲解和一些注意事项。

 

看过上一篇的小伙伴都知道,我们在用户关注的时候就已经将该用户的基本信息存入数据库中,那么如果该用户过了很久才点击公众号内的网页链接,那么我们该如何再次获取这个唯一标识呢?

重新获取openid

具体实现

首先,我们定一个获取openid的方法 ReGetOpenId

复制代码
public static void ReGetOpenId()
        {
            string url = System.Web.HttpContext.Current.Request.Url.AbsoluteUri;//获取当前url
            if (System.Web.HttpContext.Current.Session["openid"] == "" || System.Web.HttpContext.Current.Session["openid"] == null)
            {
                //先要判断是否是获取code后跳转过来的
                if (System.Web.HttpContext.Current.Request.QueryString["code"] == "" || System.Web.HttpContext.Current.Request.QueryString["code"] == null)
                {
                    //Code为空时,先获取Code
                    string GetCodeUrls = GetCodeUrl(url);
                    System.Web.HttpContext.Current.Response.Redirect(GetCodeUrls);//先跳转到微信的服务器,取得code后会跳回来这页面的

                }
                else
                {
                    //Code非空,已经获取了code后跳回来啦,现在重新获取openid
                    Log log = new Log(AppDomain.CurrentDomain.BaseDirectory + @"/log/Log.txt");
                    string openid = "";
                    openid = GetOauthAccessOpenId(System.Web.HttpContext.Current.Request.QueryString["Code"]);//重新取得用户的openid
                    System.Web.HttpContext.Current.Session["openid"] = openid;
                }
            }
        }
复制代码

注:url最好是带域名的,花生壳的域名是行不通的,再调微信平台接口的时候,会报链接不正确错误

上文中GetCodeUrl方法如下

复制代码
     #region 重新获取Code的跳转链接(没有用户授权的,只能获取基本信息)
        /// <summary>重新获取Code,以后面实现带着Code重新跳回目标页面(没有用户授权的,只能获取基本信息(openid))</summary>
        /// <param name="url">目标页面</param>
        /// <returns></returns>
        public static string GetCodeUrl(string url)
        {
            string CodeUrl = "";
            //对url进行编码
            url = System.Web.HttpUtility.UrlEncode(url);
            CodeUrl = string.Format("https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + Appid + "&redirect_uri=" + url + "?action=viewtest&response_type=code&scope=snsapi_base&state=1#wechat_redirect");

            return CodeUrl;

        }
        #endregion
复制代码

 

上文中 GetOauthAccessOpenId方法如下

复制代码
     #region 以Code换取用户的openid、access_token
        /// <summary>根据Code获取用户的openid、access_token</summary>
        public static string GetOauthAccessOpenId(string code)
        {
            Log log = new Log(AppDomain.CurrentDomain.BaseDirectory + @"/log/Log.txt");
            string Openid = "";
            string url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + Appid + "&secret=" + Secret + "&code=" + code + "&grant_type=authorization_code";
            string gethtml = MyHttpHelper.HttpGet(url);
            log.log("拿到的url是:" + url);
            log.log("获取到的gethtml是" + gethtml);
            OAuth_Token ac = new OAuth_Token();
            ac = JsonHelper.ToObject<OAuth_Token>(gethtml);
            log.log("能否从html里拿到openid=" + ac.openid);
            Openid = ac.openid;
            return Openid;
        }
        #endregion
复制代码

通过以上方法即可拿到用户的Openid,如上文所示,用户id保存在System.Web.HttpContext.Current.Session["openid"]  中,所以获取也是非常简单

在需要获取的地方执行

         #region 获取当前用户Openid
                ReGetOpenId();
                log.log("走完获取openid的方法之后,当前Session的值是:" + System.Web.HttpContext.Current.Session["openid"]);
                #endregion

因为无法使用一般的url,所以是把程序部署在服务器上,无法进行调试,只有打印日志查看效果,最后点击链接,日志如下:

日志文件

用到的简单日志类也顺便提供放上来:

复制代码
/// <summary>
    /// 日志类
    /// </summary>
    public class Log
    {
        private string logFile;
        private StreamWriter writer;
        private FileStream fileStream = null;

        public Log(string fileName)
        {
            logFile = fileName;
            CreateDirectory(logFile);
        }

        public void log(string info)
        {

            try
            {
                System.IO.FileInfo fileInfo = new System.IO.FileInfo(logFile);
                if (!fileInfo.Exists)
                {
                    fileStream = fileInfo.Create();
                    writer = new StreamWriter(fileStream);
                }
                else
                {
                    fileStream = fileInfo.Open(FileMode.Append, FileAccess.Write);
                    writer = new StreamWriter(fileStream);
                }
                writer.WriteLine(DateTime.Now + ": " + info);

            }
            finally
            {
                if (writer != null)
                {
                    writer.Close();
                    writer.Dispose();
                    fileStream.Close();
                    fileStream.Dispose();
                }
            }
        }

        public void CreateDirectory(string infoPath)
        {
            DirectoryInfo directoryInfo = Directory.GetParent(infoPath);
            if (!directoryInfo.Exists)
            {
                directoryInfo.Create();
            }
        }
    }
复制代码

调用呢,很简单,调用方法如下:

     Log log = new Log(AppDomain.CurrentDomain.BaseDirectory + @"/log/Log.txt");
        log.log("我会被输入在日志文件中")

 

最后呢,拿到当前用户Openid,就可以从数据库再次获取到该用户的其他基本信息。从而可以更好的辅助你完成你项目中其他的业务模块。

 

                                                      来自:http://www.cnblogs.com/zhangxiaoyong/

 

2017-01-06 23:33:00 weixin_30834783 阅读数 13

 目录

(一)微信公众号开发之VS远程调试
(二)微信公众号开发之基础梳理
(三)微信公众号开发之自动消息回复和自定义菜单
(四)微信公众号开发之网页授权获取用户基本信息
(五)微信公众号开发之网页中及时获取当前用户Openid及注意事项
(六)微信公众号开发之扫码支付
(七)微信公众号开发之公众号支付
(八)微信公众号开发之现金红包
(九)微信公众号开发之回复图文消息(被动)

前言

上一篇我们大致讲解了下微信公众号开发的基本原理和流程概述。本章主要是对文本消息回复和自定义菜单做一个记录和分解

消息回复

处理请求,并响应

1)关注

也可参考官网文档:https://mp.weixin.qq.com/wiki

当微信用户关注公众账号时,可以给其适当的提示。可以是欢迎词,可以是帮助提示。示例代码如下:

 class EventHandler : IHandler
    {
        /// <summary>
        /// 请求的xml
        /// </summary>
        private string RequestXml { get; set; }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="requestXml"></param>
        public EventHandler(string requestXml)
        {
            this.RequestXml = requestXml;
        }
        /// <summary>
        /// 处理请求
        /// </summary>
        /// <returns></returns>
        public string HandleRequest()
        {
            string response = string.Empty;
            EventMessage em = EventMessage.LoadFromXml(RequestXml);
            if (em.Event.Equals("subscribe",StringComparison.OrdinalIgnoreCase))
            {
                //回复欢迎消息
                TextMessage tm = new TextMessage();
                tm.ToUserName = em.FromUserName;
                tm.FromUserName = em.ToUserName;
                tm.CreateTime = Common.GetNowTime();
                tm.Content = "欢迎您关注我们,我是服务小二,有事您开口~\n\n";
                response = tm.GenerateContent();
            }

            return response;
        }
    }

官方给出的介绍是这样的

关注/取消关注事件

用户在关注与取消关注公众号时,微信会把这个事件推送到开发者填写的URL。方便开发者给用户下发欢迎消息或者做帐号的解绑。

微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。

关于重试的消息排重,推荐使用FromUserName + CreateTime 排重。

假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。

推送XML数据包示例:

<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
</xml>

参数说明:

参数描述
ToUserName 开发者微信号
FromUserName 发送方帐号(一个OpenID)
CreateTime 消息创建时间 (整型)
MsgType 消息类型,event
Event 事件类型,subscribe(订阅)、unsubscribe(取消订阅)

也可使用微信在线接口调试工具测试是否正常:取消/关注事件接口在线调试

2)问候

简单的交流问候,比如你好、帮助等等,跟我们使用微信聊天一样,不过回应是由我们的程序响应。具体功能,可以根据自己的需要进行添加。
微信本来就是沟通的平台。这个案例,可以用于在线服务机器人,类似于淘宝的客服机器人,可是我们这个是微信版的。
其实,很简单,获取请求消息,根据关键字来匹配回应。当然这里可能要做的工作很多,如何支持智能匹配,如何支持模糊匹配等。

 /// <summary>
    /// 文本信息处理类
    /// </summary>
    public class TextHandler : IHandler
    {
        /// <summary>
        /// 请求的XML
        /// </summary>
        private string RequestXml { get; set; }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="requestXml">请求的xml</param>
        public TextHandler(string requestXml)
        {
            this.RequestXml = requestXml;
        }
        /// <summary>
        /// 处理请求
        /// </summary>
        /// <returns></returns>
        public string HandleRequest()
        {
            string response = string.Empty;
            TextMessage tm = TextMessage.LoadFromXml(RequestXml);
            string content = tm.Content.Trim();
            if (string.IsNullOrEmpty(content))
            {
                response = "您什么都没输入,没法帮您啊,%>_<%。";
            }
            else
            {
                if (content.StartsWith("tq", StringComparison.OrdinalIgnoreCase))
                {
                    string cityName = content.Substring(2).Trim();
                    response = WeatherHelper.GetWeather(cityName);
                }
                else
                {
                    response = HandleOther(content);
                }
            }
            tm.Content = response;
            //进行发送者、接收者转换
            string temp = tm.ToUserName;
            tm.ToUserName = tm.FromUserName;
            tm.FromUserName = temp;
            response = tm.GenerateContent();
            return response;
        }
        /// <summary>
        /// 处理其他消息
        /// </summary>
        /// <param name="tm"></param>
        /// <returns></returns>
        private string HandleOther(string requestContent)
        {
            string response = string.Empty;
            if (requestContent.Contains("你好") || requestContent.Contains("您好"))
            {
                response = "您也好~";
            }
            else if (requestContent.Contains(""))
            {
                response = "我不傻!哼~ ";
            }
            else if (requestContent.Contains("shit") || requestContent.Contains("小王八蛋"))
            {
                response = "哼,你说脏话! ";
            }
            else if (requestContent.Contains("是谁"))
            {
                response = "我是大哥大,有什么能帮您的吗?~";
            }
            else if (requestContent.Contains("再见"))
            {
                response = "再见!";
            }
            else if (requestContent.Contains("bye"))
            {
                response = "Bye!";
            }
            else if (requestContent.Contains("谢谢"))
            {
                response = "不客气!嘿嘿";
            }
            else if (requestContent == "h" || requestContent == "H" || requestContent.Contains("帮助"))
            {
                response = @"有事找警察";
            }
            else
            {
                response = "您说的,可惜,我没明白啊,试试其他关键字吧。";
            }

            return response;
        }
    }

实现效果如图所示:

自定义菜单

 首先呢,我们要了解下微信公众号开发里面菜单有哪些类型呢?

自定义菜单接口可实现多种类型按钮,如下:

请注意,3到8的所有事件,仅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。9和10,是专门给第三方平台旗下未微信认证(具体而言,是资质认证未通过)的订阅号准备的事件类型,它们是没有事件推送的,能力相对受限,其他类型的公众号不必使用。

接口调用请求说明

http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN

click和view的请求示例

 {
     "button":[
     {    
          "type":"click",
          "name":"今日歌曲",
          "key":"V1001_TODAY_MUSIC"
      },
      {
           "name":"菜单",
           "sub_button":[
           {    
               "type":"view",
               "name":"搜索",
               "url":"http://www.soso.com/"
            },
            {
               "type":"view",
               "name":"视频",
               "url":"http://v.qq.com/"
            },
            {
               "type":"click",
               "name":"赞一下我们",
               "key":"V1001_GOOD"
            }]
       }]
 }

参数说明

参数是否必须说明
button 一级菜单数组,个数应为1~3个
sub_button 二级菜单数组,个数应为1~5个
type 菜单的响应动作类型
name 菜单标题,不超过16个字节,子菜单不超过60个字节
key click等点击类型必须 菜单KEY值,用于消息接口推送,不超过128字节
url view类型必须 网页链接,用户点击菜单可打开链接,不超过1024字节
media_id media_id类型和view_limited类型必须 调用新增永久素材接口返回的合法media_id

 

当然也可以使用在线调试接口调试菜单是否设置正确:我要在线调试菜单接口

详细步骤

1、首先获取access_token 

access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。正常情况下access_token有效期为7200秒,重复获取将导致上次获取的access_token失效。

公众号可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在开发模式中获得(需要已经成为开发者,且帐号没有异常状态)。注意调用所有微信接口时均需使用https协议。

接口调用请求说明

http请求方式: GET
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

参数说明

参数是否必须说明
grant_type 获取access_token填写client_credential
appid 第三方用户唯一凭证
secret 第三方用户唯一凭证密钥,既appsecret

返回说明

正常情况下,微信会返回下述JSON数据包给公众号:

{"access_token":"ACCESS_TOKEN","expires_in":7200}
参数说明
access_token 获取到的凭证
expires_in 凭证有效时间,单位:秒

错误时微信会返回错误码等信息,JSON数据包示例如下(该示例为AppID无效错误):

{"errcode":40013,"errmsg":"invalid appid"} 

 

创建自定义菜单

自定义菜单能够帮助公众号丰富界面,让用户更好更快地理解公众号的功能。

目前自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“...”代替。请注意,创建自定义菜单后,由于微信客户端缓存,需要24小时微信客户端才会展现出来。建议测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。

3、查询菜单

使用接口创建自定义菜单后,开发者还可使用接口查询自定义菜单的结构。

请求说明

http请求方式:GET
https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN

返回说明

对应创建接口,正确的Json返回结果:
{"menu":{"button":[{"type":"click","name":"今日歌曲","key":"V1001_TODAY_MUSIC","sub_button":[]},{"type":"click","name":"歌手简介","key":"V1001_TODAY_SINGER","sub_button":[]},{"name":"菜单","sub_button":[{"type":"view","name":"搜索","url":"http://www.soso.com/","sub_button":[]},{"type":"view","name":"视频","url":"http://v.qq.com/","sub_button":[]},{"type":"click","name":"赞一下我们","key":"V1001_GOOD","sub_button":[]}]}]}}

4、删除菜单

使用接口创建自定义菜单后,开发者还可使用接口删除当前使用的自定义菜单。

请求说明

http请求方式:GET
https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN

返回说明

对应创建接口,正确的Json返回结果:
{"errcode":0,"errmsg":"ok"}

5、事件处理

用户点击自定义菜单后,如果菜单按钮设置为click类型,则微信会把此次点击事件推送给开发者,注意view类型(跳转到URL)的菜单点击不会上报。

推送XML数据包示例:

<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[CLICK]]></Event>
<EventKey><![CDATA[EVENTKEY]]></EventKey>
</xml>

参数说明:

 

 

参数描述
ToUserName 开发者微信号
FromUserName 发送方帐号(一个OpenID)
CreateTime 消息创建时间 (整型)
MsgType 消息类型,event
Event 事件类型,CLICK
EventKey 事件KEY值,与自定义菜单接口中KEY值对应

实例讲解

我们将会在上一篇的基础上,添加自定义菜单的功能

1、获取access_token

首先需要得到AppId和AppSecret

当你成为开发者后,自然能够在,开发者模式,便可看到这两个值,可以重置。

然后调用按照二.1中所示,进行操作。

注意:access_token有过期时间,如果过期,需要重新获取。

代码如下:

  private static DateTime GetAccessToken_Time;
        /// <summary>
        /// 过期时间为7200秒
        /// </summary>
        private static int Expires_Period = 7200;
        /// <summary>
        /// 
        /// </summary>
        private static string mAccessToken;
        /// <summary>
        /// 
        /// </summary>
        public static string AccessToken
        {
            get
            {
                //如果为空,或者过期,需要重新获取
                if (string.IsNullOrEmpty(mAccessToken) || HasExpired())
                {
                    //获取
                    mAccessToken = GetAccessToken(AppID, AppSecret);
                }

                return mAccessToken;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="appSecret"></param>
        /// <returns></returns>
        private static string GetAccessToken(string appId, string appSecret)
        {
            string url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", appId, appSecret);
            string result = HttpUtility.GetData(url);

            XDocument doc = XmlUtility.ParseJson(result, "root");
            XElement root = doc.Root;
            if (root != null)
            {
                XElement access_token = root.Element("access_token");
                if (access_token != null)
                {
                    GetAccessToken_Time = DateTime.Now;
                    if (root.Element("expires_in")!=null)
                    {
                        Expires_Period = int.Parse(root.Element("expires_in").Value);
                    }
                    return access_token.Value;
                }
                else
                {
                    GetAccessToken_Time = DateTime.MinValue;
                }
            }

            return null;
        }
        /// <summary>
        /// 判断Access_token是否过期
        /// </summary>
        /// <returns>bool</returns>
        private static bool HasExpired()
        {
            if (GetAccessToken_Time != null)
            {
                //过期时间,允许有一定的误差,一分钟。获取时间消耗
                if (DateTime.Now > GetAccessToken_Time.AddSeconds(Expires_Period).AddSeconds(-60))
                {
                    return true;
                }
            }
            return false;
        }

2、设置菜单

菜单需根据需要,按照实际要求进行设定。

这里我们只做简单的演示。

然后还提供了友情链接,这里提供了view类型的菜单,直接可以跳转至URL页面,为跳转做个好的演示。

具体菜单如下:

{

    "button": [
        {
            "name": "测试跳转", 
            "sub_button": [
                {
                    "type": "view", 
                    "name": "搜索", 
                    "url": "http://www.baidu.com/"
                }, 
                {
                    "type": "view", 
                    "name": "视频", 
                    "url": "http://v.qq.com/"
                }, 
                {
                    "type": "click", 
                    "name": "赞一下我们", 
                    "key": "BTN_GOOD"
                }
            ]
        }, 
        {
            "type": "view",
            "name": "设备状态", 
            "url": "http://vanrui.com/weixin"
        }, 
        {
            "type": "click", 
            "name": "帮助", 
            "key": "BTN_HELP"
        }
    ]
}

3、管理菜单

因为菜单的变更没有那么频繁,因此通过txt文件来设置菜单,并通过管理界面来管理菜单。

主要的管理功能:

1)从文件加载菜单

2)创建菜单。即将菜单通知微信服务端,并更新至微信客户端

3)查询菜单。获取当前系统的菜单。

4)删除菜单。从微信服务器删除菜单,也可以删除后再创建。 

实现代码如下:

public class MenuManager
    {
        /// <summary>
        /// 菜单文件路径
        /// </summary>
        private static readonly string Menu_Data_Path = System.AppDomain.CurrentDomain.BaseDirectory + "/Data/menu.txt";
        /// <summary>
        /// 获取菜单
        /// </summary>
        public static string GetMenu()
        {
            string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/get?access_token={0}", Context.AccessToken);

            return HttpUtility.GetData(url);
        }
        /// <summary>
        /// 创建菜单
        /// </summary>
        public static void CreateMenu(string menu)
        {
            string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/create?access_token={0}", Context.AccessToken);
            //string menu = FileUtility.Read(Menu_Data_Path);
            HttpUtility.SendHttpRequest(url, menu);
        }
        /// <summary>
        /// 删除菜单
        /// </summary>
        public static void DeleteMenu()
        {
            string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/delete?access_token={0}", Context.AccessToken);
            HttpUtility.GetData(url);
        }
        /// <summary>
        /// 加载菜单
        /// </summary>
        /// <returns></returns>
        public static string LoadMenu()
        {
            return FileUtility.Read(Menu_Data_Path);
        }
    }

4、基本方法

上面的代码,其实我们对一些公共功能做了封装。如进行get请求、POST提交等操作,读取文件等。

这里我们提供进行get、Post提交的方法案例代码,如果使用,建议优化。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace WeChat.Utility
{
    /// <summary>
    /// 帮助类
    /// </summary>
    class HttpUtility
    {
        /// <summary>
        /// 发送请求
        /// </summary>
        /// <param name="url">Url地址</param>
        /// <param name="data">数据</param>
        public static string SendHttpRequest(string url, string data)
        {
            return SendPostHttpRequest(url, "application/x-www-form-urlencoded", data);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static string GetData(string url)
        {
            return SendGetHttpRequest(url, "application/x-www-form-urlencoded");
        }

        /// <summary>
        /// 发送请求
        /// </summary>
        /// <param name="url">Url地址</param>
        /// <param name="method">方法(post或get)</param>
        /// <param name="method">数据类型</param>
        /// <param name="requestData">数据</param>
        public static string SendPostHttpRequest(string url, string contentType, string requestData)
        {
            WebRequest request = (WebRequest)HttpWebRequest.Create(url);
            request.Method = "POST";
            byte[] postBytes = null;
            request.ContentType = contentType;
            postBytes = Encoding.UTF8.GetBytes(requestData);
            request.ContentLength = postBytes.Length;
            using (Stream outstream = request.GetRequestStream())
            {
                outstream.Write(postBytes, 0, postBytes.Length);
            }
            string result = string.Empty;
            using (WebResponse response = request.GetResponse())
            {
                if (response != null)
                {
                    using (Stream stream = response.GetResponseStream())
                    {
                        using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
                        {
                            result = reader.ReadToEnd();
                        }
                    }

                }
            }
            return result;
        }

        /// <summary>
        /// 发送请求
        /// </summary>
        /// <param name="url">Url地址</param>
        /// <param name="method">方法(post或get)</param>
        /// <param name="method">数据类型</param>
        /// <param name="requestData">数据</param>
        public static string SendGetHttpRequest(string url, string contentType)
        {
            WebRequest request = (WebRequest)HttpWebRequest.Create(url);
            request.Method = "GET";
            request.ContentType = contentType;
            string result = string.Empty;
            using (WebResponse response = request.GetResponse())
            {
                if (response != null)
                {
                    using (Stream stream = response.GetResponseStream())
                    {
                        using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
                        {
                            result = reader.ReadToEnd();
                        }
                    }
                }
            }
            return result;
        }
    }
}
class XmlUtility
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="json"></param>
        /// <param name="rootName"></param>
        /// <returns></returns>
        public static XDocument ParseJson(string json,string rootName)
        {
            return JsonConvert.DeserializeXNode(json, rootName);
        }
    }

5、事件处理

设置了菜单,这下需要处理事件了。跟我们之前设计ASPX或者WinForm一样,都要绑定按钮的事件。这里只是通过XML消息将请求传递过来。

通过“2、设置菜单"中具体的菜单内容,我们便已经知道需要进行哪些事件处理了。对于按钮类型为view的,无须处理,它会自动跳转至指定url.

需要处理的点击事件:

1)赞一下

2)帮助

这个还要沿用上章中的事件处理器EventHandler来扩展处理。

具体的实现代码吧:

class EventHandler : IHandler
    {
        /// <summary>
        /// 请求的xml
        /// </summary>
        private string RequestXml { get; set; }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="requestXml"></param>
        public EventHandler(string requestXml)
        {
            this.RequestXml = requestXml;
        }
        /// <summary>
        /// 处理请求
        /// </summary>
        /// <returns></returns>
        public string HandleRequest()
        {
            string response = string.Empty;
            EventMessage em = EventMessage.LoadFromXml(RequestXml);
            if (em != null)
            {
                switch (em.Event.ToLower())
                {
                    case ("subscribe"):
                        response = SubscribeEventHandler(em);
                        break;
                    case "click":
                        response = ClickEventHandler(em);
                        break;
                }
            }

            return response;
        }
        /// <summary>
        /// 关注
        /// </summary>
        /// <param name="em"></param>
        /// <returns></returns>
        private string SubscribeEventHandler(EventMessage em)
        {
            //回复欢迎消息
            TextMessage tm = new TextMessage();
            tm.ToUserName = em.FromUserName;
            tm.FromUserName = em.ToUserName;
            tm.CreateTime = Common.GetNowTime();
            tm.Content = "欢迎您关注我们,我是服务小二,有事您说话~\n\n";
            return tm.GenerateContent();
        }
        /// <summary>
        /// 处理点击事件
        /// </summary>
        /// <param name="em"></param>
        /// <returns></returns>
        private string ClickEventHandler(EventMessage em)
        {
            string result = string.Empty;
            if (em != null && em.EventKey != null)
            {
                switch (em.EventKey.ToUpper())
                {
                    case "BTN_GOOD":
                        result = btnGoodClick(em);
                        break;
                    case "BTN_HELP":
                        result = btnHelpClick(em);
                        break;
                }
            }

            return result;
        }
        /// <summary>
        /// 赞一下
        /// </summary>
        /// <param name="em"></param>
        /// <returns></returns>
        private string btnGoodClick(EventMessage em)
        {
            //回复欢迎消息
            TextMessage tm = new TextMessage();
            tm.ToUserName = em.FromUserName;
            tm.FromUserName = em.ToUserName;
            tm.CreateTime = Common.GetNowTime();
            tm.Content = @"谢谢您的支持!";
            return tm.GenerateContent();
        }
        /// <summary>
        /// 帮助
        /// </summary>
        /// <param name="em"></param>
        /// <returns></returns>
        private string btnHelpClick(EventMessage em)
        {
            //回复欢迎消息
            TextMessage tm = new TextMessage();
            tm.ToUserName = em.FromUserName;
            tm.FromUserName = em.ToUserName;
            tm.CreateTime = Common.GetNowTime();
            tm.Content = @"有事找警察~";
            return tm.GenerateContent();
        }
      

调试效果

图中点击的帮助菜单,如果大家仔细观察,会发现后面断点进入第二次了,这是因为前面提到的,微信服务器5秒未回复,就会重复尝试发3次请求,所以就会有多次进入断点的效果。

最终效果

Demo下载

链接:点我下载  密码:41dt

Git地址:https://github.com/XiaoYong666/-Demo

 

未完待续,持续填坑中。。。。

 

注:文中部分内容截取自:http://www.cnblogs.com/yank/p/3418194.html

转载于:https://www.cnblogs.com/zhangxiaoyong/p/6257981.html

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