BigApp2.0组件开发文档

BigApp2.0组件开发文档

1. 简介

BigApp2.0的开发使用了组件化结构设计。组件之间相互独立,不存在依赖关系,通过框架层的路由组件进行数据通信降低耦合度。组件化使得不同团队并行开发各业务,极大提高迭代效率。本文介绍了组件的开发、调试、测试、发布等一系列流程。

参照本文档进行开发时,我们已假定你有一定的使用Swift或OC语言进行iOS开发的经验和CocoaPods的使用经验。

iOS组件入口类必须继承ACComponentBase类。iOS组件入口类类名需要配置到component.xml中。

2. 开发环境

  • OS X 10.11.5+
  • Xcode 8.0+
  • iOS组件开发基础包

3. 准备组件开发

3.1 创建动态库工程

本文以创建和开发ACComponentItemMessage组件为例,参照本文开发时,创建的组件名称请根据实际情况命名。

  • 打开Xcode,在菜单栏中选择 File - New - Project...

  • 选择 iOS - Framework & Library - Cocoa Touch Framework

  • 填入组件基本信息, Product Name填ACComponentItemMessage,点击Next

  • 选择动态库工程的保存地址,点击Create,建立一个动态库工程(建议把所有组件和调试用的主工程放在同一个目录下,方便后面调试)
    [图片上传失败...(image-e54760-1561687326420)]

  • 编辑target工程Build Settings,将Mach-o Type 修改为Dynamic Library(Swift组件只支持动态库,所以选择Dynamic Library,纯OC组件可以改为Static Library,也就是静态库)
    [图片上传失败...(image-3d882d-1561687326420)]

  • 编辑target工程Build Settings,将Enable Bitcode 修改为No
    [图片上传失败...(image-a72ab7-1561687326420)]

  • 打开iOS组件开发基础包,把ACComponentItemDemo.podspecpod_spec_lintpush_specreplace_tag.gitignore文件拷贝到动态库工程根目录下
    [图片上传失败...(image-9570c4-1561687326420)]

  • 用文本编辑器分别打开pod_spec_lintpush_spec脚本,把里面的ACComponentItemDemo字段替换为自己的组件名

  • 修改ACComponentItemDemo.podspec文件,如下图
    [图片上传失败...(image-24e8cf-1561687326420)]

3.2 创建索引

  • 把iOS组件开发基础包中的ACComponentItemDemo文件夹拷贝到linewell-specs索引库的本地仓库中,文件夹名ACComponentItemDemo修改为自己创建的组件名,文件夹路径下的ACComponentItemDemo.podspec文件替换为自己组件工程目录下的podspec文件

3.3 组件提交

  • 联系GitLab管理员创建完该组件的远程仓库后,把组件工程提交到远程仓库中
  • 把linewell-specs索引库中该组件部分提交到远程仓库中

3.4 组件调试

  • 把主工程BigApp从远程仓库克隆至创建的组件同一目录下
    [图片上传失败...(image-965461-1561687326420)]
  • 修改主工程的Podfile文件,在Podfile文件添加本组件的本地引用,如图
    [图片上传失败...(image-46c5a6-1561687326420)]
  • 双击执行主工程目录下的bootstrap脚本,完成后主工程会自动打开
  • 在主工程的component.xml文件中添加该组件,这时就可以在Development Pods中进行组件的开发了,如图
    [图片上传失败...(image-1b2c2c-1561687326420)]

4. 组件开发

4.1 编写组件入口类

  • 统一规定,组件入口类与组件名相同
  • 在ACComponentItemMessage工程中创建组件入口类ACComponentItemMessage,ACComponentItemMessage中引入<ACRouterKit/ACRouterKit.h> ,并使此类继承ACComponentBase
  • ACComponentItemMessage类中实现生命周期方法:
//objc
- (instancetype)initWithApp:(id<ACComponentBaseProtocol>)app {
    self = [super initWithApp:app];
    if (self) {
        NSLog(@"BigApp-->ACComponentItemMessage-->initWithApp");
    }
    return self;
}

//swift
    override public init!() {
        super.init()
    }
    
    override public init!(app: ACComponentBaseProtocol!) {
        super.init(app: app)
        LogPrint("BigApp-->ACComponentItemMessage-->initWithApp")
    }
    

组件中类的命名规则

  • 组件的入口类必须命名为ACComponent开头的类名。
  • 组件中其他的类无命名限制,但建议增加独特的前缀,以避免和引擎以及其他组件中的类产生类名冲突,导致打包失败。
ACComponentBase简介
  • ACComponentBase是组件入口的基类,所有的组件入口类都必须继承自此类。
  • ACComponentBase拥有1个实例变量和1个实例方法
  • 实例变量appContext是一个弱引用,指向ACComponentBaseProtocol协议,该协议包含系统的UIApplication *applicationContext UIWindow *mainWindow 实例对象;
  • 实例方法initWithApp:是默认的初始化方法。
    • 程序启动时会调用组件初始化方法
    • 组件入口子类可以覆写此方法进行自定义初始化设置,但必须调用父类的此方法。


      image
ACRouter路由简介
  • ACRouter 是由ACRouterKit.framework提供的路由工具,是组件之间的通讯的桥梁,实现了组件之间不需要引用就可以交互的功能。
  • ACRouter拥有一个实例方法和一个类方法。
  • 类方法+ (instancetype) route;为ACRouter单例方法,创建路由必须通过该方法。
  • 实例方法- (id)openURL:(NSString *)URL toHandler:(ACRouterHandler)handler; 组件之间通过该方法相互通讯。

4.2 编写组件方法并调用

本小节示范了如何让一个ACComponentDemo2组件任意类去调用ACComponentDemo1组件入口类暴露的一个方法showAlert:并回调结果

  • 在ACComponentDemo1类中实现一个方法showAlert: :
//objective-c
-(void)showAlert:(NSDictionary *)params {
    
    ACRouterHandler handle = [params objectForKey:@"routehandler"];
    
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:params[@"title"] message:params[@"message"] preferredStyle:UIAlertControllerStyleAlert];
    
    UIAlertAction *ation1 = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
        if (handle) {
            handle(1,@"你点击了确定",@{@"info":@"确定"});
        }
        
    }];
    
    UIAlertAction *ation2 = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        if (handle) {
            handle(0,@"你点击了确定",@{@"info":@"取消"});
        }
    }];
    
    [alert addAction:ation1];
    [alert addAction:ation2];
    
    [self.appContext.mainWindow.rootViewController presentViewController:alert animated:YES completion:nil];

}
  • 在ACComponentSwift组件中调用ACComponentMyView组件暴露的provideAnButton方法并回调结果 :
//swift

       let routehandler = {(code:Int32, msg:Optional<String>, data:Optional<Dictionary<AnyHashable, Any>>) -> ()in
            let alert = UIAlertView.init(title: "提示", message: "swift已点击", delegate: self, cancelButtonTitle: "确定");
            alert.show();
        }
        var params : [String : AnyObject] = [String : AnyObject]()
        params["x"] = "50" as AnyObject
        params["y"] = "200" as AnyObject
        params["width"] = "200" as AnyObject
        params["height"] = "80" as AnyObject
        params["content"] = "我是Swift按钮" as AnyObject
        let myBtn = ACRouter.route().performTarget("ACComponentMyView", action:"provideAnButton", params:params,toHandler:routehandler);

  • 在ACComponentSwift组件中暴露一个方法blockTest并执行回调
//swift

    @objc func blockTest(_ params:NSDictionary) {
        
        let handler = params.value(forKey: "routehandler");
        ACRouter.route().realizeCode(1, msg: "swift11", data: nil, block:handler);
        
    };

组件入口类中实现供其他组件调用的方法的注意事项

1 方法只有一个入参 `(NSDictionary *)params`
2 暴露该组件入口类头文件中需要有必要的入参注释,如下:
/**
 入参params字典中需包含
 1 title 弹窗名称
 2 message 弹窗信息
 */
 -(void)showAlert:(NSDictionary *)params;

3 ACRouterHandler 为默认回调,在方法实现中需要实现回调可用`[params objectForKey:@"routehandler"]`取得,组件中回调均需要遵守回调格式` typedef void(^ACRouterHandler)(int code, NSString *msg ,NSDictionary *data);`
* int code 区别回调类型
* NSString *msg 回调信息 
* NSDictionary *data 回调数据

组件方法调用基本规则

  • 组件之间的通讯必须通过调用ACRouterKit中的路由方法
    -(id)openURL:(NSString *)URL toHandler:(ACRouterHandler)handler;
    • 入参(NSString *)URL 是由需要调用组件的类名,调用组件方法的入参注释拼接而成,示例如图:

      image

    • 入参(ACRouterHandler)handler 默认回调参数

组件方法调用示例

  • 在ACComponentDemo2工程中ViewController1类通过路由组件ACRouter调用ACComponentDemo1工程中组件入口类ACComponentDemoHome暴露出的方法-(void)showAlert:(NSDictionary *)params;,如下:
    //objective-c 
    [[ACRouter route] openURL:@"ACComponentDemoHome://showAlert?title=提示&message=来自Demo2" toHandler:^(int code, NSString *msg, NSDictionary *data) {
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:msg delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
            [alert show];
    }];

4.3 通过组件构建简单应用

  • 本小节示范了如何通过组件构建具有tabbar的简单应用

  • 在ACComponentDemo1工程中组件入口类ACComponentDemoHome类中,在路由分发到组件类系统ApplicationDelegate分发事件中初始化界面tabbar,调用ACComponentDemoView提供的路由方法如图:

    image

  • 在ACComponentDemo2工程中组件入口类ACComponentDemoView类中,提供tabbar子视图的方法如图:

    image

5. 生成组件包

本章讲述组件包的生成,目前已经可以通过主工程中的frameworks_build脚本批量生成,无需按下面的步骤逐步操作

5.1 编译组件静态库.framework文件

  • 关闭ACMobiNativeMain调试工程,然后打开ACComponentDemo1.xcodeproj。
  • 选择 ACComponentDemo1 - Generic iOS Device
  • 点击Product-Build,生成组件的.framework文件ACComponentDemo1.framework
  • 生成目录为ACComponentnDemo1/build/ACComponentDemo1。
  • 新建component.xml和info.xml文件。
image

5.2 编辑component.xml

  • component.xml记录了组件的入口类信息。其中plugin name是组件的入口类ACComponentDemoHome名称。
  • 最终完成的component.xml示例如下

<?xml version="1.0" encoding="utf-8" ?>
<acplugins>
    <plugin name="ACComponentDemoHome"></plugin>
</acplugins>


5.3 编辑info.xml

  • info.xml主要记录了组件的版本信息
  • 由于组件也是插件的一种形式,因此info.xml格式与插件的info.xml格式基本一致
  • 示例模板如下
<?xml version="1.0" encoding="utf-8" ?>
<acplugins>
      <plugin
        acName="ACComponentDemoHome" version="1.0.1" build="1"  desc='组件示例' type="compoment">
        </plugin>
</acplugins>

其中acName替换成组件入口类对象名。x替换成当前组件的版本号(非负整数)

  • 然后向plugin节点中加入各个版本的简介,这些简介以倒序加入,由一个<info>节点和多个(可以为0个)<build>节点构成。
    • <info>节点记录了当前版本的简介
    • <build>节点记录了历史版本的简介
    • <desc>节点记录了组件的简介
    • `<type="compoment">节点说明该库为组件
    • 当组件版本更新时,应该将当前的<info>节点改为<build>节点,同时在其之前添加新的<info>节点
  • 最终完成的info.xml范例如下
<?xml version="1.0" encoding="utf-8" ?>
<acplugins>
    <plugin
        acName="ACComponentDemoHome" version="1.0.1" build="1"  desc='组件示例' type="compoment">
        <info>1:版本更新记录</info>
        <build>0:iOS组件范例</build>
    </plugin>
</acplugins>

6. 其他开发说明

6.1 引入第三方库和bundle资源

  • 动态库形式的第三方库只需要在组件的podspec文件中添加dependency即可
  • 引入静态库第三库,需要先把真实文件放入组件工程路径下,再在组件的podspec文件中添加对应的subspec
    [图片上传失败...(image-4dc42b-1561687326420)]
  • 引入bundle,需要先把真实文件放入组件工程路径下,再在组件的podspec文件中添加resources
    [图片上传失败...(image-9ea3b5-1561687326420)]
  • bundle的swift调用方式
    [图片上传失败...(image-8b60ac-1561687326420)]
  • 注意:引用的第三方库和bundle放入本地,并在podspec文件中进行相应修改后,需要进行组件发布(见第10章节)和重新执行主工程中的bootstrap脚本,才能在主工程中进行调用的调试(引用的第三方动态库不需要放入本地)
  • 注意:静态库framework中如果没有modulemap,则swift组件无法直接调用
    [图片上传失败...(image-5adec-1561687326420)]

6.2 组件如何获取系统事件

6.2.1 ApplicationDelegate事件

路由会将大部分ApplicationDelegate事件分发到每个组件入口类,组件入口类用相应的类方法接收即可。目前组件入口类可供接收的类方法有:

+ (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
+ (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
+ (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err;
+ (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo;
+ (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
+ (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification;
+ (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url;
+ (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation;
+ (void)applicationWillResignActive:(UIApplication *)application;
+ (void)applicationDidBecomeActive:(UIApplication *)application;
+ (void)applicationDidEnterBackground:(UIApplication *)application;
+ (void)applicationWillEnterForeground:(UIApplication *)application;
+ (void)applicationWillTerminate:(UIApplication *)application;
+ (void)applicationDidReceiveMemoryWarning:(UIApplication *)application;
+ (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler;
+ (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler;
+ (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler;

//UNUserNotificationCenterDelegate方法(iOS 10+)
//注意此方法的completionHandler参数应为`UNNotificationPresentationOptions`
+ (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSUInteger))completionHandler;

+ (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler;

示例:

//objective-c

//ACComponentDemoHome.m中

static NSDictionary *AppLaunchOptions;

+ (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    NSLog(@"app launched");
    
    //存储launchOptions
    AppLaunchOptions = launchOptions;
    return YES;
}


//swift

//ACComponentSwift.swift中
@objc class func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    NSLog(@"app launched");
    return true
}

6.3.2 设置App启动页面

自定义组件中可以使用ApplicationDelegate系统事件设置app的入口界面,


image

*设置 [ACComponentMgr sharedComponentMgr].appDelegate.mainWindow.rootViewController 即可

7. 自定义组件View

自定义组件ACComponentMyView提供了自定义View的创建方法,和对外暴露的设置属性路由

7.1 自定义组件View开发

自定义组件view首先遵从组件的基本开发模式

7.2 获取自定义view实例对象

可以通过路由的方法直接获取自定义view实例对象

路由方法示例:


- (id)provideAnButton:(NSDictionary *)params {    
        CGFloat x = [params[@"x"] floatValue];
        CGFloat y = [params[@"y"] floatValue];
        CGFloat width = [params[@"width"] floatValue];
        CGFloat height = [params[@"height"] floatValue];
        NSString *content = params[@"content"];
        ACRouterHandler handler = [params objectForKey:@"routehandler"];
        ACMyButton *myBtn = [[ACMyButton alloc] initWithFrame:CGRectMake(x, y, width, height)];
        [myBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [myBtn setTitle:content forState:UIControlStateNormal];
        myBtn.handler = handler;
        return myBtn;
}

路由调用示例:

//objective-c

 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:1];
    params[@"x"] = @"50";
    params[@"y"] = @"200";
    params[@"width"] = @"200";
    params[@"height"] = @"80";
    params[@"content"] = @"我是按钮";
    params[@"routehandler"] = ^(int code, NSString *msg ,NSDictionary *data){
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:msg delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
        alert.delegate = self;
        [alert show];
    };
    
    UIView *myBtn = [[ACRouter route] performTarget:@"ACComponentMyView" action:@"provideAnButton" params:params];
    myBtn.tag = kBTNTAG;
    [self.view addSubview:myBtn];
    
    
//swift    
        let routehandler = {(code:Int32, msg:Optional<String>, data:Optional<Dictionary<AnyHashable, Any>>) -> ()in
            let alert = UIAlertView.init(title: "提示", message: "swift已点击", delegate: self, cancelButtonTitle: "确定");
            alert.show();
        }
        var params : [String : AnyObject] = [String : AnyObject]()
        params["x"] = "50" as AnyObject
        params["y"] = "200" as AnyObject
        params["width"] = "200" as AnyObject
        params["height"] = "80" as AnyObject
        params["content"] = "我是Swift按钮" as AnyObject
        let myBtn = ACRouter.route().performTarget("ACComponentMyView", action:"provideAnButton", params:params,toHandler:routehandler);

7.3 通过路由方法设置自定义view的属性

可以通过路由的方法直接设置自定义view的属性
路由方法示例:


-(void)setMyBtn:(NSDictionary *)params {
        ACMyButton *myBtn = params[@"myBtn"];
        NSString *content = params[@"content"];
        [myBtn setTitle:content forState:UIControlStateNormal];
    }

路由调用示例:


 UIView *myBtn = [self.view viewWithTag:kBTNTAG];
    
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:1];
    params[@"myBtn"] = myBtn;
    params[@"content"] = @"已点击";
    [[ACRouter route] performTarget:@"ACComponentMyView" action:@"setMyBtn" params:params];
    

8. 消息订阅和广播

实现消息的订阅和广播需要遵循一下步骤

8.1 注册消息

在组件的compoent.xml需要声明可被订阅的消息,如下


<?xml version="1.0" encoding="utf-8" ?>
<acplugins>
    <plugin name="ACComponentMyView">
        <method name="ACComponentMyViewSignal1"></method>
        <method name="ACComponentMyViewSignal2"></method>
        <method name="ACComponentMyViewSignal3"></method>
        <method name="ACComponentMyViewSignal4"></method>
    </plugin>
</acplugins>
    

8.2 订阅消息

通过路由暴露出的方法订阅消息,


/**
 监听模块信号
 */
- (void)subscriptionModule:(NSString *)moduleName Signal:(NSString *)signal Listener:(id)listenerObject withAction:(SEL)listenerAction;
    

其中moduleName为需要订阅对象的名称;
signal为订阅消息的名称;
Listener为订阅者;
action为订阅触发的方法;

示例如下


- (void)subscription {
    [[ACRouter route] subscriptionModule:@"ACComponentMyView" Signal:@"ACComponentMyViewSignal1" Listener:self withAction:@selector(alertMsg:)];
}

- (void)alertMsg:(id)data {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:data[@"msg"] delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
    [alert show];
}
    

8.3 发布消息

组件通过路由方法发布消息


/**
 发布模块信号
 */
- (void)publishModule:(NSString *)publishName withSignal:(NSString *)signal params:(id)data;
    

publishName为发布消息对象的名称;
signal为消息的名称;
params为传参;

示例如下:


[[ACRouter route] publishModule:@"ACComponentMyView" withSignal:@"ACComponentMyViewSignal1" params:@{@"msg":@"btn已修改"}];

8.4其他

移除消息订阅


/**
 移除信号
 */
- (void)removeSubscriptionModule:(NSString *)moduleName Signal:(NSString *)signal Listener:(id)listenerObject;
    

moduleName为订阅对象的名称;
signal为消息的名称;
listener为订阅者;

示例如下:


[[ACRouter route] removeSubscriptionModule:@"ACComponentMyView" Signal:@"ACComponentMyViewSignal1" Listener:self];

9. swift 组件开发需要注意

如果使用siwft 组件开发,务必遵循以下几点

  • 组件库必须使用动态库
image
  • 组件库名和组件类名需要保持一致
image
  • 组件类必须加上public字段,组件暴露的方法必须加上@objc字段
image

10. 组件发布(pod模式)

进行本章节操作时,我们已假定你已按照第3和第4章节完成组件的创建

完成组件发布后,其他开发者才能通过pod集成该组件

  • 确认组件工程本地仓库中需要提交的代码已提交完成,其中的podspec文件已是最新
  • 确认pod_spec_lintpush_spec脚本中的组件名是自己的组件名
  • 双击执行pod_spec_lint脚本(验证podspec文件中的代码是否错误),确认完成无报错
  • 双击执行push_spec脚本(将podspec文件推送到索引库)
  • 双击执行replace_tag脚本(推送新的2.0.0标签到远程仓库)
  • 以上操作完成没有问题后,开发者在主工程的Podfile文件中添加pod 'ACComponentItemDemo', '2.0.0'(ACComponentItemDemo替换为自己的组件),执行bootstrap脚本即可把组件集成到主工程中

注意:若执行bootstrap脚本时下载下来的组件代码仍不是组件最新代码,可以用文本编辑器打开bootstrap脚本文件,在pod install的命令之前写入pod cache clean ACComponentItemDemo代码,重新执行bootstrap脚本即可清除对应缓存并下载最新代码

11. 更新历史

最新版本:1.0.0

最近更新时间:20190628

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

推荐阅读更多精彩内容