flutter 网络请求封装 dio(4.0.0)

dart版本:(stable) 2.2.2(空安全)
connectivity:^3.0.6
shared_preferences:^2.0.6
注:我是抄后改成适合自己的,不过网上太多一模一样的,不知道谁才是原创,在此感谢原创作者。
现有功能。
就看http,再分别一个个看,大概能看懂了。


image.png

代码里有些做了解释,其他有些我也是抄的,也不是很懂哈哈哈,就说了,直接上代码。

api_response.dart

import 'app_exceptions.dart';
class ApiResponse<T> implements Exception {
  Status status;
  T? data;
  AppException? exception;
  ApiResponse.completed(this.data) : status = Status.COMPLETED;
  ApiResponse.error(this.exception) : status = Status.ERROR;

  @override
  String toString() {
    return "Status : $status \n Message : $exception \n Data : $data";
  }
}

enum Status { COMPLETED, ERROR }

app_exceptions.dart

import 'package:dio/dio.dart';

/// 自定义异常
class AppException implements Exception {
  final String? _message;
  final int? _code;
  AppException([
    this._code,
    this._message,
  ]);

  String toString() {
    return "$_message($_code)";
  }

  factory AppException.create(DioError error) {
    switch (error.type) {
      case DioErrorType.cancel:
        {
          return BadRequestException(-1, "请求取消");
        }
        break;
      case DioErrorType.connectTimeout:
        {
          return BadRequestException(-1, "连接超时");
        }
        break;
      case DioErrorType.sendTimeout:
        {
          return BadRequestException(-1, "请求超时");
        }
        break;
      case DioErrorType.receiveTimeout:
        {
          return BadRequestException(-1, "响应超时");
        }
        break;
      case DioErrorType.response:
        {
          try {
            int? errCode = error.response?.statusCode!;
            // String errMsg = error.response.statusMessage;
            // return ErrorEntity(code: errCode, message: errMsg);
            switch (errCode) {
              case 400:
                {
                  return BadRequestException(errCode, "请求语法错误");
                }
                break;
              case 401:
                {
                  return UnauthorisedException(errCode!, "没有权限");
                }
                break;
              case 403:
                {
                  return UnauthorisedException(errCode!, "服务器拒绝执行");
                }
                break;
              case 404:
                {
                  return UnauthorisedException(errCode!, "无法连接服务器");
                }
                break;
              case 405:
                {
                  return UnauthorisedException(errCode!, "请求方法被禁止");
                }
                break;
              case 500:
                {
                  return UnauthorisedException(errCode!, "服务器内部错误");
                }
                break;
              case 502:
                {
                  return UnauthorisedException(errCode!, "无效的请求");
                }
                break;
              case 503:
                {
                  return UnauthorisedException(errCode!, "服务器挂了");
                }
                break;
              case 505:
                {
                  return UnauthorisedException(errCode!, "不支持HTTP协议请求");
                }
                break;
              default:
                {
                  // return ErrorEntity(code: errCode, message: "未知错误");
                  return AppException(
                      errCode, error.response?.statusMessage ?? '');
                }
            }
          } on Exception catch (_) {
            return AppException(-1, "未知错误");
          }
        }
        break;
      default:
        {
          return AppException(-1, error.message);
        }
    }
  }
}

/// 请求错误
class BadRequestException extends AppException {
  BadRequestException([int? code, String? message]) : super(code!, message!);
}

/// 未认证异常
class UnauthorisedException extends AppException {
  UnauthorisedException([int code = -1, String message = ''])
      : super(code, message);
}

cache.dart

// 是否启用缓存
const CACHE_ENABLE = true;

// 缓存的最长时间,单位(秒)
const CACHE_MAXAGE = 1000;

// 最大缓存数
const CACHE_MAXCOUNT = 100;

connections_interceptor.dart

import 'package:connectivity/connectivity.dart';
import 'package:dio/dio.dart';

class ConnectsInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    // TODO: implement onRequest
    super.onRequest(options, handler);
    _request();
  }

  //不知道是不是这样写,网络的
  _request() async {
    var connectivityResult = await (Connectivity().checkConnectivity());
    if (connectivityResult == ConnectivityResult.mobile) {
      // I am connected to a mobile network.
    } else if (connectivityResult == ConnectivityResult.wifi) {
      // I am connected to a wifi network.
    } else {
      print('没有网络');
      //在这里加一个错误弹窗
    }
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    // TODO: implement onResponse
    super.onResponse(response, handler);
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) {
    // TODO: implement onError
    super.onError(err, handler);
  }
}

http.dart

import 'dart:io';

import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:pp/MyHttp/connections_interceptor.dart';
import 'package:pp/MyHttp/net_cache.dart';
import 'package:pp/MyHttp/proxy.dart';
import 'package:pp/MyHttp/request_interceptor.dart';

class Https {
  ///超时时间
  static const int CONNECT_TIMEOUT = 10000;
  static const int RECEIVE_TIMEOUT = 10000;

  static Https instance = Https._internal();

  factory Https() => instance;

  Dio dio = Dio();
  CancelToken _cancelToken = new CancelToken();
  Https._internal() {
    dio.options
      ..baseUrl = 'https://mobile.pku-hit.com/smc/'
      ..connectTimeout = CONNECT_TIMEOUT
      ..receiveTimeout = RECEIVE_TIMEOUT
      ..validateStatus = (int? status) {
        return status != null && status > 0;
      }
      ..headers = {};
    dio.interceptors.add(RequestInterceptor()); //自定义拦截
    dio.interceptors.add(ConnectsInterceptor());//拦截网络
    dio.interceptors.add(LogInterceptor()); //打开日志
    dio.interceptors.add(NetCacheInterceptor()); //缓存

    // 在调试模式下需要抓包调试,所以我们使用代理,并禁用HTTPS证书校验
    if (PROXY_ENABLE) {
      (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
          (client) {
        client.findProxy = (uri) {
          return "PROXY $PROXY_IP:$PROXY_PORT";
        };
        //代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验
        client.badCertificateCallback =
            (X509Certificate cert, String host, int port) => true;
      };
    }
  }
  void init(//这个在main或者初始化的时候先调用一下
      {String? baseUrl,
      int? connectTimeout,
      int? receiveTimeout,
      List<Interceptor>? interceptors}) {
    dio.options = dio.options.copyWith(
      baseUrl: baseUrl,
      connectTimeout: connectTimeout,
      receiveTimeout: receiveTimeout,
    );
    if (interceptors != null && interceptors.isNotEmpty) {
      dio.interceptors.addAll(interceptors);
    }
  }

  /// 设置headers
  void setHeaders(Map<String, dynamic> map) {
    dio.options.headers.addAll(map);
  }

/*
   * 取消请求
   *
   * 同一个cancel token 可以用于多个请求,当一个cancel token取消时,所有使用该cancel token的请求都会被取消。
   * 所以参数可选
   */
  void cancelRequests({CancelToken? token}) {
    token ?? _cancelToken.cancel("cancelled");
  }

  /// restful get 操作
  Future get(
    String path, {
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
    bool refresh = false,
    bool noCache = true,
    String? cacheKey,
    bool cacheDisk = false,
  }) async {
    Options requestOptions = options ?? Options();
    requestOptions = requestOptions.copyWith(extra: {
      "refresh": refresh,
      "noCache": noCache,
      "cacheKey": cacheKey,
      "cacheDisk": cacheDisk,
    });
    Response response;
    response = await dio.get(path,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken);
    return response.data;
  }

  /// restful post 操作
  Future post(
    String path, {
    Map<String, dynamic>? params,
    data,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    Options requestOptions = options ?? Options();
    var response = await dio.post(path,
        data: data,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken);
    return response.data;
  }

  /// restful put 操作
  Future put(
    String path, {
    data,
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    Options requestOptions = options ?? Options();

    var response = await dio.put(path,
        data: data,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken);
    return response.data;
  }

  /// restful patch 操作
  Future patch(
    String path, {
    data,
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    Options requestOptions = options ?? Options();
    // Map<String, dynamic> _authorization = getAuthorizationHeader();
    // if (_authorization != null) {
    //   requestOptions = requestOptions.merge(headers: _authorization);
    // }
    var response = await dio.patch(path,
        data: data,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken);
    return response.data;
  }

  /// restful delete 操作
  Future delete(
    String path, {
    data,
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    Options requestOptions = options ?? Options();
    var response = await dio.delete(path,
        data: data,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken);
    return response.data;
  }

  /// restful post form 表单提交操作
  Future postForm(
    String path, {
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    Options requestOptions = options ?? Options();
    var data = FormData.fromMap(params!);
    var response = await dio.post(path,
        data: data,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken);
    return response.data;
  }
}

net_cache.dart

import 'dart:collection';
import 'package:dio/dio.dart';
import 'package:pp/MyHttp/cache.dart';
import 'package:pp/MyHttp/sp.dart';

class CacheObject {
  CacheObject(this.response)
      : timeStamp = DateTime.now().millisecondsSinceEpoch;
  Response response;
  int timeStamp;

  @override
  bool operator ==(other) {
    return response.hashCode == other.hashCode;
  }

  @override
  int get hashCode => response.realUri.hashCode;
}

class NetCacheInterceptor extends Interceptor {
  var cache = LinkedHashMap<String, CacheObject>();
  @override
  Future<void> onRequest(
      RequestOptions options, RequestInterceptorHandler handler) async {
    // TODO: implement onRequest
    super.onRequest(options, handler);
    if (!CACHE_ENABLE) handler.next(options);

    // refresh标记是否是刷新缓存
    bool refresh = options.extra["refresh"] == true;

    // 是否磁盘缓存
    bool cacheDisk = options.extra["cacheDisk"] == true;

    // 如果刷新,先删除相关缓存
    if (refresh) {
      // 删除uri相同的内存缓存
      delete(options.uri.toString());

      // 删除磁盘缓存
      if (cacheDisk) {
        await SpUtil().remove(options.uri.toString());
      }

      handler.next(options);
    }

    // get 请求,开启缓存
    if (options.extra["noCache"] != true &&
        options.method.toLowerCase() == 'get') {
      String key = options.extra["cacheKey"] ?? options.uri.toString();

      // 策略 1 内存缓存优先,2 然后才是磁盘缓存

      // 1 内存缓存
      var ob = cache[key];
      if (ob != null) {
        //若缓存未过期,则返回缓存内容
        if ((DateTime.now().millisecondsSinceEpoch - ob.timeStamp) / 1000 <
            CACHE_MAXAGE) {
          handler.resolve(cache[key]!.response);
        } else {
          //若已过期则删除缓存,继续向服务器请求
          cache.remove(key);
        }
      }

      // 2 磁盘缓存
      if (cacheDisk) {
        var cacheData = SpUtil().getJSON(key);
        if (cacheData != null) {
          handler.resolve(Response(
            statusCode: 200,
            data: cacheData,
            requestOptions: options,
          ));
        }
      }
    }
  }

  @override
  Future<void> onResponse(
      Response response, ResponseInterceptorHandler handler) async {
    // TODO: implement onResponse
    super.onResponse(response, handler);
    // 如果启用缓存,将返回结果保存到缓存
    if (CACHE_ENABLE) {
      await _saveCache(response);
    }
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) {
    // TODO: implement onError
    super.onError(err, handler);
  }

  void delete(String key) {
    cache.remove(key);
  }

  Future<void> _saveCache(Response object) async {
    RequestOptions options = object.requestOptions;

    // 只缓存 get 的请求
    if (options.extra["noCache"] != true &&
        options.method.toLowerCase() == "get") {
      // 策略:内存、磁盘都写缓存

      // 缓存key
      String key = options.extra["cacheKey"] ?? options.uri.toString();

      // 磁盘缓存
      if (options.extra["cacheDisk"] == true) {
        await SpUtil().setJSON(key, object.data);
      }

      // 内存缓存
      // 如果缓存数量超过最大数量限制,则先移除最早的一条记录
      if (cache.length == CACHE_MAXCOUNT) {
        cache.remove(cache[cache.keys.first]);
      }

      cache[key] = CacheObject(object);
    }
  }
}

proxy.dart

// 是否启用代理
const PROXY_ENABLE = false;

/// 代理服务IP
const PROXY_IP = '192.168.2.237';

/// 代理服务端口
const PROXY_PORT = 8888;

request_interceptor.dart

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'app_exceptions.dart';

/// 请求处理拦截器
class RequestInterceptor extends Interceptor {
  onRequest(options, handle) {
    debugPrint(
        '======================\n*** Request *** \nData:\n ${options.data.toString()} \nQuery:\n ${options.queryParameters.toString()} \n======================');
    // // 设置cookie
    // var cookie = SpUtil.getStringList('cookie');登录时保存cookie

    // if (options.path != 'api/auth/login' &&
    //     cookie != null &&
    //     cookie.length > 0) {
    //   options.headers['cookie'] = cookie;//这里就是除了登录的情况其他都加cookie
    // }
    // options.headers['User-Agent'] = 'gzzoc-1';//
    //
    // if (options.data?.runtimeType == FormData) {
    //   options.headers['content-type'] = 'multipart/form-data';//FormData这种情况改content-type
    // }

    // // 加载动画----这个就是请求页面时的那个loading窗 --处理逻辑 我是用options?.data['showLoading']或options?.queryParameters['showLoading'],
    //就是我们在传参的时候多加一个参数,这个因为前人就这样做的,也跟后端约定的,后端见showLoading不做处理。这样不是很好,反正options是有其他字段加的
    // if (options?.data?.runtimeType == FormData) {
    //   Alert.showLoading();
    // } else if ((options?.data != null &&
    //         false == options?.data['showLoading']) ||
    //     (options?.queryParameters != null &&
    //         false == options?.queryParameters['showLoading'])) {
    //   // 不显示加载动画
    // } else {
    //   Alert.showLoading();
    // }
    ///在这做请求时显不显示Loading的处理

    handle.next(options);
    //return super.onRequest(options);
  }

  @override
  onResponse(response, handle) {
    debugPrint(
        '======================\n*** Response *** \n${response.toString()}');
    if (response.data != null &&
        response.data is Map &&
        response.data['code'] == '0') {// 这个条件也是根据自己情况加的
      ///    Alert.hide();请求成功后关闭loading窗

      // 登录请求
      if (response.requestOptions.path == 'api/auth/login') {
        // 缓存cookie
        var cookie = response.headers['set-cookie'];
        //   SpUtil.putStringList('cookie', cookie!);缓存cookie
      }
      handle.next(response);
      //     return super.onResponse(response);
    } else if (response.requestOptions.path ==
            'api/auth/login' && // 登录登录成功, 但没有默认就诊人// 缓存cookie以便后续创建默认就诊人(需求)
        response.data != null &&
        response.data is Map &&
        response.data['code'] == '11') {
      // 缓存cookie
      var cookie = response.headers['set-cookie'];
      //    SpUtil.putStringList('cookie', cookie!);

      //     Alert.hide();
      handle.next(response);
    } else {
      handle.reject(DioError(
          requestOptions: response.requestOptions,
          error: response.data != null &&
                  response.data is Map &&
                  response.data['msg'] != null &&
                  response.data['msg'].length > 0
              ? response.data['msg']
              : '未知错误',
          response: response));
    }
  }

  @override
  onError(err, handle) {
    // Alert.hide();关闭弹窗

    // 账户登录异常
    if (err.response != null &&
        err.response?.data != null &&
        err.response?.data is Map &&
        err.response?.data != null &&
        err.response?.data['code'] == '2') {
      // Alert.showAlert(
      //   message: err.message ?? '未知错误',
      //   showCancel: false,
      //   onConfirm: () {
      //     // 清除账号缓存
      //     SpUtil.putString("account_phone", '');
      //     SpUtil.putString("account_password", '');
      //     SpUtil.putObject("account", '');
      //
      //     // 退出到登录页面
      //     //  push(Routes.login, replace: true, clearStack: true);
      //   },
      // );
    } else {
      //    Alert.showAlert(message: err.message ?? '未知错误', showCancel: false);//在页面显示一个错误弹窗
    }
    AppException appException = AppException.create(err);
    err.error = appException;
    return super.onError(err, handle);
  }
}

sp.dart

import 'dart:convert';

import 'package:shared_preferences/shared_preferences.dart';

/// 本地存储
class SpUtil {
  static SpUtil _instance = new SpUtil._();
  factory SpUtil() => _instance;
  static SharedPreferences? _prefs;

  SpUtil._();

  static Future<void> init() async {
    if (_prefs == null) {
      _prefs = await SharedPreferences.getInstance();
    }
  }

  Future<bool> setJSON(String key, dynamic jsonVal) {
    String jsonString = jsonEncode(jsonVal);
    return _prefs!.setString(key, jsonString);
  }

  dynamic getJSON(String key) {
    String? jsonString = _prefs?.getString(key);
    return jsonDecode(jsonString!);
  }

  Future<bool> setBool(String key, bool val) {
    return _prefs!.setBool(key, val);
  }

  bool? getBool(String key) {
    bool? val = _prefs?.getBool(key);
    return val;
  }

  Future<bool> remove(String key) {
    return _prefs!.remove(key);
  }
}

下面就是使用了:
首先先建个model(为了方便哪种)我一般用https://javiercbk.github.io/json_to_dart/建滴。
get_science_article_entity.dart

class GetScienceArticleEntity {
  String? code;
  String? msg;
  Data? data;

  GetScienceArticleEntity({this.code, this.msg, this.data});

  GetScienceArticleEntity.fromJson(Map<String, dynamic> json) {
    code = json['code'];
    msg = json['msg'];
    data = json['data'] != null ? new Data.fromJson(json['data']) : null;
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['code'] = this.code;
    data['msg'] = this.msg;
    if (this.data != null) {
      data['data'] = this.data?.toJson();
    }
    return data;
  }
}

class Data {
  int? pageCount;
  int? pageTotal;
  int? pageSize;
  List<PageList>? pageList;
  int? pageNum;

  Data(
      {this.pageCount,
      this.pageTotal,
      this.pageSize,
      this.pageList,
      this.pageNum});

  Data.fromJson(Map<String, dynamic> json) {
    pageCount = json['pageCount'];
    pageTotal = json['pageTotal'];
    pageSize = json['pageSize'];
    if (json['pageList'] != null) {
      pageList = <PageList>[];
      json['pageList'].forEach((v) {
        pageList?.add(new PageList.fromJson(v));
      });
    }
    pageNum = json['pageNum'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['pageCount'] = this.pageCount;
    data['pageTotal'] = this.pageTotal;
    data['pageSize'] = this.pageSize;
    if (this.pageList != null) {
      data['pageList'] = this.pageList?.map((v) => v.toJson()).toList();
    }
    data['pageNum'] = this.pageNum;
    return data;
  }
}

class PageList {
  int? articleId;
  String? articleTitle;
  int? articleType;
  String? articleContent;
  int? articleContentType;
  int? articleStatus;
  String? articleCreateTime;
  String? articleUpdateTime;
  int? articleSort;

  PageList(
      {this.articleId,
      this.articleTitle,
      this.articleType,
      this.articleContent,
      this.articleContentType,
      this.articleStatus,
      this.articleCreateTime,
      this.articleUpdateTime,
      this.articleSort});

  PageList.fromJson(Map<String, dynamic> json) {
    articleId = json['articleId'];
    articleTitle = json['articleTitle'];
    articleType = json['articleType'];
    articleContent = json['articleContent'];
    articleContentType = json['articleContentType'];
    articleStatus = json['articleStatus'];
    articleCreateTime = json['articleCreateTime'];
    articleUpdateTime = json['articleUpdateTime'];
    articleSort = json['articleSort'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['articleId'] = this.articleId;
    data['articleTitle'] = this.articleTitle;
    data['articleType'] = this.articleType;
    data['articleContent'] = this.articleContent;
    data['articleContentType'] = this.articleContentType;
    data['articleStatus'] = this.articleStatus;
    data['articleCreateTime'] = this.articleCreateTime;
    data['articleUpdateTime'] = this.articleUpdateTime;
    data['articleSort'] = this.articleSort;
    return data;
  }
}

创一个类(为了方便而已)
testApi.dart

import 'package:dio/dio.dart';
import 'package:pp/MyHttp/api_response.dart';
import 'package:pp/MyHttp/http.dart';
import 'package:pp/get_science_article_entity.dart';

class TestApi {
  static String _article = 'api/article/getScienceArticle';

  static Future<ApiResponse<GetScienceArticleEntity>> getScienceArticle(
      {int? pageNum}) async {
    try {
      final response =
          await Https.instance.get(_article, params: {"pageNum": pageNum});
      var data = GetScienceArticleEntity.fromJson(response);
      return ApiResponse.completed(data);
    } on DioError catch (e) {
      return ApiResponse.error(e.error);
    }
  }
}

使用:

  void _do() async {
    ApiResponse<GetScienceArticleEntity> res =
        await TestApi.getScienceArticle();
    if (res.status != Status.COMPLETED) return;
    print(res.data?.data?.pageCount);
  }

结果:


171625219598_.pic_hd.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容