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

一、微信网页版前端结构

微信网页版为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 = $('<a href="javascript:;" onclick="var url = window.msg_source_url || window.location.href; var win = window.top.open(url, \'_blank\'); win.focus();" style="position: absolute; bottom: 20px; left: 15px; width: 4em; height: 25px; background: #FFFFFF;">阅读原文</a>');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。

<script type="text/ng-template" id="contentRead.html"><div class="box reader"><div class="box_hd with_border read_list_header"><div class="ext" ng-if="readItem"><a href="javascript:;" ng-click="optionMenu();"><i class="titlebar_menuicon"></i></a></div><div class="title_wrap"><div class="title">{{readItem.AppName}}</div></div></div><div class="box_bd"><iframe ng-src="{{readItem.Url}}" frameborder="0" class="iframe" id="reader"></iframe></div>
</div></script>

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

<script type="text/ng-template" id="navRead.html"><!--BEGIN chat list-->
<div jquery-scrollbar class="read_list scrollbar-dynamic" id="J_NavReadScrollBody"><p class="ico_loading" ng-show="subscribeMsgs.defaultValue"><img src="https://res.wx.qq.com/zh_CN/htmledition/v2/images/icon/ico_loading31e225.gif" alt=""/>加载中...</p><p class="ico_loading" ng-show="!subscribeMsgs.defaultValue && subscribeMsgs.length == 0">暂无文章...</p><div  data-no-cache="true" mm-repeat="readItem in articleList" data-height-calc="heightCalc" data-buffer-height="200" mm-repeat-keyboard mm-repeat-keyboard-scroll-selector="#J_NavReadScrollBody"><div class="just_for_bg" ng-if="readItem.UserName" ng-class="{first: readItem._index === 0}"><div class="read_item_hd"><p class="date">{{readItem.Time|timeFormat}}</p><div class="avatar"><img class="img" src="https://res.wx.qq.com/zh_CN/htmledition/v2/images/img31e225.gif" mm-src="{{readItem.HeadImgUrl}}" alt=""/></div><p class="info"><span class="username">{{readItem.NickName}}</span></p></div></div><div ng-if="!readItem.UserName" class="read_item slide-left"ng-click="itemClick(readItem)"ng-class="{'active': (readItem == currentItem)}"><div class="cont"><h3 class="title">{{readItem.Title}}</h3></div><div class="ext"><div class="cover"><div class="img" ng-style="{'background-image': 'url('+ readItem.Cover +')'}"></div></div></div></div></div>
</div>

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

<script type="text/ng-template" id="readMenu.html"><ul class="dropdown_menu"><li><a href="javascript:;" title="复制网页链接" clip-copy="copyLink()" clip-click="copyCallback()"><i class="menuicon_copylink"></i>复制网页链接        </a></li><!--<li>--><!--<a href="javascript:;" title="转发" ng-click="forwarding()">转发</a>--><!--</li>--><li class="last_child"><a href="javascript:;" title="新窗口中打开" ng-click="openTab()"><i class="menuicon_newtab"></i>新窗口中打开        </a></li>
</ul>
</script>

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

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=-1311101047
b. Request Method:
POST
c. Status Code:
200 OK
d. Remote Address:
101.226.76.164:443
请求头
a. Accept:
application/json, text/plain, */*
b. Accept-Encoding:
gzip, deflate, br
c. Accept-Language:
zh,zh-CN;q=0.8
d. Cache-Control:
no-cache
e. Connection:
keep-alive
f. Content-Length:
100
g. Content-Type:
application/json;charset=UTF-8
h. Cookie:
xxxxxxxxxxxxx
i. Host:
wx.qq.com
j. Origin:
https://wx.qq.com
k. Pragma:
no-cache
l. Referer:
https://wx.qq.com/?mmdebug
m. 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-alive
b. Content-Encoding:
gzip
c. Content-Length:
30065
d. 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

微信网页版前端源码分析(一)源码结构和公众号处理逻辑相关推荐

  1. 微信网页版协议分析和实现机器人

    原文链接:https://github.com/biezhi/wechat-robot/blob/master/doc/protocol.md 分析微信网页版协议,使用普通微信号开发微信机器人. -- ...

  2. 浅谈扫描二维码登录微信网页版与摇一摇传图的实现原理

    前言:简单体验了下微信网页版通过二维码登录和摇一摇传图功能,从技术角度看,网上专家吹捧的 [隔空取物]其实并不神秘,我先简单分析一下. 1. 微信移动端扫描二维码登录(C-S-C模式) CSC模式为: ...

  3. [微信] 微信网页版扫码登录的实现

    我们先来回顾一下微信网页版的扫码登录过程 1. 打开微信网页版,https://wx.qq.com/ 2. 打开手机微信客户端,扫一扫 3. 点击确定,登录 看似简单的操作流程,中间涉及的数据交互有很 ...

  4. 「微信群合影2.5.0」- 微信网页版账号不能登录解决办法,扫码登录

    「微信群合影qunheying.com」- 一键生成微信全家福 「 微信群合影 2.5.0 」版本更新: 支持微信网页版不能登录账号生成群合影, 通过扫码登录获取 在一键生成全家福的过程中,有一些用户 ...

  5. springboot+netty 仿微信网页版聊天工具

    本程序仿照微信界面进行开发,使用springboot+netty完成整体的框架开发,数据库方面使用h2数据库,前端部分使用thymeleaf,后期将会继续开发Ant Design React版.打包时 ...

  6. 基于.Net平台C#的微信网页版API

    git上有很多类似的项目,但大多都是python和js的,为了便于.Net windows平台的使用,我重构了一个.Net版本的,已整理开源 https://github.com/leestar54/ ...

  7. 网页版登录入口_企业微信网页版怎么登录?企业微信客户端和网页版有什么区别?...

    文丨语鹦企服私域管家原创,未经授权不得转载 企业微信有网页版也有客户端,很多小伙伴可能搞不清,今天语鹦企服就带你一起看看,企业微信客户端和网页版有什么区别?以及如何登录使用. ▎企业微信网页版: 与微 ...

  8. Linux内核 eBPF基础:kprobe原理源码分析:源码分析

    Linux内核 eBPF基础 kprobe原理源码分析:源码分析 荣涛 2021年5月11日 在 <Linux内核 eBPF基础:kprobe原理源码分析:基本介绍与使用>中已经介绍了kp ...

  9. 用python 、itchat登录微信网页版 微商自动回复功能、抓取微信好友信息列表。

    最近用Python实现了一些微信的简单玩法 我们可以通过网页版的微信 微信网页版 ,扫码登录后去抓包爬取微信信息,还可以post去发送信息. >>安装itchat这个库 pip insta ...

最新文章

  1. 计算机应用基础电子演示文稿系统行考作业,最新电大计算机应用基础形考PowerPoint答案...
  2. __stdcall 和 __cdecl 的区别浅析
  3. 神策用户画像 Demo 来了!(文末免费体验)
  4. NAO机器人学习小计
  5. html中高与行高的区别,深入了解css的行高Line Height属性
  6. php 怎么使循环少一次,PHP-如何让一个类仅在循环中应用一次?
  7. 随想录(形式化验证小结)
  8. application实现网页计数_手把手教你利用爬虫爬网页(Python代码)
  9. 聊聊Elasticsearch RestClient的RequestLogger
  10. 11.Axis客户端接收不同参数类型
  11. .tar.gz和.tar.bz2解压命令
  12. 简单破解闪电视频转换王
  13. MySQL 分页查询
  14. JAVA代码实现计算器功能
  15. oracle正则表达式非数字,oracle 判断是否数字 正则表达式法
  16. Java面试快问快答-Instrument机制
  17. php strtotime技巧,获取前几天、前几周、后几天、后几周,本月开始和本月结束时间
  18. 给元素设置鼠标移入后变为手型的属性
  19. 微商最低成本引流,学会这招日引精准粉1000+
  20. 04 : mysql 基础命令操作,字符集

热门文章

  1. 为什么使用计算机网络连接,为什么无线网络连接上却不能上网,教您电脑连上无线网却不能上网怎么办...
  2. 「Python条件结构」显示学号及提示信息
  3. 股票大作手回忆录(读书笔记)
  4. nas修改启动盘sn和mac
  5. vmware虚拟机连接usb,显示:无法识别的usb设备,跟这台计算机连接的前一个usb设备工作不正常
  6. npm下载swiper包报错
  7. 腾讯招聘信息 爬取案例
  8. 前端-js网页特效(一)倒计时效果及原理
  9. uniapp MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 upgrade listeners
  10. 企业邮箱能传多大的附件?企业邮箱附件大小有限制吗?