iOS 网络层架构

前言

在此所说的网络层架构,无非就是针对iOS网络请求的现状与问题,做出相应的解决方案。

iOS网络请求的现状与问题

  1. 网络接口规范化:所谓的规范、没有什么一定的规范,每个人都有每个人的规范,无破不立,也总会有不在规范之类的。虽然网络接口规范化能带来很多好处,可是开发中往往会遇到特例。很多时候,我们无法要求别人要怎样配合自己。那么唯有灵活多变,而又方便易用的网络框架被提到日程上来了。
  2. 网络请求处理过程重复啰嗦,而又惊人的相似。那么在网络请求过程处理上的复用就显得非常有必要。
  3. 取消网络请求麻烦,不能自动取消不必要的请求。那么也就需要对网络请求的取消做一些分装,提高一些所谓的性能。

这个版本的主要针对以上三个方面进行的架构,以后升级会往网络安全与性能优化方向靠。

技术层面的选择与看法

  1. 跟业务层对接的交互模式

跟业务层对接主要就Delegate,Notification,Block,KVO和Target-Action这几种方式吧;它们之间有好坏优劣我不想在此比较,可以自行查阅。我选择的方式是Block,没有其他原因,有且只有一个那就是Block简单粗暴,用起来方便、用起来爽在我看来很重要,所谓的那些问题其实都能够避免。

  1. 集约型和离散型API调用方式
    先来解释一下离散型吧,“离散型API调用是这样的,一个API对应于一个APIManager”,听到这句话我就烦,我就pass掉了,一个API对应于一个APIManager。其实每个请求之间是多么惊人的相似,而用一个独立的Manager来管理,我有点无法接受。或许在自动取消这样的问题上有点优势吧。

网络层架构实现

AFNetworking基本是iOS网络请求的标配,各种优势不用言表,这个框架也是基于AFN针对上述问题与目标进行的二次封装架构。

架构思想

  1. 配置对象架构:由于本着灵活多变目标,我在几度考量之后决定使用配置对象的方式进行架构,如果对配置对象进行多层控制,相互组合能够非常灵活控制网络请求。再一个就是使用配置对象能够承载非常多的参数、而通常情况这些参数有基本相同,通过默认参数就可以解决掉相当一部分问题,遇到特殊情况再特殊配置,这样相当的简洁而又易于扩展。其实从这个方面来看,这种使用配置对象的方式起到了离散型API调用的作用,说白了也算是离散型API调用的一种变相。
  2. 多层控制架构:多层控制考虑更多的是对网络请求过程的复用,另一个就是这样多层组合灵活性更大,业务层有更大的发挥空间。

配置对象的实现

本次网络层架构主要依赖于QSPNetworkingConfig(网络控制)、QSPParameterConfig(请求空)、QSPLoadConfig(加载处理)、QSPErrorConfig(错误处理)这四个配置对象,下面分别介绍。

QSPNetworkingConfig配置对象的实现

QSPNetworkingConfig:这个配置对象用于配置网络层的参数,说白了就是表示一个AFHTTPSessionManager对象。目前抽象出如下参数进行控制,如后期有需要完全可以自由添加,扩展性非常高

/**
 超时时间(默认:15)
 */
@property (assign, nonatomic) NSTimeInterval timeoutInterval;

/**
 服务器地址
 */
@property (copy, nonatomic) NSString *basePath;

/**
 验证模式(默认:AFSSLPinningModeNone)
 */
@property (nonatomic, assign) AFSSLPinningMode SSLPinningMode;

/**
 本地绑定的证书(默认:nil)
 */
@property (nonatomic, strong) NSSet <NSData *> *pinnedCertificates;

/**
 是否允许无效证书(默认:NO)
 */
@property (nonatomic, assign) BOOL allowInvalidCertificates;

/**
 是否验证域名(默认:NO)
 */
@property (nonatomic, assign) BOOL validatesDomainName;

/**
 数据解析格式(默认:[NSSet setWithObjects:@"application/json",  @"text/json", @"text/javascript",@"text/html", @"text/plain", nil])
 */
@property (copy, nonatomic) NSSet<NSString *> *acceptableContentTypes;

/**
 请求头设置字典(默认:[NSDictionary dictionaryWithObject:@"ios" forKey:@"request-type"])
 */
@property (copy, nonatomic) NSDictionary<NSString *, NSString *> *HTTPHeaderDictionary;

QSPLoadConfig配置对象的实现

QSPLoadConfig:这个配置对象就是对网络请求过程进行控制,目前只有对只使用loadBegin、loadEnd两个block对开始请求和结束请求两个节点进行控制,非常简单在此略去。

QSPErrorConfig配置对象的实现

QSPErrorConfig:这个配置对象控制着网络请求的错误处理,这里把错误区分为网络请求错误与返回的数据错误两种形式如下:

/**
 网络错误处理(默认无)
 */
@property (copy, nonatomic) QSPNetworkingErrorBlock networkingErrorPrompt;

/**
 数据错误处理(默认无)
 */
@property (copy, nonatomic) QSPDataErrorBlock dataErrorPrompt;

QSPParameterConfig配置对象的实现

QSPParameterConfig:这个配置对象是对网络请求参数控制,不仅包含了网络请求所需要的各种参数,还包涵了上三个配置对象、这样可以为每条请求做出特定的控制,达到离散型API调用的效果。它的属性与工厂方法如下:

/**
 请求参数
 */
@property (copy, nonatomic) NSDictionary *parameters;

/**
 请求类型(默认为POST)
 */
@property (assign, nonatomic) QSPNetworkingType type;

/**
 上传文件数组(默认为nil)
 */
@property (copy, nonatomic) NSArray<QSPUploadModel *> *uploadModels;

/**
 请求api
 */
@property (copy, nonatomic) NSString *apiPath;

/**
 是否自动取消(默认为YES)
 */
@property (assign, nonatomic) BOOL autoCancel;

/**
 自动取消操作的依赖对象(此对象一销毁,就执行取消操作,所以autoCancel设置为YES的时候必须设置此参数自动取消才会起作用,默认为nil)
 */
@property (weak, nonatomic) id cancelDependence;

/**
 网络请求发生的控制器(默认为nil)
 */
@property (weak, nonatomic) UIViewController *controller;

/**
 是否处理错误(默认为YES)
 */
@property (assign, nonatomic) BOOL showError;

/**
 是否显示加载(默认为YES)
 */
@property (assign, nonatomic) BOOL showLoad;

/**
 是否执行成功的条件(默认为YES,如果为NO,只要网络请求成功就表示成功)
 */
@property (assign, nonatomic) BOOL executeConditionOfSuccess;

/**
 网络配置对象(默认为nil)
 */
@property (strong, nonatomic) QSPNetworkingConfig *networkingConfig;

/**
 错误配置对象(默认为nil)
 */
@property (strong, nonatomic) QSPErrorConfig *errorConfig;

/**
 加载配置对象(默认为nil)
 */
@property (strong, nonatomic) QSPLoadConfig *lodaConfig;

/**
 进度回调(默认为nil)
 */
@property (copy, nonatomic) QSPProgressBolck progress;

/**
 请求完成回调(默认为nil)
 */
@property (copy, nonatomic) QSPCompletionBlock completion;

+ (instancetype)parameterConfigWithParameters:(NSDictionary *)parameters apiPath:(NSString *)apiPath cancelDependence:(id)cancelDependence controller:(UIViewController *)controller completion:(QSPCompletionBlock)completion;
- (instancetype)initWithParameters:(NSDictionary *)parameters apiPath:(NSString *)apiPath cancelDependence:(id)cancelDependence controller:(UIViewController *)controller completion:(QSPCompletionBlock)completion;

核心逻辑实现

QSPNetworkingManager

QSPNetworkingManager:这是网络管理对象,使用QSPNetworkingConfig、QSPErrorConfig、QSPLoadConfig三个配置对象配置并控制着。然而每个请求的发送都会提供一个QSPNetworkingConfig配置对象这个对象有着更高的控制能力,这样就达到了两层控制,QSPNetworkingConfig其实为方便起见又用参数对某些地方进行控制,可以说有的具有三层控制。其头文件如下:

/**
 配置对象
 */
@property (strong, nonatomic) QSPNetworkingConfig *config;

/**
 错误配置对象
 */
@property (strong, nonatomic) QSPErrorConfig *errorConfig;

/**
 加载配置对象
 */
@property (strong, nonatomic) QSPLoadConfig *loadConfig;

/**
 成功的条件(如果不设置,只要网络请求成功,就认为是成功)
 */
@property (copy, nonatomic) QSPConditionOfSuccessBolck conditionOfSuccess;

/**
 配置框架
 
 @param networkingConfig 网络配置
 @param errorConfig 错误配置
 @param loadConfig 加载配置
 @param condictionOfSuccess 成功的条件(如果不设置,只要网络请求成功,就认为是成功)
 */
+ (void)configWithNetworkingConfig:(QSPNetworkingConfig *)networkingConfig errorConfig:(QSPErrorConfig *)errorConfig loadConfig:(QSPLoadConfig *)loadConfig condictionOfSuccess:(QSPConditionOfSuccessBolck)condictionOfSuccess;

/**
 单例方法
 */
+ (instancetype)manager;

/**
 默认调用(POST方式调用,最终转化为callWithParameterConfig方法调用)

 @param apiPath api
 @param parameters 参数
 @param dependence 依赖对象
 @param completion 完成的回调
 @return QSPNetworkingObject对象
 */
+ (QSPNetworkingObject *)defaultCall:(NSString *)apiPath parameters:(NSDictionary *)parameters cancelDependence:(id)dependence controller:(UIViewController *)controller completion:(QSPCompletionBlock)completion;

/**
 get调用(最终转化为callWithParameterConfig方法调用)
 
 @param apiPath api
 @param parameters 参数
 @param dependence 依赖对象
 @param completion 完成的回调
 @return QSPNetworkingObject对象
 */
+ (QSPNetworkingObject *)getCall:(NSString *)apiPath parameters:(NSDictionary *)parameters cancelDependence:(id)dependence controller:(UIViewController *)controller completion:(QSPCompletionBlock)completion;

/**
 使用配置对象调用

 @param parameterConfig 配置对象
 @return QSPNetworkingObject对象
 */
+ (QSPNetworkingObject *)callWithParameterConfig:(QSPParameterConfig *)parameterConfig;
QSPNetworkingConfig对应AFHTTPSessionManager的设置
- (AFHTTPSessionManager *)sessionManagerWithNetworkingConfig:(QSPNetworkingConfig *)config
{
    AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:config.basePath]];
    sessionManager.requestSerializer.timeoutInterval = config.timeoutInterval;
    sessionManager.requestSerializer = [AFJSONRequestSerializer serializer];
    AFJSONResponseSerializer *serializer = [AFJSONResponseSerializer serializer];
    sessionManager.responseSerializer = serializer;
    [config.HTTPHeaderDictionary enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) {
        [sessionManager.requestSerializer setValue:key forHTTPHeaderField:obj];
    }];
    sessionManager.responseSerializer.acceptableContentTypes = config.acceptableContentTypes;
    sessionManager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:config.SSLPinningMode];
    sessionManager.securityPolicy.allowInvalidCertificates = config.allowInvalidCertificates;//忽略https证书
    sessionManager.securityPolicy.validatesDomainName = config.validatesDomainName;//是否验证域名
    
    return sessionManager;
}
请求执行过程(以POST方式为例)
[sessionManager POST:parameterConfig.apiPath parameters:parameterConfig.parameters constructingBodyWithBlock:^(id <AFMultipartFormData> formData) {
        //上传文件
        for (QSPUploadModel *uploadModel in parameterConfig.uploadModels) {
            [formData appendPartWithFileData:uploadModel.data name:uploadModel.name fileName:uploadModel.fileName mimeType:uploadModel.mimeType];
        }
    } progress:^(NSProgress * _Nonnull uploadProgress) {
        //出来进度
        if (parameterConfig.progress) {
            parameterConfig.progress(uploadProgress);
        }
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        //成功着陆
        [weakSelf susseccWithParameterConfig:parameterConfig task:task responseObject:responseObject];
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        //失败着陆
        [weakSelf failureWithParameterConfig:parameterConfig task:task error:error];
    }];
请求成功着陆的执行过程
- (void)susseccWithParameterConfig:(QSPParameterConfig *)parameterConfig task:(NSURLSessionDataTask *)task responseObject:(id)responseObject
{
    //打印请求信息
    QSPNetworkingLog(@"\n接口地址:%@\n接口参数:%@\n上传文件:%@\n接口返回:%@", task.currentRequest.URL.absoluteString, parameterConfig.parameters, parameterConfig.uploadModels, responseObject);
    [self removeDependence:parameterConfig andTask:task];
    [self removeLoad:parameterConfig];
    
    //出来数据错误
    if (parameterConfig.showError) {
        //选择QSPErrorConfig,这里以parameterConfig的优先级高
        QSPErrorConfig *errorConfig = parameterConfig.errorConfig ? parameterConfig.errorConfig : self.errorConfig;
        if (errorConfig.dataErrorPrompt) {
            errorConfig.dataErrorPrompt(responseObject, parameterConfig.controller);
        }
    }
    
    //执行成功条件
    if (parameterConfig.executeConditionOfSuccess) {
        if (self.conditionOfSuccess) {
            if (self.conditionOfSuccess(responseObject)) {
                if (parameterConfig.completion) {
                    parameterConfig.completion(YES, responseObject, nil);
                }
            }
            else
            {
                if (parameterConfig.completion) {
                    parameterConfig.completion(NO, responseObject, nil);
                }
            }
        }
        else
        {
            if (parameterConfig.completion) {
                parameterConfig.completion(YES, responseObject, nil);
            }
        }
    }
    else
    {
        if (parameterConfig.completion) {
            parameterConfig.completion(YES, responseObject, nil);
        }
    }
}
对单例的设计方式进行说明

QSPNetworkingManager这个管理对象采用的是单例的设计模式,设计之初是为了避免麻烦的创建过程,但是随着框架在项目中的使用,我发现单例的设计模式限制了一定的灵活性,以后我一定会对此作出调整。为什么如此说,是因为虽然两层控制已经很灵活了,普遍适应了基本请求、特例请求这种模式。然而计划永远赶不上变化,如果基本请求形式有很多种呢?

自动取消的设计

设计思想
  1. 自动取消时机:自动取消,如何取消,什么情况取消?其实普遍来说当一个视图控制器销毁的时候,起生命周期内发送的任何网络请求只要没有着陆的都应该取消。
  2. 设计方案:为每一条请求设置一个自动取消操作的依赖对象,只要依赖对象销毁,那么我们就取消请求。有人说使用离散型API调用来实现要好,不需要设置依赖对象,我想说的是其实一样,离散型方式视图控制器就需要持有APIManager,并没有任何不简便之处。
  3. 实现步骤
  • 包装一个能够取消网络请求的QSPNetworkingObject对象,其头文件如下:
/**
 网络请求对象
 */
@interface QSPNetworkingObject : NSObject

@property (strong, nonatomic, readonly) NSURLSessionTask *task;

+ (instancetype)networkingObjectWithTask:(NSURLSessionTask *)task autoCancel:(BOOL)autoCancel;
- (instancetype)initWithTask:(NSURLSessionTask *)task autoCancel:(BOOL)autoCancel;

/**
 取消请求
 */
- (void)cancel;

@end
  • 动态给取消依赖对象添加能够取消请求的网络对象(如上步骤所述),这里使用的数组,然后再把网络对象加在数组中,因为存在多个请求依赖一个对象,具体如下:
        QSPNetworkingObject *obj = [QSPNetworkingObject networkingObjectWithTask:task autoCancel:(parameterConfig.autoCancel && parameterConfig.cancelDependence) ? YES : NO];
        
        if (parameterConfig.autoCancel && parameterConfig.cancelDependence) {
            NSMutableArray *networkingObjects = objc_getAssociatedObject(parameterConfig.cancelDependence, QSPNetworkingObject_arrayName);
            if (networkingObjects == nil) {
                networkingObjects = [NSMutableArray arrayWithCapacity:1];
                objc_setAssociatedObject(parameterConfig.cancelDependence, QSPNetworkingObject_arrayName, networkingObjects, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            }
            
            if (obj) {
                [networkingObjects addObject:obj];
            }
        }
  • 取消网络请求,因为依赖对象销毁的时候,上步骤中添加包装取消网络请求的对象也随之销毁,所以在其dealloc方法中把网络请求取消掉。
- (void)dealloc
{
//    QSPNetworkingLog(@"%@取消啦---------------------------------------------------", self.task.currentRequest.URL.absoluteString);
    if (self.autoCancel) {
        [self cancel];
    }
}
- (void)cancel
{
    //    QSPNetworkingLog(@"---------------------------------------------------%@%zi", self.task.currentRequest.URL.absoluteString, self.task.state);
    if (self.task.state != NSURLSessionTaskStateCompleted && self.task.state != NSURLSessionTaskStateCanceling) {
        QSPNetworkingLog(@"---------------------------------------------------%@取消啦", self.task.currentRequest.URL.absoluteString);
        [self.task cancel];
    }
}
  • 把动态给依赖对象中添加对象移除掉,在每一条请求着陆后都移除,避免依赖对象持有太多的之前包装的具有取消网络请求能力的对象。

        NSMutableArray *networkingObjects = objc_getAssociatedObject(parameterConfig.cancelDependence, QSPNetworkingObject_arrayName);
        if (networkingObjects) {
            QSPNetworkingObject *netObj;
            for (QSPNetworkingObject *obj in networkingObjects) {
                if (obj.task == task) {
                    netObj = obj;
                    break;
                }
            }
            [networkingObjects removeObject:netObj];
        }

框架的使用

配置网络框架

此网络框架的配置需要QSPNetworkingConfig(网络配置对象)、QSPErrorConfig(错误处理配置对象)、QSPLoadConfig(加载处理配置对象)以及一个判断网络请求数据正确的block共同完成。

- (void)configNetworking {
    //创建网络配置对象,控制网络请求
    QSPNetworkingConfig *networkingConfig = [QSPNetworkingConfig networkingConfigWithBasePath:K_NetService_Base];
    
    //创建错误处理配置对象,处理网络、数据错误
    QSPErrorConfig *errorConfig = [QSPErrorConfig errorConfigWithNetworkingErrorPrompt:^(NSError *error, UIViewController *controller) {
        if (error.code == -999) {
            [LoadClass showMessage:K_NetRequestMessage_Cancel toView:self.window];
        }
        else if (error.code == -1001)
        {
            [LoadClass showMessage:K_NetRequestMessage_TimeOut toView:controller.view];
        }
        else
        {
            [LoadClass showMessage:K_NetRequestMessage_Failure toView:controller.view];
        }
    } dataErrorPrompt:^void(id responseObject, UIViewController *controller) {
        if (responseObject == nil || [responseObject isKindOfClass:[NSNull class]]) {
            [LoadClass showMessage:K_NetRequestMessage_NoData toView:controller.view];
        } else {
            if ([responseObject[@"status"] intValue] != 1) {
                NSString *message = responseObject[@"message"] ? responseObject[@"message"] : K_NetRequestMessage_Error;
                [LoadClass showMessage:message toView:controller.view];
            }
        }
    }];
    
    //创建加载处理配置对象,处理加载过程
    QSPLoadConfig *loadConfig = [QSPLoadConfig loadConfigWithLoadBegin:^(UIViewController *controller){
        [LoadClass beginLoadWithMessage:K_NetRequestMessage_Load toView:controller.view];
    } loadEnd:^(UIViewController *controller){
        [LoadClass endLoadFromView:controller.view];
    }];
    
    //配置网络框架
    [QSPNetworkingManager configWithNetworkingConfig:networkingConfig errorConfig:errorConfig loadConfig:loadConfig condictionOfSuccess:^BOOL(id responseObject) {
        return [responseObject[@"status"] integerValue] == 1;
    }];
}

发送请求

分装了如下三个方法来发送请求,分别为默认方式(post)、get方式、参数配置方式(可以通过配置对象配置各种适合自己的请求)。

+ (QSPNetworkingObject *)defaultCall:(NSString *)apiPath parameters:(NSDictionary *)parameters cancelDependence:(id)dependence controller:(UIViewController *)controller completion:(QSPCompletionBlock)completion;
+ (QSPNetworkingObject *)getCall:(NSString *)apiPath parameters:(NSDictionary *)parameters cancelDependence:(id)dependence controller:(UIViewController *)controller completion:(QSPCompletionBlock)completion;
+ (QSPNetworkingObject *)callWithParameterConfig:(QSPParameterConfig *)parameterConfig;

发送一个get请求


        [QSPNetworkingManager getCall:K_NetService_Regeo parameters:K_NetService_RegeoParamery cancelDependence:weakSelf controller:weakSelf completion:^(BOOL success, id responseObject, NSError *error) {
            if (success) {
                [LoadClass showMessage:@"请求成功!" toView:weakSelf.view];
            }
        }];
        [weakSelf.navigationController popViewControllerAnimated:NO];

最后当然是代码地址

代码下载

预告

下一篇记录下把这个框架加入到CocoaPods中管理的全过程。

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

推荐阅读更多精彩内容