H5 代码调试大法

1、PC端项目

用Chrome调试工具能解决大部分问题

怎么办呢,把错误捕获就OK啦

  1. 同步代码运行时异常(非异步代码,无语法错误)
    用try,catch包裹就完事了
  2. 异步代码(setTimeout等)和语法错误
window.onerror = function(message, source, lineno, colno, error){
  console.log(lineno+'行捕获到异常:',message,source,error);
  return true; //阻止异常继续向上抛
}
setTimeout(function(){ throw Error("oh no!") }, 0)
  1. promise产生的异常
    在new promise时链式调用.catch就可以捕获到异常。或者用
window.addEventListener("unhandledrejection", function(e){
  // Event新增属性
  // @prop {Promise} promise - 状态为rejected的Promise实例
  // @prop {String|Object} reason - 异常信息或rejected的内容
  // 会阻止异常继续抛出,不让Uncaught(in promise) Error产生
  e.preventDefault()
})
  1. 网络请求异常,如404等
window.addEventListener("error", function(e){
  console.log('网络错误:'+e) // 回显false
}, true)

2、移动端项目

  • vconsole
    该插件能在手机端查看:
    1、后台接口请求,
    2、index.html引入了哪些文件;
    3、html的element;
    4、查看具体css、js源文件
    缺陷:不能抓js.css.图片等静态资源的包,只能抓ajax包
    用法: 引入以下js文件即可
//js引入
<script src='https://res.wx.qq.com/mmbizwap/zh_CN/htmledition/js/vconsole/3.0.0/vconsole.min.js'></src>
<script src='https://raw.githubusercontent.com/WechatFE/vConsole-sources/dev/dist/vconsole-sources.min.js'></src>
<script src='https://raw.githubusercontent.com/WechatFE/vConsole-elements/master/dist/vconsole-elements.min.js'></src>

也可用webpack引入
https://github.com/diamont1001/vconsole-webpack-plugin

  • weinre
    weinre比vconsole更强大,可在手机端高亮显示鼠标选中的区域
    1、npm install -g weinre
    2、任意目录下执行:weinre --httpPort 8080 --boundHost -all-
    3、浏览器打开localhost:8080(最好将localhost改为本机ip地址)
    将以下代码引到index.html
    <script src="http://10.2.137.143:8080/target/target-script-min.js#anonymous"></script>


  • Fiddle
    Fiddle几乎什么包都能抓,只是使用时要设代理,稍微麻烦点
    IPhone设置fidder代理
    1、 设置-无线局域网-HTTP代理(手动)-服务器(填写本机IP地址,比如10.2.136.85),端口8888
    2、 在浏览器打开本机IP+端口,比如10.2.136.85:8888,此时会要求你安装证书,选择安装
    3、 设置-通用-关于本机-证书信任设置-打开信任证书开关
    4、 打开fidder,在手机上进入任何应用都会被记录

Fidder 的原理:
当不设置代理时,手机上打开一个网页是通过自己手机的网络访问的
当设置代理时,手机上打开网页是通过你代理设置的IP网络访问的,一般也就是通过自己本机地址访问的,在电脑上打开fidder,fidder就可以通过8888端口监听到手机上的请求(fidder的设置项中有设置用哪个端口监听,Tools-options-connections)


fidder.png

Fidder的作用
1、 线上代码映射到本地代码
当把代码上传到服务器了,如果想改代码,可以做到本地修改代码看效果,不用将代码上传到服务器才看到效果,用到的是fidder的AutoResponder


image.png

bpafter https://ink-staging.sohusce.com/h5/info-detail/detail.html?message_id=9225
a--jump https://ink.sohu.com/h5/info-detail/detail.html?message_id=9225

HTTP/1.1 302 OK
Server: nginx
Date: Wed, 04 Apr 2018 14:16:43 GMT
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Last-Modified: Wed, 04 Apr 2018 13:43:15 GMT
Expires: Thu, 05 Apr 2018 14:16:43 GMT
Cache-Control: max-age=86400
Content-Length: 1366
Location: https://ink.sohu.com/h5/info-detail/detail.html?message_id=9225

// 自定义fidder输出
/*!
 * Fiddler CustomRules
 * @Version 1.0.0
 * @Author xxxily
 * @home https://github.com/xxxily/Fiddler-plus
 * @bugs https://github.com/xxxily/Fiddler-plus/issues
 */

/**
 * 全局配置项
 * 可配置链接类型的颜色,代理、替换地址等,
 * 默认对象的键【key】为要匹配的规则,值【key】为匹配后的配置
 */
var GLOBAL_SETTING:Object = {
    // 开启或禁止缓存
    disableCaching:false,
    // 过滤配置【用于过滤出哪些URL需要显示,哪些需要隐藏】
    Filter:{
        // 只显示URL包含以下字符的连接
        showLinks:[
        //  "fe.w.sohu.com",
          //  "fe.ink.sohu.com",
//  "ink.sohu.com",
        //  "cdn.sohusce.com",
        //  "10.2.138.70",
        //  "cdn.sohucs.com",
        //  "scdn.itc.cn",
//  "cdn.mxpnl.com",
//  "ink-test.sce.sohuno.com",
//  "ink.sce.sohuno.com",
//  "sns-dev.bjcnc.img.sohucs.com",
//  "527920a520000.cdn.sohucs.com",
//  "ink-public.bjcnc.img.sohucs.com",
//  "ink-backflow.sce.sohuno.com"
        //     "baidu.com",
            // "youdao.com"
        ],
        // 隐藏URL包含以下字符串的连接 过滤
        hideLinks:[

        ],
        // 只显示以下文件类型【注意:是根据header的 Content-Type字段进行匹配的,所以js文件直接写js是不行的,但支持模糊匹配 】
        // 附注:使用ContentType过滤的时候不一定准确,不带 ContentType的连接会被自动隐藏,该过滤选项的逻辑还有待优化和完善
        showContentType:[
            // "image",
            //"css",
           // "html",
          //  "javascript"
       ],
        // 隐藏以下文件类型
        hideContentType:[
            //"css",
            //"html",
            //"javascript"
        ]
    },
    // 替换URL【可用于多环境切换、解决跨域、快速调试线上脚本等】
    replace:{
        "":""
    },

    // 界面显示配置【可以对不同链接进行颜色标识,以便快速定位相关链接】
    UI:{
        // 默认文本颜色
        color:"white",//灰白色
        // 默认背景颜色
        bgColor:"black",//浅黑
        bgColor_02:"#2f2f2f",//浅黑【用于做交替背景,可以不设置】
        // bgColor_02:"#4b4a4a",
        // 链接返回报错时的颜色
        onError:{
            // bgColor:"#2c2c2c",
            color:"#FF0000" //错误红
            // ,bold:"true"
        },
        // 不同关键词匹配对应的连接颜色,key 对应的是匹配的关键字,val对应的是匹配的颜色
        linkColor:{
            "\\.jpg|\\.png|\\.gif":"#ffccff", //粉紫色
            "\\.js":"#00ff00", //原谅色
            "\\.css":"#ffcc66", //米黄
            "\\.html":"#00d7ff", //蓝色
            "\\.php":"#fff32d", //大黄
            "\\.jsp":"#fd4107" //砖红
        },
        // 可以为特殊状态码设置不同颜色,方便快速定位一些错误链接,例如404等
        // 注意:这个只是根据responseCode 来匹配的,一些不存在response的链接配置是无效的,例如 502,504状态,应该是在onError里配置的
        statusCode:{
            "404|408|500|502|504":"#FF0000", //错误红
            "304":"#5e5e5e" //浅灰色
        },
        // 高亮,对特殊的链接进行高亮设置,方便跟踪查看链接
        highlight:{
            "http://localhost|192.168":{
                // bgColor:"#2c2c2c", //浅黑
                color:"#00ff00", //原谅色
                bold:"true",
                describe:"高亮测试"
            },
            "www.greatytc.com":{
                bgColor:"#FF0000", //红色
                color:"#fdf404", //黄色
                bold:"true",
                describe:"高亮测试"
            },
            "":""
        }
    },
    // 一些实用工具集,先列个可能会开发的工具集,留个坑以后有时间再开发
    Tools:{
        // TODO API 测试工具
        apiTest:{},
        // TODO 重放攻击工具
        replay:{},
        // TODO js 格式化工具
        jsFormat:{},
        // TODO css 格式化工具
        cssFormat:{},
        // TODO 内容注入工具
        contentInject:{},
        // TODO 类似 weinre 这样的注入调试工具
        weinre:{}
    },
    // 多项分隔符号【同一个配置需匹配多项规则时可以通过分隔符进行区分,这样就不用每个规则都要新开一份配置那么繁琐】
    splitStr:"|",
    // 正则匹配的修饰符:i,g,m 默认i,不区分大小写
    regAttr:"i"
};
//全局配置项 END



// 调试方法 BEGIN
if( !console ){
    var console = {} ;
    console.log = function (arg1,arg2,arg3,arg4,arg5,arg6) {

        // 不支持 arguments ,尴尬!
        var args = [arg1,arg2,arg3,arg4,arg5,arg6] ;
        var argsLen = 0 ;
        for( var i = 0 ; i < args.length ; i++ ){
            var arg = args[i] ;
            if( typeof arg === "undefined"){
                break ;
            }
            argsLen += 1 ;

            var argType = typeof arg ;

            if( argType === "string" || argType === "number" ){
                FiddlerObject.log( arg );
            }else if( argType === "boolean"){
                FiddlerObject.log( "boolean:"+arg );
            }else if( argType === "object" && arg.toString ){
                FiddlerObject.log( arg.toString() );
            }else {
                try {
                    FiddlerObject.log( "尝试遍历输出:"+argType );
                    for( var str = "" in arg ){
                        FiddlerObject.log( str+":"+arg[str] );
                    }
                }catch (ex){
                    FiddlerObject.log( "遍历输出失败:"+ex );
                    FiddlerObject.log( arg );
                }
            }
        }
        if(argsLen > 1){
            FiddlerObject.log( "----------------------------------------------" );
        }
    }
}
if( !alert ){
    var alert = FiddlerObject.alert ;
}
// 调试方法 END


import System;
import System.Windows.Forms;
import Fiddler;
// INTRODUCTION
//
// Well, hello there!
//
// Don't be scared! :-)
//
// This is the FiddlerScript Rules file, which creates some of the menu commands and
// other features of Fiddler. You can edit this file to modify or add new commands.
//
// The original version of this file is named SampleRules.js and it is in the
// \Program Files\Fiddler\ folder. When Fiddler first runs, it creates a copy named
// CustomRules.js inside your \Documents\Fiddler2\Scripts folder. If you make a
// mistake in editing this file, simply delete the CustomRules.js file and restart
// Fiddler. A fresh copy of the default rules will be created from the original
// sample rules file.

// The best way to edit this file is to install the FiddlerScript Editor, part of
// the free SyntaxEditing addons. Get it here: http://fiddler2.com/r/?SYNTAXVIEWINSTALL

// GLOBALIZATION NOTE: Save this file using UTF-8 Encoding.

// JScript.NET Reference
// http://fiddler2.com/r/?msdnjsnet
//
// FiddlerScript Reference
// http://fiddler2.com/r/?fiddlerscriptcookbook

class Handlers
{
    // *****************
    //
    // This is the Handlers class. Pretty much everything you ever add to FiddlerScript
    // belongs right inside here, or inside one of the already-existing functions below.
    //
    // *****************

    // The following snippet demonstrates a custom-bound column for the Web Sessions list.
    // See http://fiddler2.com/r/?fiddlercolumns for more info
    /*
     public static BindUIColumn("Method", 60)
     function FillMethodColumn(oS: Session): String {
     return oS.RequestMethod;
     }
     */

    // The following snippet demonstrates how to create a custom tab that shows simple text
    /*
     public BindUITab("Flags")
     static function FlagsReport(arrSess: Session[]):String {
     var oSB: System.Text.StringBuilder = new System.Text.StringBuilder();
     for (var i:int = 0; i<arrSess.Length; i++)
     {
     oSB.AppendLine("SESSION FLAGS");
     oSB.AppendFormat("{0}: {1}\n", arrSess[i].id, arrSess[i].fullUrl);
     for(var sFlag in arrSess[i].oFlags)
     {
     oSB.AppendFormat("\t{0}:\t\t{1}\n", sFlag.Key, sFlag.Value);
     }
     }
     return oSB.ToString();
     }
     */

    // You can create a custom menu like so:
    /*
     QuickLinkMenu("&Links")
     QuickLinkItem("IE GeoLoc TestDrive", "http://ie.microsoft.com/testdrive/HTML5/Geolocation/Default.html")
     QuickLinkItem("FiddlerCore", "http://fiddler2.com/fiddlercore")
     public static function DoLinksMenu(sText: String, sAction: String)
     {
     Utilities.LaunchHyperlink(sAction);
     }
     */

    public static RulesOption("Hide 304s")
    BindPref("fiddlerscript.rules.Hide304s")
    var m_Hide304s: boolean = false;

    // Cause Fiddler to override the Accept-Language header with one of the defined values
    public static RulesOption("Request &Japanese Content")
    var m_Japanese: boolean = false;

    // Automatic Authentication
    public static RulesOption("&Automatically Authenticate")
    BindPref("fiddlerscript.rules.AutoAuth")
    var m_AutoAuth: boolean = false;

    // Cause Fiddler to override the User-Agent header with one of the defined values
    // The page http://browserscope2.org/browse?category=selectors&ua=Mobile%20Safari is a good place to find updated versions of these
    RulesString("&User-Agents", true)
    BindPref("fiddlerscript.ephemeral.UserAgentString")
    RulesStringValue(0,"Netscape &3", "Mozilla/3.0 (Win95; I)")
    RulesStringValue(1,"WinPhone8.1", "Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 520) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537")
    RulesStringValue(2,"&Safari5 (Win7)", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1")
    RulesStringValue(3,"Safari9 (Mac)", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11) AppleWebKit/601.1.56 (KHTML, like Gecko) Version/9.0 Safari/601.1.56")
    RulesStringValue(4,"iPad", "Mozilla/5.0 (iPad; CPU OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12F5027d Safari/600.1.4")
    RulesStringValue(5,"iPhone6", "Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12F70 Safari/600.1.4")
    RulesStringValue(6,"IE &6 (XPSP2)", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)")
    RulesStringValue(7,"IE &7 (Vista)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1)")
    RulesStringValue(8,"IE 8 (Win2k3 x64)", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; WOW64; Trident/4.0)")
    RulesStringValue(9,"IE &8 (Win7)", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)")
    RulesStringValue(10,"IE 9 (Win7)", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)")
    RulesStringValue(11,"IE 10 (Win8)", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)")
    RulesStringValue(12,"IE 11 (Surface2)", "Mozilla/5.0 (Windows NT 6.3; ARM; Trident/7.0; Touch; rv:11.0) like Gecko")
    RulesStringValue(13,"IE 11 (Win8.1)", "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko")
    RulesStringValue(14,"Edge (Win10)", "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.11082")
    RulesStringValue(15,"&Opera", "Opera/9.80 (Windows NT 6.2; WOW64) Presto/2.12.388 Version/12.17")
    RulesStringValue(16,"&Firefox 3.6", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.7) Gecko/20100625 Firefox/3.6.7")
    RulesStringValue(17,"&Firefox 43", "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0")
    RulesStringValue(18,"&Firefox Phone", "Mozilla/5.0 (Mobile; rv:18.0) Gecko/18.0 Firefox/18.0")
    RulesStringValue(19,"&Firefox (Mac)", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0")
    RulesStringValue(20,"Chrome (Win)", "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.48 Safari/537.36")
    RulesStringValue(21,"Chrome (Android)", "Mozilla/5.0 (Linux; Android 5.1.1; Nexus 5 Build/LMY48B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.78 Mobile Safari/537.36")
    RulesStringValue(22,"ChromeBook", "Mozilla/5.0 (X11; CrOS x86_64 6680.52.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.74 Safari/537.36")
    RulesStringValue(23,"GoogleBot Crawler", "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)")
    RulesStringValue(24,"Kindle Fire (Silk)", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.0.22.79_10013310) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=true")
    RulesStringValue(25,"&Custom...", "%CUSTOM%")
    public static var sUA: String = null;

    // Cause Fiddler to delay HTTP traffic to simulate typical 56k modem conditions
    public static RulesOption("Simulate &Modem Speeds", "Per&formance")
    var m_SimulateModem: boolean = false;

    // Removes HTTP-caching related headers and specifies "no-cache" on requests and responses
    public static RulesOption("&Disable Caching", "Per&formance")
    // 默认禁止缓存
    var m_DisableCaching: boolean = false;

    public static RulesOption("Cache Always &Fresh", "Per&formance")
    var m_AlwaysFresh: boolean = false;

    // Force a manual reload of the script file.  Resets all
    // RulesOption variables to their defaults.
    public static ToolsAction("Reset Script")
    function DoManualReload() {
        FiddlerObject.ReloadScript();
    }

    public static ContextAction("Decode Selected Sessions")
    function DoRemoveEncoding(oSessions: Session[]) {
    for (var x:int = 0; x < oSessions.Length; x++){
        oSessions[x].utilDecodeRequest();
        oSessions[x].utilDecodeResponse();
    }
    UI.actUpdateInspector(true,true);
}

    // ------------ 通用公共方法 BEGIN ------------

    /**
     * 准确地获取对象的具体类型 参见:https://www.talkingcoder.com/article/6333557442705696719
     * @param obj { all } -必选 要判断的对象
     * @returns {*} 返回判断的具体类型
     */
    public static function getType(obj){
        if(obj == null){
            return String(obj);
        }
        return typeof obj === 'object' || typeof obj === 'function' ?
            obj.constructor && obj.constructor.name && obj.constructor.name.toLowerCase() ||
            /function\s(.+?)\(/.exec(obj.constructor)[1].toLowerCase():
            typeof obj;
    }

    /**
     * 根据字符串创建一个正则表达式
     * @param str (String) -必选 正则字符串
     * @param attributes (String) -可选 匹配修饰符,i,g,m, 默认不添加任何修饰符
     */
    static function createPattern(str,attributes) {
        try {
            var att = attributes || GLOBAL_SETTING.regAttr || "" ;
            return new RegExp( str,att ) ;
        }catch (ex){
            console.log( ex.toString() );
            return false ;
        }
    }

    /**
     * 判断某个字符串是否存在另外一个字符串里面,如果存在则进行相关回调操作,不存在则忽略
     * @param s_str (String) -必选 源字符串
     * @param m_str (String) -必选 需要匹配的字符串
     * @param callback (Function) -必选 匹配成功的回调操作,回调中返回匹配到的字符串、源字符串、需匹配的字符串
     * @param splitStr (String) -可选 多项分隔符,默认为:,
     * @param attributes (String) -可选 匹配修饰符,i,g,m, 默认不添加任何修饰符
     */
    public static function matchPlus( s_str,m_str,callback,splitStr,attributes ){
        if( !s_str || !m_str || typeof callback !== "function"){
            return false ;
        }
        var spStr = splitStr || GLOBAL_SETTING.splitStr || ",",
            att = attributes || "",
            regArr = m_str.split( spStr ),
            len = regArr.length,
            i = 0,
            canBreak = false ;
        for( i ; i < len ; i++ ){
            var regStr = regArr[i] ;
            // var patt = new RegExp(regStr,att);
            var patt = createPattern( regStr,attributes );
            if( !patt ){
                canBreak = callback(null);
            }else if( patt.test( s_str ) ){
                canBreak = callback(regStr);
            }else {
                continue ;
            }
            if( canBreak === true ){
                break ;
            }
        }
    }

    /**
     * 根据配置项 匹配 oSession里面的url对象,进行相关处理,注意:为了方便查看,配置项使用"|"进行多项分隔,所以如果写复杂的正则也包含|时会存在匹配冲突
     * 故有特殊需要的可以改变下面代码中的分隔符,使其能进行更复杂的正则匹配(配置项注意使用跟如下一致的分隔符)
     * @param str (String) -必选 要进行匹配判断的字符串(例如url)
     * @param settingItem (Object|Array) -必选 GLOBAL_SETTING 下的某个配置项,可以是对象,也可以是数组
     * @param callback (Function) -必选 如果存在跟配置项匹配的选项则进行相关回调处理
     * @param errorMsg (String) -可选 出错时提示信息,方便快速定位问题
     */
    public static function settingMatch ( str,settingItem,callback,errorMsg ) {

        var settingItemType = getType(settingItem) ;

        if( !str || !settingItem || (settingItemType === "array" && settingItem.length === 0 ) ){
            return -1 ;
        }

        var canBreak = false; // 是否可以终止循环,停掉没必要的循环,减少资源消耗
        var errorMsg = errorMsg||"url匹配时出现错误,请检查您的配置" ;
        // 为了节省代码类型为object 和 array的其实可以合并一起处理,这里为了区分对待,暂时不打算合并
        if( settingItemType === "object" ){
            // 对象类的配置是包含匹配项和下级配置项的,所以要进行特殊处理
            for( var itemKw = "" in settingItem ){
                matchPlus( str,itemKw,function ( matchStr ) {
                    if( !matchStr ){
                        console.log( str,errorMsg );
                    }else {
                        canBreak = callback( settingItem[itemKw],matchStr );
                        return canBreak ;
                    }
                },null,null);
                if( canBreak === true ){
                    break ;
                }
            }
        }else if( settingItemType === "array" ){
            // 数组类的配置项其实和字符串类的配置项一样,只是为了进行多项配置时查看更方便
            var tmLen = settingItem.length ;
            for( var i = 0 ; i < tmLen ; i++ ){
                matchPlus( str,settingItem[i],function ( matchStr ) {
                    if( !matchStr ){
                        console.log( str,errorMsg );
                    }else {
                        canBreak = callback( settingItem[i],matchStr );
                        return canBreak ;
                    }
                },null,null);
                if( canBreak === true ){
                    break ;
                }
            }
        }else if( settingItemType === "string" ){
            // 字符串类的配置,只关注是否匹配成功,不存在下级配置项
            matchPlus( str,settingItem,function ( matchStr ) {
                if( !matchStr ){
                    console.log( str,errorMsg );
                }else {
                    canBreak = callback( settingItem,matchStr );
                    return canBreak ;
                }
            },null,null);
        }else {
            return false ;
        }
    }

    /**
     * 所有参数和settingMatch一致,唯一区别是,在不匹配时进行回调,回调里不带任何参数
     */
    public static function settingUnMatch ( str,settingItem,callback,errorMsg ) {
        var isMatch = false ;
        var result = settingMatch(str,settingItem,function( conf,matchStr ){
            isMatch = true ;
            return true ;
        },errorMsg);
        if( !isMatch && result !== -1 ){
            callback();
        }
    }

    // 用于背景做交替显示的记号
    public static var showLinkCount = 0;

    /**
     * 隐藏连接,为了确保程序没错误隐藏连接,固写成统一的方法,方便快速调试
     * @param oSession (Session) -必选,Session 对象
     */
    public static function hideLink ( oSession ) {
        // console.log("以下连接已被隐藏:",oSession.fullUrl);
        showLinkCount -= 1 ;
        oSession["ui-hide"] = "true" ;
        oSession.Ignore();
    }

    /**
     * 设置 Session 的界面呈现
     * @param oSession (Session) -必选,Session 对象
     * @param conf (Object) -可选,要设置 Session 呈现的界面配置,形如:{bgColor:"#2c2c2c",color:"#FF0000",bold:true}
     */
    public static function setSessionDisplay ( oSession,conf ) {
        conf.bgColor ? oSession["ui-backcolor"] = conf.bgColor : "" ;
        conf.color ? oSession["ui-color"] = conf.color : "" ;
        conf.bold ? oSession["ui-bold"]= "true" : "" ;
    }

    // ------------ 通用公共方法 END ------------

    // 参考文档 http://docs.telerik.com/fiddler/KnowledgeBase/FiddlerScript/ModifyRequestOrResponse
    static function OnBeforeRequest(oSession: Session) {

        showLinkCount += 1 ;

        // 过滤出需要显示或隐藏的连接 BEGIN

        var showLinks = GLOBAL_SETTING.Filter.showLinks,
            hideLinks = GLOBAL_SETTING.Filter.hideLinks;
        // 过滤出要显示的连接,把不在显示列表里的连接隐藏掉
        settingUnMatch(oSession.fullUrl,showLinks,function(){
            hideLink( oSession );
        },"【showLinks】配置出错,请检查你的配置");


        // 过滤出要隐藏的连接,把在隐藏列表里的连接隐藏掉
        settingMatch(oSession.fullUrl,hideLinks,function( conf,matchStr ){
            hideLink( oSession );
            return true ;
        },"【hideLinks】配置出错,请检查你的配置");

        // 过滤出需要显示或隐藏的连接 END


        // 配色 BEGIN

        // 默认背景【存在 bgColor_02 时进行交替显示】 注:因为http请求的无序特性,所以不能确保百分百准确交替,待深入优化
        if( GLOBAL_SETTING.UI.bgColor_02 && (showLinkCount%2 === 0) ){
            oSession["ui-backcolor"] = GLOBAL_SETTING.UI.bgColor_02 ;
        }else {
            oSession["ui-backcolor"] = GLOBAL_SETTING.UI.bgColor || "#2c2c2c" ;
        }

        // 默认文本颜色
        oSession["ui-color"] = GLOBAL_SETTING.UI.color ;

        // 根据关键词设置连接渲染的颜色
        settingMatch(oSession.fullUrl,GLOBAL_SETTING.UI.linkColor,function( conf,matchStr ){
            conf ? oSession["ui-color"] = conf : "" ;
        },"【linkColor】配置出错,请检查你的配置");

        // 高亮特殊连接
        settingMatch(oSession.fullUrl,GLOBAL_SETTING.UI.highlight,function( conf,matchStr ){
            setSessionDisplay( oSession,conf );
        },"【highlight】配置出错,请检查你的配置");

        // 配色 END

        // 接管替换URL BEGIN

        // 简单替换
        settingMatch(oSession.fullUrl,GLOBAL_SETTING.replace,function( conf,matchStr ){
            // System.Text.RegularExpressions.Regex.IsMatch(oSession.fullUrl, "https://" );
            oSession.fullUrl = System.Text.RegularExpressions.Regex.Replace(oSession.fullUrl, matchStr , conf);
        },"【replace】配置出错,请检查你的配置");

        // 高级替换
        var replacePlus = GLOBAL_SETTING.replacePlus ;
        if( replacePlus && replacePlus.length > 0 ){
            var rpLen = replacePlus.length ;
            for( var i = 0 ; i < rpLen ; i++ ){
                var rpSettingItem = replacePlus[i] ;
                if( rpSettingItem.enabled === true && rpSettingItem.replaceWith ){
                    settingMatch(oSession.fullUrl,rpSettingItem.source,function( conf,matchStr ){
                        var newUrl = System.Text.RegularExpressions.Regex.Replace(oSession.fullUrl, matchStr , rpSettingItem.replaceWith) ;
                        if( rpSettingItem.urlContain || rpSettingItem.urlUnContain ){
                            // 进行二级匹配

                            settingMatch( oSession.fullUrl,rpSettingItem.urlContain,function( matchStr02 ){
                                oSession.fullUrl = newUrl ;
                                setSessionDisplay( oSession,rpSettingItem );
                            },"【replacePlus里面的urlContain】配置出错,请检查你的配置");

                            settingUnMatch( oSession.fullUrl,rpSettingItem.urlUnContain,function( matchStr02 ){
                                oSession.fullUrl = newUrl ;
                                setSessionDisplay( oSession,rpSettingItem );
                            },"【replacePlus里面的urlUnContain】配置出错,请检查你的配置");

                        }else {
                            oSession.fullUrl = newUrl ;
                            setSessionDisplay( oSession,rpSettingItem );
                        }
                    },"【replacePlus】配置出错,请检查你的配置");
                }
            }
        }

        // 接管替换URL END

        // Sample Rule: Color ASPX requests in RED
        // if (oSession.uriContains(".aspx")) { oSession["ui-color"] = "red";   }

        // Sample Rule: Flag POSTs to fiddler2.com in italics
        // if (oSession.HostnameIs("www.fiddler2.com") && oSession.HTTPMethodIs("POST")) {  oSession["ui-italic"] = "yup";  }

        // Sample Rule: Break requests for URLs containing "/sandbox/"
        // if (oSession.uriContains("/sandbox/")) {
        //     oSession.oFlags["x-breakrequest"] = "yup";   // Existence of the x-breakrequest flag creates a breakpoint; the "yup" value is unimportant.
        // }

        // 通过QuickExec 输入字符串来筛选出要高亮的url
        if( (null != filter_and_highlight_url) && oSession.uriContains( filter_and_highlight_url ) ){
            oSession["ui-color"] = "#FF0000" ;
            oSession["ui-bold"]="true";
        }

        if ((null != gs_ReplaceToken) && (oSession.fullUrl.indexOf(gs_ReplaceToken)>-1)) {   // Case sensitive
            oSession.fullUrl = oSession.fullUrl.Replace(gs_ReplaceToken, gs_ReplaceTokenWith);
        }
        if ((null != gs_OverridenHost) && (oSession.host.toLowerCase() == gs_OverridenHost)) {
            oSession["x-overridehost"] = gs_OverrideHostWith;
        }

        if ((null!=bpRequestURI) && oSession.uriContains(bpRequestURI)) {
            oSession["x-breakrequest"]="uri";
        }

        if ((null!=bpMethod) && (oSession.HTTPMethodIs(bpMethod))) {
            oSession["x-breakrequest"]="method";
        }

        if ((null!=uiBoldURI) && oSession.uriContains(uiBoldURI)) {
            oSession["ui-bold"]="QuickExec";
        }

        if (m_SimulateModem) {
            // Delay sends by 300ms per KB uploaded.
            oSession["request-trickle-delay"] = "300";
            // Delay receives by 150ms per KB downloaded.
            oSession["response-trickle-delay"] = "150";
        }

        if (m_DisableCaching || GLOBAL_SETTING.disableCaching) {
            oSession.oRequest.headers.Remove("If-None-Match");
            oSession.oRequest.headers.Remove("If-Modified-Since");
            oSession.oRequest["Pragma"] = "no-cache";
        }

        // User-Agent Overrides
        if (null != sUA) {
            oSession.oRequest["User-Agent"] = sUA;
        }

        if (m_Japanese) {
            oSession.oRequest["Accept-Language"] = "ja";
        }

        if (m_AutoAuth) {
            // Automatically respond to any authentication challenges using the
            // current Fiddler user's credentials. You can change (default)
            // to a domain\\username:password string if preferred.
            //
            // WARNING: This setting poses a security risk if remote
            // connections are permitted!
            oSession["X-AutoAuth"] = "(default)";
        }

        if (m_AlwaysFresh && (oSession.oRequest.headers.Exists("If-Modified-Since") || oSession.oRequest.headers.Exists("If-None-Match")))
        {
            oSession.utilCreateResponseAndBypassServer();
            oSession.responseCode = 304;
            oSession["ui-backcolor"] = "Lavender";
        }
    }
    //OnBeforeRequest END

    // This function is called immediately after a set of request headers has
    // been read from the client. This is typically too early to do much useful
    // work, since the body hasn't yet been read, but sometimes it may be useful.
    //
    // For instance, see
    // http://blogs.msdn.com/b/fiddler/archive/2011/11/05/http-expect-continue-delays-transmitting-post-bodies-by-up-to-350-milliseconds.aspx
    // for one useful thing you can do with this handler.
    //
    // Note: oSession.requestBodyBytes is not available within this function!
    /*
     static function OnPeekAtRequestHeaders(oSession: Session) {
     var sProc = ("" + oSession["x-ProcessInfo"]).ToLower();
     if (!sProc.StartsWith("mylowercaseappname")) oSession["ui-hide"] = "NotMyApp";
     }
     */

    //
    // If a given session has response streaming enabled, then the OnBeforeResponse function
    // is actually called AFTER the response was returned to the client.
    //
    // In contrast, this OnPeekAtResponseHeaders function is called before the response headers are
    // sent to the client (and before the body is read from the server).  Hence this is an opportune time
    // to disable streaming (oSession.bBufferResponse = true) if there is something in the response headers
    // which suggests that tampering with the response body is necessary.
    //
    // Note: oSession.responseBodyBytes is not available within this function!
    //
    static function OnPeekAtResponseHeaders(oSession: Session) {

        //FiddlerApplication.Log.LogFormat("Session {0}: Response header peek shows status is {1}", oSession.id, oSession.responseCode);
        if (m_DisableCaching  || GLOBAL_SETTING.disableCaching) {
            oSession.oResponse.headers.Remove("Expires");
            oSession.oResponse["Cache-Control"] = "no-cache";
        }

        if ((bpStatus>0) && (oSession.responseCode == bpStatus)) {
            oSession["x-breakresponse"]="status";
            oSession.bBufferResponse = true;
        }

        if ((null!=bpResponseURI) && oSession.uriContains(bpResponseURI)) {
            oSession["x-breakresponse"]="uri";
            oSession.bBufferResponse = true;
        }

    }

    static function OnBeforeResponse(oSession: Session) {

        // 过滤出需要显示或隐藏的连接 BEGIN

        var contentType = oSession.oResponse["Content-Type"],
            showContentType = GLOBAL_SETTING.Filter.showContentType,
            hideContentType = GLOBAL_SETTING.Filter.hideContentType;

        //开启了 ContentType 过滤的时候, 把不带 Content-Type 全部过滤掉
        if( !contentType && showContentType.length > 0 ){
            console.log("隐藏不带 ContentType 的连接");
            hideLink( oSession );
        }

        // 过滤出要显示的连接,把不在显示列表里的连接隐藏掉
        settingUnMatch(contentType,showContentType,function(){
            hideLink( oSession );
        },"【showContentType】配置出错,请检查你的配置");

        // 过滤出要隐藏的连接,把在隐藏列表里的连接隐藏掉
        settingMatch(contentType,hideContentType,function( conf,matchStr ){
            hideLink( oSession );
            return true ;
        },"【hideContentType】配置出错,请检查你的配置");

        // 过滤出需要显示或隐藏的连接 END

        // 根据不同状态码设置链接颜色
        var statusCode = GLOBAL_SETTING.UI.statusCode ;
        settingMatch(oSession.responseCode,statusCode,function( conf,matchStr ){
            oSession["ui-color"] = GLOBAL_SETTING.UI.color ;
            conf ? oSession["ui-color"] = conf : "" ;
            return true ;
        },"【statusCode】配置出错,请检查你的配置");

        if (m_Hide304s && oSession.responseCode == 304) {
            oSession["ui-hide"] = "true";
        }

    }
    //OnBeforeResponse END

    // 请求完成时的回调
    static function OnDone(oSession: Session) {
        //
    }

    /**
     * 链接返回出错时的回调方法
     */
    static function OnReturningError(oSession: Session) {
        // 出错时的颜色配置
        var onErrorConf = GLOBAL_SETTING.UI.onError ;
        !onErrorConf.bgColor ? onErrorConf.bgColor = GLOBAL_SETTING.UI.bgColor : "" ;
        setSessionDisplay( oSession,onErrorConf );
    }

    /*
     // This function executes just before Fiddler returns an error that it has
     // itself generated (e.g. "DNS Lookup failure") to the client application.
     // These responses will not run through the OnBeforeResponse function above.
     static function OnReturningError(oSession: Session) {
     }
     */
    /*
     // This function executes after Fiddler finishes processing a Session, regardless
     // of whether it succeeded or failed. Note that this typically runs AFTER the last
     // update of the Web Sessions UI listitem, so you must manually refresh the Session's
     // UI if you intend to change it.
     static function OnDone(oSession: Session) {
     }
     */

    /*
     static function OnBoot() {
     MessageBox.Show("Fiddler has finished booting");
     System.Diagnostics.Process.Start("iexplore.exe");

     UI.ActivateRequestInspector("HEADERS");
     UI.ActivateResponseInspector("HEADERS");
     }
     */

    /*
     static function OnBeforeShutdown(): Boolean {
     // Return false to cancel shutdown.
     return ((0 == FiddlerApplication.UI.lvSessions.TotalItemCount()) ||
     (DialogResult.Yes == MessageBox.Show("Allow Fiddler to exit?", "Go Bye-bye?",
     MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2)));
     }
     */

    /*
     static function OnShutdown() {
     MessageBox.Show("Fiddler has shutdown");
     }
     */

    /*
     static function OnAttach() {
     MessageBox.Show("Fiddler is now the system proxy");
     }
     */

    /*
     static function OnDetach() {
     MessageBox.Show("Fiddler is no longer the system proxy");
     }
     */

    // The Main() function runs everytime your FiddlerScript compiles
    static function Main() {
        var today: Date = new Date();
        FiddlerObject.StatusText = " CustomRules.js was loaded at: " + today;

        // Uncomment to add a "Server" column containing the response "Server" header, if present
        // UI.lvSessions.AddBoundColumn("Server", 50, "@response.server");

        // Uncomment to add a global hotkey (Win+G) that invokes the ExecAction method below...
        // UI.RegisterCustomHotkey(HotkeyModifiers.Windows, Keys.G, "screenshot");
    }

    // These static variables are used for simple breakpointing & other QuickExec rules
    BindPref("fiddlerscript.ephemeral.bpRequestURI")
    public static var bpRequestURI:String = null;

    BindPref("fiddlerscript.ephemeral.bpResponseURI")
    public static var bpResponseURI:String = null;

    BindPref("fiddlerscript.ephemeral.bpMethod")
    public static var bpMethod: String = null;

    static var bpStatus:int = -1;
    static var uiBoldURI: String = null;
    static var gs_ReplaceToken: String = null;
    static var gs_ReplaceTokenWith: String = null;
    static var gs_OverridenHost: String = null;
    static var gs_OverrideHostWith: String = null;
    static var filter_and_highlight_url: String = null; //根据匹配的字符来高亮筛选出的url

    // The OnExecAction function is called by either the QuickExec box in the Fiddler window,
    // or by the ExecAction.exe command line utility.
    static function OnExecAction(sParams: String[]): Boolean {

        FiddlerObject.StatusText = "ExecAction: " + sParams[0];

        var sAction = sParams[0].toLowerCase();
        switch (sAction) {
            case "bold":
                if (sParams.Length<2) {uiBoldURI=null; FiddlerObject.StatusText="Bolding cleared"; return false;}
                uiBoldURI = sParams[1]; FiddlerObject.StatusText="Bolding requests for " + uiBoldURI;
                return true;
            case "bp":
                FiddlerObject.alert("bpu = breakpoint request for uri\nbpm = breakpoint request method\nbps=breakpoint response status\nbpafter = breakpoint response for URI");
                return true;
            case "bps":
                if (sParams.Length<2) {bpStatus=-1; FiddlerObject.StatusText="Response Status breakpoint cleared"; return false;}
                bpStatus = parseInt(sParams[1]); FiddlerObject.StatusText="Response status breakpoint for " + sParams[1];
                return true;
            case "bpv":
            case "bpm":
                if (sParams.Length<2) {bpMethod=null; FiddlerObject.StatusText="Request Method breakpoint cleared"; return false;}
                bpMethod = sParams[1].toUpperCase(); FiddlerObject.StatusText="Request Method breakpoint for " + bpMethod;
                return true;
            case "bpu":
                if (sParams.Length<2) {bpRequestURI=null; FiddlerObject.StatusText="RequestURI breakpoint cleared"; return false;}
                bpRequestURI = sParams[1];
                FiddlerObject.StatusText="RequestURI breakpoint for "+sParams[1];
                return true;
            case "bpa":
            case "bpafter":
                if (sParams.Length<2) {bpResponseURI=null; FiddlerObject.StatusText="ResponseURI breakpoint cleared"; return false;}
                bpResponseURI = sParams[1];
                FiddlerObject.StatusText="ResponseURI breakpoint for "+sParams[1];
                return true;
            case "overridehost":
                if (sParams.Length<3) {gs_OverridenHost=null; FiddlerObject.StatusText="Host Override cleared"; return false;}
                gs_OverridenHost = sParams[1].toLowerCase();
                gs_OverrideHostWith = sParams[2];
                FiddlerObject.StatusText="Connecting to [" + gs_OverrideHostWith + "] for requests to [" + gs_OverridenHost + "]";
                return true;
            case "urlreplace":
                if (sParams.Length<3) {gs_ReplaceToken=null; FiddlerObject.StatusText="URL Replacement cleared"; return false;}
                gs_ReplaceToken = sParams[1];
                gs_ReplaceTokenWith = sParams[2].Replace(" ", "%20");  // Simple helper
                FiddlerObject.StatusText="Replacing [" + gs_ReplaceToken + "] in URIs with [" + gs_ReplaceTokenWith + "]";
                return true;
            case "allbut":
            case "keeponly":
                if (sParams.Length<2) { FiddlerObject.StatusText="Please specify Content-Type to retain during wipe."; return false;}
                UI.actSelectSessionsWithResponseHeaderValue("Content-Type", sParams[1]);
                UI.actRemoveUnselectedSessions();
                UI.lvSessions.SelectedItems.Clear();
                FiddlerObject.StatusText="Removed all but Content-Type: " + sParams[1];
                return true;
            case "stop":
                UI.actDetachProxy();
                return true;
            case "start":
                UI.actAttachProxy();
                return true;
            case "cls":
            case "clear":
                UI.actRemoveAllSessions();
                return true;
            case "g":
            case "go":
                UI.actResumeAllSessions();
                return true;
            case "goto":
                if (sParams.Length != 2) return false;
                Utilities.LaunchHyperlink("http://www.google.com/search?hl=en&btnI=I%27m+Feeling+Lucky&q=" + Utilities.UrlEncode(sParams[1]));
                return true;
            case "help":
                Utilities.LaunchHyperlink("http://fiddler2.com/r/?quickexec");
                return true;
            case "hide":
                UI.actMinimizeToTray();
                return true;
            case "log":
                FiddlerApplication.Log.LogString((sParams.Length<2) ? "User couldn't think of anything to say..." : sParams[1]);
                return true;
            case "nuke":
                UI.actClearWinINETCache();
                UI.actClearWinINETCookies();
                return true;
            case "screenshot":
                UI.actCaptureScreenshot(false);
                return true;
            case "show":
                UI.actRestoreWindow();
                return true;
            case "tail":
                if (sParams.Length<2) { FiddlerObject.StatusText="Please specify # of sessions to trim the session list to."; return false;}
                UI.TrimSessionList(int.Parse(sParams[1]));
                return true;
            case "quit":
                UI.actExit();
                return true;
            case "dump":
                UI.actSelectAll();
                UI.actSaveSessionsToZip(CONFIG.GetPath("Captures") + "dump.saz");
                UI.actRemoveAllSessions();
                FiddlerObject.StatusText = "Dumped all sessions to " + CONFIG.GetPath("Captures") + "dump.saz";
                return true;

            default:
                if (sAction.StartsWith("http") || sAction.StartsWith("www.")) {
                    System.Diagnostics.Process.Start(sParams[0]);
                    return true;
                }else if( sParams[0] === "*" ){
                    filter_and_highlight_url = null ;
                    FiddlerObject.StatusText="取消URL高亮";
                }else{
                    filter_and_highlight_url = sParams[0] ;
                    FiddlerObject.StatusText="将为你高亮包含【" + filter_and_highlight_url + "】的url";
                    // FiddlerObject.StatusText = "Requested ExecAction: '" + sAction + "' not found. Type HELP to learn more.";
                    return true;
                }
        }
    }
}
  • eruda 和console差不多
    <script src="//cdn.jsdelivr.net/npm/eruda"></script>
    <script>eruda.init();</script>
  • spy-debugger
    eruda的优点在于,提供了一些工具条,比如加时间戳刷新页面(防缓存),查看设备宽高,像素比等信息,从而加快调试速度;缺点在于:接口请求的数据显示不美观,并且其html元素是静态的,即不显示JS操作DOM生成的页面。vconsole的优缺点刚好相反。二者都有的缺点是:不能调试线上应用(一般不会在线上环境引入调试类的库文件),且无法实时编辑CSS。spy-debugger很强大,eruda和vconsole有的他都有,没有的他也有。不额外加任何文件即可调试线上应用(css,抓包等)。
    使用方式:https://github.com/wuchangming/spy-debugger
//1、全局安装
npm install spy-debugger -g
// 2、启动
spy-debugger -b false
// 3、设置手机的HTTP代理
- Android设置代理步骤:设置 - WLAN - 长按选中网络 - 修改网络 - 高级 - 代理设置 - 手动
- iOS设置代理步骤:设置 - 无线局域网 - 选中网络 - HTTP代理手动
// 4、手机安装证书
手机访问:http://s.xxx
// 5、开启证书信任(略)
// 6、手机访问想调试的页面即可

注意点:

  • 不能和vconsole混用(vconsole会触发意外bug)
  • 启动方式:spy-debugger -b false // false代表抓http和https;true只抓https

以上三种调试方式建议优先使用vconsole,基本能满足需求,有一个问题是有的webview不支持console.log等方法,当在webview里使用时需android或ios小伙伴配合开启console.log开关

3、实际操作(移动端项目)

如何将手机端展示项目移到PC端展示,从而用chrome开发者工具来诊断问题?

  • 注意项目初始化代码位置,有时初始化代码是放在ios或安卓相关函数的回调里的,在PC端模拟器没有这个环境,也就不能初始化代码。如果遇到这种情况,只需将初始化代码拿出来,确保得到调用;
  • 注意请求所需header,有的接口需要通过客户端传header或设备相关信息才能拿到数据,若要在pc端看,需要伪造header,这个可以通过用fidder抓包得到。
  • 并不是所有情况都能用PC模拟,如手机端键盘弹起,PC无能为力
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,825评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 01谁的大学不迷茫 我曾经是一个体育生,考的是一个普通师范学院,还是专科。大学期间,学校仍像高中一样刻板管理,玩的...
    奔跑在知行路上阅读 20,855评论 24 11
  • 《孕妈日记》目录 欢迎戳进来 上一章 孕妈日记(五) **17** **孕27周+5天** **等待的过程有时候是...
    娜娜cai阅读 208评论 0 1
  • 这不是鸡汤,只是白水。 教师资格证考试在考场,满眼都是比你年轻比你漂亮的妹子。突然弥漫出大片大片的兵荒马乱,一个问...
    桐缱花花阅读 241评论 0 1