iOS 一行命令发布 Pod 框架

目前比较流行的组件化开发,针对多个 app 要用同一套代码,将其做成 pod 仓库是比较好的解决方案。代码只有一份放在组件仓库,需要集成的 app 只需要将其 pod 到工程内部即可。

如果很多组件都需要做成 pod 库,每一步都手动去做,显得繁琐而且容易出错。本文主要讲一下,怎么自动化去实现这些事情。不过,在此之前,先概述一下,发布框架具体需要做哪些事情。如果对发 pod 的流程比较熟悉,直接跳过看 自动化实现 部分。

先上 GitHub 仓库地址

发布框架到官方库

image

注:图中标号并非和发布流程标号对应,图中表述的是从框架发布到使用的大致流程。

想要发布框架到 CocoaPods 官方库,需要有以下流程:

  1. 安装 CocoaPods
  2. 注册 CocoaPods 账号
  3. 创建 spec 文件
  4. 提交代码并为框架打 tag
  5. 验证 spec 文件
  6. 通过 pod trunk push 推送 spec 文件

<span id="a1">安装 CocoaPods</span>

网上有很多教程,这里不再赘述。需要注意的是,RubyGems淘宝镜像 已经不再维护,已迁移至 Ruby China,安装 CocoaPods 前记得替换 gem sources

<span id="a2">注册 CocoaPods 账号</span>

首先需要注册一个 CocoaPods 账号,用于发布 pod 仓库。

$ pod trunk register EMAIL [NAME]

trunk 命令用于创建一个账号或是一个 seession。

  • 如果你还没有账号,则需要将邮箱和名字一起填入,创建账号的同时会自动创建 session
  • 如果已经有了账号,但是没有在当前这台电脑上发布过 pod 仓库,这个时候就需要创建一个 session,仅需要填邮箱即可,除非你想改名字,否则不用填写名字。

你可以对你的 session 进行描述,这样在利用 pod trunk me 命令查看所有 session 信息的时候,可以很清晰地看到在哪些电脑上创建过 session。

$ pod trunk register orta@cocoapods.org 'Orta Therox' --description='macbook air'

命令成功之后,你的邮箱应该会收到一份邮件。查看邮件信息,访问对应网址即可验证你的账号。当然,这个事情做一次就好了,以后基本上不用再操作。

<span id="a3">创建 spec 文件</span>

spec 文件即一个 pod 仓库的描述信息。所有通过 pod search 搜索出来的框架,都是根据这个文件中的描述信息进行匹配的。以下为部分描述信息:

  • 仓库名字
  • 作者
  • 证书
  • 版本
  • 描述
  • 代码源路径
  • 使用哪些文件
  • 依赖哪些其他框架
  • ...

一个简单的 spec 文件如下:

Pod::Spec.new do |s|
  s.name             = 'ZYTemplateName'
  s.version          = '0.1.0'
  s.summary          = 'A short description of ZYTemplateName.'
  s.homepage         = 'https://github.com/ripperhe/ZYTemplateName'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'ripperhe' => 'ripperhe@qq.com' }
  s.source           = { :git => 'https://github.com/ripperhe/ZYTemplateName.git', :tag => s.version.to_s }
  s.ios.deployment_target = '8.0'
  s.source_files = 'ZYTemplateName/Classes/**/*'
end

先进入需要发布的框架的根目录,执行

$ pod spec create [NAME]

这里的 NAME 即为框架名字,不需要手动加 .podspec 后缀。执行完成之后,会生成 ZYTemplateName.podspec 文件(这里以 ZYTemplateName 为例),进入该文件,将对应信息改为你具体的信息即可。

如果你发现你的 spec 文件默认填写的用户名和邮箱不是你刚才利用 trunk 命令注册账号时候填写的不用惊讶,因为默认取的是 git 的用户名和邮箱,所以想修改的话,去修改 ~/.gitconfig 文件中的用户名和邮箱。以下为相关源码:

def default_data_for_template(name)
    data = {}
    data[:name]          = name
    data[:version]       = '0.0.1'
    data[:summary]       = "A short description of #{name}."
    data[:homepage]      = "http://EXAMPLE/#{name}"
    data[:author_name]   = `git config --get user.name`.strip
    data[:author_email]  = `git config --get user.email`.strip
    data[:source_url]    = "http://EXAMPLE/#{name}.git"
    data[:ref_type]      = ':tag'
    data[:ref]           = '#{s.version}'
    data
end

spec 文件的这些描述,基本上看命名是可以看明白的。实在不明白可以查看 官方文档。需要注意的是,s.version 是你将要发布的 pod 仓库的版本,s.source 是你的仓库源地址和对应的 tag 信息。

versiontag 可以是不同的,但是为了方便管理和维护,我们一般将其设置为一样的,所以 tag 可以 直接使用 s.version.to_s。既然这里用到了 tag,那么接下来,就需要为仓库代码打上 tag

<span id="a4">提交代码并为框架打 tag</span>

首先,需要将本仓库的所有修改提交到远程仓库。

$ git add .
$ git ci -m 'release pod'
$ git push

上一步在仓库的根目录创建了 spec 文件,随着代码的提交,spec 文件也会提交到自己的远程代码仓库。这个 spec 文件和需要发布的框架放在一起并不是必须,只是这样可以方便我们以后维护这个框架。

发布 pod 仓库,需要和自己框架的远程仓库代码版本对应,所以这里需要为当前代码打上 tag,这个 tag 是和前面的 spec 文件中填写的 tag 对应的。框架发布成功之后,CocoaPods 会根据 tag 信息去获取相应代码。

$ git push origin master
$ git tag '0.1.0' 
$ git push --tags 

<span id="a5">验证 spec 文件</span>

用于验证 spec 文件是否正确,可以及早发现问题。同样,也是在需要发布的框架的根目录,执行

$ pod spec lint ZYTemplateName.podspec

这里需要加上 .podspec 后缀来验证这个文件。如果有报错,仔细查看报错信息,一般可以定位问题,可以加上 --verbose 查看详细的验证过程,方便定位问题。

$ pod spec lint ZYTemplateName.podspec --verbose

验证 spec 文件也可以用另外一个命令

$ pod lib lint ZYTemplateName.podspec

pod spec lintpod lib lint 最主要的区别是,前者会根据 spec 文件 tag 信息去验证远程仓库代码是否存在,后者不会。简单理解就是,pod spec lint 联网检查,pod lib lint 不联网检查。

验证的时候,可能会报错,或是报警告。报错的话,必须解决,详细看具体信息,一般能找到问题,如果不能,自行 google 😁。警告的话,可以忽略,不过能解决的话,最好解决。

$ pod lib lint ZYTemplateName.podspec --allow-warnings

<span id="a6">推送 spec 文件</span>

验证文件通过之后,需要将文件推送到 CocoaPods 描述文件仓库,在框架根目录,执行以下命令:

$ pod trunk push ZYTemplateName.podspec

这个命令,可以不在后面加描述文件名称,如果不加的话,会在终端当前工作目录下查找 .podspec 文件,执行 trunk push 命令。

这一步稍微会久一点,需要等待一会儿,因为 CocoaPods 需要先将 spec 文件上传到 CocoaPods/Specs 仓库,再 git pull 到本地 CocoaPods/Specs 仓库。等待显示成功之后,可以验证以下自己的框架是否真的发布成功了。搜索一下:

$ pod search [NAME]

如果搜索不到,可能是仓库的索引库出现问题,将其删除,重新生成索引。索引缓存地址:

~/Library/Caches/CocoaPods/search_index.json

如果这样还不行,有可能是官方的 spec 仓库 出现问题。官方 spec 仓库路径:

~/.cocoapods/repos/master

将其删除,再重新克隆官方的 spec 仓库:

$ pod setup

这个时候再试试 pod search 应该就有了。

发布框架到私有库

image

注:图中标号并非和发布流程标号对应,图中表述的是从框架发布到使用的大致流程。

对于公司内部使用的组件模块,一般是不公开的,所以就需要用到私有 sepc 仓库。

将代码发布到私有仓库和 CocoaPods 官方库最大的区别就是,不需要利用 trunk 命令创建 session。既然整个私有库都是你的,所以也没有必要再有账号机制了,自己想怎样就怎样。

少做了一件事情,但也有额外的事情需要做。前面 spec 文件都是 CocoaPods 帮你管理的,所以做私有 pod,就需要创建私有 spec 仓库。

整体流程如下:

  1. 安装 CocoaPods
  2. 创建远程私有 spec 仓库
  3. 克隆远程私有 spec 仓库到本地
  4. 创建 spec 文件
  5. 提交代码并为框架打 tag
  6. 验证 spec 文件
  7. 通过 pod repo push 推送 spec 文件

很多流程是相同的,相同的流程一笔带过。

<span id="b1">安装 CocoaPods</span>

同上。👆

<span id="b2">创建远程私有 spec 仓库</span>

所谓的远程私有 spec 仓库,其实也就是一个普通远程代码仓库,到一个 git 仓库托管网站创建一个空仓库即可。GitHub 创建私有仓库是需要付费的,所以可以在 Bitbucket码市 创建免费的私有仓库。

这里以码市为例。创建账号并点击创建仓库

image

选中私有属性,点击创建即可

image

<span id="b3">克隆远程私有 spec 仓库到本地</span>

首先,复制远程仓库地址。

image

利用以下命令,克隆远程仓库,并为自己的私有仓库命名

$ pod repo add NAME URL

克隆完成之后,你会发现,在 ~/.cocoapods/repos/ 文件夹下,除了 CocoaPods 官方 spec 仓库 master 之外,还多了自己的仓库。

<span id="b4">创建 spec 文件</span>

同上。👆

<span id="b5">提交代码并为框架打 tag</span>

同上。👆

<span id="b6">验证 spec 文件</span>

同上。👆

<span id="b7"> 推送 spec 文件</span>

这一步同发布到官方库不同,利用以下命令:

$ pod repo push REPO [NAME.podspec]

以我自己的仓库为例:

$ pod repo push ZYSpec ZYTemplateName.podspec

这个命令实质上是先将该 spec 文件提交到本地的私有仓库,然后再 push 到远程仓库。而发布到官方库时,是通过 pod trunk push 通过请求,将 spec 文件加入到了官方库远程仓库,再从远程仓库拉到了本地,不过最终的效果是相同的。当命令显示成功,框架就发布完毕了。

因为是推送到了自己的仓库,所以在使用的时候需要注意。在 podfile 中需要添加私有 spec 仓库地址:

source 'https://github.com/CocoaPods/Specs.git'   # 官方库地址
source 'https://git.coding.net/ripper/Specs.git'  # 私有仓库地址

platform :ios, '8.0'

target 'PodDemo' do
  # 官方库中的框架
  pod 'MJExtension', '~> 3.0.13'
  # 私有库中的框架
  pod 'ZYLibXXX'
end

如果需要官方库中的框架,也需要将官方库地址写上,如果 source 都不写,默认是从官方库中查找框架信息。

可能这里会存在一个问题,同一个 spec 仓库不能够有重名的框架,但是多个 spec 仓库之间,是可以有重名的框架的。所以,可能会存在名字相同的问题,一般采取自己特有的前缀的办法来避免这个问题。

<span id="fastlane">利用 fastlane 实现自动化</span>

从前面可以看到,发布 pod 的流程虽然不算复杂,但是每次一步步地做这些事情,非常的没有必要,所以这里考虑用自动化来实现。自动化实现,就需要用到 fastlane。

fastlane 简介

Fastlane 是用 Ruby 编写的一套自动化工具框架,最开始主要针对 iOS 开发,后来因为社区呼声太高,于是也支持了 Android。Fastlane 内置了许多任务脚本,使用者可以自行组合,涵盖了打包、签名、测试、发布等移动开发常用功能。

使用 Fastlane 首先理解两个概念,action 和 lane。一个 action 其实就是一个 ruby 脚本,每个 action 的粒度大小不一,现在 Fastlane 官方库中有 100 多个 action,其中有很多 action 都是 github 社区成员贡献的。一个 lane 就是一条自动化命令流程,lane 是由一个或多个 action 组成。

安装 fastlane

Fastlane 的安装仅需一条命令即可,官网 有更详细的解释。

$ sudo gem install fastlane

书写 Fastfile

如果使用 Fastlane 进行提包等操作,就在工程根目录下执行以下命令:

$ fastlane init

这样就会询问你的 Apple ID 以及其他的一些配置,并且生成一大堆东西,而我们这里只是想要发布 pod,与 Apple ID 这些没用关系,所以我们手动创建 Fastfile。同样,在工程根目录:

$ mkdir fastlane
$ touch fastlane/Fastfile

我们可以在 Fastfile 开始书写我们的 lane,不过,写之前,我们需要分析一下哪些步骤需要自动化。就发布官方库而言:

步骤 是否需要自动
安装 CocoaPods
注册 CocoaPods 账号
创建(更新) spec 文件 ❌ - ✅
提交代码并为框架打 tag
验证 spec 文件
推送 spec 文件

首先,安装 CocoaPods 肯定是手动安装,第二次也不需要再安装。利用 trunk 注册账号也是同样的,如果不换电脑仅需调用一次 pod trunk 命令。创建 spec 文件,第一的时候肯定是需要手动创建的,需要填写很多自己的信息。不过在今后框架升级,如果 sepc 文件中不需要修改多余的信息,仅仅只需要修改 s.version 的话,可以通过自动化实现。提交代码并打 tag、验证 spec 文件以及推送文件都可以利用自动化实现。

那我们再看一下发布私有库:

步骤 是否需要自动
安装 CocoaPods
创建远程私有 spec 仓库
克隆远程私有 spec 仓库到本地
创建(更新) spec 文件 ❌ - ✅
提交代码并为框架打 tag
验证 spec 文件
推送 spec 文件

创建远程仓库和克隆远程仓库到本地一般不需要自动实现,毕竟如果是公司使用,公司的所有框架放到一个私有 spec 仓库就好了,只需要创建一次、克隆一次,不需要每次都创建私有库,所以不需要自动化,其他基本和上面一致。

接下来,在 Fastlane 中写我们 lane,兼容这两种情况

lane :release_pod do |options|
  target_repo    = options[:repo]
  target_project = options[:project]
  target_version = options[:version]
  spec_path = "#{target_project}.podspec"
  
  # git pull
  git_pull 
  # 确认是 master 分支
  ensure_git_branch
  # 修改 spec 为即将发布的版本
  version_bump_podspec(path: spec_path, version_number: target_version)
  # 提交代码到远程仓库
  git_add(path: '.')
  git_commit(path: '.', message: 'release')
  push_to_git_remote
  # 检查对于 tag 是否已经存在
  if git_tag_exists(tag: target_version)
      # 删除对应 tag
      remove_git_tag(tag: target_version)
  end
  # 添加 tag
  add_git_tag(tag: target_version)
  # 提交 tag
  push_git_tags
  # 验证 spec 文件
  pod_lib_lint(allow_warnings: true)
  # 检查是否传了 repo 参数
  if target_repo
    # pod repo push 'target_repo' 'spec_path'
    pod_push(path: spec_path, repo: target_repo, allow_warnings: true)
  else
    # pod trunk push 'spec_path'
    pod_push(path: spec_path, allow_warnings: true)
  end
end

代码并不多,从头开始就是我们 lane 的名字为 release_pod,接下来就是三个参数。

  • repo:指定的 specs 仓库,如果发布私有框架,就需要传自己的 spec 仓库名,发布到官方库不需要传。
  • project:想要发布的框架名字
  • version:想要发布的框架版本

为了安全起见,先拉取代码,并确认是 master 分支。之后,version_bump_podspec 可以自动修改 spec 文件的版本信息为我们想要发布的版本。紧接着提交代码,之所以判断 tag 是否存在,是因为我们可能在上一次走到了验证 spec 文件,但是没通过,所以我们修改了之后重新发布,但是刚才已经打了相应 tag,这一次就打不上相同的 tag,所以先判断是否存在,存在将错误的 tag 删除。当然,也可能会因为自己输入错误了想要发布的 verison 而导致误删 tag,所以可以自行斟酌这一步是否需要。

后面就是验证 spec 文件,前面说过 pod spec lintpod lib lint 主要区别是是否联网检查对于 tag 的代码是否存在,所以用两个检查都可以,但是由于 Fastlane 官方工具库中只有 pod_lib_lint 这个 action,所以这里也不折腾了,直接使用 pod_lib_lint

最后就是推送 spec 文件。我们这里的设定是,如果指定仓库,那就发布到自己的私有仓库,不指定仓库,就发布到官方库。你也许会看到,我们这里是同一个 action,都是 pod_push 只是参数不同,其实这是因为这个 action 内部做了判断。查看这个 Fastlane 的官方 action 源码即可看到内部的判断,这里截取部分源码如下:

if params[:repo]
  repo = params[:repo]
  command = "pod repo push #{repo}"
else
  command = 'pod trunk push'
end

if params[:path]
  command << " '#{params[:path]}'"
end

我们这条 lane 中,git_pullensure_git_branch...每一句都是一个 action。这里面除了 remove_git_tag 之外,都是官方自带的 action,由于找遍了官方的 actions 并没有找到删除 git tag 的,所以自定义了一个 action。

自定义 action,在工程根目录执行

$ fastlane new_action --name remove_git_tag

之后 fastlane/actions 文件下,就会生成对应的 remove_git_tag.rb 文件,我们修改该文件为我们想要的样子就好。这里代码不全贴了,就贴上核心代码,如果感兴趣,可以在 github 仓库中下载查看。

command = []

target_tag = params[:tag]
remove_local = params[:remove_local]
remove_remote = params[:remove_remote]

command << "git tag -d #{target_tag}" if remove_local
command << "git push origin :#{target_tag}" if remove_remote

if command.empty?
  UI.message('👉 If you really want to delete a tag, you should to set up remove_local and remove_remote at least one for true!')
else
  result = command.join(' & ')
  Actions.sh(result)
  UI.message('Remove git tag Successfully! 🎉')
end

执行 Fastlane

说了这么多,终于到了使用我们的 fastlane 的环节。对于我们发布官方库而言,假设我们 CocoaPods 安装,注册账号,创建 spec 文件都已经准备完毕,我们直接到根目录执行

$ fastlane release_pod project:'框架名' version:'版本'

接下来只需要静静等待一步步自动执行。

发布私有库也同样简单,假设私有库已经创建好,克隆到本地并命名为 ZYSpec,那么,到根目录执行

$ fastlane release_pod repo:ZYSpec project:'框架名' version:'版本'
image

以后我们升级框架,假如没有依赖新的三方库之类的,我们同样直接执行一行命令即可。麻烦一点的情况,也不过就是手动修改一下 spec 文件,然后再执行命令即可。

复用 Fastfile

上面已经写好 Fastfile,但是每次发布新的 pod,我们都需要将 fastlane 整个文件夹拷贝到新的工程根目录中去,这显得非常的麻烦。另一个问题是,如果某一天我们觉得我们这个发布 pod 的流程不是很完善,我们也需要到每次仓库去修改 Fastfile,不方便统一管理。

不过 Fastlane 有一个 import 机制。也就是说,一个 Fastfile 可以导入另一个 Fastfile,并且还可以重写 lane。有两个命令用于 import:

import

从本地路径导入 Fastfile

import "../GeneralFastfile"

override_lane :from_general do
  # ...
end

import_from_git

从一个 git 仓库导入 Fastfile,并且这个命令会导入所有自定义的 action

import_from_git(url: 'https://github.com/fastlane/fastlane/tree/master/fastlane')
# or
import_from_git(url: 'git@github.com:MyAwesomeRepo/MyAwesomeFastlaneStandardSetup.git',
               path: 'fastlane/Fastfile')

lane :new_main_lane do
  # ...
end

对于着两种方式,我这里选择后者,虽然前者在本地执行比较快,但是我换了电脑就不行了。所以,我把当前写好的 Fastfile 放到了 GitHub 上,仓库地址 → fastlane-files

这样,我在每次用的时候,仅需在新的工程根目录创建 fastlane 文件夹,并在 fastlane 文件夹创建 Fastfile 文件,并在文件中写入

import_from_git(url: 'https://github.com/ripperhe/fastlane-files', branch: 'master')

这里可以不指定 branch,默认就是 master 分支。这样,我们就可以使用同一份 Fastfile,便于维护。

利用 pod 模板仓库

如果做成 pod 的仓库比较多,每次都有去创建 spec 文件等,也显得比较麻烦,但是 CocoaPods 为我们提供了一个命令,直接创建一个仓库。

$ pod lib create ZYLib

执行该命令之后会问几个问题,最终生成文件目录

$ tree ZYLib -L 2

ZYLib
├── Example
│   ├── Podfile
│   ├── Podfile.lock
│   ├── Pods
│   ├── Tests
│   ├── ZYLib
│   ├── ZYLib.xcodeproj
│   └── ZYLib.xcworkspace
├── LICENSE
├── README.md
├── ZYLib
│   ├── Assets
│   └── Classes
├── ZYLib.podspec
└── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj

10 directories, 5 files

包含了 LICENSE、README.md、.podspec 等文件,并且创建好 git 管理,自己仅需将仓库与自己的远程仓库关联即可,非常方便,推荐使用。看更多信息,可以查看 官网的解释

其实 CocoaPods 是先克隆 pod-template 仓库到本地,再根据参数进行一些配置。可以将 pod-template fork 到自己账户下,并进行一些修改,每次使用 pod lib create 命令时拼上--template-url=URL,其中 URL 填写自己的模板地址。

利用 ruby 脚本创建模板

虽然有 pod lib create 这么好用的命令,但是其实我经常会写一些东西,在开始写的时候并没有想做成 pod,又或者不想要 pod lib create 创建出来的这么多文件,只想要其中一些。虽然可以 fork 一份过来修改,但觉得稍显麻烦。

所以这里准备自己写一个小脚本,创建几项必要的文件。以下为利用脚本创建的文件:

文件 说明
README.md 写一个模板,以后填空就好
LICENSE 一般都是 MIT 证书
fastlane/Fastfile 虽然这个 Fastfile 只有一句代码,但是我还是记不住
xxx.podspec spec 文件必填的就那么几项,写个模板可以把大部分都填好,不用改了

到工程根目录下执行脚本:

$ ruby ~/Desktop/pod-template/prepare_release.rb

[20:56:35]: Please input the lib name:👇

执行脚本,这里做了一个逻辑是询问 lib 名字,如果直接回车,就默认为当前文件夹名字。假设创建一个空文件夹 DemoLib,则结果如下:

$ tree DemoLib

DemoLib
├── DemoLib.podspec
├── LICENSE
├── README.md
└── fastlane
    └── Fastfile

1 directory, 4 files

也存在一种情况,有的文件已经存在了,为了不覆盖当前存在的文件,所以前缀加一个 template.,结果如下:

$ tree DemoLib

DemoLib
├── DemoLib.podspec
├── LICENSE
├── README.md
├── fastlane
│   ├── Fastfile
│   └── template.Fastfile
├── template.DemoLib.podspec
├── template.LICENSE
└── template.README.md

因为需要在工程目录执行脚本,但是脚本一般是放在其他文件夹下,所以每次执行的时候也不是很方便。所以这里建议设置一个命令别名,我是用的 Oh My Zsh,所以我直接在 ~/.zshrc 文件末尾加上别名设置

alias pre='ruby ~/Desktop/pod-template/prepare_release.rb'

这里等于符号两边不要留空格,之后,我要发布某一个组件的时候,只需要到工程根目录输入:

$ pre
[21:06:38]: Please input the lib name:👇
image

现在终于不会忘记了...(当然,要是移动了模板文件的路径又得改... 😂 )如果你也想使用这个脚本,可以 fork 这个仓库 到你的 GitHub 账号下,修改模板文件即可。以下为该仓库的文件:

$ tree pod-template

├── LICENSE
├── README.md
├── prepare_release.rb
└── template-files
    ├── %ZYTemplateName%.podspec
    ├── Fastfile
    ├── LICENSE
    └── README.md

其中 template-files 文件夹下面的均为模板文件,不要去删除文件,脚本内部没有做什么容错处理。所以想要写框架名字的地方,全部用 %ZYTemplateName% 代替。

总结

整理后的发布框架流程

  1. 利用 pod lib create 创建工程并与远程代码仓库关联
  2. 修改各种文件信息为当前要发布的框架信息
  3. 利用 fastlane release_pod 命令发布或更新框架

如果已经有工程,直接在工程根目录

  1. 利用 ruby 脚本创建相应文件并修改需要发布的框架信息
  2. 利用 fastlane release_pod 命令发布或更新框架

综上,虽然不能所以情况都一行命令发布框架,但是更新框架的时候基本上一行命令没问题。

文章到这里就结束了,如有疏漏,还望指正。如果对您有帮助,请不要吝啬你的 star🌹 哦!

参考

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

推荐阅读更多精彩内容