博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
微信网页版前端源码分析(一)源码结构和公众号处理逻辑
阅读量:4981 次
发布时间:2019-06-12

本文共 15797 字,大约阅读时间需要 52 分钟。

(原创,转载请注明出处)

一、微信网页版前端结构

微信网页版为angular应用。

angular应用启动代码

angular.bootstrap(document, ["webwxApp"])

angular应用主要Module

angular.module("Services")angular.module("Controllers")angular.module("Directives")

angular应用主要模版文件

/readMenu.html

angular应用配置代码

微信网页版采用了ui-route模块来配置路由,与页面相对应,聊天二级页对应'chat' state,联系人二级页对应'contact' state, 公众号二级页对应'read' state。

angular.module("webwxApp", ["ui.router", "ngAnimate", "Services", "Controllers", "Directives", "Filters", "ngDialog", "jQueryScrollbar", "ngClipboard", "exceptionOverride"]).run(["$rootScope", "$state", "$stateParams", function(e, t, o) {    e.$state = t, e.$stateParams = o}]).factory("httpInterceptor", ["accountFactory", function(e) {    return {        request: function(t) {            if (!t.cache && t.url.indexOf(".html") < 0 && (t.params || (t.params = {}), t.params.pass_ticket = e.getPassticket()), t.url.indexOf(".html") < 0) {                var o = location.href.match(/(\?|&)lang=([^&#]+)/);                if (o) {                    var n = o[2];                    t.params || (t.params = {}), t.params.lang = n                }            }            return t        }    }}]).config(["$sceProvider", "$httpProvider", "$logProvider", "$stateProvider", "$urlRouterProvider", "ngClipProvider", function(e, t, o, n, r, a) {    e.enabled(!1), o.debugEnabled(!0), a.setPath(window.MMSource.copySwfPath), t.interceptors.push("httpInterceptor");    var i = document.domain.indexOf("qq.com") < 0;    i || (document.domain = "qq.com");    var c;    n.state("chat", {        url: "",        params: {            userName: ""        },        views: {            navView: {                controller: ["$stateParams", "chatFactory", "contactFactory", "stateManageService", "$rootScope", function(e, t, o, n, r) {                    function a() {                        var n = o.getContact(e.userName, "", !0);                        r.$broadcast("root:statechange"), t.setCurrentUserName(e.userName), t.addChatList([n || {                            FromUserName: e.userName                        }]), e.userName = ""                    }                    if (n.change("navChat:active", !0), e.userName) {                        var i = o.getContact(e.userName, "", !0);                        i ? a() : o.addBatchgetContact({                            UserName: e.userName,                            ChatRoomId: ""                        }, !0).then(function(e) {                            a(), console.log("addBatchgetContact now ok", e)                        }, function(e) {                            console.error("addBatchgetContact now err", e)                        })                    }                }]            },            contentView: {                templateUrl: "contentChat.html",                controller: "contentChatController"            }        }    }).state("contact", {        url: "",        views: {            navView: {                controller: ["stateManageService", function(e) {                    e.change("navContact:active", !0)                }]            },            contentView: {                templateUrl: "contentContact.html",                controller: "contentContactController"            }        }    }).state("read", {        url: "",        params: {            readItem: ""        },        views: {            navView: {                controller: ["stateManageService", function(e) {                    e.change("navRead:active", !0)                }]            },            contentView: {                templateUrl: "contentRead.html",                controller: ["$scope", "$stateParams", "subscribeMsgService", "mmpop", function(e, t, o, n) {                    if (t.readItem) c = e.readItem = t.readItem;                    else {                        var r = o.getSubscribeMsgs()[0];                        e.readItem = c || r && r.MPArticleList[0]                    }                    e.optionMenu = function() {                        n.toggleOpen({                            templateUrl: "readMenu.html",                            container: angular.element(document.querySelector(".read_list_header")),                            controller: "readMenuController",                            singletonId: "mmpop_reader_menu",                            className: "reader_menu"                        })                    }, i || $("#reader").load(function() {                        var e = $(this).contents().find("body"),                            t = e.find("#js_view_source");                        if (t.length > 0) {                            e.css({                                position: "relative"                            });                            var o = $('阅读原文');                            e.append(o)                        }                    })                }]            }        }    })}]);

二、微信网页版全局数据初始化

全局控制器appController

angular.module("Controllers").controller("appController", ["$rootScope", "$scope", "$timeout", "$log", "$state", "$window", "ngDialog", "mmpop", "appFactory", "loginFactory", "contactFactory", "accountFactory", "chatFactory", "confFactory", "contextMenuFactory", "notificationFactory", "utilFactory", "reportService", "actionTrack", "surviveCheckService", "subscribeMsgService", "stateManageService", function(e, t, o, n, r, a, i, c, s, l, u, f, d, g, m, p, h, M, y, C, S, v) {//controller初始化}

全局初始化调用代码

window._appTiming = {}, r.go("chat"), e.CONF = g,t.isUnLogin = !window.MMCgi.isLogin,t.debug = !0,t.isShowReader = /qq\.com/gi.test(location.href), window.MMCgi.isLogin && (T(), h.browser.chrome && !MMDEV && (window.onbeforeunload = function(e) {    return e = e || window.event, e &&     (e.returnValue =     "关闭浏览器聊天内容将会丢失。"),     "关闭浏览器聊天内容将会丢失。"})), t.$on("newLoginPage", function(e, t) {    console.log("newLoginPage", t),    f.setSkey(t.SKey),     f.setSid(t.Sid), f.setUin(t.Uin),     f.setPassticket(t.Passticket),     T()});

通过分析以上代码,T()才是初始化的具体执行方法。

全局数据初始化方法

function T() {    t.isLoaded = !0, t.isUnLogin = !1, M.report(M.ReportType.timing, {        timing: {            initStart: Date.now()        }    }), s.init().then(function(n) {        if (h.log("initData", n), n.BaseResponse && "0" != n.BaseResponse.Ret) return console.log("BaseResponse.Ret", n.BaseResponse.Ret), void(l.timeoutDetect(n.BaseResponse.Ret) || i.openConfirm({            className: "default ",            templateUrl: "comfirmTips.html",            controller: ["$scope", function(e) {                e.title = MM.context("02d9819"), e.content = MM.context("0d2fc2c"), M.report(M.ReportType.initError, {                    text: "程序初始化失败,点击确认刷新页面",                    code: n.BaseResponse.Ret,                    cookie: document.cookie                }), e.callback = function() {                    document.location.reload(!0)                }            }]        }));        f.setUserInfo(n.User), f.setSkey(n.SKey), f.setSyncKey(n.SyncKey), u.addContact(n.User), u.addContacts(n.ContactList), d.initChatList(n.ChatSet), d.notifyMobile(f.getUserName(), g.StatusNotifyCode_INITED), S.init(n.MPSubscribeMsgList), e.$broadcast("root:pageInit:success"), h.setCheckUrl(f), h.log("getUserInfo", f.getUserInfo()), t.$broadcast("updateUser"), M.report(M.ReportType.timing, {            timing: {                initEnd: Date.now()            }        });        var r = n.ClickReportInterval || 3e5;        setTimeout(function a() {            y.report(), setTimeout(a, r)        }, r), o(function() {            function e(o) {                u.initContact(o).then(function(o) {                    u.addContacts(o.MemberList), M.report(M.ReportType.timing, {                        timing: {                            initContactEnd: Date.now()                        },                        needSend: !0                    }), 16 >= t && o.Seq && 0 != o.Seq && (t++, e(o.Seq))                })            }            M.report(M.ReportType.timing, {                timing: {                    initContactStart: Date.now()                }            });            var t = 1;            e(0)        }, 0), t.account = u.getContact(f.getUserName()), E()    })}

通过分析以上代码,可以看到,s.init()为初始化主要方法,其中s为appFactory;初始化后,通过各种set方法来为各个model赋值。

appFactory的init方法

angular.module("Services").factory("appFactory", ["$http", "$q", "confFactory", "accountFactory", "loginFactory", "utilFactory", "reportService", "mmHttp", function(e, t, o, n, r, a, i, c) {    var s = {        globalData: {            chatList: []        },        init: function() {            var e = t.defer();            return c({                method: "POST",                url: o.API_webwxinit,                MMRetry: {                    count: 1,                    timeout: 1                },                data: {                    BaseRequest: {                        Uin: n.getUin(),                        Sid: n.getSid(),                        Skey: n.getSkey(),                        DeviceID: n.getDeviceID()                    }                }            }).success(function(t) {                e.resolve(t)            }).error(function(t) {                e.reject("error:" + t)            }), e.promise        },        sync: function() {            var e = t.defer();            return c({                method: "POST",                MMRetry: {                    serial: !0                },                url: o.API_webwxsync + "?" + ["sid=" + n.getSid(), "skey=" + n.getSkey()].join("&"),                data: angular.extend(n.getBaseRequest(), {                    SyncKey: n.getSyncKey(),                    rr: ~new Date                })            }).success(function(t) {                e.resolve(t), a.getCookie("webwx_data_ticket") || i.report(i.ReportType.cookieError, {                    text: "webwx_data_ticket 票据丢失",                    cookie: document.cookie                })            }).error(function(t) {                e.reject("error:" + t), a.log("sync error")            }), e.promise        },        syncCheck: function() {            var e = t.defer(),                c = this,                s = o.API_synccheck + "?" + ["r=" + a.now(), "skey=" + encodeURIComponent(n.getSkey()), "sid=" + encodeURIComponent(n.getSid()), "uin=" + n.getUin(), "deviceid=" + n.getDeviceID(), "synckey=" + encodeURIComponent(n.getFormateSyncCheckKey())].join("&");            return window.synccheck && (window.synccheck.selector = 0), $.ajax({                url: s,                dataType: "script",                timeout: 35e3            }).done(function() {                window.synccheck && "0" == window.synccheck.retcode ? "0" != window.synccheck.selector ? c.sync().then(function(t) {                    e.resolve(t)                }, function(e) {                    console.log("syncCheck sync nothing", e)                }) : e.reject(window.synccheck && window.synccheck.selector) : !window.synccheck || "1101" != window.synccheck.retcode && "1102" != window.synccheck.retcode ? window.synccheck && "1100" == window.synccheck.retcode ? r.loginout(0) : (e.reject("syncCheck net error"), i.report(i.ReportType.netError, {                    text: "syncCheck net error",                    url: s                })) : r.loginout(1)            }), e.promise        },        report: function() {}    };    return s}])

三、微信网页版公众号页面逻辑

涉及公众号的ui-route state为read。包含有两个view,分别为navView和contentView。navView为概览导航,contentView为正文阅读。根据contentView的定义,可以看到正文阅读的模版为ContentRead.html。

涉及公众号的Directive为navReadDirective,对应的模版为navRead.html。

公众号正文窗口右上角的按钮模版:

其中,涉及公众号数据初始化的代码如下(见全局数据初始化代码):

S.init(n.MPSubscribeMsgList)

S为subscribeMsgService, subscribeMsgService定义如下。

angular.module("Services").factory("subscribeMsgService", ["$rootScope", "contactFactory", "accountFactory", "confFactory", "utilFactory", function(e, t, o, n, r) {    var a = [],        i = {            current: null,            changeFlag: 0,            init: function(e) {                this.changeFlag = Date.now(), this.add(e)            },            getSubscribeMsgs: function() {                return a            },            add: function(e) {                e.length > 0 && (this.changeFlag = Date.now());                for (var t = 0, n = e.length; n > t; t++) {                    var i = e[t];                    i.HeadImgUrl = i.HeadImgUrl = r.getContactHeadImgUrl({                        UserName: i.UserName,                        Skey: o.getSkey()                    });                    for (var c = i.MPArticleList, s = 0; s < c.length; s++) {                        var l = c[s];                        l.AppName = i.NickName, /dev\.web\.weixin/.test(location.href) || (l.Url = l.Url.replace(/^http:\/\//, "https://"))                    }                    a.push(i)                }            }        };    return i}])

公众号更新频率

通过实际运行测试,以及代码分析,可以看到,微信网页版仅在页面载入时初始化公众号文章。

一旦载入,不再刷新,除非刷新页面。

但凡使用到公众号的数据的地方,都是调用的subscribeMsgService的getSubscribeMsgs方法,这个方法直接返回的是subscribeMsgService内部的变量a。

四、微信网页版数据初始化之获取分析

通过访问 cgi-bin/mmwebwx-bin/webwxinit 来获取。

HTTP请求包分析

通过chrome的network选项卡,可以看到该请求。

请求概览
a. Request URL:https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-1311101047b. Request Method:POSTc. Status Code:200 OKd. Remote Address:101.226.76.164:443
请求头
a. Accept:application/json, text/plain, */*b. Accept-Encoding:gzip, deflate, brc. Accept-Language:zh,zh-CN;q=0.8d. Cache-Control:no-cachee. Connection:keep-alivef. Content-Length:100g. Content-Type:application/json;charset=UTF-8h. Cookie:xxxxxxxxxxxxxi. Host:wx.qq.comj. Origin:https://wx.qq.comk. Pragma:no-cachel. Referer:https://wx.qq.com/?mmdebugm. User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36
请求数据Palyload
BaseRequest:{Uin: "xxxxxx", Sid: "xxxxxx", Skey: "", DeviceID: "e085070410028608"}

代码片段

以下通过代码说明。

c({    method: "POST",    url: o.API_webwxinit,    MMRetry: {        count: 1,        timeout: 1    },    data: {        BaseRequest: {            Uin: n.getUin(),            Sid: n.getSid(),            Skey: n.getSkey(),            DeviceID: n.getDeviceID()        }    }})

请求为POST方法,url为o.API_webwxinit方法获取,payload数据为一个JSON对象。

其中API_webxinit方法为

"/cgi-bin/mmwebwx-bin/webwxinit?r=" + ~new Date

JSON对象中,Uin和Sid都是cookie中获取的,DeviceID为临时生成,具体参见getDeviceID()。

getDeviceID: function() {    return "e" + ("" + Math.random().toFixed(15)).substring(2, 17)},

HTTP响应包

响应头
a. Connection:keep-aliveb. Content-Encoding:gzipc. Content-Length:30065d. Content-Type:text/plain
响应数据
{    "MPSubscribeMsgList": [{        "UserName": "@5a655a99997e77131aeec13f150dea45",        "MPArticleCount": 2,        "MPArticleList": [{            "Title": "xxx",            "Digest": "xxx",            "Cover": "http://mmbiz.qpic.cn/mmbiz_jpg/oZ9tOATGCKc1OIicaT9SGz2O3vUorCj7IdrCr0Al8F6cTMzvsMkVsgwS6iaablSEibLDrsjoUNvlc8Q7RxEqnLfibA/640?wxtype=jpeg&wxfrom=0",            "Url": "http://mp.weixin.qq.com/s?__biz=MzA4NDc2MzIwNA==&mid=2656521000&idx=1&sn=d30318e4b975a806a71abfca19d11128&chksm=8441dc03b3365515536c5bcacea1796d438c68eff39172537c78c0f27c1a13003e8050f2056f&scene=0#rd"        }, {            "Title": "xxxx",            "Digest": "xxxx",            "Cover": "http://mmbiz.qpic.cn/mmbiz_jpg/oZ9tOATGCKc1OIicaT9SGz2O3vUorCj7IdrjZg89vPLkg1gcHN2iaYD35WVVaVZ4AnicRKUBBxmBZcWgX5PslHmAA/300?wxtype=jpeg&wxfrom=0",            "Url": "http://mp.weixin.qq.com/s?__biz=MzA4NDc2MzIwNA==&mid=2656521000&idx=2&sn=7e8fab0fd99b7aa32c971164887e4da9&chksm=8441dc03b33655150ffc203bcb7d7eac2ddb8694a98ed3eededa18c532908a01757cd534ba7f&scene=0#rd"        }],        "Time": 1483759210,        "NickName": "xxxx"    }]}

五、总结

微信网页版前端的调试和生产环境在同一个库文件中,根据URL地址的请求参数来判断是否调试环境。如果是生产环境,则将console.log赋值为null,这样,在chrome的F12下将看不到调试信息。

开启微信网页版调试模式,在微信网页版的地址栏后增加/?mmdebug。

转载于:https://www.cnblogs.com/vimisky/p/6260713.html

你可能感兴趣的文章
HDU 1102 Constructing Roads
查看>>
多线程之ThreadLocal类
查看>>
OC语言description方法和sel
查看>>
C#中得到程序当前工作目录和执行目录的五种方法
查看>>
python 迭代器与生成器
查看>>
[django]form的content-type(mime)
查看>>
仿面包旅行个人中心下拉顶部背景放大高斯模糊效果
查看>>
C# 小叙 Encoding (二)
查看>>
CSS自学笔记(14):CSS3动画效果
查看>>
项目应用1
查看>>
基本SCTP套接字编程常用函数
查看>>
C 编译程序步骤
查看>>
[Git] 005 初识 Git 与 GitHub 之分支
查看>>
【自定义异常】
查看>>
pip install 后 importError no module named "*"
查看>>
springmvc跳转方式
查看>>
IOS 第三方管理库管理 CocoaPods
查看>>
背景色渐变(兼容各浏览器)
查看>>
iOS 电话在后台运行时,我的启动图片被压缩
查看>>
运用PCA进行降维的好处
查看>>