Flutter 學(xué)習(xí) 之 LogUtil 的 封裝與實(shí)現(xiàn) (一)

一. 為什么要封裝打印類

雖然 flutter/原生給我們提供了日志打印的功能,但是超出一定長(zhǎng)度以后會(huì)被截?cái)?br> Json打印擠在一起看不清楚
堆棧打印深度過深多打印一些不需要的東西
實(shí)現(xiàn) log 的多種展示方式


屏幕截圖 2022-06-09 000012.png
屏幕截圖 2022-06-09 000147.png
屏幕截圖 2022-06-09 000210.png

二. 需要哪些類

為了可以實(shí)現(xiàn)對(duì)日志的多種內(nèi)容格式化和各種顯示輸出所以抽出來以下幾個(gè)類

  • 一些常量的字符串表示
  • 對(duì)日志內(nèi)容的打印輸出抽象類
  • 對(duì)日志內(nèi)容格式化的抽象類
  • 日志工具的config類
  • 日志工具的管理類
  • 日志工具的Util類

三. 打印輸出的抽象類

打印類核心的功能就是打印日志 所以它有一個(gè)方法就是打印的方法
而我們要打印輸出的內(nèi)容有 當(dāng)前 log等級(jí) log的tag 需要打印的數(shù)據(jù) 當(dāng)前堆棧信息 亦或是獲取的Json數(shù)據(jù)

/// 日志打印輸出的接口類
abstract class IHCLogPrint {
  void logPrint({
    required LogType type,
    required String tag,
    required String message,
    StackTrace? stackTrace,
    Map<String, dynamic>? json,
  });
}

四. 格式化日志內(nèi)容

這里定義一個(gè)IHCLogFormatter抽象類

///格式化的接口類
abstract class IHCLogFormatter<T> {
  String format(T data);
}

格式化堆棧

堆棧的格式例如這樣
#0 LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
#1 LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
#2 LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
....
會(huì)返回來很多無用的數(shù)據(jù) 而我們實(shí)際用到的也不過前五層就可以了
所以需要一個(gè)工具來剔除無用的數(shù)據(jù)和當(dāng)前自己的包名

堆棧裁切工具類

class StackTraceUtil {
  ///正則表達(dá)式 表示#+數(shù)字+空格的格式
  static final RegExp _startStr = RegExp(r'#\d+[\s]+');

  ///正則表達(dá)式表示 多個(gè)非換行符+ (非空) 正則表達(dá)式中()代表子項(xiàng) 如果需要正則()需要轉(zhuǎn)義\( \)
  ///了解更多 https://www.runoob.com/regexp/regexp-syntax.html
  static final RegExp _stackReg = RegExp(r'.+ \(([^\s]+)\)');

  /// 把StackTrace 轉(zhuǎn)成list 并去除無用信息
  /// [stackTrace] 堆棧信息
  ///#0      LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
  static List<String> _fixStack(StackTrace stackTrace) {
    List tempList = stackTrace.toString().split("\n");
    List<String> stackList = [];
    for (String str in tempList) {
      if (str.startsWith(_startStr)) {
        //又是#號(hào)又是空格比較占地方 這里省去了 如果你不想省去直接傳入str即可
        stackList.add(str.replaceFirst(_startStr, ' '));
      }
    }
    return stackList;
  }

  ///獲取剔除忽略包名及其其他無效信息的堆棧
  /// [stackTrace] 堆棧
  /// [ignorePackage] 需要忽略的包名
  static List<String> _getRealStackTrack(
      StackTrace stackTrace, String ignorePackage) {
    ///由于Flutter 上的StackTrack上的不太一樣,Android返回的是list flutter返回的是StackTrack 所以需要手動(dòng)切割 再處理
    List<String> stackList = _fixStack(stackTrace);
    int ignoreDepth = 0;
    int allDepth = stackList.length;
    //倒著查詢 查到倒數(shù)第一包名和需要屏蔽的包名一致時(shí),數(shù)據(jù)往上的數(shù)據(jù)全部舍棄掉
    for (int i = allDepth - 1; i > -1; i--) {
      Match? match = _stackReg.matchAsPrefix(stackList[i]);
      //如果匹配且第一個(gè)子項(xiàng)也符合  group 0 表示全部 剩下的數(shù)字看子項(xiàng)的多少返回
      if (match != null &&
          (match.group(1)!.startsWith("package:$ignorePackage"))) {
        ignoreDepth = i + 1;
        break;
      }
    }
    stackList = stackList.sublist(ignoreDepth);
    return stackList;
  }

  /// 裁切堆棧
  /// [stackTrace] 堆棧
  /// [maxDepth] 深度
  static List<String> _cropStackTrace(List<String> stackTrace, int? maxDepth) {
    int realDeep = stackTrace.length;
    realDeep =
        maxDepth != null && maxDepth > 0 ? min(maxDepth, realDeep) : realDeep;
    return stackTrace.sublist(0, realDeep);
  }

  ///裁切獲取到最終的stack 并獲取最大深度的棧信息

  static getCroppedRealStackTrace(
      {required StackTrace stackTrace, ignorePackage, maxDepth}) {
    return _cropStackTrace(
        _getRealStackTrack(stackTrace, ignorePackage), maxDepth);
  }
}

格式化堆棧信息


class StackFormatter implements ILogFormatter<List<String>> {
  @override
  String format(List<String> stackList) {
    ///每一行都設(shè)置成單獨(dú)的 字符串
    StringBuffer sb = StringBuffer();

    ///堆棧是空的直接返回
    if (stackList.isEmpty) {
      return "";

      ///堆棧只有一行那么就返回 - 堆棧
    } else if (stackList.length == 1) {
      return "\n\t-${stackList[0].toString()}\n";

      ///多行堆棧格式化
    } else {
      for (int i = 0; i < stackList.length; i++) {
        if (i == 0) {
          sb.writeln("\n\t┌StackTrace:");
        }
        if (i != stackList.length - 1) {
          sb.writeln("\t├${stackList[i].toString()}");
        } else {
          sb.write("\t└${stackList[i].toString()}");
        }
      }
    }
    return sb.toString();
  }
}

格式化JSON

class JsonFormatter extends ILogFormatter<Map<String, dynamic>> {
  @override
  String format(Map<String, dynamic> data) {
    ///遞歸調(diào)用循環(huán)遍歷data 在StringBuffer中添加StringBuffer
    String finalString = _forEachJson(data, 0);
    finalString = "\ndata:$finalString";
    return finalString;
  }

  /// [data]  傳入需要格式化的數(shù)據(jù)
  /// [spaceCount]  需要添加空格的長(zhǎng)度 一個(gè)數(shù)字是兩個(gè)空格
  /// [needSpace] 需不需要添加空格
  /// [needEnter] 需不需要回車
  String _forEachJson(dynamic data, int spaceCount,
      {bool needSpace = true, needEnter = true}) {
    StringBuffer sb = StringBuffer();
    int newSpace = spaceCount + 1;
    if (data is Map) {
      ///如果它是Map走這里
      ///是否需要空格
      sb.write(buildSpace(needSpace ? spaceCount : 0));
      sb.write(needEnter ? "{\n" : "{");
      data.forEach((key, value) {
        ///打印輸出 key
        sb.write("${buildSpace(needEnter ? newSpace : 0)}$key: ");

        ///遞歸調(diào)用看value是什么類型 如果字符長(zhǎng)度少于30就不回車顯示
        sb.write(_forEachJson(value, newSpace,
            needSpace: false,
            needEnter: !(value is Map ? false : value.toString().length < 50)));

        ///不是最后一個(gè)就加,
        if (data.keys.last != key) {
          sb.write(needEnter ? ",\n" : ",");
        }
      });
      if (needEnter) {
        sb.writeln();
      }
      sb.write("${buildSpace(needEnter ? spaceCount : 0)}}");
    } else if (data is List) {
      ///如果他是列表 走這里
      sb.write(buildSpace(needSpace ? spaceCount : 0));
      sb.write("[${needEnter ? "\n" : ""}");
      for (var item in data) {
        sb.write(_forEachJson(item, newSpace,
            needEnter: !(item.toString().length < 30)));

        ///不是最后一個(gè)就加的,
        if (data.last != item) {
          sb.write(needEnter ? ",\n" : ",");
        }
      }
      sb.write(needEnter ? "\n" : "");
      sb.write("${buildSpace(needSpace?spaceCount:0)}]");
    } else if (data is num || data is bool) {
      ///bool 或者數(shù)組不加雙引號(hào)
      sb.write(data);
    } else if (data is String) {
      ///string 或者其他的打印加雙引號(hào) 如果他是回車就改變他 按回車分行會(huì)錯(cuò)亂
      sb.write("\"${data.replaceAll("\n", r"\n")}\"");
    } else {
      sb.write("$data");
    }
    return sb.toString();
  }

  ///構(gòu)造空格
  String buildSpace(int deep) {
    String temp = "";
    for (int i = 0; i < deep; i++) {
      temp += "  ";
    }
    return temp;
  }
}

五. 需要用到的常量

///常量
//log的type
enum LogType {
  V, //VERBOSE
  E, //ERROR
  A, //ASSERT
  W, //WARN
  I, //INFO
  D, //DEBUG
}
int logMaxLength=1024;

///log的type 字符串說明
List logTypeStr = ["VERBOSE", "ERROR", "ASSERT", "WARN", "INFO", "DEBUG"];

///log的type 數(shù)字說明(匹配的Android原生,ios暫不清楚)
List<int> logTypeNum = [2, 6, 7, 5, 4, 3];

六. 為了控制多個(gè)打印器的設(shè)置做了一個(gè)配置類

class LogConfig {
  ///是否開啟日志
  bool _enable = false;

  ///默認(rèn)的Tag
  String _globalTag = "LogTag";

  ///堆棧顯示的深度
  int _stackTraceDepth = 0;

  ///打印的方式
  List<ILogPrint>? _printers;

  LogConfig({enable, globalTag, stackTraceDepth, printers}) {
    _enable = enable;
    _globalTag = globalTag;
    _stackTraceDepth = stackTraceDepth;
    _printers?.addAll(printers);
  }


  @override
  String toString() {
    return 'LogConfig{_enable: $_enable, _globalTag: $_globalTag, _stackTraceDepth: $_stackTraceDepth, _printers: $_printers}';
  }

  get enable => _enable;

  get globalTag => _globalTag;

  get stackTraceDepth => _stackTraceDepth;

  get printers => _printers;
}

七. Log的管理類

class LogManager {
  ///config
  late LogConfig _config;

  ///打印器列表
  List<ILogPrint> _printers = [];

  ///單例模式
  static LogManager? _instance;

  factory LogManager() => _instance ??= LogManager._();

  LogManager._();

  ///初始化 Manager方法
  LogManager.init({config, printers}) {
    _config = config;
    _printers.addAll(printers);
    _instance = this;
  }

  get printers => _printers;

  get config => _config;

  void addPrinter(ILogPrint print) {
    bool isHave = _printers.any((element) => element == print);
    if (!isHave) {
      _printers.add(print);
    }
  }

  void removePrinter(ILogPrint print) {
    _printers.remove(print);
  }
}

九. 調(diào)用LogUtil

class LogUtil {
  static const String _ignorePackageName = "log_demo/utils/log";

  static void V(
      {String? tag,
      dynamic? message,
      LogConfig? logConfig,
      StackTrace? stackTrace,
      Map<String, dynamic>? json}) {
    _logPrint(
        type: LogType.V,
        tag: tag ??= "",
        logConfig: logConfig,
        message: message,
        json: json,
        stackTrace: stackTrace);
  }

  static void E(
      {String? tag,
      dynamic? message,
      LogConfig? logConfig,
      StackTrace? stackTrace,
      Map<String, dynamic>? json}) {
    _logPrint(
        type: LogType.E,
        tag: tag ??= "",
        message: message,
        logConfig: logConfig,
        json: json,
        stackTrace: stackTrace);
  }

  static void I(
      {String? tag,
      dynamic? message,
      LogConfig? logConfig,
      StackTrace? stackTrace,
      Map<String, dynamic>? json}) {
    _logPrint(
        type: LogType.I,
        tag: tag ??= "",
        message: message,
        json: json,
        stackTrace: stackTrace);
  }

  static void D(
      {String? tag,
      dynamic? message,
      LogConfig? logConfig,
      StackTrace? stackTrace,
      Map<String, dynamic>? json}) {
    _logPrint(
        type: LogType.D,
        tag: tag ??= "",
        logConfig: logConfig,
        message: message,
        json: json,
        stackTrace: stackTrace);
  }

  static void A(
      {String? tag,
      LogConfig? logConfig,
      dynamic? message,
      StackTrace? stackTrace,
      Map<String, dynamic>? json}) {
    _logPrint(
        type: LogType.A,
        tag: tag ??= "",
        message: message,
        logConfig: logConfig,
        json: json,
        stackTrace: stackTrace);
  }

  static void W(
      {String? tag,
      dynamic? message,
      LogConfig? logConfig,
      StackTrace? stackTrace,
      Map<String, dynamic>? json}) {
    _logPrint(
        type: LogType.W,
        tag: tag ??= "",
        message: message,
        logConfig: logConfig,
        json: json,
        stackTrace: stackTrace);
  }

  static Future<void> _logPrint({
    required LogType type,
    required String tag,
    LogConfig? logConfig,
    dynamic message,
    StackTrace? stackTrace,
    Map<String, dynamic>? json,
  }) async {
    ///如果logConfig為空那么就用默認(rèn)的
    logConfig ??= LogManager().config;
    if (!logConfig?.enable) {
      return;
    }
    StringBuffer sb = StringBuffer();

    ///打印當(dāng)前頁面
    if (message.toString().isNotEmpty) {
      sb.write(message);
    }

    ///如果傳入了棧且 要展示的深度大于0
    if (stackTrace != null && logConfig?.stackTraceDepth > 0) {
      sb.writeln();
      String stackTraceStr = StackFormatter().format(
          StackTraceUtil.getCroppedRealStackTrace(
              stackTrace: stackTrace,
              ignorePackage: _ignorePackageName,
              maxDepth: logConfig?.stackTraceDepth));
      sb.write(stackTraceStr);
    }
    if (json != null) {
      sb.writeln();
      String body = JsonFormatter().format(json);
      sb.write(body);
    }

    ///獲取有幾個(gè)打印器
    List<ILogPrint> prints = logConfig?.printers ?? LogManager().printers;
    if (prints.isEmpty) {
      return;
    }

    ///遍歷打印器 分別打印數(shù)據(jù)
    for (ILogPrint print in prints) {
      print.logPrint(type: type, tag: tag, message: sb.toString());
    }
  }
}

十. 定義一個(gè)Flutter 控制臺(tái)打印輸出的方法

class ConsolePrint extends ILogPrint {
  @override
  void logPrint(
      {required LogType type,
      required String tag,
      required String message,
      StackTrace? stackTrace,
      Map<String, dynamic>? json}) {
    ///如果要開啟顏色顯示 那么就是1000
    ///如果不開啟顏色顯示 那么就是1023
    int _maxCharLength = 1000;

    //匹配中文字符以及這些中文標(biāo)點(diǎn)符號(hào) 挤土。 ? ! 间校, 挥下、 ; : “ ” ‘ ' ( ) 《 》 〈 〉 【 】 『 』 「 」 ﹃ ﹄ 〔 〕 … — ~ ﹏ ¥
    RegExp _chineseRegex = RegExp(r"[\u4e00-\u9fa5|\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]");

    ///用回車做分割
    List<String> strList = message.split("\n");

    ///判斷每句的長(zhǎng)度 如果長(zhǎng)度過長(zhǎng)做切割
    for (String str in strList) {
      ///獲取總長(zhǎng)度
      int len = 0;

      ///獲取當(dāng)前長(zhǎng)度
      int current = 0;

      ///獲取截?cái)帱c(diǎn)數(shù)據(jù)
      List<int> entry = [0];

      ///遍歷文字 查看真實(shí)長(zhǎng)度
      for (int i = 0; i < str.length; i++) {
        //// 一個(gè)漢字再打印區(qū)占三個(gè)長(zhǎng)度,其他的占一個(gè)長(zhǎng)度
        len += str[i].contains(_chineseRegex) ? 3 : 1;

        ///尋找當(dāng)前字符的下一個(gè)字符長(zhǎng)度
        int next = (i + 1) < str.length
            ? str[i + 1].contains(_chineseRegex)
                ? 3
                : 1
            : 0;

        ///當(dāng)前字符累計(jì)長(zhǎng)度 如果達(dá)到了需求就清空
        current += str[i].contains(_chineseRegex) ? 3 : 1;
        if (current < _maxCharLength && (current + next) >= _maxCharLength) {
          entry.add(i);
          current = 0;
        }
      }

      ///如果最后一個(gè)階段點(diǎn)不是最后一個(gè)字符就添加上
      if (entry.last != str.length - 1) {
        entry.add(str.length);
      }

      ///如果所有的長(zhǎng)度小于1023 那么打印沒有問題
      if (len < _maxCharLength) {
        _logPrint(type, tag, str);
      } else {
        ///按照獲取的截?cái)帱c(diǎn)來打印
        for (int i = 0; i < entry.length - 1; i++) {
      
          _logPrint(type, tag, str.substring(entry[i], entry[i + 1]));
        }
      }
    }
  }

  _logPrint(LogType type, String tag, String message) {
    ///前面的\u001b[31m用于設(shè)定SGR顏色,后面的\u001b[0m相當(dāng)于一個(gè)封閉標(biāo)簽作為前面SGR顏色的作用范圍的結(jié)束點(diǎn)標(biāo)記瞭吃。
    /// \u001b[3 文字顏色范圍 0-7 標(biāo)準(zhǔn)顏色 0是黑色 1是紅色 2是綠色 3是黃色 4是藍(lán)色 5是紫色 6藍(lán)綠色 是 7是灰色 范圍之外都是黑色
    /// \u001b[9 文字顏色范圍 0-7 高強(qiáng)度顏色 0是黑色 1是紅色 2是綠色 3是黃色 4是藍(lán)色 5是紫色 6藍(lán)綠色 是 7是灰色 范圍之外都是黑色
    /// 自定義顏色 \u001b[38;2;255;0;0m 表示文字顏色 2是24位 255 0 0 是顏色的RGB 可以自定義顏色
    /// \u001b[4 數(shù)字 m 是背景色
    /// \u001b[1m 加粗
    /// \u001b[3m 斜體
    /// \u001b[4m 下劃線
    /// \u001b[7m 黑底白字
    ///\u001b[9m 刪除線
    ///\u001b[0m 結(jié)束符
    //////詳情看 https://www.cnblogs.com/zt123123/p/16110475.html
    String colorHead = "";
    String colorEnd = "\u001b[0m";
    switch (type) {
      case LogType.V:
        // const Color(0xff181818);
        colorHead = "\u001b[38;2;187;187;187m";
        break;
      case LogType.E:
        colorHead = "\u001b[38;2;255;0;6m";
        break;
      case LogType.A:
        colorHead = "\u001b[38;2;143;0;5m";
        break;
      case LogType.W:
        colorHead = "\u001b[38;2;187;187;35m";
        break;
      case LogType.I:
        colorHead = "\u001b[38;2;72;187;49m";
        break;
      case LogType.D:
        colorHead = "\u001b[38;2;0;112;187m";
        break;
    }

    /// 這里是純Flutter項(xiàng)目所以在控制臺(tái)打印這樣子是可以有顏色的 如果是flutter混編 安卓原生側(cè)打印\u001b 可能是一個(gè)亂碼也沒有變色效果
    /// 如果你不想只在調(diào)試模式打印 你可以把debugPrint換成print
    debugPrint("$colorHead$message$colorEnd");

    /// 如果原生側(cè)有封裝log工具直接 寫一個(gè)methodChannel 傳參數(shù)就好 ,如果沒有,可以調(diào)用原生的log打印 傳入 level tag 和message
    /// kDebugMode 用這個(gè)可以判斷是否在debug模式下
    /// if(kDebugMode){
    /// 在debug模式下打印日志
    //  bool? result=await CustomChannelUtil.printLog(level:logTypeNum[type.index],tag:tag,message:message);
    /// }
  }
}

十一. 使用

現(xiàn)在使用前初始化log打印器一次

 Widget build(BuildContext context) {
    LogManager.init(
        config: LogConfig(enable: true, globalTag: "TAG", stackTraceDepth: 5),
        printers: [ConsolePrint()]);

使用

///打印堆棧
    LogUtil.I(tag: "test", stackTrace: StackTrace.current);
///打印json
    LogUtil.E(tag: "JSON", json: json);
///打印信息 
    LogUtil.V(tag: "LogText", message: message);

源碼地址 https://gitee.com/half_city/flutter_log_util.git

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恐似,一起剝皮案震驚了整個(gè)濱河市杜跷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌矫夷,老刑警劉巖葛闷,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異双藕,居然都是意外死亡淑趾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門忧陪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來漱受,“玉大人琉朽,你說我怎么就攤上這事。” “怎么了撰洗?”我有些...
    開封第一講書人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵汉规,是天一觀的道長(zhǎng)包斑。 經(jīng)常有香客問我摘昌,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任沥匈,我火速辦了婚禮蔗喂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘高帖。我一直安慰自己缰儿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開白布棋恼。 她就那樣靜靜地躺著返弹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪爪飘。 梳的紋絲不亂的頭發(fā)上义起,一...
    開封第一講書人閱讀 52,196評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音师崎,去河邊找鬼默终。 笑死,一個(gè)胖子當(dāng)著我的面吹牛犁罩,可吹牛的內(nèi)容都是我干的齐蔽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼床估,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼含滴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起丐巫,我...
    開封第一講書人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤谈况,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后递胧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碑韵,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年缎脾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祝闻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡遗菠,死狀恐怖联喘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情辙纬,我是刑警寧澤耸袜,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站牲平,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏域滥。R本人自食惡果不足惜纵柿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一蜈抓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧昂儒,春花似錦沟使、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拾酝,卻和暖如春燕少,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒿囤。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工客们, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人材诽。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓底挫,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親脸侥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子建邓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容