跟我学企业级flutter项目:如何用dio封装一套企业级可扩展高效的网络层

前言

网上有很多,比如说“Flutter Dio 亲妈级别封装教程”这篇文章,该文章上有几点问题:

  1. 重试机制代码错误
  2. token存取耦合很高
  3. 网络请求只能针对单一地址进行访问
  4. 网络请求缓存机制也不是很完美。

一旦依照这样的封装去做,那么项目后期的扩展性和易用性会有一定的阻碍,那么如何做到token存取无耦合,而且还能让app多种网络地址一同请求,还可以做到针对不同请求不同超时时长处理,网络缓存还加入可自动清理的lru算法呢?那么今天这篇文章为你揭晓企业级flutter dio网络层封装。

搭建前夕准备

三方库:

dio_cache_interceptor lru缓存库
dio 网络库
retrofit 网络生成库
connectivity_plus 网络情况判断

技能:

单例模式
享元模式
迭代

文章:

持久化:跟我学企业级flutter项目:dio网络框架增加公共请求参数&header

准备好如上技能,我们来封装一套优秀的网络层

一、准备好几个基本拦截器

1、超时拦截器

import 'dart:collection';

import 'package:dio/dio.dart';
import 'package:flutter_base_lib/src/app/contants.dart';
import 'package:flutter_base_lib/src/tools/net/cache_object.dart';

class TimeInterceptor extends Interceptor {

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    Map<String, dynamic> extra = options.extra;
    bool connect = extra.containsKey(SysConfig.connectTimeout);
    bool receive = extra.containsKey(SysConfig.receiveTimeOut);
    if(connect||receive){
      if(connect){
        int connectTimeout = options.extra[SysConfig.connectTimeout];
        options.connectTimeout = connectTimeout;
      }
      if(receive){
        int receiveTimeOut = options.extra[SysConfig.receiveTimeOut];
        options.receiveTimeout = receiveTimeOut;
      }
    }
    super.onRequest(options, handler);

  }

}

作用:单独针对个别接口进行超时时长设定,如(下载,长链接接口)

2、缓存拦截器

dio_cache_interceptor 这个库中有lru算法缓存拦截库,可直接集成

3、持久化拦截器

跟我学企业级flutter项目:dio网络框架增加公共请求参数&header 本篇文章介绍了如何持久化

4、重试拦截器


import 'dart:async';
import 'dart:io';

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:flutter_base_lib/src/app/application.dart';
import 'package:flutter_base_lib/src/app/contants.dart';
import 'package:flutter_ulog/flutter_ulog.dart';

import '../dio_utli.dart';

/// 重试拦截器
class RetryOnConnectionChangeInterceptor extends Interceptor {
  Dio? dio;

  RequestInterceptorHandler? mHandler;
  // RetryOnConnectionChangeInterceptor(){
  //
  // }

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    mHandler = handler;
    super.onRequest(options, handler);
  }


  @override
  Future onError(DioError err, ErrorInterceptorHandler handler) async{
    if (dio!=null&&Application.config.httpConfig.retry&&await _shouldRetry(err)) {
      return await retryLoop(err,handler,1);
    }
    return super.onError(err, handler);
  }

  Future retryLoop(DioError err, ErrorInterceptorHandler handler,int retry) async {
    try {
      ULog.d("${err.requestOptions.uri.toString()} retry : ${retry}",tag: "${SysConfig.libNetTag}Retry");
      await retryHttp(err,handler);
    } on DioError catch (err) {
      if(await _shouldRetry(err)&&retry<Application.config.httpConfig.retryCount){
        await retryLoop(err,handler,retry+1);
      }else{
        return super.onError(err, handler);
      }
    }
  }

  Future retryHttp(DioError err, ErrorInterceptorHandler handler) async {
    RequestOptions requestOptions = err.requestOptions;
    Options options = Options(
      method: requestOptions.method,
      sendTimeout: requestOptions.sendTimeout,
      receiveTimeout: requestOptions.receiveTimeout,
      extra: requestOptions.extra,
      headers: requestOptions.headers,
      responseType: requestOptions.responseType,
      contentType: requestOptions.contentType,
      validateStatus: requestOptions.validateStatus,
      receiveDataWhenStatusError: requestOptions.receiveDataWhenStatusError,
      followRedirects: requestOptions.followRedirects,
      maxRedirects: requestOptions.maxRedirects,
      requestEncoder: requestOptions.requestEncoder,
      responseDecoder: requestOptions.responseDecoder,
      listFormat: requestOptions.listFormat,
    );

    var res = await dio?.request(
      requestOptions.path,
      cancelToken: requestOptions.cancelToken,
      data: requestOptions.data,
      onReceiveProgress: requestOptions.onReceiveProgress,
      onSendProgress: requestOptions.onSendProgress,
      queryParameters: requestOptions.queryParameters,
      options: options,
    );

    return handler.resolve(res!);
  }

  ///要重试的类型
  Future<bool> _shouldRetry(DioError err) async{
    return err.error != null && err.error is SocketException && await isConnected();
  }

  Future<bool> isConnected() async {
    var connectivityResult = await (Connectivity().checkConnectivity());
    return connectivityResult != ConnectivityResult.none;
  }
}

该重试拦截器与其他文章封装不同,主要是用重试次数来管理重试机制。

5、日志拦截器


import 'dart:convert';

import 'package:dio/dio.dart';
import 'package:flutter_base_lib/src/app/contants.dart';
import 'package:flutter_ulog/flutter_ulog.dart';
typedef void LibLogPrint(String message);
class LibLogInterceptor extends Interceptor {
  LibLogInterceptor({
    this.request = true,
    this.requestHeader = true,
    this.requestBody = false,
    this.responseHeader = true,
    this.responseBody = false,
    this.error = true
  });

  /// Print request [Options]
  bool request;

  /// Print request header [Options.headers]
  bool requestHeader;

  /// Print request data [Options.data]
  bool requestBody;

  /// Print [Response.data]
  bool responseBody;

  /// Print [Response.headers]
  bool responseHeader;

  /// Print error message
  bool error;

  @override
  void onRequest(
      RequestOptions options, RequestInterceptorHandler handler) async {
    var builder = StringBuffer('*** Request *** \n');
    builder.write(_printKV('uri', options.uri));
    //options.headers;

    if (request) {
      builder.write(_printKV('method', options.method));
      builder.write(_printKV('responseType', options.responseType.toString()));
      builder.write(_printKV('followRedirects', options.followRedirects));
      builder.write(_printKV('connectTimeout', options.connectTimeout));
      builder.write(_printKV('sendTimeout', options.sendTimeout));
      builder.write(_printKV('receiveTimeout', options.receiveTimeout));
      builder.write(_printKV(
          'receiveDataWhenStatusError', options.receiveDataWhenStatusError));
          builder.write(_printKV('extra', options.extra));
    }
    if (requestHeader) {
      builder.write('headers:\n');
      options.headers.forEach((key, v) => builder.write(_printKV(' $key', v)));
    }
    if (requestBody) {
      var res = options.data;
      builder.write('data:\n');
      builder.write(_message(res));
      // try{
      //   ULog.json(res.toString(),tag: "${SysConfig.libNetTag}RequestJson");
      // } on Exception catch (e) {
      //   ULog.d(res,tag: "${SysConfig.libNetTag}RequestJson");
      // }
    }
    ULog.d(builder.toString(),tag: "${SysConfig.libNetTag}Request");
    handler.next(options);
  }

  // Handles any object that is causing JsonEncoder() problems
  Object toEncodableFallback(dynamic object) {
    return object.toString();
  }

  String _message(dynamic res) {
    if (res is Map || res is Iterable) {
      var encoder = JsonEncoder.withIndent('  ', toEncodableFallback);
      return encoder.convert(res);
    } else {
      return res.toString();
    }
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) async {
    var builder = StringBuffer('*** Response *** \n');
    _printResponse(response,builder,(message){
      ULog.d(message,tag: "${SysConfig.libNetTag}Response");
    });
    handler.next(response);
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) async {
    if (error) {
      var builder = StringBuffer('*** DioError *** \n');
      builder.write('uri: ${err.requestOptions.uri}\n');
      builder.write('$err');
      if (err.response != null) {
        _printResponse(err.response!,builder,(message){
          ULog.e(message,tag: "${SysConfig.libNetTag}Error");
        });
      }else{
        ULog.e(builder.toString(),tag: "${SysConfig.libNetTag}Error");
      }
    }

    handler.next(err);
  }

  void _printResponse(Response response,StringBuffer builder,LibLogPrint pr) {
    builder.write(_printKV('uri', response.requestOptions.uri));
    if (responseHeader) {
      builder.write(_printKV('statusCode', response.statusCode));
      if (response.isRedirect == true) {
        builder.write(_printKV('redirect', response.realUri));
      }

      builder.write('headers:\n');
      response.headers.forEach((key, v) => builder.write(_printKV(' $key', v.join('\r\n\t'))));
    }
    if (responseBody) {
      var res = response.toString();
      builder.write('Response Text:\r\n');
      var resJ = res.trim();
      if (resJ.startsWith("{")) {
        Map<String, dynamic> decode = JsonCodec().decode(resJ);
        builder.write(_message(decode));
      }else if (resJ.startsWith("[")) {
        List decode = JsonCodec().decode(resJ);
        builder.write(_message(decode));
      }else {
        builder.write(res);
      }

      // try{
      //   ULog.json(res,tag: "${SysConfig.libNetTag}ResponseJson");
      // } on Exception catch (e) {
      //   ULog.d(res,tag: "${SysConfig.libNetTag}ResponseJson");
      // }
    }
    pr(builder.toString());

  }

  String _printKV(String key, Object? v) {
    return '$key: $v \n';
  }

}

在这里插入图片描述

主要是日志拦截打印

6、错误拦截器


import 'dart:io';

import 'package:dio/dio.dart';
import 'package:flutter_base_lib/src/app/contants.dart';
import 'package:flutter_base_lib/src/exception/lib_network_exception.dart';
import 'package:flutter_base_lib/src/tools/net/dio_utli.dart';
import 'package:flutter_ulog/flutter_ulog.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_base_lib/src/lib_localizations.dart';

/// 错误处理拦截器
class ErrorInterceptor extends Interceptor {
// 是否有网
  Future<bool> isConnected() async {
    var connectivityResult = await (Connectivity().checkConnectivity());
    return connectivityResult != ConnectivityResult.none;
  }
  @override
  Future<void> onError(DioError err, ErrorInterceptorHandler handler) async {
    if (err.type == DioErrorType.other) {
      bool isConnectNetWork = await isConnected();
      if (!isConnectNetWork && err.error is SocketException) {
        err.error = SocketException(LibLocalizations.getLibString().libNetWorkNoConnect!);
      }else if (err.error is SocketException){
        err.error = SocketException(LibLocalizations.getLibString().libNetWorkError!);
      }
    }
    err.error = LibNetWorkException.create(err);
    ULog.d('DioError : ${err.error.toString()}',tag: "${SysConfig.libNetTag}Interceptor");
    super.onError(err, handler);
  }

}

与其他人封装不同,服务器请求异常code,我将其抛到业务层自主处理。常规异常则走库文案。


import 'dart:io';

import 'package:dio/dio.dart';
import 'package:flutter_base_lib/src/lib_localizations.dart';

class LibNetWorkException implements Exception{

  final String _message;
  final int _code;

  int get code{
    return _code;
  }

  String get message{
    return _message;
  }

  LibNetWorkException( this._code,this._message);

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



  factory LibNetWorkException.create(DioError error) {
    switch (error.type) {
      case DioErrorType.cancel:{
          return LibNetWorkException(-1, LibLocalizations.getLibString().libNetRequestCancel!);
        }
      case DioErrorType.connectTimeout:{
          return LibNetWorkException(-1, LibLocalizations.getLibString().libNetFailCheck!);
        }
      case DioErrorType.sendTimeout:{
          return LibNetWorkException(-1, LibLocalizations.getLibString().libNetTimeOutCheck!);
        }
      case DioErrorType.receiveTimeout:{
          return LibNetWorkException(-1, LibLocalizations.getLibString().libNetResponseTimeOut!);
        }
      case DioErrorType.response:{
          try{
            return LibNetWorkException(error.response!.statusCode!,"HTTP ${error.response!.statusCode!}:${LibLocalizations.getLibString().libNetServerError!}");
          } on Exception catch (_) {
            return LibNetWorkException(-1, error.error.message);
          }
        }
      default:
        {
          return LibNetWorkException(-1, error.error.message);
        }
    }
  }
}

二、工具类封装

1、主要类



import 'dart:io';

import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:flutter_base_lib/src/tools/net/interceptor/error_interceptor.dart';
import 'package:flutter_base_lib/src/tools/net/interceptor/lib_log_interceptor.dart';

import '../../../flutter_base_lib.dart';
import 'interceptor/presistent_interceptor.dart';
import 'interceptor/retry_on_connection_change_interceptor.dart';
import 'interceptor/time_interceptor.dart';

class DioUtil{

  final String _baseUrl;
  final HttpConfig _config;
  final List<Interceptor> _interceptors;

  late Dio _dio;

  Dio get dio{
    return _dio;
  }
  DioUtil._internal(this._baseUrl, this._config, this._interceptors){
    BaseOptions options = new BaseOptions(
      baseUrl: _baseUrl,
      connectTimeout: _config.connectTimeout,
      receiveTimeout: _config.receiveTimeOut,
    );
    _dio = new Dio(options);
    var retry = new Dio(options);
    _interceptors.forEach((element) {
      if(element is RetryOnConnectionChangeInterceptor){
        element.dio = retry;
      }else{
        if(!(element is ErrorInterceptor)){
          retry.interceptors.add(element);
        }
      }
      _dio.interceptors.add(element);
    });
    proxy(_dio);
    proxy(retry);
  }

  void proxy(Dio dio){
    if (SpSotre.instance.getBool(SysConfig.PROXY_ENABLE)??false) {
      String? porxy = SpSotre.instance.getString(SysConfig.PROXY_IP_PROT)??null;
      if(porxy!=null){
        (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
            (client) {
          client.findProxy = (uri) {
            return "PROXY $porxy";
          };
          //代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验
          client.badCertificateCallback =
              (X509Certificate cert, String host, int port) => true;
        };
      }
    }
  }

  static late Map<String,DioUtil> _dioUtils = Map();

  static DioUtil instance(String baseUrl,{HttpConfig? config, List<Interceptor>? interceptors,List<Interceptor>? applyInterceptors}){
    if(!_dioUtils.containsKey(baseUrl)){
      List<Interceptor> list = [PresistentInterceptor(),TimeInterceptor(),RetryOnConnectionChangeInterceptor(),LibLogInterceptor(requestBody: Application.config.debugState,responseBody: Application.config.debugState),ErrorInterceptor()];
      // List<Interceptor> list = [ErrorInterceptor(),PresistentInterceptor()];
      var inter = interceptors??list;
      if(applyInterceptors!=null){
        inter.addAll(applyInterceptors);
      }
      _dioUtils[baseUrl] = DioUtil._internal(baseUrl,config??Application.config.httpConfig,inter);
    }
    return _dioUtils[baseUrl]!;
  }

  // CancelToken _cancelToken = new CancelToken();


}

工具类封装,主要运用享元模式,可以支持多种url进行访问,不同的url有不同的配置。(灵活可用)

2、辅助类:



class HttpConfig{
  final int _connectTimeout ;
  final int _receiveTimeOut ;
  final bool _retry;
  final int _retryCount;

  get connectTimeout{
    return _connectTimeout;
  }

  get receiveTimeOut{
    return _receiveTimeOut;
  }

  get retry{
    return _retry;
  }
  get retryCount{
    return _retryCount;
  }

  HttpConfig(HttpConfigBuilder builder): _connectTimeout = builder._connectTimeout,_receiveTimeOut = builder._receiveTimeOut,_retry = builder._retry,_retryCount = builder._retryCount;
}

class HttpConfigBuilder {
  int _connectTimeout = 10000;//连接超时时间
  int _receiveTimeOut = 30000;//接收超时时间
  bool _retry = false;
  int _retryCount = 3;

  // var maxRetry = 1 重试次数

  HttpConfigBuilder setConnectTimeout(int connectTimeout){
    _connectTimeout = connectTimeout;
    return this;
  }

  HttpConfigBuilder setReceiveTimeOut(int receiveTimeOut){
    _receiveTimeOut = receiveTimeOut;
    return this;
  }

  HttpConfigBuilder setRetry(bool retry){
    _retry = retry;
    return this;
  }

  HttpConfigBuilder setRetryCount(int retryCount){
    _retryCount = _retryCount;
    return this;
  }

  HttpConfig build() => HttpConfig(this);
}

三、使用


import 'package:flutter_app_me/data/model/api_result.dart';
import 'package:flutter_app_me/data/model/user.dart';
import 'package:flutter_app_me/data/model/user_infos.dart';
import 'package:flutter_base_lib/flutter_base_lib.dart';
import 'package:retrofit/retrofit.dart';
import 'package:dio/dio.dart';

import 'api_methods.dart';

part 'api_service.g.dart';

@RestApi()
abstract class RestClient {
  factory RestClient(Dio dio, {String baseUrl}) = _RestClient;

  @GET(ApiMethods.userinfoJson)
  Future<ApiResult<UserInfos>> userinfoJson();

  // "test123332","123456"
  @POST(ApiMethods.login)
  @Extra({SysConfig.connectTimeout:100000})
  Future<ApiResult<UserInfos>> userLogin(@Queries() User user);
}

网络请求配置




class BusinessErrorException implements Exception {
  final int _errorCode;
  final String? _errorMsg;

  BusinessErrorException(this._errorCode, this._errorMsg);

  int get errorCode {
    return _errorCode;
  }

  String? get errorMsg => _errorMsg;
}


class TokenTimeOutException implements Exception {
  final String? _errorMsg;
  TokenTimeOutException(this._errorMsg);
  String? get errorMsg => _errorMsg;

}

class RequestCodeErrorException implements Exception {
  final String? _errorMsg;
  final int _errorCode;
  RequestCodeErrorException(this._errorCode, this._errorMsg);

  int get errorCode {
    return _errorCode;
  }

  String? get errorMsg => _errorMsg;
}

业务基本异常


import 'package:business_package_auth/business_package_auth.dart';
import 'package:flutter_base_lib/flutter_base_lib.dart';
import 'package:flutter_base_ui/flutter_base_ui.dart';
import 'package:dio/dio.dart';
import 'package:wisdomwork_lib/src/model/api_result.dart';

const int httpSuccessCode = 0;
const int httpErrorCode = 1;
const int httpTokenExt = 10001;

extension SuccessExt<T> on Success<T> {
  Success<T> appSuccess() {
    var data = this.data;
    if (data is ApiResult) {
      if (data.code != httpSuccessCode) {
        switch (data.code){
          case httpTokenExt:
            TipToast.instance.tip(data.msg ?? LibLocalizations.getLibString().libBussinessTokenTimeOut!,tipType: TipType.warning);
            BlocProvider.of<AuthenticationBloc>(LibRouteNavigatorObserver.instance.navigator!.context).add(LogOut());
            throw TokenTimeOutException(data.msg);
          case httpErrorCode:
            TipToast.instance.tip(data.msg ?? LibLocalizations.getLibString().libBussinessRequestCodeError!,tipType: TipType.error);
            throw RequestCodeErrorException(data.code!,data.msg);
          default:
            throw BusinessErrorException(data.code!, data.msg);
        }

      }
    }
    return this;
  }
}

extension ErrorExt<T> on Error<T> {
  void appError() {
    var exception = this.exception;
    if (exception is LibNetWorkException) {
      TipToast.instance.tip(exception.message, tipType: TipType.error);
    }
  }
}


typedef ResultF<T> = Future<ApiResult<T>> Function();

mixin RemoteBase {

  Future<DataResult<ApiResult<T>>> remoteDataResult<T>(ResultF<T> resultF) async {
    try {
      var data = await resultF.call();
      return Success(data).appSuccess();
    } on DioError catch (err, stack) {
      var e = err.error;
      ULog.e(e.toString(), error: e, stackTrace: stack);
      return Error(e)..appError();
    } on Exception catch (e, stack) {
      ULog.e(e.toString(), error: e, stackTrace: stack);
      return Error(e)..appError();
    }
  }

}

业务基本异常处理方式


import 'package:flutter_base_lib/flutter_base_lib.dart';
import 'package:flutter_base_ui/flutter_base_ui.dart';
import 'package:wisdomwork/data/services/api_service.dart';
import 'package:wisdomwork_lib/wisdomwork_lib.dart';

mixin WisdomworkRemoteBase{
  var rest = RestClient(DioUtil.instance(
      AppEnvironment.envConfig![AppConfig.apiName]!,
      applyInterceptors: [UiNetInterceptor()]).dio);
}

业务请求接口,实现

     final data = await AppResponsitory.instance.login(state.phoneText, state.codeText);
     if (userResult != null) {
                if (userResult is Success) {
                  if (userResult.data!.data!= null) {
                    onGetUser(userResult.data!.data!, context);
                  }
                } else if(userResult is Error){
                  var exception = (userResult as Error).exception;
                  if(exception is BusinessErrorException){
                    Fluttertoast.showToast(msg: exception.errorMsg.toString());
                  }
                }
              }

业务请求与异常处理

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

推荐阅读更多精彩内容