微信分享_微信分享接口 - CSDN
  • 想在微站里面实现分享帖子给朋友和朋友圈,显示图片和简介,就这么简单的功能折腾了1星期。。。主要是微信官方文档没看清楚,怪自己了。官方文档在这里,...

    想在微站里面实现分享帖子给朋友和朋友圈,显示图片和简介,就这么简单的功能折腾了1星期。。。主要是微信官方文档没看清楚,怪自己了。

    官方文档在这里,https://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html

    参考 http://www.cnblogs.com/stoneniqiu/p/6286599.html 这篇文章。

    遇到invalid signature签名错误。找了半天,各种调试,终于找到问题了,每个新闻的id是变动的,

    url需要传入完整的地址,在微信官方手册里面查到的。

    比如页面是http://www.baidu.com/wx.aspx?id=111,需要完整传入,不能仅仅在url里面传入http://www.baidu.com/wx.aspx

    下面是 stoneniqiu  的具体做法,大家可以参考一下。

     

    内嵌在微信中的网页,右上角都会有一个默认的分享功能。如下图所示,第一个为自定义的效果,第二个为默认的效果。实现了自定义的分享链接是不是更让人有点击的欲望?下面讲解下开发的过程。

    一、准备,设置js接口安全域名

    这需要使用微信的jssdk,先需要在微信公众号后台进行设置:公众号设置-->功能设置-->JS接口安全域名。打开这个页面之后你会看到下面的提示。需要先下载这个文件并上传到指定域名的根目录。

    这个文件里面是一个字符串,从名称看是用来校验用的。先上传了这个文件,你才能保存成功。这样你就可以使用jssdk了。

     二、前端配置

     首先要说明的是分享功能是一个配置功能,绑定在按钮的click事件中是没有效果的。也就是说只有点击右上角的分享才有效果(有的文字内容分享不知道是怎么实现的)。官方的js有四个步骤,首先是引入jssdk:

    <script src="http://res.wx.qq.com/open/js/jweixin-1.1.0.js"></script>

    根据官方的配置参数,我们可以定义一个WXShareModel对象:

    复制代码
    复制代码
       public class WXShareModel
        {
            public string appId { get; set; }
            public string nonceStr { get; set; }
            public long timestamp { get; set; }
    
            public string signature { get; set; }
    
            public string ticket { get; set; }
            public string url { get; set; }
    
            public void MakeSign()
            {
                 var string1Builder = new StringBuilder();
                 string1Builder.Append("jsapi_ticket=").Append(ticket).Append("&")
                              .Append("noncestr=").Append(nonceStr).Append("&")
                              .Append("timestamp=").Append(timestamp).Append("&")
                              .Append("url=").Append(url.IndexOf("#") >= 0 ? url.Substring(0, url.IndexOf("#")) : url);
                var string1 = string1Builder.ToString();
                signature = Util.Sha1(string1, Encoding.Default);
    
            }
        }
    复制代码
    复制代码

    然后是进行配置:

    复制代码
    复制代码
    wx.config({
            debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
            appId: '@Model.appId', // 必填,公众号的唯一标识
            timestamp: '@Model.timestamp', // 必填,生成签名的时间戳
            nonceStr: '@Model.nonceStr', // 必填,生成签名的随机串
            signature: '@Model.signature',// 必填,签名,见附录1
            jsApiList: ["checkJsApi", "onMenuShareTimeline", "onMenuShareAppMessage", "onMenuShareQQ", "onMenuShareQZone"] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
        });
    
        wx.ready(function () {
            document.querySelector('#checkJsApi').onclick = function () {
                wx.checkJsApi({
                    jsApiList: [
                'getNetworkType',
                'previewImage'
                    ],
                    success: function (res) {
                        alert(JSON.stringify(res));
                    }
                });
            };
        //朋友圈
            wx.onMenuShareTimeline({
                title: '暖木科技', // 分享标题
                link: 'http://www.warmwood.com/home/lampindex', // 分享链接
                imgUrl: 'http://www.warmwood.com/images/s1.jpg',
                success: function (res) {
                    alert('已分享');
                },
                cancel: function (res) {
                    alert('已取消');
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                }
            });
            //朋友
            wx.onMenuShareAppMessage({
                title: '暖木科技', // 分享标题
                desc: '宝宝的睡眠很重要,你的睡眠也很重要', // 分享描述
                link: 'http://www.warmwood.com/home/lampindex', // 分享链接
                imgUrl: 'http://www.warmwood.com/images/s1.jpg', // 分享图标
                type: '', // 分享类型,music、video或link,不填默认为link
                dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
                success: function () {
                    // 用户确认分享后执行的回调函数
                    alert("分享");
                },
                cancel: function () {
                    // 用户取消分享后执行的回调函数
                    alert("取消分享");
                }
            });
        });
    复制代码
    复制代码

    然后剩下就是后端的事情了。后端的关键是获取access_token和jsapi_ticket以及生成正确的签名。另外如果要统计分享的数量,最好就是在success方法中进行统计了。

    三、生成签名

    1.access_token 

    获取access_token方法全平台都是一致的。

    public const string AccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}";
    复制代码
    复制代码
     public TokenResult GetAccessToken()
            {
                var url = string.Format(WxDeviceConfig.AccessTokenUrl, WxDeviceConfig.AppId, WxDeviceConfig.APPSECRET);
                var res = SendHelp.Send<TokenResult>(null, url, null, CommonJsonSendType.GET);
                return res;
            }
    复制代码
    复制代码

    access_token的超时时间是7200秒,所以先可以缓存起来。SendHelp文章末尾可下载

    2.获取jsapi_ticket

    access_token的作用就是为了获取jsapi_ticket。用get方式获取,url:https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi,返回的JSON对象如下。

    复制代码
    复制代码
    {
    "errcode":0,
    "errmsg":"ok",
    "ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
    "expires_in":7200
    }
    复制代码
    复制代码

    所以可以定义一个模型:

    复制代码
    复制代码
    public class jsapiTicketModel
        {
            public string errcode { get; set; }
            public string errmsg { get; set; }
    
            public string ticket { get; set; }
    
            public string expires_in { get; set; }
        }
    复制代码
    复制代码

    再完成获取ticket的方法:

     public jsapiTicketModel GetJsApiTicket(string accessToken)
            {
                var url = string.Format(WxPayConfig.Jsapi_ticketUrl, accessToken);
                return SendHelp.Send<jsapiTicketModel>(accessToken, url, "", CommonJsonSendType.GET);
            }

    ticket过期时间也是7200秒,并且不能频繁的请求,所以也需要再服务端缓存起来。

     private void setCacheTicket(string cache)
            {
                _cacheManager.Set(tokenKey, cache, 7200);
            }

    MemoryCacheManager:

    View Code

    3.签名

    终于到这一步了,然后你在文档中看到让你失望的一幕:

    么有C#的demo,支付那边都提供了,为啥jssdk没有提供,好吧先不吐槽了。官方也说明白签名的规则。一开始我使用的是https://github.com/night-king/weixinSDK中的签名:

    复制代码
    复制代码
     public static string Sha1(string orgStr, string encode = "UTF-8")
            {
                var sha1 = new SHA1Managed();
                var sha1bytes = System.Text.Encoding.GetEncoding(encode).GetBytes(orgStr);
                byte[] resultHash = sha1.ComputeHash(sha1bytes);
                string sha1String = BitConverter.ToString(resultHash).ToLower();
                sha1String = sha1String.Replace("-", "");
                return sha1String;
            }//错误示例
    复制代码
    复制代码

    得出的结果和官方校验的不一致,一直提示签名错误。

     

     正确的写法是:

    复制代码
    复制代码
    public static string Sha1(string orgStr, Encoding encode)
            {
                SHA1 sha1 = new SHA1CryptoServiceProvider();
                byte[] bytes_in = encode.GetBytes(orgStr);
                byte[] bytes_out = sha1.ComputeHash(bytes_in);
                sha1.Dispose();
                string result = BitConverter.ToString(bytes_out);
                result = result.Replace("-", "");
                return result;  
            }
    复制代码
    复制代码

    和官方校验的结果一直后,就ok了(忽略大小写)。另外一个需要注意的地方是签名中的url。如果页面有参数,model中的url也需要带参数,#号后面的不要。不然也是会报签名错误。

    复制代码
    复制代码
     public ActionResult H5Share()
            {
                var model = new WXShareModel();
                model.appId = WxPayConfig.APPID;
                model.nonceStr = WxPayApi.GenerateNonceStr();
                model.timestamp = Util.CreateTimestamp();
                model.ticket = GetTicket();
                model.url = "http://www.warmwood.com/AuthWeiXin/share";// domain + Request.Url.PathAndQuery;
                model.MakeSign();
                Logger.Debug("获取到ticket:" + model.ticket);
                Logger.Debug("获取到签名:" + model.signature);
                return View(model);
            }
    复制代码
    复制代码

    四、小结

    wx.config中的debug为true会alert各种操作结果。参数正确之后界面会提示:

     

    至此,分享的功能就ok了。也就打开了调用其他jssdk的大门。另外文中的SendHelp对象是用的Senparc (基于.net4.5)的dll。

    参考资料:

    签名校验:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign

    官方文档:https://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html

     

    我的核心代码

    复制代码
    [System.Web.Services.WebMethod]
        public static WXShareModel GetKey(string str)
        {
            WXShareModel aModel = new WXShareModel();
            WXToolsHelper tb = new WXToolsHelper();
            string AppId = "你的APPID";
            string secret = "你的secret";
            string access_token = tb.GetAccess_Token(AppId, secret);
            aModel.appId = AppId;
            aModel.nonceStr = tb.CreatenNonce_str();
            aModel.timestamp = tb.CreatenTimestamp();
            aModel.ticket = tb.GetTicket(access_token);
            aModel.url = str;
            aModel.MakeSign();
            return aModel;
        }
    public class WXShareModel
        {
            public string appId { get; set; }
            public string nonceStr { get; set; }
            public long timestamp { get; set; }
            public string ticket { get; set; }
            public string url { get; set; }
            public string signature { get; set; }
    
            public void MakeSign()
            {
                var string1Builder = new StringBuilder();
                string1Builder.Append("jsapi_ticket=").Append(ticket).Append("&")
                             .Append("noncestr=").Append(nonceStr).Append("&")
                             .Append("timestamp=").Append(timestamp).Append("&")
                             .Append("url=").Append(url.IndexOf("#") >= 0 ? url.Substring(0, url.IndexOf("#")) : url);
                var string1 = string1Builder.ToString();
                signature = Sha1(string1, Encoding.Default);
            }
     public static string Sha1(string orgStr, Encoding encode)
            {
                SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
                byte[] bytes_in = encode.GetBytes(orgStr);
                byte[] bytes_out = sha1.ComputeHash(bytes_in);
                sha1.Dispose();
                string result = BitConverter.ToString(bytes_out);
                result = result.Replace("-", "");
                return result;
            }
    public class WXToolsHelper
        {
            /// <summary>
            /// 获取全局的access_token,程序缓存
            /// </summary>
            /// <param name="AppId">第三方用户唯一凭证</param>
            /// <param name="AppSecret">第三方用户唯一凭证密钥,即appsecret</param>
            /// <returns>得到的全局access_token</returns>
            public string GetAccess_Token(string AppId, string AppSecret)
            {
                try
                {
                    //先查缓存数据
                    if (HttpContext.Current.Cache["access_token"] != null)
                    {
                        return HttpContext.Current.Cache["access_token"].ToString();
                    }
                    else
                    {
                        return GetToken(AppId, AppSecret);
                    }
                }
                catch
                {
                    return GetToken(AppId, AppSecret);
                }
            }
    
            /// <summary>
            /// 获取全局的access_token
            /// </summary>
            /// <param name="AppId">第三方用户唯一凭证</param>
            /// <param name="AppSecret">第三方用户唯一凭证密钥,即appsecret</param>
            /// <returns>得到的全局access_token</returns>
            public string GetToken(string AppId, string AppSecret)
            {
                var client = new System.Net.WebClient();
                client.Encoding = System.Text.Encoding.UTF8;
                var url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", AppId, AppSecret);
                var data = client.DownloadString(url);
                var jss = new JavaScriptSerializer();
                var access_tokenMsg = jss.Deserialize<Dictionary<string, object>>(data);
                //放入缓存中
                HttpContext.Current.Cache.Insert("access_token", access_tokenMsg["access_token"], null, DateTime.Now.AddSeconds(7100), TimeSpan.Zero, CacheItemPriority.Normal, null);
    
                //清除jsapi_ticket缓存
                HttpContext.Current.Cache.Remove("ticket");
    
                //获取jsapi_ticket,为了同步
                GetTicket(access_tokenMsg["access_token"].ToString());
    
                return access_tokenMsg["access_token"].ToString();
            }
    
    
            /// <summary>
            /// 获取jsapi_ticket,程序缓存
            /// </summary>
            /// <param name="access_token">全局的access_token</param>
            /// <returns>得到的jsapi_ticket</returns>
            public string GetJsapi_Ticket(string access_token)
            {
                try
                {
                    //先查缓存数据
                    if (HttpContext.Current.Cache["ticket"] != null)
                    {
                        return HttpContext.Current.Cache["ticket"].ToString();
                    }
                    else
                    {
                        return GetTicket(access_token);
                    }
                }
                catch
                {
                    return GetTicket(access_token);
                }
            }
    
    
            /// <summary>
            /// 获取jsapi_ticket
            /// </summary>
            /// <param name="access_token">全局的access_token</param>
            /// <returns>得到的jsapi_ticket</returns>
            public string GetTicket(string access_token)
            {
                var client = new System.Net.WebClient();
                client.Encoding = System.Text.Encoding.UTF8;
                var url = string.Format("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={0}&type=jsapi", access_token);
                var data = client.DownloadString(url);
                var jss = new JavaScriptSerializer();
                var ticketMsg = jss.Deserialize<Dictionary<string, object>>(data);
                try
                {
                    //放入缓存中
                    HttpContext.Current.Cache.Insert("ticket", ticketMsg["ticket"], null, DateTime.Now.AddSeconds(7100), TimeSpan.Zero, CacheItemPriority.Normal, null);
                    return ticketMsg["ticket"].ToString();
                }
                catch (Exception ex)
                {
                    return ex.Message;
                }
            }
    
            /// <summary>
            /// 微信权限签名的 sha1 算法
            /// 签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同
            /// </summary>
            /// <param name="jsapi_ticket">获取到的jsapi_ticket</param>
            /// <param name="noncestr">生成签名的随机串</param>
            /// <param name="timestamp">生成签名的时间戳</param>
            /// <param name="url">签名用的url必须是调用JS接口页面的完整URL</param>
            /// <returns></returns>
            public string GetShal(string jsapi_ticket, string noncestr, long timestamp, string url)
            {
                string strSha1 = string.Format("jsapi_ticket={0}&noncestr={1}&timestamp={2}&url={3}", jsapi_ticket, noncestr, timestamp, url);
                return System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(strSha1, "sha1").ToLower();
            }
    
            /// <summary>
            /// 微信权限签名( sha1 算法 )
            /// 签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同
            /// </summary>
            /// <param name="AppId">第三方用户唯一凭证</param>
            /// /// <param name="AppSecret">第三方用户唯一凭证密钥,即appsecret</param>
            /// <param name="noncestr">生成签名的随机串</param>
            /// <param name="timestamp">生成签名的时间戳</param>
            /// <param name="url">签名用的url必须是调用JS接口页面的完整URL</param>
            /// <returns></returns>
            public string Get_Signature(string AppId, string AppSecret, string noncestr, long timestamp, string url)
            {
                string access_token = GetAccess_Token(AppId, AppSecret); //获取全局的access_token
                string jsapi_ticket = GetJsapi_Ticket(access_token); //获取jsapi_ticket
    
                string strSha1 = string.Format("jsapi_ticket={0}&noncestr={1}&timestamp={2}&url={3}", jsapi_ticket, noncestr, timestamp, url);
                return System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(strSha1, "sha1").ToLower();
            }
    
    
            /// <summary>
            /// 微信权限签名( sha1 算法 )
            /// 签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同
            /// </summary>
            /// <param name="AppId">第三方用户唯一凭证</param>
            /// /// <param name="AppSecret">第三方用户唯一凭证密钥,即appsecret</param>
            /// <param name="noncestr">生成签名的随机串</param>
            /// <param name="timestamp">生成签名的时间戳</param>
            /// <param name="url">签名用的url必须是调用JS接口页面的完整URL</param>
            /// <returns></returns>
            public void signatureOut(string AppId, string AppSecret, string noncestr, long timestamp, string url, out string access_token, out string jsapi_ticket, out string signature)
            {
                access_token = GetAccess_Token(AppId, AppSecret); //获取全局的access_token
    
                jsapi_ticket = GetJsapi_Ticket(access_token); //获取jsapi_ticket
    
                string strSha1 = string.Format("jsapi_ticket={0}&noncestr={1}&timestamp={2}&url={3}", jsapi_ticket, noncestr, timestamp, url);
    
                signature = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(strSha1, "sha1").ToLower();
            }
    
            private string[] strs = new string[]
    {
    "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
    "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"
    };
            /// <summary>
            /// 创建随机字符串 
            /// </summary>
            /// <returns></returns>
            public string CreatenNonce_str()
            {
                Random r = new Random();
                var sb = new StringBuilder();
                var length = strs.Length;
                for (int i = 0; i < 15; i++)
                {
                    sb.Append(strs[r.Next(length - 1)]);
                }
                return sb.ToString();
            }
    
    
            /// <summary>
            /// 创建时间戳 
            /// </summary>
            /// <returns></returns>
            public long CreatenTimestamp()
            {
                return (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000;
            }
    
    
        }
    复制代码

     

     

    前段调用

    复制代码
    var strUrl = location.href.split('#')[0];
    $.ajax({
        type: "Post",
        url: "config.aspx/GetKey",
        //方法传参的写法一定要对,strUrl为形参的名字    
        data: "{'str':'" + strUrl + "'}",
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: function (data) {
            //返回的数据用data.d获取内容    
            $("#wx-share-sign").val(data.d.signature);
            wxconifg(data.d);
        },
        error: function (err) {
            alert('55');
        }
    });
    function wxconifg(WXDate) {
        wx.config({
            debug: false,
            appId: '你的APPID',
            timestamp: WXDate.timestamp,
            nonceStr: WXDate.nonceStr,
            signature: WXDate.signature,
            jsApiList: ["checkJsApi", "onMenuShareTimeline", "onMenuShareAppMessage", "onMenuShareQQ", "onMenuShareQZone"]
        });
        wx.ready(function () {
            wx.onMenuShareAppMessage({
                title: $("#wx-share-title").val(),
                desc: $("#wx-share-desc").val(),
                link: strUrl,
                imgUrl: $("#wx-share-img").val(),
                trigger: function (res) {
                },
                success: function (res) {
                },
                cancel: function (res) {
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                }
            });
            //分享到朋友圈
            wx.onMenuShareTimeline({
                title: 'XX新闻|'+$("#wx-share-desc").val(),
                desc: $("#wx-share-desc").val(),
                link: $("#wx-share-link").val(),
                imgUrl: $("#wx-share-img").val(),
                type: 'link',
                dataUrl: strUrl,
                trigger: function (res) {
                },
                success: function (res) {
                },
                cancel: function (res) {
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                }
            });
        });
    } 
    复制代码
    展开全文
  • 微信分享获得图片和文字描述,微信分享源码,手机网页分享源码,独立编写源码,帮助微信初学者学习分享代码源码
  • ​想必大家都在自己的微信朋友圈看到过别人分享的一些网页链接消息,但是你...今天要跟大家分享的就是如何让自己的网页在微信分享朋友圈中拥有一个自己个性的图标以及自定义分享中的描述文字。--->详情见我的博客哦!
  • 微信分享功能

    2018-07-11 10:27:44
    最近做微信分享功能,第一次做,看了好久的官方文档,最终解决了,在这里记录一下。1.登录微信公众平台:https://mp.weixin.qq.com/需要在微信公众号后台进行设置:公众号设置--&gt;功能设置--&gt;JS接口...

    最近做微信分享功能,第一次做,看了好久的官方文档,最终解决了,在这里记录一下。

    1.登录微信公众平台:https://mp.weixin.qq.com/

    需要在微信公众号后台进行设置:公众号设置-->功能设置-->JS接口安全域名。打开这个页面之后你会看到下面的提示。需要先下载这个文件并上传到指定域名的根目录(配置的域名+MP_verify_bjZ2nipw3mAGlvUF.txt必需要能访问才能保存成功)。

    2.前台页面的开发

    页面中引入<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>   
    和以下代码;

    $(function(){
        var busiParam={"busiData":{}};
        var title,desc,link,imgUrl;
        //对ajax的封装(自己用的框架,你们可以直接用ajax),去后台数据库查询分享的标题,图片路径,描述,link
        stc.rest.query("/AgentWechat/ShareData",{"busiParam":JSON.stringify(busiParam)},function(data){
            var wechatData=data.shareData[0];
            title=wechatData.wechat_title;
            desc=wechatData.wechat_description;
            link=location.href.split('#')[0];
            imgUrl=wechatData.wechat_imgurl;
                });
        var url= location.href.split('#')[0];
        var appId,timestamp,nonceStr,signature;
        var busiParam={"busiData":{"url":url}};
        //对ajax的封装,根据appId等数据,得到以下数据
        stc.rest.query("/AgentWechat/wechatShare",{"busiParam":JSON.stringify(busiParam)},function(data){
            appId=data.appid;// 必填,公众号的唯一标识  
            timestamp=data.timestamp; // 必填,生成签名的时间戳  
            nonceStr=data.noncestr;// 必填,生成签名的随机串  
            signature= data.signature;// 必填,签名  
            wx.config({    
                debug: false,  //是否开启调试模式 
                appId: appId,    
                timestamp: timestamp,    
                nonceStr: nonceStr,    
                signature: signature,    
                jsApiList: [    
                            'onMenuShareTimeline',    //调用的接口
                            'onMenuShareAppMessage'    
                            ]    
            });
        });
        wx.ready(function () {    
            var shareData = {    
                    title: title,    
                    desc: desc,    
                    link: link,    
                    imgUrl: imgUrl,    
                    success: function (res) {    
                        console('已分享');    
                    },    
                    cancel: function (res) {    
                    }    
            };    
            wx.onMenuShareAppMessage({    
                title: title,    
                desc: desc,    
                link: link,    
                imgUrl: imgUrl,    
                trigger: function (res) {    
                    //  console('用户点击发送给朋友');    
                },    
                success: function (res) {    
                    console('已分享');    
                },    
                cancel: function (res) {    
                    //alert('已取消');    
                },    
                fail: function (res) {    
                    console(JSON.stringify(res));    
                }    
            });    
            wx.onMenuShareTimeline(shareData);    
        });    
        wx.error(function (res) {    
            console("error: " + res.errMsg);    
        });    

    });


    3.后台开发,主要是根据appid,AppSecret等数据获取参数

    @RequestMapping(value="/wechatShare",method=RequestMethod.GET)
        @ResponseBody
        public JSONObject wechatShare(HttpServletRequest request,HttpServletResponse response,
                Locale  locale) throws Exception{
            JSONObject json = new JSONObject();
            String[] params=new String[]{"url"};
            Map<String,Object> paramMap=RequestUtils.getParamMap(request, params);
            logger.info("~~~~~url~~~~~::"+paramMap.get("url").toString().replace("&amp;", "&"));
            // 微信appid  
            String appid = bank_config.getString("WECHAY_APPID");
            logger.info("~~~~~appid~~~~~::"+appid);
            // 微信secret   
            String secret = bank_config.getString("WECHAT_AppSecret");
            logger.info("~~~~~AppSecret~~~~~::"+secret);
            // 初始化access_token  
            String access_token = "";  
            //  获取URL 这里的URL指的是需要分享的那个页面地址,建议这里不要写成固定地址,而是获取当前地址.  
            String url = paramMap.get("url").toString().replace("&amp;", "&");

            // 创建通过Api获取Token的链接与参数  
            String requestTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=SECRET";  
            requestTokenUrl = requestTokenUrl.replace("APPID", appid);  
            requestTokenUrl = requestTokenUrl.replace("SECRET", secret);  
            logger.info("~~~~~requestTokenUrl~~~~~::"+requestTokenUrl);
            JSONObject jsonObject2 = CommonUtil.httpsRequestResult(appid, secret);
            logger.info("~~~~~jsonObject2~~~~~::"+jsonObject2);
            if(jsonObject2!=null){
                // 创建日期赋值为当前日期  
                long  createDate =new Date().getTime()/1000;
                // 获取Token值  
                access_token = jsonObject2.getString("access_token");
                logger.info("~~~~~access_token~~~~~::"+access_token);
                // 获取Token有效期值  
                String expires_in = jsonObject2.getString("expires_in");
                
            }
                 //获取ACCESS_TOKEN
            String requestUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";  
            requestUrl = requestUrl.replace("ACCESS_TOKEN", access_token);  
            logger.info("~~~~~requestUrl~~~~~::"+requestUrl);
            // 获取凭证  
            JSONObject jsonObject = CommonUtil.httpsRequest(requestUrl, "GET", null);  
            logger.info("~~~~~获取凭证~~~~~::"+jsonObject);
            if(jsonObject!=null){  

                try { 

               //jsapi_ticket是公众号用于调用微信JS接口的临时票据。

                    String ticket = jsonObject.getString("ticket");  
                    logger.info("~~~~~获取凭证jsapi_ticket~~~~~::"+ticket);
                    Sign sign = new Sign();
                    Map<String, String> MapRestult = sign.sign(ticket, url);
                    json.put("noncestr", MapRestult.get("nonceStr"));
                    logger.info("~~~~~noncestr~~~~~::"+MapRestult.get("nonceStr"));
                    json.put("timestamp", MapRestult.get("timestamp"));
                    logger.info("~~~~~timestamp~~~~~::"+MapRestult.get("timestamp"));
                    json.put("jsapi_ticket", MapRestult.get("jsapi_ticket"));
                    logger.info("~~~~~jsapi_ticket~~~~~::"+MapRestult.get("jsapi_ticket"));
                    json.put("signature", MapRestult.get("signature"));
                    logger.info("~~~~~signature~~~~~::"+MapRestult.get("signature"));
                    json.put("url", MapRestult.get("url"));
                    logger.info("~~~~~url~~~~~::"+MapRestult.get("url"));
                    json.put("appid",bank_config.getString("WECHAY_APPID"));
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }
            return json;  
        }
       
    在这里数据返回正常,就可以正常分享了,下面上一下我做测分享成功的截图,其实不难,调式代码的时候仔细看日志,就行

    以下是上面用到的工具类 CommonUtil,

    package com.xxx.prm.util;xxx
    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.ConnectException;
    import java.net.URL;

    import javax.net.ssl.HttpsURLConnection;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSocketFactory;
    import javax.net.ssl.TrustManager;

    import net.sf.json.JSONObject;

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

    import com.xxx.prm.cdsp.userCenter.localreq.bean.MyX509TrustManager;



    /**
    * 类名: CommonUtil </br>
    * 描述: 通用工具类 </br>
    * 开发人员: tomcat </br>
    * 创建时间:  2018-06-30 </br>
    * 发布版本:V1.0  </br>
     */
    public class CommonUtil {
        private static Logger log = LoggerFactory.getLogger(CommonUtil.class);

        /**
         * 发送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;
        }
        /**
         * 获取接口访问凭证
         *
         * @param appid 凭证
         * @param appsecret 密钥
         * @return
         */
        public static JSONObject httpsRequestResult(String appid, String appsecret) {
             // 凭证获取(GET)
            String token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
            String requestUrl = token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
            // 发起GET请求获取凭证
            log.info("~~~~~~~~~token_url~~~~~~~~~"+token_url);
            JSONObject jsonObject = httpsRequest(requestUrl, "GET", null);
            return jsonObject;
        }
    }

    package com.sitech.prm.cdsp.userCenter.localreq.bean;
    /** 
     * @author 作者 E-mail: 
     * @version 创建时间:2018年5月30日 下午3:13:18 
     * 类说明 
     */

    工具类Sign

    import java.io.UnsupportedEncodingException;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Arrays;
    import java.util.Formatter;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.UUID;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    public class Sign {
        private Log logger = LogFactory.getLog(Sign.class);
        public Map<String, String> sign(String jsapi_ticket, String url) {
            Map<String, String> ret = new HashMap<String, String>();
            String nonce_str = create_nonce_str();
            String timestamp = create_timestamp();
            String string1;
            String signature = "";
            // 注意这里参数名必须全部小写,且必须有序
            string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str
                    + "&timestamp=" + timestamp + "&url=" + url;
            logger.debug("[string1] = " + string1);
            try {
                MessageDigest crypt = MessageDigest.getInstance("SHA-1");
                crypt.reset();
                crypt.update(string1.getBytes("UTF-8"));
                signature = byteToHex(crypt.digest());
                logger.debug("[signature] = " + signature);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            ret.put("url", url);
            ret.put("jsapi_ticket", jsapi_ticket);
            ret.put("nonceStr", nonce_str);
            ret.put("timestamp", timestamp);
            ret.put("signature", signature);
            logger.debug("[ret] = " + ret);
           return ret;
        }
        private static String byteToHex(final byte[] hash) {
            Formatter formatter = new Formatter();
            for (byte b : hash) {
                formatter.format("%02x", b);
            }
            String result = formatter.toString();
            formatter.close();
            return result;
        }
        private static String create_nonce_str() {
            return UUID.randomUUID().toString();
        }
        private static String create_timestamp() {
            return Long.toString(System.currentTimeMillis() / 1000);
        }
        /**
         * 用SHA1算法生成安全签名
         * 
         * @param token
         *            票据
         * @param timestamp
         *            时间戳
         * @param nonce
         *            随机字符串
         * @param encrypt
         *            密文
         * @return 安全签名
         * @throws NoSuchAlgorithmException
         * @throws AesException
         */
        public String getSHA1(String token, String timestamp, String nonce)
                throws NoSuchAlgorithmException {
            String[] array = new String[] { token, timestamp, nonce };
            StringBuffer sb = new StringBuffer();
            // 字符串排序
            Arrays.sort(array);
            for (int i = 0; i < 3; i++) {
                sb.append(array[i]);
            }
            String str = sb.toString();
            // SHA1签名生成
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(str.getBytes());
            byte[] digest = md.digest();
            StringBuffer hexstr = new StringBuffer();
            String shaHex = "";
            for (int i = 0; i < digest.length; i++) {
                shaHex = Integer.toHexString(digest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexstr.append(0);
                }
                hexstr.append(shaHex);
            }
            return hexstr.toString();
        }

    }

    工具类MyX509TrustManager 

    package com.xx.prm.cdsp.userCenter.localreq.bean;
    /** 
     * @author 作者 E-mail: 
     * @version 创建时间:2018年5月30日 下午3:20:11 
     * 类说明 
     */
    import java.security.cert.CertificateException;  
    import java.security.cert.X509Certificate;  
      
    import javax.net.ssl.X509TrustManager;  
      
    public class MyX509TrustManager implements X509TrustManager {  
        public void checkClientTrusted(X509Certificate[] chain, String authType)  
                throws CertificateException  
              {  
              }  
      
              public void checkServerTrusted(X509Certificate[] chain, String authType)  
                throws CertificateException  
              {  
              }  
      
              public X509Certificate[] getAcceptedIssuers()  
              {  
                return null;  
              }  
    }  



    展开全文
  • 但是通过微信分享的信息就无法跳转。测试的时候还试过,把分享的连接改成一个有按钮的页面,点击分享信息可以启动这个页面,然后点按钮跳转到前面的myapp://www.myapp.com启动app。但是点击那个按钮也没反应。在...
  • 微信JSSDK分享可以实现自己的网页在微信自定义图标,标题,还有描述,还可以分享到朋友圈,分享微信群,分享给朋友等操作。 上图就是,左侧是分享到朋友圈,右侧是分享微信群。 很多开发者下载官方的demo进行...

    微信JSSDK分享可以实现自己的网页在微信自定义图标,标题,还有描述,还可以分享到朋友圈,分享到微信群,分享给朋友等操作。

    上图就是,左侧是分享到朋友圈,右侧是分享到微信群。

    很多开发者下载官方的demo进行开发,最后debug的时候,显示的是config:invalid signature ,这个原因其实很有可能是签名不一致。

    我们需要保持签名一致,才能完整注入这个权限的。

    一般,签名不一致的是因为access_token生成的时候,获取jsapi_ticket的时候有问题造成的,我一开始弄了好久都没搞对,后来一个个尝试。

    我先尝试生成的一个jsapi_ticket,写死在页面,发现没问题,然后再尝试获取access_token写死在页面,也没问题,但是直接在页面上生成access_token再调用就不行。

    我就另外写了一个access.php进行生成access_token,把access_token存入数据库,搞一个定时任务,每隔1小时生成一个新的access_token,因为access_token仅有2小时有效期,而且每天最多调用2000次,所以我觉得存数据库是比较好的。

    然后在分享页面取数据库的access_token就行了。

    下面是生成access_token的代码: access.php

    <?php
    header("Content-type:text/html;charset=utf-8");
    //获取access_token
    $appId = '填写您的';
    $appSecret = '填写您的';
    $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".$appId."&secret=".$appSecret;
    $ch = curl_init();//初始化curl
    curl_setopt($ch, CURLOPT_URL,$url); //要访问的地址 
    curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);//跳过证书验证
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在
    $data = json_decode(curl_exec($ch));
    $token = $data->access_token;
     
    //连接数据库
    $con = mysql_connect("数据库地址","数据库账号","数据库密码");
    mysql_select_db("数据库名", $con);
    mysql_query("INSERT INTO access_token (access_token) VALUES ('$token')");
    mysql_close($con);
    ?>
    复制代码

    下面是分享页面代码: index.php

    <?php
    //获取jsapi_ticket
     function getjsapi_ticket(){
     
      //获取微信access_token
      //连接数据库
      $con = mysql_connect("数据库地址","数据库账号","数据库密码");
      mysql_select_db("数据库名", $con);
      $result = mysql_query("SELECT access_token FROM access_token ORDER BY ID DESC LIMIT 1");
      while($row = mysql_fetch_array($result)){
        $token = $row["access_token"];
      }
     
      $access_token = $token;
      $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token={$access_token}";
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL,$url);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,0);
      $data = curl_exec($ch);
      curl_close($ch);
      $data = json_decode($data,true);
      return $data['ticket'];
     }
      
     // mysql_close($con);
     
     //默认生成16位随机数
     function createNonceStr($length = 16) {
         $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
         $str = "";
         for ($i = 0; $i < $length; $i++) {
           $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
         }
         return $str;
       } 
     
    //获取要排序的signature相关代码
      function getSignPackage() {
        $jsapiTicket = getjsapi_ticket();
        $url = "当前页面的URL";
        $timestamp = time();
        $nonceStr = createNonceStr();
      
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr×tamp=$timestamp&url=$url";
        $signature = sha1($string);
        $signPackage = array(
          "appId"     => 'ADDPID修改为您的',
          "nonceStr"  => $nonceStr,
          "timestamp" => $timestamp,
          "url"       => $url,
          "signature" => $signature
        );
        return $signPackage; 
      }
      $signPackage = getSignPackage();
    ?>
    <html lang="en">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="format-detection" content="telephone=no">
    <title>微信分享DEMO</title>
    </head>
    <body>
    test
    </body>
    <script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
    <script>
      wx.config({
        debug: true,
        appId: '<?php echo $signPackage["appId"];?>',
        timestamp: <?php echo $signPackage["timestamp"];?>,
        nonceStr: '<?php echo $signPackage["nonceStr"];?>',
        signature: '<?php echo $signPackage["signature"];?>',
        jsApiList: [
          // 所有要调用的 API 都要加到这个列表中
          'onMenuShareTimeline',
          'onMenuShareAppMessage'
        ]
      });
     
     
      //分享到朋友圈
      wx.ready(function () {
          wx.onMenuShareTimeline({
            title: '吾爱破解论坛,微信JSSDK分享学习',
            link: "修改为当前JS接口安全域名下的页面的自定义URL",
            imgUrl: 'http://wxpad.cn/editor/php/upload/20181025/1540469570441.png',
            success: function (res) {
              alert('已分享到朋友圈');
            },
            cancel: function (res) {
              alert('已取消分享');
            },
            fail: function (res) {
              alert(JSON.stringify(res));
            }
          })
           
              //分享给朋友
          wx.onMenuShareAppMessage({
                title: "吾爱破解论坛,微信JSSDK分享学习", // 分享标题
                desc: "学破解,学技术,就来吾爱破解论坛!", // 分享描述
                link: "修改为当前JS接口安全域名下的页面的自定义URL", // 分享链接
                imgUrl: "http://wxpad.cn/editor/php/upload/20181025/1540469570441.png", // 分享图标
                type: '', // 分享类型,music、video或link,不填默认为link
                dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
                success: function () { 
                    // 用户确认分享后执行的回调函数
                },
                cancel: function () { 
                    // 用户取消分享后执行的回调函数
                }
            });
     
      });
     
      alert(location.href.split('#')[0])
    </script>
    </html>
    复制代码

    大概就这样就可以config:ok了
    当然,开发过程中需要设置IP白名单,JS接口安全域名这些我就不多说了。

    作者:TANKING
    2018-10-15
    likeyunba.com

    转载于:https://juejin.im/post/5bd1c04bf265da0ae3443903

    展开全文
  • 在上一篇文章中介绍了微信自定义分享对页面进行的配置。想要实现页面的这些配置,需要后台获取相应的值传到页面中。  那么那些值都是如何获取的呢?  如需获取页面中的值,APPID是公众号的唯一标识(存储在数据库...

      在上一篇文章中介绍了微信自定义分享对页面进行的配置。想要实现页面的这些配置,需要后台获取相应的值传到页面中。

      那么那些值都是如何获取的呢?

      如需获取页面中的值,APPID是公众号的唯一标识(存储在数据库中),可通过accountID(公众号id)来得到。timestamp ,生成签名的时间戳和nonceStr签名生成的字符串可以这样获取:

    <span style="font-size:18px;"><span style="font-size:18px;"> private static String create_nonce_str() {
            return UUID.randomUUID().toString();//随机字符串
        }
    
        private static String create_timestamp() {
            return Long.toString(System.currentTimeMillis() / 1000);//时间戳
        }</span></span>

      最后的signatrue签名需要签名机制来生成。

      签名机制:

      1、通过access_token采用http Get方式的请求获取jsapi_ticket(有效期为7200秒)

      2、通过得到的jsapi_ticket和随机字符串noncestr,时间戳timestamp,当前网页的URL(不包含#及其后面部分),对这几个参数按照ASCII码从小到大排序(字典序),使用URL键值对的格式(key1=value1&key2=value2…)拼接成字符串string1,这里所有的参数均为小写字符。

      3、然后对string1进行SHA1加密。字段名和字段值都采用原始值,不进行URL转义。

    <span style="font-size:18px;"><span style="font-size:18px;"> public Map<String, String> test(HttpServletRequest requesturl, String accountid) throws Exception {
            String ticket = getWeiXinTicket(accountid);
    
            // 注意 URL 一定要动态获取,不能 hardcode
            String url = requesturl.getRequestURL().toString();
            String domain = ResourceUtil.getConfigByName("domain");
            String param = requesturl.getQueryString();
            url = url + "?" + param;
            Map<String, String> ret = sign(ticket, url);
            for (Map.Entry entry : ret.entrySet()) {
                System.out.println(entry.getKey() + ", " + entry.getValue());
            }
            ret.put("appId", appId);
            return ret;
        }
    
        public static Map<String, String> sign(String jsapi_ticket, String url) {
            Map<String, String> ret = new HashMap<String, String>();
            String nonce_str = create_nonce_str();
            String timestamp = create_timestamp();
            String string1;
            String signature = "";
    
            //注意这里参数名必须全部小写,且必须有序
            string1 = "jsapi_ticket=" + jsapi_ticket +
                    "&noncestr=" + nonce_str +
                    "×tamp=" + timestamp +
                    "&url=" + url;
            System.out.println(string1);
    
            try {
                MessageDigest crypt = MessageDigest.getInstance("SHA-1");
                crypt.reset();
                crypt.update(string1.getBytes("UTF-8"));
                signature = byteToHex(crypt.digest());
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
    
            ret.put("url", url);
            ret.put("jsapi_ticket", jsapi_ticket);
            ret.put("nonceStr", nonce_str);
            ret.put("timestamp", timestamp);
            ret.put("signature", signature);
    
            return ret;
        }
     private static String byteToHex(final byte[] hash) {
            Formatter formatter = new Formatter();
            for (byte b : hash) {
                formatter.format("%02x", b);
            }
            String result = formatter.toString();
            formatter.close();
            return result;
        }
    </span></span>

    <span style="font-size:18px;"><span style="font-size:18px;">  public String getWeiXinTicket(String accountid) throws Exception {
            String token = getAccessToken(accountid);
    
    //        if(null==apiticket){
    //            URL url1=new URL("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+accessToken.getToken()+"&type=jsapi");
    //        String urll = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=accessToken&type=jsapi";
            String urll = WeixinUtil.share_url;
    
            String requestUrll = urll.replace("accessToken", token);
            JSONObject json1 = httpRequest(requestUrll, "GET", null);
            String ticket = (String) json1.get("ticket");
            return ticket;
            // 断开连接
    
        }</span></span>
    <span style="font-size:18px;"><span style="font-size:18px;">  public String getAccessToken(String accountId) throws Exception {
            String token = "";
    //        WeixinAccountServiceImpl impl=new WeixinAccountServiceImpl();
            WeixinAccountEntity account = weixinAccountService.findUniqueByProperty(WeixinAccountEntity.class, "id", accountId);
    
            appId = account.getAccountappid();
    
            if (!account.getAuthorizationType().equalsIgnoreCase("0")) {
                return this.getAuthorizerAccessToken(account.getId());
            }
    
            token = account.getAccountaccesstoken();
            if (token != null && !"".equals(token)) {
                // 判断有效时间 是否超过2小时
                java.util.Date end = new java.util.Date();
                java.util.Date start = new java.util.Date(account.getAddtoekntime()
                        .getTime());
                if ((end.getTime() - start.getTime()) / 1000 / 3600 >= 2) {
                    // 失效 重新获取
                    String requestUrl = WeixinUtil.access_token_url.replace(
                            "APPID", account.getAccountappid()).replace(
                            "APPSECRET", account.getAccountappsecret());
                    JSONObject jsonObject = WeixinUtil.httpRequest(requestUrl,
                            "GET", null);
                    if (null != jsonObject) {
                        try {
                            token = jsonObject.getString("access_token");
                            // 重置token
                            account.setAccountaccesstoken(token);
                            // 重置事件
                            account.setAddtoekntime(new Date());
                            weixinAccountService.saveOrUpdate(account);
                        } catch (Exception e) {
                            token = null;
                            // 获取token失败
                            String wrongMessage = "获取token失败 errcode:{} errmsg:{}"
                                    + jsonObject.getInt("errcode")
                                    + jsonObject.getString("errmsg");
                        }
                    }
                } else {
                    return account.getAccountaccesstoken();
                }
            } else {
                String requestUrl = WeixinUtil.access_token_url.replace("APPID",
                        account.getAccountappid()).replace("APPSECRET",
                        account.getAccountappsecret());
                JSONObject jsonObject = WeixinUtil.httpRequest(requestUrl, "GET",
                        null);
                if (null != jsonObject) {
                    try {
                        token = jsonObject.getString("access_token");
                        // 重置token
                        account.setAccountaccesstoken(token);
                        // 重置事件
                        account.setAddtoekntime(new Date());
                        weixinAccountService.saveOrUpdate(account);
                    } catch (Exception e) {
                        token = null;
                        // 获取token失败
                        String wrongMessage = "获取token失败 errcode:{} errmsg:{}"
                                + jsonObject.getInt("errcode")
                                + jsonObject.getString("errmsg");
                    }
                }
            }
            return token;
        }</span></span>
    <span style="font-size:18px;"> public String getWeiXinTicket(String accountid) throws Exception {
            WeixinAccountEntity account = weixinAccountService.findUniqueByProperty(WeixinAccountEntity.class, "id", accountid);
            String ticket="";
            ticket=account.getJsapiTicket();
    
            if (ticket != null && !"".equals(ticket)) {
                // 判断有效时间 是否超过2小时
                java.util.Date end = new java.util.Date();
                java.util.Date start = new java.util.Date(account.getJsapiTicketTime()
                        .getTime());
                if ((end.getTime() - start.getTime()) / 1000 / 3600 >= 2) {
                    // 失效 重新获取
                    String urll = WeixinUtil.share_url;
                    String token = getAccessToken(accountid);
                    String requestUrll = urll.replace("accessToken", token);
                    JSONObject json1 = httpRequest(requestUrll, "GET", null);
                    if (null != json1) {
                        try {
                            ticket  = (String) json1.get("ticket");
                            // 重置ticket
                            account.setJsapiTicket(ticket);
                            // 重置事件
                            account.setJsapiTicketTime(new Date());
                            weixinAccountService.saveOrUpdate(account);
                        } catch (Exception e) {
                            token = null;
                            // 获取token失败
                            String wrongMessage = "获取ticket失败 errcode:{} errmsg:{}"
                                    + json1.getInt("errcode")
                                    + json1.getString("errmsg");
                        }
                    }
                } else {
                    ticket= account.getJsapiTicket();
                }
            } else {
                String urll = WeixinUtil.share_url;
                String token = getAccessToken(accountid);
                String requestUrll = urll.replace("accessToken", token);
                JSONObject json1 = httpRequest(requestUrll, "GET", null);
                if (null != json1) {
                    try {
                        ticket  = (String) json1.get("ticket");
                        // 重置ticket
                        account.setJsapiTicket(ticket);
                        // 重置事件
                        account.setJsapiTicketTime(new Date());
                        weixinAccountService.saveOrUpdate(account);
                    } catch (Exception e) {
                        token = null;
                        // 获取token失败
                        String wrongMessage = "获取ticket失败 errcode:{} errmsg:{}"
                                + json1.getInt("errcode")
                                + json1.getString("errmsg");
                    }
                }
            }</span>


    <span style="font-size:18px;"><span style="font-size:18px;"> public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) {
            JSONObject jsonObject = null;
            StringBuffer buffer = new StringBuffer();
            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 httpUrlConn = (HttpsURLConnection) url.openConnection();
                httpUrlConn.setSSLSocketFactory(ssf);
    
                httpUrlConn.setDoOutput(true);
                httpUrlConn.setDoInput(true);
                httpUrlConn.setUseCaches(false);
                // 设置请求方式(GET/POST)
                httpUrlConn.setRequestMethod(requestMethod);
    
                if ("GET".equalsIgnoreCase(requestMethod))
                    httpUrlConn.connect();
    
                // 当有数据需要提交时
                if (null != outputStr) {
                    OutputStream outputStream = httpUrlConn.getOutputStream();
                    // 注意编码格式,防止中文乱码
                    outputStream.write(outputStr.getBytes("UTF-8"));
                    outputStream.close();
                }
    
                // 将返回的输入流转换成字符串
                InputStream inputStream = httpUrlConn.getInputStream();
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
    
                String str = null;
                while ((str = bufferedReader.readLine()) != null) {
                    buffer.append(str);
                }
                bufferedReader.close();
                inputStreamReader.close();
                // 释放资源
                inputStream.close();
                inputStream = null;
                httpUrlConn.disconnect();
                jsonObject = JSONObject.fromObject(buffer.toString());
                // jsonObject = JSONObject.fromObject(buffer.toString());
            } catch (ConnectException ce) {
                org.jeecgframework.core.util.LogUtil.info("Weixin server connection timed out.");
            } catch (Exception e) {
                org.jeecgframework.core.util.LogUtil.info("https request error:{}" + e.getMessage());
            }
            return jsonObject;
        }</span></span>

    <span style="font-size:18px;"><span style="font-size:18px;"> public String getAuthorizerAccessToken(String id) {
            String ret = "";
            if (StringUtils.isBlank(id)) {
                return ret;
            }
            WeixinAccountEntity account = weixinAccountService.findUniqueByProperty(WeixinAccountEntity.class, "id", id);
    //        WeixinAccountEntity account = this.getEntity(WeixinAccountEntity.class, id);
            if (account.getAuthorizationType().equalsIgnoreCase("0")) {
                logger.info(String.format("weixin account %s 没有使用第三方授权", id));
            } else {
                String token = account.getAuthorizerAccessToken();
                if (StringUtils.isNotBlank(token) && account.getAuthorizerAccessTokenExpireTime().after(new Date())) {
                    ret = token;
                } else {
                    String platformId = account.getOpenPlatformId();
                    WeixinOpenPlatformEntity platform = weixinOpenPlatformService.getEntity(WeixinOpenPlatformEntity.class, platformId);
                    String componentAccessToken = getComponentAccessToken(id);
                    String componentAppId = platform.getAppId();
                    String authorizerAppId = account.getAccountappid();
                    String refreshToken = account.getAuthorizerRefreshToken();
                    String requestUrl = API_AUTHORIZER_TOKEN.replace("COMPONENT_ACCESS_TOKEN", componentAccessToken);
                    JSONObject postData = new JSONObject();
                    postData.put("component_appid", componentAppId);
                    postData.put("authorizer_appid", authorizerAppId);
                    postData.put("authorizer_refresh_token", refreshToken);
                    JSONObject obj = WeixinUtil.httpRequest(requestUrl, "POST", postData.toString());
                    if (obj.containsKey("authorizer_access_token")) {
                        String authorizerAccessToken = obj.getString("authorizer_access_token");
                        String authorizerRefreshToken = obj.getString("authorizer_refresh_token");
                        int expiresIn = obj.getInt("expires_in");
                        Date expireTime = addTime(new Date(), expiresIn, Calendar.SECOND);
                        ret = authorizerAccessToken;
                        account.setAuthorizerAccessToken(authorizerAccessToken);
                        account.setAuthorizerRefreshToken(authorizerRefreshToken);
                        account.setAuthorizerAccessTokenExpireTime(expireTime);
                        weixinAccountService.saveOrUpdate(account);
                    } else {
                        logger.info(String.format("获取authorizer_access_token失败: %s", obj.toString()));
                    }
                }
            }
    
            return ret;
        }
    
        public String getComponentAccessToken(String id) {
            String ret = "";
            WeixinAccountEntity account = weixinAccountService.findUniqueByProperty(WeixinAccountEntity.class, "id", id);
            if (account.getId().equalsIgnoreCase("-1")) {
                logger.info("找不到当前登录微信公众号");
                return ret;
            } else {
                if (account.getAuthorizationType().equalsIgnoreCase("1") || account.getAuthorizationType().equalsIgnoreCase("2")) {
                    String platformId = account.getOpenPlatformId();
                    ret = this.getComponentAccessToken(platformId, "temp");
                } else {
                    logger.info(String.format("公众号未采用第三方授权. id:%s, authorizationType:%s, accountId:%s", account.getId(), account.getAuthorizationType(), account.getWeixin_accountid()));
                }
            }
    
            return ret;
        }
    
        public String getComponentAccessToken(String platformId, String temp) {
            String ret = "";
            if (StringUtils.isBlank(platformId)) {
                return ret;
            }
    
            WeixinOpenPlatformEntity entity = systemService.get(WeixinOpenPlatformEntity.class, platformId);
            Date now = new Date();
            if (StringUtils.isNotBlank(entity.getComponentAccessToken()) && entity.getTokenExpireTime().after(now)) {
                ret = entity.getComponentAccessToken();
            } else {
                JSONObject postData = new JSONObject();
                postData.put("component_appid", entity.getAppId());
                postData.put("component_appsecret", entity.getAppSecret());
                postData.put("component_verify_ticket", entity.getComponentVerifyTicket());
                JSONObject obj = WeixinUtil.httpRequest(API_COMPONENT_TOKEN, "POST", postData.toString());
                if (obj.containsKey("component_access_token")) {
                    String token = obj.getString("component_access_token");
                    Integer expiresIn = obj.getInt("expires_in");
                    ret = token;
                    Date expireTime = addTime(new Date(), expiresIn, Calendar.SECOND);
                    entity.setComponentAccessToken(token);
                    entity.setTokenExpireTime(expireTime);
                    systemService.save(entity);
                } else {
                    logger.info(String.format("获取component_access_token失败. %s", obj.toString()));
                }
            }
    
            return ret;
        }
    
        private Date addTime(Date current, int value, int unit) {
            Calendar c = Calendar.getInstance();
            c.setTime(current);
            c.add(unit, value);
            return c.getTime();
        }</span></span>




    展开全文
  • 最近在搞微信公众号开发,进行到网页开发部分被坑了一天,最坑的问题就是invalid signature,而网上大部分解答这个问题的都没有说清楚,都直接丢文档。博主认为这样很不好。本文是博主结合自身遇到的问题所写,整个...

    最近在搞微信公众号开发,进行到网页开发部分被坑了一天,最坑的问题就是invalid signature,而网上大部分解答这个问题的都没有说清楚,都直接丢文档。博主认为这样很不好。本文是博主结合自身遇到的问题所写,整个流程跟问题都很详细,虽然排版可能有点不好。但是绝对对遇到类似问题的朋友有所帮助。请认真看下去

    一、绑定JS接口安全域名

    生产号绑定方法:登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。
    测试号绑定方法:进入测试号管理页面,找到JS接口安全域名项绑定
    JS接口安全域名官方说法是:开发者可在该域名下调用微信开放的JS接口
    域名格式:如果你的项目域名是http://test.domain.com,那么JS接口安全域名为test.domain.com。切记!
    域名绑定失败或者域名不存在会报错误:invalid url domain

    二、引入微信js文件

    引入方法:在需要调用JS接口的页面引入JS文件,用script标签引入即可
    JS文件路径:http://res.wx.qq.com/open/js/jweixin-1.2.0.js(支持https)

    三、通过wx.config接口注入权限验证

    1、每个需要使用jssdk的页面都要使用config接口注入配置信息,wx.config调用方法如下:

     

    
     
    1. wx.config({

    2. debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。

    3. appId: '', // 必填,公众号的唯一标识

    4. timestamp: , // 必填,生成签名的时间戳,精确到秒

    5. nonceStr: '', // 必填,生成签名的随机串

    6. signature: '',// 必填,签名

    7. jsApiList: [] // 必填,需要使用的JS接口列表,例如:['chooseImage','previewImage','uploadImage']

    8. })

    其中appId,timestamp,nonceStr,signature必须从后台获取

     

    四、后台生成并返回前端所需参数

    1、jsapi_ticket

    jsapi_ticket是公众号用于调用js接口的临时票据。有效期7200秒,跟公众号普通access_token一样,必须全局缓存起来,因为这个ticket获取次数有限,超过次数将无法使用。建议设置缓存时间为7198秒,因为当请求微信端生成jsapi_ticket返回给后台保存这个动作需要时间,如果设置7200秒,实际上最后一两秒时,缓存里面还存在,但实际在微信那边已经过期了,再拿这个ticket会出错。生成jsapi_ticket如下:

    (1)获取普通access_token(GET请求): 

     

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

     

    (2)用第一步获取的access_token使用GET请求获取jsapi_ticket

     

    https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
    

    正确获取信息如下:

    
     
    1. {

    2. "errcode":0,

    3. "errmsg":"ok",

    4. "ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",

    5. "expires_in":7200

    6. }

    2、生成签名(signature)

     

    签名规则:1、参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分)。2、对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序,sort()即可)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串(string)。3、使用sha1加密拼接成的字符串string。注意:字段名和字段值都要使用原值,不要进行url转义

    参与的字段示例:

    
     
    1. noncestr=Wm3WZYTPz0wzccnW

    2. jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg

    3. timestamp=1414587457

    4. url=http://mp.weixin.qq.com?params=value

    拼接完成的字符串:

    jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW×tamp=1414587457&url=http://mp.weixin.qq.com?params=value

    使用sha1加密后的signature:

    0f9de62fce790f9a083d5c99e95740ceb90c27ed

    3、签名生成完后将appId、timestamp、nonceStr、signature一起返回到前端。格式示例:

     

    
     
    1. {

    2. appId:appId,

    3. timestamp:timestamp,

    4. nonceStr: noncestr,

    5. signature: signature

    6. }

    前端拿到值后,写入到wx.config中相应字段即可

    注意:

    1、前端wx.config配置中的nonceStr字段名称的's'是大写。但是后台生成签名的noncestr字段的‘s’是小写,千万要注意,博主在这里浪费了不少时间。囧囧

    2、时间戳(timestamp)值要记住精确到秒,不是毫秒。

    3、生成签名的url(使用jssdk的页面地址,这个页面地址可以在浏览器访问),包含“?”号后面的所有参数,不包含“#”号后面的值。

    如果是静默授权或者授权页面同意授权后跳转到的页面,页面路径会添加两个参数:code和state。

    即授权后跳转页面为http://redirect.page.com,则完整路径为

    http://redirect.page.com?code=kdijafdhjaikeiu20kaiela&state=STATE。

    那么生成签名的url必须为授权后跳转页面的完整路径。前端获取这个路径:location.href.split('#')[0]

    注意注意:这里有个大坑。。如果前端使用ajax(使用jquery)获取wx.config配置所需的几个参数的值,可以这样做:

    
     
    1. $.ajax({

    2. url: 'http://backend.com?fullUrl=' + location.href.split('#')[0], //这里的参数fullUrl是当前页面的完整url(除去#后面部分)

    3. type: 'GET',

    4. success: function(res) {

    5. //操作后台返回值

    6. }

    7. })

     

    后台要怎么操作前端传过来的query值呢?大家肯定一眼看出来 获取query中的fullUrl字段不就行了。

    博主也是这么做的,请看博主后台代码(使用nodejs的koa框架)

    
     
    1. let query = this.request.query;//获取查询字符串

    2. let fullUrl = query.fullUrl;//获取查询字符串中的fullUrl字段

     

    怎么样,有没有看出什么不对劲的地方?没有?

    博主把所有后续的获取access_token、jsapi_ticket、计算签名、返回值到前端、前端wx.config配置好这些所有操作做完,信心满满的开始用测试号访问页面。结果直接弹出config: invalid signature(开启debug模式)。WTF ???

    当然,搞这一行早就做好了遇到问题的心理准备。。。。开始排错呗。。

    首先在后台将获取access_token、jsapi_ticket、计算签名的参数字典序排序后的字符串、sha1加密后的字符串全部console.log出来。

    发现,没有错误的地方。那行,看来有可能是我代码那个地方写错了,或者签名算错了?

    到微信在线接口调试把刚刚打印的计算签名的几个参数分别填好。生成。。。发现跟我的代码生成的是一样的。

    结果博主是各种排错,各种百度,各种google。结果还是config: invalid signature...................................此处省略博主心里一万句话

    又这样过了一个多小时,无果。。。。。。博主一脸颓废的到茶水间泡了杯咖啡。。提提神。。又回到了岗位,准备从头开始跑一遍再仔细看看。

    从前端请求获取签名接口开始,博主打印了query的值。。也就是let query  = this.request.query的值。。结果发现。query值为:

    
     
    1. {

    2. fullUrl: 'http://redirect.page.com?code=kdijafdhjaikeiu20kaiela',

    3. state: 'STATE'

    4. }

     

    好吧,问题在这里,有没有人早就看出来的?前面说过,微信网页授权后跳转的页面完整路径为

    http://redirect.page.com?code=kdijafdhjaikeiu20kaiela&state=STATE

    有没有注意路径最后面的'&state=STATE'。当我们把这个完整路径当做查询字符串传到后台的时候,因为没有对这个路径进行encodeURIComponent.所以后台将'&state=STATE'单独看成了一个查询字符串参数,问题点在这里。。

    后来查看公众号文档发现'附录5-常见错误及解决方法'第六条有说明

    问题解决

    使用encodeURIComponent(location.href.split('#')[0])即可

    五、调用接口

    wx.config配置完成后会执行wx.ready方法,所有接口必须要在config返回结果之后操作。config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。当前页面要使用的接口,要写入到config配置中的jsApiList中

    1、拍照或从手机相册中选图接口

    
     
    1. document.getElementById('chooseImage').onclick = function(){

    2. wx.chooseImage({

    3. count: 1, // 默认9

    4. sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有

    5. sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有

    6. success: function (res) {

    7. var localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片

    8. }

    9. });

    10. }

     

    六、常见问题

    1、invalid url domain:

    js接口安全域名错误。可以看看第一项

    2、invalid signature:

    要么是jsapi_ticket错误,要么是签名算法问题,要么是算法的参数有问题,注意noncestr中的's'是小写的。如果都是对的。那就是前端传的url有问题了。。。好好检查一下,不要像博主一样啊。。。。

    展开全文
  • 如果要完成微信分享,还是有需要去微信官方开发文档看一下的。附上地址:微信开发平台微信公众平台开发文档自己老年记忆力过段时间肯定会忘记如何去实现这个功能,所以记录一下。因为自己不是写客户端的(Android、...
  • 在微信公众平台的接口权限内可以看到,个人版公众号是没有权限自定义微信分享的,所以需要企业版公众号并开通认证。 具体步骤: 步骤一:绑定域名 先登录微信公众平台进入“公众号设置”的“功能设置”里...
  • vue分享微信、朋友圈

    2020-05-14 14:40:06
    文章目录vue实现微信分享效果图前提: 公众号相关配置使用遇到问题 vue实现微信分享 效果图 前提: 公众号相关配置 参考微信公众平台 使用 mounted() { this.shareWx(); }, methods: { shareWx() { // alert...
  • 二、配置微信分享的SDK 三、微信分享示例 四、微信分享的结果回调 五、微信分享的踩坑总结 一、申请应用 1、首先到 [ 微信开放平台官网] 申请注册帐号,这些流程就忽略了到官网一看...
  • 看到网上很多关于微信分享的博客、帖子,说实话,没几篇写的全的,很多都是复制粘贴,介绍的也不全,缺少代码的分析,关键性的代码总是漏一句两句,看着就很难受。所以,在这里我打算写一篇关于微信分享的博客,总结...
  • 用的友盟SDK实现微信分享,发现微信分享途中如果取消分享,回调的error是nil,微信分享成功的回调返回的error也是nil,无法识别是否分享成功。问了友盟的客服才知道微信在8月13号对分享功能做了调整。...
  • 作为系列文章的第五篇,本文重点探讨数据采集层中的微信分享追踪系统。微信分享,早已成为移动互联网运营的主要方向之一,以Web H5页面(下面称之为微信海报)为载体,利用微信庞大的好友关系进行传播,实现宣传、...
  • 原先的网页需要分享到微信,可是...后台基于javaweb,微信的分享流程大致如下,详细见官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115java版微信分享的步骤首先要有一个公众号和已经备案
  • 本片文章的主要内容:微信分享,包括分享到好友、分享到朋友圈。 解决方案: 使用原生微信提供的sdk及其解决方案 使用已有的轮子(github里找) 使用第三方,如极光推送等。 方案一 首先,来探讨一下第一种方案:...
  • 在上个项目中主要用的是微信分享,由于项目前期的产品需求要求QQ分享、微信分享和微博分享等,所以集成了友盟的分享SDK, 友盟分享SDK主要是对以上各家分享做了封装,方便开发者使用. 下面是微信分享的具体流程图 (*...
  • 微信分享网页的时候,希望分享出来的链接是标题+描述+缩略图,微信开发代码示例里已提供了方法,但只适用于动态页面。我使用phpcms把页面生成了静态文件,示例代码就起不到作用了。在网上找了下,有大神使用ajax实现...
  • ionic3 微信分享插件

    2018-05-06 00:00:09
    前几天实现了微信分享和朋友圈分分享的功能,由于现有的例子太多了,我实现的过程还算比较顺利。但是网上的例子多是多,但都不够详细,在这里,我来分享一下详细版的微信分享实现过程。首先,下载微信分享插件的代码...
1 2 3 4 5 ... 20
收藏数 188,759
精华内容 75,503
关键字:

微信分享