Realm数据库存储 使用详解

文章目录

一 Realm 框架
  • 概念介绍
  • 开发辅助工具
二 Realm 使用教程
  • 1 简单的数据操作
  • 创建数据模型
  • 使用RLMRealm对象保存指定模型
  • 使用RLMRealm对象 更新指定模型
  • 使用RLMRealm对象 删除数据
  • 查询数据
  • 2 支持的数据类型
  • 3 模型与模型之间的关系
  • 对一关系
  • 对多关系
  • 反向关系
  • 4 可空属性&默认值&忽略属性
  • 5 通知
  • 6 Realm数据库
  • 用户机制
  • 只读方式打开数据库
  • 数据库文件删除
  • 7 数据库迁移
  • 数据结构的迁移
  • 数据迁移
  • 属性重命名
  • 多版本增量式迁移

一 Realm 框架

概念介绍:

1 Realm 本质上是一个嵌入式数据库,但是它也是看待数据的另一种方式。它用另一种角度来重新看待你移动应用中的模型和业务逻辑。我们所做的就是尝试减少数据库读写的开销。我们尽可能让其运行得足够快,因此我们一直在调整性能数据。

2 Realm核心数据库引擎打造的,并不是建立在SQLite之上的ORM(对象-关系映射(OBJECT/RELATIONALMAPPING,简称ORM)) 是拥有独立的数据库存储引擎

3 性能: 比SQLite和coreData牛逼😁(ps:FMDB和coreData 都是基于SQLite封装的)

4 易用 相对于SQLite 和coreData 使用更加简单 更易入门

辅助工具

1 在App Store 搜索 Realm Browser(可视化访问Realm数据库)

image.png

2 Xcode 插件 https://github.com/alcatraz/Alcatraz (下载下来后直接编译就会发现xcode中会多了个这个 如下图)

image.png

二 realm使用教程:

使用cocoapod集成

pod 'Realm'

1 简单的数据操作

(1) 创建数据模型 继承自RLMObject 也就是上图中的类

创建的模型属性不需要任何属性修饰符 这是因为
由于Realm 在自己的引擎内部有很好的语义解释系统,所以 Objective‑C 的许多属性特性将被忽略,如nonatomic, atomic, strong, copy 和 weak 等。 因此为了避免误解,官方推荐在编写数据模型的时候不要使用任何的属性特性。
请注意,所有的必需属性都必须在对象添加到 Realm 前被赋值

image.png

创建对象的方式

//    后边的值 可以出 字典 也可以传数组
  // 创建对象  字典
  testOneModel *model = [[testOneModel alloc]initWithValue:@{@"num":@2,
                                                             @"name":@"奥卡姆剃须刀"
                                                             }];
  // 数组  后边保存的值 要和模型中的属性 保持一致并且顺序也一致  一一对应
  testOneModel *model = [[testOneModel alloc]initWithValue:@[@2,@"奥卡姆剃须刀"]];
  
(2) 使用RLMRealm对象保存指定模型

    // 1 获取RLMRealm对象   可以看做是操作数据库的句柄  操作数据库全靠它
    RLMRealm *realm = [RLMRealm defaultRealm];
    
    // 写入方法一
    // 开始写入事物
    [realm beginWriteTransaction];
    // 保存数据库
    [realm addObject:model];
    // 提交写入事物
    [realm commitWriteTransaction];
    
    // 方法二  简洁
    // 提供的有block  省去创建Model
    [realm transactionWithBlock:^{
        [realm addObject:model];
    }];
    
    
//    方法三  省去了创建Model
    [realm transactionWithBlock:^{
        [testOneModel createInRealm:realm withValue:@{@"num":@2,
                                                      @"name":@"奥卡姆剃须刀"
                                                      }];
    }];

(3) 使用RLMRealm对象 更新指定模型
//    方式一
//    然后在事物中直接更改
    [realm transactionWithBlock:^{
        model.num = 27;
    }];

// 方式二  直接从realm中取到模型来更新
    RLMResults *results = [testOneModel objectsWhere:@"num = 27"];
    testOneModel *model = results.firstObject;
    [realm transactionWithBlock:^{
        model.num = 30;
    }];


    // 方式三 根据主键进行更新 但是前提是必须在模型中设置好主键  然后此方法会判断该主键是否存在  如果存在就更改 不存在就添加
    [realm transactionWithBlock:^{
        [realm addOrUpdateObject:model];
    }];

    // 方式4 和之前的就差不多了  直接在事物中创建模型
    [realm transactionWithBlock:^{
        [testOneModel createOrUpdateInRealm:realm withValue:@[@10,@"奥卡刀"]];
    }];

    

方式三的配图

image.png

设置完成后数据库中该属性也变成主键


image.png
(4) 使用RLMRealm对象 删除数据
  //  1 删除指定的对象
  // 删除的模型一定要求是被realm所管理的  也就是改模型一定是要从realm中取出来的 
    RLMResults *results = [testOneModel objectsWhere:@"num = 27"];
    testOneModel *model = results.firstObject;
    [realm transactionWithBlock:^{
        [realm deleteObject:model];
    }];

   // 2 删除指定模型里边的所有数据 通过遍历  只会删除该模型
    RLMResults *modelRes = [testOneModel allObjects];
    for (testOneModel *model in modelRes) {
        [realm transactionWithBlock:^{
            [realm deleteObject:model];
        }];
    }


  //  3 删除所有的数据 所有的模型数据都会被清空
  [realm transactionWithBlock:^{
// 删除所有的模型
        [realm deleteAllObjects];
    }];    

    // 4 根据主键查询到改模型来删除
    testOneModel *testModel = [testOneModel objectInRealm:realm forPrimaryKey:@27];
    [realm transactionWithBlock:^{
        [realm deleteObject:testModel];
    }];

(5) 查询数据

//  注意事项:  所有的查询(包括查询和属性访问)在Realm中都是懒加载的  只有当属性被访问时  才能够读取相应的数据
// 查询结棍并不是数据的拷贝(只是数据的映射) 修改查询结果(在写入事务中)会直接修改硬盘上的数据。
 //一旦检索执行之后, RLMResults 将随时保持更新

//  1  查询所有数据
    RLMResults *modelRes = [testOneModel allObjects];

//  2 条件查询
  RLMResults *results = [testOneModel objectsWhere:@"num = 27"];

//  3 查询结果排序
    RLMResults *modelRes = [testOneModel allObjects];
// 排序
   RLMResults *sortRes = [modelRes sortedResultsUsingKeyPath:@"num" ascending:YES];

//  4 链式查询
// (1) 得到一个查询结果
    RLMResults *modelRes2 = [testOneModel objectsWhere:@"num > 1"];
// (2) 在该结果集上继续进行查询 得到一个新的结果集
   RLMResults *modelRes3 = [modelRes objectsWhere:@"num < 10"];
    
// 5 分页  (这个分页我们客户端做的比较少 一般都是后台做处理 )
// 查询出来的结果对象是懒加载 只有真正访问时 才会加载相应对象 所以这里的分页 其实就是从所有集合中分页获取即可

    RLMResults *allModels = [testOneModel allObjects];
    for (int i = 3; i <= 6; i++) {
        testOneModel *model = allModels[i];
    }

2 支持的数据类型

支持的类型:   BOOL, bool, int, NSInteger, long, long long, float, double, NSString, NSDate, NSData, and NSNumber
// 不支持集合类型   
// 解决方案  1 序列化成NSData进行存储 2   转换成RLMArray<RLMObject>进行存储
// 例如存储Image
    testOneModel *model = [[testOneModel alloc]init];
    model.num = 10;
    model.name = @"奥卡姆剃须刀";
    // realm不支持image 不能这样存错
//    model.image = [UIImage imageNamed:@"123.jpg"];

正确的存储方式
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"123.jpg" ofType:nil];
    NSData *data = [NSData dataWithContentsOfFile:filePath];
    model.imageData = data;
    
    RLMRealm *realm = [RLMRealm defaultRealm];
    [realm transactionWithBlock:^{
        [realm addOrUpdateObject:model];
    }];

但是需要忽略到模型中的image
采用readOnly修饰即可
然后重写image的get方法 可以在外界取到改图片

  • (UIImage *)image{
    return [UIImage imageWithData:self.imageData];
    }

包括集合类的也不能直接存储


存储集合 我们需要在创建一个继承自RLMObject的模型

@interface testTwoModel : RLMObject

@property NSString *videoName;

@end

// 这个testTwoModel 是 专门转换数组的     返回来的字符串类边先转换成该模型


// 这个集合有个要求  里边存储的属性必须是继承自RLMObject类型的
@property RLMArray<testTwoModel *><testTwoModel> *arrays;

3 模型与模型之间的关系

(1) 对一关系

当一个对象持有另外一个对象时, 比如人有一辆车

@interface Person : RLMObject
@property int num;
@property NSString *name;
// 对一关系
@property Car *car;
(2) 对多关系

当一个对象持有另外一个对象时, 人有车 但是 不是只有一辆 是有很多辆车 是一个集合
有两个规则是要遵守的

  1. 在Dog中, 遵循指定协议方法 RLM_ARRAY_TYPE(Dog)
    RLM_ARRAY_TYPE宏创建了一个协议,从而允许 RLMArray<Car *><Car> 语法的使用。
image.png

2 在Person中, 定义属性

@property (nonatomic, strong) RLMArray<Dog *><Dog> *dogs;

注意 虽然可以给 RLMArray 属性赋值为 nil,但是这仅用于“清空”数组,而不是用以移除数组。这意味着您总是可以向一个 RLMArray 属性中添加对象,即使其被置为了 nil。

    Person *p = [[Person alloc]init];
    p.num = 10;
    p.name = @"剃须刀";   
    Car *car1 = [Car new];
    car1.name = @"Smart";   
    Car *car2 = [Car new];
    car2.name = @"BMW";
    
    // 初始化的集合为nil 但是其内部已经帮我们做好了 我们可以这样直接添加
    [p.cars addObject:car1];
    [p.cars addObject:car2];
    
    RLMRealm *realm = [RLMRealm defaultRealm];
    
    [realm transactionWithBlock:^{       
        [realm addObject:p];
    }];
  
(3) 反向关系

人拥有汽车 汽车又拥有人

1 在car.h中定义属性

@property (readonly)RLMLinkingObjects *master;

2 在car.m 中实现协议方法 表明链接关系 即可  
+ (NSDictionary<NSString *,RLMPropertyDescriptor *> *)linkingObjectsProperties{
    return @{
             @"master":[RLMPropertyDescriptor descriptorWithClass:NSClassFromString(@"Person") propertyName:@"cars"]
             
             };
}
外界不需要单独给car的属性master复制 就可以得到master
如下
  Person *p = [Person allObjects].firstObject;
    NSLog(@"%@",p.cars.firstObject.master);
4 可空属性&默认值&忽略属性
(1) 默认情况下 属性值可以为空 如果强制要求每个属性非空,可以使用如下方法
//在.m 中实现这个方法  如果在此复制为nil 则会抛出异常
// 限制此属性不能为空  
+ (NSArray<NSString *> *)requiredProperties{
    return @[@"name"];
}

(2)给属性设置默认值
// 设置指定属性的默认值  
+ (NSDictionary *)defaultPropertyValues{
    return @{@"name":@"黑娃"};
}
(3)忽略属性不存储
//1  使用readonly修饰 就不会存储改属性
@property (readonly)NSString *time;

// 2  也可以实现下边的这个方法  也不会存储该数组中的属性
+ (NSArray *)ignoredProperties
{ 
  return @[@"time"];   
}

可以借助这几个 忽略属性 和只读属性 打造计算属性 完成集合 以及UIImage 对象的存储

5 通知

一般用于 realm 存储数据成功后 发送通知 刷新列表

//  必须要将token有强引用
@property(nonatomic, strong)RLMNotificationToken *token;

// 初始化
- (void)setUp {
  [super setUp];
  
  RLMRealm *realm = [RLMRealm defaultRealm];
  // 1   第一种方式添加通知  必须要将token有强引用
  self.token = [realm addNotificationBlock:^(RLMNotification  _Nonnull notification, RLMRealm * _Nonnull realm) {
      // 接收到通知后  可做刷新
  }];
}
  //  第二种方式添加通知  

 //  可以具体拿到更改了什么数据
  self.token2 = [results addNotificationBlock:^(RLMResults * _Nullable results, RLMCollectionChange * _Nullable change, NSError * _Nullable error) {
      NSLog(@"%@---%@---%@",results,change,error);
  }];



  //  2 存储成功后 会默认调用通知的方法
  NoticeModel *model = [[NoticeModel alloc]initWithValue:@[@10]];
  RLMRealm *realm = [RLMRealm defaultRealm];    
  [realm transactionWithBlock:^{
      [realm addObject:model];
  }];

// 释放
- (void)tearDown {
  // 3  停止token 相当去移除通知
  [self.token stop];
  
  [super tearDown];
}

6 Realm数据库

(1)用户机制

在我们实际开发中肯定会有很多的用户 所以不同的用户 。肯定要使用不同的数据库文件


//  调用此方法  配置数据库的名字
    [self setDefailtRealmForUser:@"tixvdao"];
    
//  然后存储的数据 都会在此数据库中
    RLMRealm *realm = [RLMRealm defaultRealm];
    Data *data = [Data new];
    data.a = 10;
    
    [realm transactionWithBlock:^{
        [realm addObject:data];
    }];
    
//  配置数据的名字
- (void)setDefaultRealmForUser:(NSString *)username {
  RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
  // 使用默认的目录,但是使用用户名来替换默认的文件名
  config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent]
                      URLByAppendingPathComponent:username]
                      URLByAppendingPathExtension:@"realm"];
  // 将这个配置应用到默认的 Realm 数据库当中
  [RLMRealmConfiguration setDefaultConfiguration:config];

(2) 只读方式打开数据库

    RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
    // 以只读模式打开文件 因为应用数据包并不可写
    config.readOnly = YES;
    
    [RLMRealmConfiguration setDefaultConfiguration:config];
    
    RLMRealm *realm = [RLMRealm defaultRealm];
    
    // 设置了只读属性后  就不能再添加或修改数据了  此步骤会报错
    Data *data = [Data new];
    data.a = 12;
    [realm transactionWithBlock:^{
        [realm addObject:data];
    }];

(3) 数据库文件删除

注意 需要删除数据库文件以及辅助文件

 //1 设置默认数据库的路径
    [self setDefailtRealmForUser:@"tixvdao"];
    
    NSFileManager *manager = [NSFileManager defaultManager];    
    RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
    
    NSArray <NSURL *>* realmFileURLs = @[
                                         config.fileURL,
                                         [config.fileURL URLByAppendingPathExtension:@"lock"],
                                         [config.fileURL URLByAppendingPathExtension:@"log_a"],
                                         [config.fileURL URLByAppendingPathExtension:@"log_b"],
                                         [config.fileURL URLByAppendingPathExtension:@"note"],
                                         ];
    
    for (NSURL *url in realmFileURLs) {
        NSError *error = nil;
        [manager removeItemAtURL:url error:&error];
    }


7 数据库迁移

适用于修改了数据模型的情况

(1) 数据结构的迁移

    // 正常的应该吧此数据库迁移放在appdelegate中  程序一启动就要做数据库的迁移
    
    // 1  获取默认配置
    RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
    
    // 2 设置新的架构版本  这个版本高于之前的所用的版本 如果之前没有设置版本 那版本号就是0
    
    int nerVersion = 1;
    
    config.schemaVersion = nerVersion;
    
    // 3 具体迁移  设置闭包 这个闭包将会在打开低于上边的版本号的realm 数据库的时候被自动调用
    config.migrationBlock = ^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) {
      
        // 目前我们还未进行数据迁移 因此oldSchemaVersion == 0
        
        if (oldSchemaVersion < nerVersion) {
            
            // 这里边什么都不需要做  realm会自行检测新增和需要移除的属性 然后自动更新硬盘上的数据库架构
            
        }
    };
    // 4 告诉realm 为默认的realm 数据库使用这个新的配置对象
    [RLMRealmConfiguration setDefaultConfiguration:config];
    
    //  现在我们已经告诉 了  realm 如何处理架构的变化  打开文件之后将会自动执行迁移
    //  如果需要立即迁移 只要访问数据后就会直接执行
    [RLMRealm defaultRealm];

(2) 数据迁移
    // 正常的应该吧此数据库迁移放在appdelegate中  程序一启动就要做数据库的迁移
    
    // 1  获取默认配置
    RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
    
    // 2 设置新的架构版本  这个版本高于之前的所用的版本 如果之前没有设置版本 那版本号就是0
    // 一定要记得每一次版本号叠加
    int nerVersion = 2;
    
    config.schemaVersion = nerVersion;
    
    // 3 具体迁移  设置闭包 这个闭包将会在打开低于上边的版本号的realm 数据库的时候被自动调用
    config.migrationBlock = ^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) {
      
        // 目前我们还未进行数据迁移 因此oldSchemaVersion == 0
        
        if (oldSchemaVersion < nerVersion) {
            //  (1)  数据结构迁移的话 这里什么都不需要做
            // 这里边什么都不需要做  realm会自行检测新增和需要移除的属性 然后自动更新硬盘上的数据库架构
            
            
            //  (2)  数据迁移的话 可以把原来的 值 合并到新的值
            // enumerateObjects:block: 方法遍历了存储在 Realm 文件中的每一个“Migretion”对象
            [migration enumerateObjects:@"Migretion" block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) {
                newObject[@"fullName"] = [NSString stringWithFormat:@"%@%@",oldObject[@"preName"],oldObject[@"lastName"]];
            }];
            
        }
    };
    
    // 4 告诉realm 为默认的realm 数据库使用这个新的配置对象
    [RLMRealmConfiguration setDefaultConfiguration:config];
    
    //  现在我们已经告诉 了  realm 如何处理架构的变化  打开文件之后将会自动执行迁移
    //  如果需要立即迁移 只要访问数据后就会直接执行
    [RLMRealm defaultRealm];
    



    // 1  preName和 lastName 字段都已经没有了 将之前的数据全部合并到新的fullName字段  
    Migretion *model = [[Migretion alloc]init];
//    
//    model.preName = @"aokamu";
//    model.lastName = @"tixvdao";
    
    model.fullName = @"tixvdao";
    
    RLMRealm *realm = [RLMRealm defaultRealm];
    
    [realm transactionWithBlock:^{
        [realm addObject:model];
    }];
    
(3) 属性重命名
            //  3  更高属性名  这中方法性能不好
//            [migration enumerateObjects:@"Migretion" block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) {
//                newObject[@"fullTitle"] = oldObject[@"fullName"];
//            }];
            
            // 更名属性 特有的额方法
            [migration renamePropertyForClass:@"Migretion" oldName:@"fullName" newName:@"fullTitle"];
(4) 多版本增量式迁移

多版本增量式迁移的意思就是 版本升级 会有不同的情况
V0 -> V1
V1 -> V2
V0 -> V2

//V0
@property int age;
@property NSString *preName;
@property NSString *lastName;

//V1
@property int age;
@property NSString *fullName;

// V2
@property int age;
@property NSString *fullTitle;

// 迁移核心代码

  // 3 具体迁移  设置闭包 这个闭包将会在打开低于上边的版本号的realm 数据库的时候被自动调用
    config.migrationBlock = ^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) {
      
        // 目前我们还未进行数据迁移 因此oldSchemaVersion == 0
        
        
        if (oldSchemaVersion < 1) {
            //  (1)  数据结构迁移的话 这里什么都不需要做
            // 这里边什么都不需要做  realm会自行检测新增和需要移除的属性 然后自动更新硬盘上的数据库架构
            
        }
        
        
        if (oldSchemaVersion < 2) {
            //  (2)  数据迁移的话 可以把原来的 值 合并到新的值
            // enumerateObjects:block: 方法遍历了存储在 Realm 文件中的每一个“Migretion”对象
//            [migration enumerateObjects:@"Migretion" block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) {
//                newObject[@"fullName"] = [NSString stringWithFormat:@"%@%@",oldObject[@"preName"],oldObject[@"lastName"]];
//            }];

        }
       
        if (oldSchemaVersion < 3) {        
            //  3  更高属性名  这中方法性能不好
//            [migration enumerateObjects:@"Migretion" block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) {
//                newObject[@"fullTitle"] = oldObject[@"fullName"];
//            }];
            // 更名属性 特有的额方法
            [migration renamePropertyForClass:@"Migretion" oldName:@"fullName" newName:@"fullTitle"];            
        }   
    };

《经过多次更新终于完结...》

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

推荐阅读更多精彩内容