翻译|react-native-code-push热更新部署

英语太烂,菜鸟水平,如有不妥之处,请各位大神指正

  • 工作原理


React Native应用程序由JavaScript文件和附带的图像组成,它们由打包程序捆绑在一起,并作为平台特定的二进制文件(即.ipa或.apk文件)的一部分分发。 当应用程序发布后,更新JavaScript代码(例如进行错误修复,添加新功能)或图像资源时,需要重新编译和重新分发整个二进制文件。(简单说就是React Native App如果不使用热更新的话,和原生App发布没什么区别,同样需要提交应用商店审核然后等待发布)

通过使用CodePush 插件,可以让你的APP 时时和CodePush服务的数据保持同步更新,实现热更新,而不用再去繁琐的把应用提交到应用商店,等待审核。这样可以让你的APP更快的更新,获得最新的体验,实现双赢。

为了确保你的APP用户始终能正常使用您的应用程序,CodePush插件将会保留你的APP最新版本以前的副本,以便在您推送的最新版本发生崩溃时,可以自动回滚到上一个正常版本。

注意:如果你的产品修改了 原生文件代码(例如修改AppDelegate.m/MainActivity.java等原生文件,或者添加新插件)不能通过CodePush进行更新,只能通过提交相应的应用商店更新。

  • 支持平台


  • iOS(7+)
  • Anddroid (4.1+)
  • Windows(UWP)

我们会尽最大努力去维护CodePush和React Native版本的兼容性,但由于各个平台的升级,会更改一些特性,你可能需要使用特定版本的CodePush插件,以支持你正在使用的ReactNative版本。下面的表格中列出了CodePush版本正式支持的ReactNative版本:

ReactNative 版本 支持CodePush版本
<0.14 不支持
v0.14 v1.3.0(introduced Android support(支持安卓))
v0.15-v0.18 v1.4.0 - v1.6.0(introduced iOS asset support (不太懂,应该是支持iOS原生图片添加)
v0.19-v0.28 v1.7.0+(introduced Android asset support)
v0.29-v0.30 v1.13.0+(RN refactored native hosting code) (这个意思应该是改变了这个 RCTBundleURLProvider 写法)
v0.31-v0.33 v1.14.6+(RN refactored native hosting code)
v0.34-v0.35 v1.15.0+(RN refactored native hosting code)
v0.36-v0.39 v1.16.0+(RN refactored resume handler) (这个是什么鬼?👻 )
v0.40-v0.41 v1.17.0+(RN refactored iOS header files)
v0.42+ 适配中
  • 已经支持的组件


当React Native 引用本地图片时(例如:require("./foo.png")),支持CodePush热更新,以下列表是支持CodePush热更新的React Native组件

组件 属性
Image source
MapView.Marker(Requiresreact-native-maps >=O.3.2) image
ProgressViewIOS progressImage, trackImage
TabBarIOS.Item icon, selectedIcon
ToolbarAndroid(React Native 0.21.0+) actions[].icon, logo, overflowIcon

以下列表的组件的某些属性目前还不支持CodePush更新

组件 属性
SliderIOS maximumTrackImage, minimumTrackImage, thumbImage, trackImage
Video source
  • 开始入门


CodePush是微软提供的一套用于 React Native 和 Cordova 应用的热更新服务。CodePush 是给 React Native 和 Cordova 开发者提供移动应用直接部署更新给用户设备的一项云服务。CodePush 作为一个中央仓库,开发者可以推送更新 (JS, HTML, CSS and images)到CodePush,应用可以从客户端 SDK 里面查询更新。CodePush 可以让应用有更多的可确定性,也可以让你直接接触用户群。在修复一些小问题和添加新特性的时候,不需要经过二进制打包,可以直接推送代码进行实时更新。

  • iOS

1. 安装 CodePush CLI

您可以使用基于NodeJSCLI来管理您的CodePush帐户。 要安装它,请打开命令提示符或终端,然后键入

npm install -g code-push-cli //安装客户端

code-push -v                 //可以查看版本

注意:在OSX和Linux上,您可能需要使用sudo对此命令进行前缀

2. 创建一个 CodePush 账号

在使用发布更新之前,您首先需要创建一个CodePush账号,使用以下命令:

code-push register     //注册账号 授权登录并得到access key

会打开如下注册页面让你选择授权账号


注册界面
注册界面

授权通过之后,CodePush会告诉你“access key”,复制此key到终端即可完成注册。(access key每次都不一样,不必记住)


access key
access key

登录

code-push login //登录,登陆成功后,你的session文件将会写在 /Users/你的用户名/.code-push.config。

//其他一些命令
code-push logout //注销

code-push access-key ls //列出登陆的token

code-push access-key rm <accessKye> //删除某个 access-key
登录界面输入access key
登录界面输入access key

3.使用服务注册你的APP

//添加一个APP
code-push app add [app名称]

//移除
code-push app remove [app名称]

//重命名chong'ming
code-push app rename [app名称]

//列出账号下的所有app
code-push app list 

//把app的所有权转移到另外一个账号
code-push app transfer 

注意:一个app名字对应两个部署环境:生产环境Production 、模拟或演示环境 Staging
//可以使用如下命令查看
//查询部署环境的key也是一个app对应两个:(Deployment Key)
code-push deployment ls <appName> -k

查看部署的key

4.在你的APP里配置code-push SDK

  • 进入到你的项目目录下,在终端里运行
    npm install --save react-native-code-push@latest
    安装code-push组件

  • 与所有其他React Native第三方组件一样,iOS和Android的集成方式不同,因此根据您平台执行以下设置步骤。 请注意,如果您要同时配置这两个平台,建议为每个平台创建单独的CodePush应用程序。

iOS 配置


一旦你获得了CodePush插件,你需要将它集成到React Native应用程序的Xcode项目并正确配置。 为此,请执行以下步骤:
为了迎合尽可能多的开发人员的偏好,CodePush支持三种安装方式:

1. RNPM安装 - React Native包管理器 (RNPM) 是一个非常棒的工具,它为React Native插件提供了最简单的安装体验。 我们推荐这种方法。

(1)从React Native v0.27开始,rnpm链接已经合并到React Native CLI中。运行以下命令:

react-native link react-native-code-push

如果您的应用程序使用的版本低于v0.27的React Native,请运行以下命令:

rnpm link react-native-code-push

注意:如果您尚未安装RNPM,可以通过简单地运行npm i -g rnpm安装rnpm然后再执行上述命令。

就是这样!!这就是RNPM?哈哈😆

(2) 系统将提示您输入要使用的部署密钥。 如果你还没有它,你可以通过运行code-push deployment ls <appName> -k来检索这个值,或者你可以选择忽略它(直接按<ENTER>),并在以后添加它。 开始吧,我们建议您先使用您的Staging部署密钥进行测试。

2. CocoaPods安装 - 如果你在iOS应用里嵌入使用React Native,或者就是任性,喜欢使用CocoaPods,那么建议使用我们插件中提供的Podspec文件.

(1)把CodePush依赖添加到Podfile里,并指向NPM的安装路径

pod 'CodePush', :path => '../node_modules/react-native-code-push'

(2)运行

pod install
3. "手动安装" -如果你不想依赖任何额外的工具,你可以使用这个方法.

(1)打开你的APP的Xcode工程
(2)点击Libraries文件夹右键‘Add Files to ....’,在node_modules里找到‘CodePush.xcodeproj’

(3)把.a文件从Libraries/CodePush.xcodeproj/Products拖拽到Link Binary With Libraries选项


(4)选择“Link Binary With Libraries”列表下方的加号,添加libz.tbd


(5)在项目配置的“"Build Settings"”选项卡下,找到“Header Search Paths”。 添加路径$(SRCROOT)/../node_modules/react-native-code-push/ios并在下拉菜单中选择“recursive”。

(6)插件配置

注意:如果你使用RNPM 或者 react-native link自动配置了组件,这一步已经完成,你可以跳过这一步了!!!

1.打开AppDelegate.m文件,导入CodePush头文件:
#import "CodePush.h"
2.将

jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];

替换为

#ifdef DEBUG 
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
#else 
jsCodeLocation = [CodePush bundleURL];
#endif

3.在Info.plist里添加CodePushDeploymentKey字段,填入Deployment Key

codepush02.png

通过code-push deployment ls <appName> -k查询Deployment Key

codepush02.png
codepush02.png

4.在Info.plist中将Bundle versions string, short的值修改为1.0.0

iOS里的配置到此就完成了

Andriod 配置


注意:如果你安装的是React Native 0.29-0.32版本,我们建议按照手动步骤安装

1.RNMP

(1)从React Native v0.27开始,rnpm链接已经合并到React Native CLI中。运行一下命令:

react-native link react-native-code-push

如果您的应用程序使用的版本低于v0.27的React Native,请运行以下命令:

rnpm link react-native-code-push

注意:如果您尚未安装RNPM,可以通过简单地运行npm i -g rnpm安装rnpm然后再执行上述命令。

(2)手动配置

1.在您的android / settings.gradle文件中,添加以下代码:

include ':app', ':react-native-code-push'
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')

2.在你的android / app / build.gradle文件中,添加:react-native-code-push依赖:

...dependencies { ... compile project(':react-native-code-push')}

3.在你的android / app / build.gradle文件中,添加codepush.gradle文件路径

...
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"
...

4.修改MainApplication.java文件

React Native >= v0.29

...
// 1. Import the plugin class.

import com.microsoft.codepush.react.CodePush;

public class MainApplication extends Application implements ReactApplication { 
    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 
        ... 
        // 2. Override the getJSBundleFile method in order to let 
        // the CodePush runtime determine where to get the JS 
        // bundle location from on each app start 
        @Override 
        protected String getJSBundleFile() { 
              return CodePush.getJSBundleFile(); 
        } 

        @Override 
        protected List<ReactPackage> getPackages() { 
              // 3. Instantiate an instance of the CodePush runtime and add it to the list of 
              // existing packages, specifying the right deployment key. If you don't already 
              // have it, you can run "code-push deployment ls <appName> -k" to retrieve your key. 
              return Arrays.<ReactPackage>asList( 
                    new MainReactPackage(), 
                    new CodePush("deployment-key-here", MainApplication.this, BuildConfig.DEBUG) 
              ); 
        } 
    };
}

For React Native v0.19 - v0.28

...
// 1. Import the plugin class (if you used RNPM to install the plugin, this
// should already be done for you automatically so you can skip this step).
import com.microsoft.codepush.react.CodePush;

public class MainActivity extends ReactActivity { 
    // 2. Override the getJSBundleFile method in order to let 
    // the CodePush runtime determine where to get the JS 
    // bundle location from on each app start 
    @Override 
    protected String getJSBundleFile() { 
        return CodePush.getJSBundleFile(); 
    } 

    @Override 
    protected List<ReactPackage> getPackages() { 
        // 3. Instantiate an instance of the CodePush runtime and add it to the list of 
        // existing packages, specifying the right deployment key. If you don't already 
        // have it, you can run "code-push deployment ls <appName> -k" to retrieve your key. 
        return Arrays.<ReactPackage>asList( 
            new MainReactPackage(), 
            new CodePush("deployment-key-here", this, BuildConfig.DEBUG)        
        ); 
    } 
    ...
}

这一部分不太懂(安卓用户自行翻译吧)

Background React Instances

This section is only necessary if you're * explicitly * launching a React Native instance without an Activity
(for example, from within a native push notification receiver). For these situations, CodePush must be told how to find your React Native instance.

In order to update/restart your React Native instance, CodePush must be configured with a ReactInstanceHolder
before attempting to restart an instance in the background. This is usually done in your Application
implementation.

For React Native >= v0.29
Update the MainApplication.java file to use CodePush via the following changes:

...
// 1. Declare your ReactNativeHost to extend ReactInstanceHolder. ReactInstanceHolder is a subset of ReactNativeHost, so no additional implementation is needed.
import com.microsoft.codepush.react.ReactInstanceHolder;

public class MyReactNativeHost extends ReactNativeHost implements ReactInstanceHolder { 
    // ... usual overrides
}

// 2. Provide your ReactNativeHost to CodePush.
public class MainApplication extends Application implements ReactApplication { 
    private final MyReactNativeHost mReactNativeHost = new MyReactNativeHost(this); 

    @Override 
    public void onCreate() { 
      CodePush.setReactInstanceHolder(mReactNativeHost); 
      super.onCreate(); 
    }
}

For React Native v0.19 - v0.28

Before v0.29, React Native did not provide a ReactNativeHost
abstraction. If you're launching a background instance, you'll likely have built your own, which should now implement ReactInstanceHolder
. Once that's done:

// 1. Provide your ReactInstanceHolder to CodePush.

public class MainApplication extends Application { 

    @Override 
    public void onCreate() { 
        // ... initialize your instance holder 
        CodePush.setReactInstanceHolder(myInstanceHolder); 
        super.onCreate(); 
    }
}

In order to effectively make use of the Stagingand Productiondeployments that were created along with your CodePush app, refer to the multi-deployment testing docs below before actually moving your app's usage of CodePush into production.

在项目中使用CodePush


在项目里使用CodePush时可以设置以下功能:
1、何时更新,多长时间检查一次更新。比如:应用启动后,设置检查更新按钮,以及固定间隔周期检查更新

2、当更新完成后,怎样展示给用户

最简单的更新方式是:在你的项目根组件处设置一下两个选项之一:

1.使用codepush组件去 包裹你的 根组件

//在js文件中引入react-native-code-push
import codePush from "react-native-code-push";

class MyApp extends Component {

}

MyApp = codePush(MyApp);

2.ES7语法
此方法需要安装和使用 babel-preset-react-native-stage-0.

import codePush from "react-native-code-push";

@codePush

class MyApp extends Component {
}
更新方式

默认情况下,CodePush将在应用每次启动时检查更新,如果有可用的更新,它将被静默下载,并在应用下次重新启动时安装

如果你希望你的应用程序更快的发现更新,你还可以选择每次应用程序从后台恢复时与CodePush服务器同步。

//每次应用程序从后台恢复时与CodePush服务器同步
let codePushOptions = { checkFrequency: codePush.CheckFrequency.ON_APP_RESUME };

class MyApp extends Component {

}

MyApp = codePush(codePushOptions)(MyApp);

如果你希望当你的App启动后在后台进行静默更新,可以直接调用 CodePush.sync()
这个方法

import codePush from "react-native-code-push";

//在componentDidMount调用sync方法,当你的App启动的时候会在后台更新
componentDidMount(){
    codePush.sync();
}

class MyApp extends Component {

}

MyApp = codePush(MyApp);

如果你想可控的进行检查更新(例如添加检查按钮,或者计时器间隔),你可以调用 CodePush.sync()
这个方法,添加到你的控制选项里。

//进行可控的手动检查更新模式
let codePushOptions = { checkFrequency: codePush.CheckFrequency.MANUAL };

class MyApp extends Component { 
    onButtonPress() { 
        codePush.sync({ 
            updateDialog: true, 
            installMode: codePush.InstallMode.IMMEDIATE 
        }); 
    } 

    render() { 
        <View> 
            <TouchableOpacity onPress={this.onButtonPress}> 
                <Text>Check for updates</Text> 
            </TouchableOpacity> 
        </View> 
    }
}

MyApp = codePush(codePushOptions)(MyApp);

如果在更新时要提示给用户去安装更新,请进行相关的配置(例如,点击立即更新后,会强制立即重新启动),详情请参阅 codePush() API

注意:如果你正在使用 Redux 或者 Redux Saga, 你可以使用react-native-code-push-saga 组件,它将会有一个更简单的方式去调用同步更新

注意:虽然苹果公司允许使用 js进行热更新,但是弹出更新的提示框是违反苹果的政策的,因此,我们建议在提交苹果审核时,不启用 更新提示,而谷歌商店可以进行更新提示

5.发布APP更新

在你的应用发布以后,当你利用CodePush推送发布你的下一个版本时,最简单(推荐)的方法是利用一下命令去部署,它将自动绑定你的js文件和asset 然后更新到你的CodePush服务器上。

这个命令只需要两个参数,你的 应用程序名称和 发布平台(iOS或Android)

code-push release-react <appname> <platform>

code-push release-react  MyApp-iOS ios

code-push release-react  MyApp-Android android

release-react命令启用了这样一个简单的工作流程,因为它提供了许多明智的默认值(例如,生成发布包,假设您的应用程序的入口文件在iOS上是index.ios.js或index.js)。 但是,所有这些默认值都可以进行自定义,以允许根据需要增加灵活性,这使得它非常适合大多数情况。

#发布带有日志说明的强制更新
code-push release-react MyApp-iOS ios -m --description "Modified the header color"

#发布入口文件名字命名不标准的更新,
#并捕获由react-native bundle生成的源映射文件
code-push release-react MyApp-iOS ios --entryFile MyApp.js --sourcemapOutput ../maps/MyApp.map

#发布一个安卓开发版 更新,给总用户的1/4用户?
code-push release-react MyApp-Android android --rollout 25% --dev true

#发布给 运行1.1.*版本的用户,而不是将更新限制在build.gradle文件中的完全版本名称
code-push release-react MyApp-Android android --targetBinaryVersion "~1.1.0"

Multi-Deployment Testing 多环境部署测试的步骤

StagingProduction 部署环境,可以让你制定一个有效的发布流程
1.使用code-push release-react 命令去发布一个CodePush更新到你的Staging 部署环境下。
2.运行 你的stagging或测试版本应用,同步服务器的更新,验证其是否正常工作
3.使用code-push promote命令 将测试版本 从Stagging 升级到Production
4.运行 你的production或发布版本应用,同步服务器的更新,验证其是否正常工作

注意:更给力的是,你可以选择“分阶段推送”作为第3步的一部分,以减轻额外的风险,因此,你可以先向一定百分比的用户推送更新,(例如:code-push promote <APP_NAME> Staging Production -r 20%)然后查看在一定时间内是否发生崩溃,和客户反馈,再根据情况去进行整体推送code-push patch <APP_NAME> Production -r 100%

You'll notice that the above steps refer to a "staging build" and "production build" of your app. If your build process already generates distinct binaries per "environment", then you don't need to read any further, since swapping out CodePush deployment keys is just like handling environment-specific config for any other service your app uses (e.g. Facebook). However, if you're looking for examples on how to setup your build process to accommodate this, then refer to the following sections, depending on the platform(s) your app is targeting.

Android部署

Android 的Android Gradle 插件允许你为每个 build type (eg: debug, release)自定义配置,所以你可以轻松的使用Staging 部署密钥配置你的debug环境,用production 部署密钥配置你的release环境。

按一下步骤进行设置:
1.打开你应用的 build.gradle 文件(eg: android/app/build.gradle)
2.在 {buildTypes{}}部分,添加debug和release 的buildConfigField 条目,

android { 
    ... 
    buildTypes { 
        debug { 
            ... 
            buildConfigField "String", "CODEPUSH_KEY", '"<INSERT_STAGING_KEY>"' 
            ... 
        } 

        release { 
            ... 
            buildConfigField "String", "CODEPUSH_KEY", '"<INSERT_PRODUCTION_KEY>"' 
            ... 
        } 
    } 
    ...
}

For React Native >= v0.29

打开MainApplication.java文件并进行以下更改:

new CodePush(BuildConfig.CODEPUSH_KEY, MainApplication.this, BuildConfig.DEBUG);

For React Native v0.19 - v0.28

new CodePush(BuildConfig.CODEPUSH_KEY, this, BuildConfig.DEBUG);

这样就OK了,当你运行你的程序时,你的debug版本将自动 配置你的 Staging 部署,你的release版本 将自动配置你的 Production 部署

注意: 默认情况下,react-native run-android命令只会构建你的Debug版本,如果你想测试你的 release/production版本,需要运行react-native run-android --variant release

如果你想在同一台设备上同时运行 Debug 和release 版本,那么你需要确保 这两个版本有不同的 id 和 图标,否则系统不能区分两者,你可以通过以下步骤来执行:

1.在buile.gradle文件中,为你的debug版本添加applicationIdSuffix字段,为debug版本设置 一个id(eg:com.foo 或者 com.foo.debug)

buildTypes { 
    debug { 
        applicationIdSuffix ".debug" 
    }
}

2.创建app/src/debug/res目录,

3.在第二步的res目录下创建一个名为values的目录,把app/src/main/res/values目录下的string.xml文件拷贝到此目录下

4.打开 debug目录下的 string.xml文件,修改<string name="app_name">元素的值为其他值,eg:foo.debug,确保你的debug版本具有不同的名字,以便和其他版本区分

5.在app/src/debug/res创建mirrored目录,为debug版本创建不同的图标,以便区分

iOS部署

在info.plist 文件中添加 CodePushDeploymentKey字段

1.在Xcode 里打开项目 ,选择工程根目录,选择info

2.点击➕按钮,选择Duplicate "Release" Configuration

3.命名为 Staging或者你喜欢的

4.选择 Build Setting

5.点击工具栏上的+号按钮,选择Add User-Defined Setting

6.命名为CONDPUSH_KEY,添加Staging和Production环境的deployment key

7.打开你工程的Info.plist文件,修改CodePushDeploymentKey的值为:$(CODEPUSH_KEY)

到此,你的iOS动态部署配置已完成,当你运行项目时,根据你运行的版本(Debug,Release)会去自动匹配StagingProduction环境

动态部署分配 - 项目端修改

The above section illustrated how you can leverage multiple CodePush deployments in order to effectively test your updates before broadly releasing them to your end users. However, since that workflow statically embeds the deployment assignment into the actual binary, a staging or production build will only ever sync updates from that deployment. In many cases, this is sufficient, since you only want your team, customers, stakeholders, etc. to sync with your pre-production releases, and therefore, only they need a build that knows how to sync with staging. However, if you want to be able to perform A/B tests, or provide early access of your app to certain users, it can prove very useful to be able to dynamically place specific users (or audiences) into specific deployments at runtime.

In order to achieve this kind of workflow, all you need to do is specify the deployment key you want the current user to syncronize with when calling the codePush method. When specified, this key will override the "default" one that was provided in your app's Info.plist (iOS) or MainActivity.java (Android) files. This allows you to produce a build for staging or production, that is also capable of being dynamically "redirected" as needed.

// Imagine that "userProfile" is a prop that this component received
// which includes the deployment key that the current user should use.
codePush.sync({ deploymentKey: userProfile.CODEPUSH_KEY });

API 文档

API文档可以参考以下这位大神的帖子写的非常好(安卓端配置也可以参考)
React Native热更新部署/热更新-CodePush最新集成总结

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

推荐阅读更多精彩内容