搭建安卓源码服务器,repo+gerrit+git环境,代码审核

安装ubuntu-14.04.5-server-amd64

我司使用的是Dell服务器,做好启动U盘,使用工具rufus-usb。
Dell服务器的界面与普通PC不同,不过基本原理是一样的,BIOS设置为UEFI。
开机时按F11,进入BootManager,选择启动项,其中有启动U盘的启动项,确认后即可开始安装。
为方便使用,安装的语言选择为中文。
分区使用了整个磁盘,将原来的系统删除。

安装gitolite服务器

Gitolite介绍

Gittolite是基于git搭建一个中央代码服务器,有以下特点:

  • 支持多个用户,多个仓库
  • 支持多个层次的权限控制,支持到仓库的branch和tag的权限访问
  • ssh安全访问协议
  • 不需要http服务器等其他依赖,更少的资源占用
  • 简单、灵活的管理

Gitolite安装成功后会创建一个名为gitolite-admin的特殊仓库,这个仓库里面管理着Gitolite的配置文件,在这些配置文件中可以添加删除用户、仓库,也可以定义一些权限控制规则。

认证与授权

Gitolite不做认证(authentication),只做授权(authorisation)。

  • 认证(authentication)。由ssh服务来认证访问账号的合法性。
  • 授权(authorisation)。授予某个账号的访问权限。

Gitolite安装和启动

Gitolite是管理Git的仓库,所以必须要安装Git。

安装Gitolite

  1. 切换到git用户账号:su – git
  2. 安装SSH,一般来说服务器上都已经安装SSH了
  3. 获取Gitolite代码,git clone git://github.com/sitaramc/gitolite
  4. 创建Gitolite安装目录。mkdir -p ~/bin
  5. 开始安装。gitolite/install -to ~/bin
  6. 添加管理员公钥。这个管理员公钥就是对应着拥有管理代码仓库的权限,非常重要。把自己的公钥admin.pub上传到服务器,添加到Gitolite中。gitolite setup -pk admin.pub

管理Gitolite

管理Gitolite也很简单,它是通过一个Git仓库来管理的。安装好Gitolite后,会默认生成一个gitolite-admin的仓库。管理员可以把这个仓库clone到本地:

git clone git@host:gitolite-admin
git clone git@host:gitolite-admin
这个仓库有两个目录:
keydir,存放用户认证的公钥文件目录
conf,存放Gitolite配置的文件的目录
gitolite.conf,gitolite配置文件

用户

添加用户本质是把该用户的公钥文件添加到gitolite-admin仓库中。比如想添加一个用户foo:
把foo的公钥文件重命名为foo.pub
把foo.pub文件放到gitolite-admin/keydir目录中,并提交到远程仓库
删除用户也类似,只不过把该用户的的公钥文件移出gitolite-admin仓库。
有时候一个用户有多个公钥文件。这种情况下,就需要把这些同名的公钥文件放到keydir不同的子目录下。比如foo用户的多个公钥文件如下放置:

keydir/home/foo.pub
keydir/laptop/foo.pub
keydir/desktop/foo.pub

只要这些公钥都是命名为foo.pub,在不同的目录下不冲突。

仓库

添加仓库
添加一个仓库很简单,编辑gitolite-admin仓库里面的gitolite-admin/conf/gitolite.conf即可。我们可以看下初始的gitolite.conf文件内容:

repo gitolite-admin
    RW+     =   admin

repo testing
    RW+     =   @all

repo gitolite-admin
    RW+     =   admin
 
repo testing
    RW+     =   @all

repo name,就是定义一个名为name的仓库。可以看到Gitolite里面有两个默认的仓库,gitolite-admin和testing。

下面的RW+ = admin则便是这个仓库的权限控制规则,可以看到这个仓库只允许admin可读可写可管理。

我们可以用以下代码添加一个新的bar仓库:

repo bar
    RW+     =   foo

repo bar
    RW+     =   foo

我们在gitolite.conf文件中添加一个foo的仓库,实际存放在服务器上的仓库目录名是foo.git。这个新仓库只允许foo用户可读可写可管理。

你可以把两个具有一样的访问权限的仓库定义成一行,如下:

repo a b c
    RW+     =   foo
    R       =   admin

repo a b c
    RW+     =   foo
    R       =   admin

这样,a、b、c就是3个具有相同的访问权限的仓库了,foo用户可以读可写可管理,admin用户只能可读。

删除、重名仓库

删除仓库稍微麻烦一点:

  1. 从gitolite.conf删除仓库的定义
  2. 登陆到服务器,删除服务器上对应的仓库
    重命名仓库也是如此,修改gitolite.conf文件,然后修改服务器上对应仓库的名字。

导入现有仓库

直接把一个仓库放入Gitolite仓库目录是不行的,它有几个要求:

  • 仓库必须是bare仓库
  • 仓库目录名必须以.git结尾
  • 仓库里面所有的文件和目录的归属(ownded)和可写于Gitolite用户账号。
  • 运行gitolite setup

用户组和仓库组

你可以把用户或者仓库定义成一个组,对这个组赋予某种属性,就是对组内所有的成员都赋予同样的属性。这对批量的处理某些问题很有帮助。
组名以@开头,如下定义一个3个成员的组:

@developers = dilbert alice wally

也可以累积分别定义,效果跟上面一样:


@developers = dilbert
@developers = alice
@developers = wally

你也可以把一个组放到另外一个组里面:
@developers     =   dilbert alice
@interns        =   ashok
@staff          =   @interns @developers
@developers     =   wally

注意,后来添加到develpoers组的wally并不在staff组里面。

如下是个使用组定义仓库和它用户的例子:

@developers     =   dilbert alice wally//三个用户
@foss-repos     =   git gitolite//两个仓库

repo @foss-repos
    RW+         =   @developer

@developers     =   dilbert alice wally
@foss-repos     =   git gitolite
 
repo @foss-repos
    RW+         =   @developer

特殊组

@all表示所有的仓库或者所有的用户。

访问权限

紧跟着一个仓库后面的就是这个仓库的访问权限规则,有以下几种权限:
R,表示可读权限
RW,表示fast-forward push分支,创建新分支和tag权限。不能回转、删除分支
RW+,可以做任何事情
'-'表示拒绝访问
以下面的配置为例子:

repo foo bar

    RW+                     =   alice @teamleads
    -   master              =   dilbert @devteam
    -   refs/tags/v[0-9]    =   dilbert @devteam
    RW+ dev/                =   dilbert @devteam
    RW                      =   dilbert @devteam
    R                       =   @managers
  • alice 和teamleads组可以做任何事情
  • dilbert @devteam权限
    • 可以对/dev分支做任何事情,不能写,删除master
    • 可以fast-forward push分支,创建新分支和tag权限
    • 除了以v开头tag,可以创建其他tags
  • managers只能读仓库

你还可以创建一个仓库组,在同一个仓库组里面的仓库具有相同的权限,如下:

repo @myrepos
    RW+     =   alice

@myrepos    =   foo
@myrepos    =   bar
@myrepos    =   zzq

这样,有3个仓库foo、bar、zzq都在myrepos同一个仓库组里面,具有相同的访问权限。

gitolite.conf

gitolite.conf有两个重要的作用:定义仓库名和定义仓库的访问权限。

基本语法
所有的东西都是用空格分隔
可以使用#表示注释
用户名和仓库名都是以字母开头,可以包含.、_、-。用户名还可以是电子邮件地址
组名类似用户名,但不能是电子邮件地址
仓库名可以包含/,用来表示目录结构

include其他配置

Gitolite允许你把配置放在另外的.conf文件里,然后在gitolite.conf把这些配置文件include进来就可以了。比如include “foo.conf”,就把foo.conf文件里面的配置包括进来了。

gitolite配置参考链接

完成和验证

到打印出如下结果gitolite安装完成。

初始化空的 Git 仓库于 /home/gitolite/repositories/gitolite-admin.git/
初始化空的 Git 仓库于 /home/gitolite/repositories/testing.git

这是创建了两个仓库,一个是管理仓库gitolite-admin.git,另一个是测试使用的testings.git.
现在要做的是要从gitolite服务器中把gitolite-admin.git拉取下来进行project和权限的管理

遇到的问题及其说明

创建新用户并同时创建主目录--useradd -m xxxname
设置密码--passwd xxxname,然后再输入密码

gitolite 不在 sudoers 文件中。此事将被报告

sudo命令可以让你以root身份执行命令,来完成一些我们这个帐号完成不了的任务。
其实并非所有用户都能够执行sudo,因为有权限的用户都在/etc/sudoers中呢。
我们可以通过编辑器来打开/etc/sudoers,或者直接使用命令visudo来搞定这件事情。
打开sudoers后,像如下那样加上自己的帐号保存后就可以了。

# User privilege specification
root    ALL=(ALL:ALL) ALL
gitolite ALL=(ALL:ALL) ALL

sudoers的权限是0440,即只有root才能读。在你用root或sudo后强行保存(wq!)即可.
测试通过后开始下面的安装

repo环境配置

概要

repo是Android为了方便管理多个git库而开发的Python脚本。repo的出现,并非为了取代git,而是为了让Android开发者更为有效的利用git。
Android源码包含数百个git库,仅仅是下载这么多git库就是一项繁重的任务,所以在下载源码时,Android就引入了repo。 Android官方推荐下载repo的方法是通过Linux curl命令,下载完后,为repo脚本添加可执行权限:

$ git clone https://gerrit-googlesource.lug.ustc.edu.cn/git-repo  > ~/bin/repo
$ chmod a+x ~/bin/repo
$ cp repo/repo .    //在~bin目录下
再将~/bin添加到环境变量,这样就可以使用repo命令了

工作原理

repo需要关注当前git库的数量、名称、路径等,有了这些基本信息,才能对这些git库进行操作。通过集中维护所有git库的清单,repo可以方便的从清单中获取git库的信息。 这份清单会随着版本演进升级而产生变化,同时也有一些本地的修改定制需求,所以,repo是通过一个git库来管理项目的清单文件的,这个git库名字叫manifest。

当打开repo这个可执行的python脚本后,发现代码量并不大(不超过1000行),难道仅这一个脚本就完成了AOSP数百个git库的管理吗?并非如此。 repo是一系列脚本的集合,这些脚本也是通过git库来维护的,这个git库名字叫repo

在客户端使用repo初始化一个项目时,就会从远程把manifests和repo这两个git库拷贝到本地,但这对于Android开发人员来说,又是近乎无形的(一般通过文件管理器,是无法看到这两个git库的)。 repo将自动化的管理信息都隐藏根目录的.repo子目录中。

项目清单库(.repo/manifests)

<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<remote  name="origin" fetch=".." review="https://android-review.googlesource.com/" />
<default revision="master" remote="origin"/>
<project path="repo-test1" name="platform/repo-test1"/>
<project path="repo-test2" name="platform/repo-test2"/>
</manifest>

注意project的path和name后面不要有/

  • <remote>:描述了远程仓库的基本信息。name描述的是一个远程仓库的名称,通常我们看到的命名是origin;fetch用作项目名称的前缘,在构造项目仓库远程地址时使用到;review描述的是用作code review的server地址

  • <default>:default标签的定义的属性,将作为<project>标签的默认属性,在<project>标签中,也可以重写这些属性。属性revision表示当前的版本,也就是我们俗称的分支;属性remote描述的是默认使用的远程仓库名称,即<remote>标签中name的属性值;属性sync-j表示在同步远程代码时,并发的任务数量,配置高的机器可以将这个值调大

  • <project>:每一个repo管理的git库,就是对应到一个<project>标签,path描述的是项目相对于远程仓库URL的路径,同时将作为对应的git库在本地代码的路径; name用于定义项目名称,命名方式采用的是整个项目URL的相对地址。 譬如,AOSP项目的URL为https://android.googlesource.com/,命名为platform/build的git库,访问的URL就是https://android.googlesource.com/platform/build
    如果需要新增或替换一些git库,可以通过修改default.xml来实现,repo会根据配置信息,自动化管理。但直接对default.xml的定制,可能会导致下一次更新项目清单时,与远程default.xml发生冲突。 因此,repo提供了一个种更为灵活的定制方式local_manifests:所有的定制是遵循default.xml规范的,文件名可以自定义,譬如local_manifest.xml, another_local_manifest.xml等, 将定制的XML放在新建的.repo/local_manifests子目录即可。repo会遍历.repo/local_manifests目录下的所有*.xml文件,最终与default.xml合并成一个总的项目清单文件manifest.xml。
    问题的疑难点在于manifest中的配置,manifest.git相对于它管理的仓库的路径关系在remote节点配置的

在服务器创建仓库

在创建好default.xml后,提交到服务器。
然后在服务器端,利用default.xml生成src.txt,再写个脚本自动创建所有的git仓库。
生成src.txt

cat default.xml | cut -d '"'   -f 2 > src.txt
命令解释:
cat default.xml是输出文件内容
| 是管道,文件内容被定向输出到管道
cut 是针对行截取,-d是自定义分隔符为 "
-f 2显示之前分隔开的第二个区域,比如行
   <project name="platform/build" path="build/" />
按"截取后为 project  name=  platform/build   path=  build/
下标从0开始,第二个区域为platform/build.
>src.txt定向输出到文件src.txt

cut详细参数:
-b :以字节为单位进行分割。这些字节位置将忽略多字节字符边界,除非也指定了 -n 标志。
-c :以字符为单位进行分割。
-d :自定义分隔符,默认为制表符。
-f :与-d一起使用,指定显示哪个区域。
-n :取消分割多字节字符。仅和 -b 标志一起使用。如果字符的最后一个字节落在由 -b 标志的 List 参数指示的<br />范围之内,该字符将被写出;否则,该字符将被排除。
cut详细用法
生成src.txt后,里面会有一些不必要的行,文件首尾有一些不需要的行,如下:

1.0  //去掉
<manifest>  //去掉
origin  //去掉
origin  //去掉

android/platform/9820e/build
core/root.mk  //去掉
    </project>  //去掉


#/bin/bash
set -x
set -e
pwd=${PWD}

cd /home/gitolite/repositories#此处要根据实际的repositories路径修改
while read line; do
    if [ -z "$line" ]; then
        echo $work_dir not exist !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 1>&2
        continue
    fi
        git init --bare $line.git    
#初始化裸仓库,使用--bare选项时,不再生成.git目录,而是只生成.git目录下面的版本历史记录文件,这些版本历史记录文件也不再存放在.git目录下面,而是直接存放在版本库的根目录下面
        echo ==== $line
        pwd
done

运行此脚本,会在/home/gitolite/repositoires下面生成相关仓库
如下:

abi.git       build.git    developers.git   external.git    hardware.git         ndk.git    sdk.git       u-boot.git
art.git       chipram.git  development.git  flyscale.git    kernel.git           packages   system.git    vendor.git
bionic.git    cts.git      device.git       frameworks.git  libcore.git          pdk.git    tools.git
bootable.git  dalvik.git   docs.git         gen.git         libnativehelper.git  prebuilts  u-boot64.git

但是有一个问题,这样虽然初始化了裸仓库,但是由于没有在gitolite中进行添加和配置,客户端是不能提交成功能,所以所有的这些仓库必须都被 gitolite管理起来才可以。那如上方法就不可取了。

使用脚本生成gitolite.conf

因为要建立上百个仓库,不可能全部手动进行配置,我们需要使用脚本来生成gitolite.conf。
脚本很简单:

#/bin/bash

set -x #追踪代码执行情况
set -e #当脚本执行出现意料之外的情况时,立即退出,避免错误被忽略,导致最终结果不正确

work_dir=$1 #传入的第一个参数

pwd=${PWD}
echo $pwd start create gitrepo...
while read line; do
        echo repo $line >> gitolite.conf  #仓库名称
        echo     RW+     =   @user >> gitolite.conf  #权限配置
done

同样利用到了src.txt,执行命令cat src.txt | ./con-tools.sh
这样就配置好了,再执行如下命令提交gitolite-admin

git add -all
git commit -m "更新gitolite.conf"
git push origin master

执行完之后 在/home/gitolite/repositories下面就生成了对应的所有仓库。

客户端上传源码

首先要准备一份没有建立git的源码。根据default.xml生成des.txt,生成的命令如下

cat default.xml | cut -d ‘”’ -f 4 > des.txt
//关于该命令的含义前面已经说过,

使用如下脚本进行仓库的批量初始化和提交

#/bin/bash

set -x #追踪代码执行情况
set -e #当脚本执行出现意料之外的情况时,立即退出,避免错误被忽略,导致最终结果不正确

para1=
work_dir=$1 #传入的第一个参数

pwd=${PWD}
echo $pwd start create gitrepo...
while read line; do
    echo readline
    line1=${line%%/*}
    if [ -z "$line" ]; then
        echo $work_dir not exist !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 1>&2
        continue
    fi
    if [ $(ls -A $pwd/$line | wc -l) -eq 0 ]; then
        echo $work_dir empty !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 1>&2
        continue
    fi
    workdir=$pwd/$line
    echo ==== $workdir
    cd $workdir  #以下是提交流程
        rm -rf .git
        git init .  1>&2
        git add . -f 1>&2
        git commit -m "Initial commit" 1>&2
        git push --set-upstream gitolite@192.168.1.104:/platform/$line.git master --force
done

执行如下命令进行批量提交:
cat des.txt | ./des.sh

客户端下载代码

  1. 客户端需要首先下载git-repo仓库,并配置环境变量,这样才能使用repo命令
    git clone gitolite@192.168.1.104:git-repo.git
    然后把repo配置到环境变量中。
  2. repo init -u gitolite@192.168.1.104:/platform/manifest.git
  3. repo sync

repo upload

set -x
set -e
Shell脚本$的含义

错误处理

  1. 执行repo init报错
    rror: manifest missing or unreadable -- please run init
  2. repo: no branches ready for upload
    https://blog.csdn.net/armwind/article/details/52488961

参考链接:
repo工作原理
default.xml文件配置详解
repo客户端安装
修改/etc/profile出错后恢复

搭建Gerrit代码审核服务器

参考链接
参考链接
下载wget http://gerrit-releases.storage.googleapis.com/gerrit-2.8.1.war
下载路径/usr/local/

安装apache2报依赖错误的问题,只需要把依赖包安装指定版本的即可。
解决依赖错误

apache2启动失败,参考Log:/var/log/apache2/error.log

登录mysql并修改用户

启动gerrit服务
./gerrit.sh start

登录mysql数据库
格式:mysql -u[用户名] -p
示例,用户名root:
mysql -uroot -p
show databases;//查看所有数据库

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| reviewdb           |
+--------------------+
4 rows in set (0.02 sec)

information_schema数据库是MySQL自带的,它提供了访问数据库元数据的方式。详细information_schema
reviewdb是gerrit使用的数据库
use reviewdb;//使用reviewd数据库
//确认使用的数据库

mysql> select database();
+------------+
| database() |
+------------+
| reviewdb   |
+------------+
1 row in set (0.00 sec)

//查看reviewdb中的所有表

mysql> show tables;
+-----------------------------+
| Tables_in_reviewdb          |
+-----------------------------+
| account_group_by_id         |
| account_group_by_id_aud     |
| account_group_id            |
| account_group_members       |
| account_group_members_audit |
| account_group_names         |
| account_groups              |
| account_id                  |
| change_id                   |
| change_messages             |
| changes                     |
| patch_comments              |
| patch_set_approvals         |
| patch_sets                  |
| schema_version              |
| system_config               |
+-----------------------------+
16 rows in set (0.01 sec)

修改gerrit认证方式

gerrit有多种身份验证方法,身份验证方法决定了如何登录Gerrit。

  1. OPENID,如果你想挂入某个现有的身份验证提供方(例如GoogleAccounts),那么可以使用OpenID。
  2. development_become_any_account,如果是用于测试和学习,可以选择最简单的development_become_any_account。
  3. HTTP认证也是可选的认证方式,此认证方式下需要配置Apache的反向代理,并在Apache中配置Web站点的口令认证,通过口令认证后gerrit在创建账号的过程中会询问用户的邮件地址并发送确认邮件。
  4. LDAP, LDAP全称Lightweight Directory Access Protocol,轻量目录访问协议。使用用户名和密码。
    在etc/gerrit.config下的
[auth]
        type = LDAP

使用HTTP认证需要使用反向代理

我司使用的HTTP进行认证。
反向代理:反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。

配置说明

下面是我的配置,服务器地址192.168.1.104,代理端口9999,被代理端口10000。本机回环IP127.0.1.1

  1. 在/etc/apache2/下创建httpd.conf
#add gerrit reverse proxy --bianjb
<VirtualHost *:9999>    #代理端口9999
    ServerName 192.168.1.104
    ProxyPreserveHost On    #开启反向代理
    ProxyRequests Off
    <Proxy *>    #访问权限规则
        Order deny,allow  
        Allow from all  
    </Proxy>
    <Location /login/>  #登录成功转到登录界面
      AuthType Basic
      AuthName "Welcomme to Gerrit Code Review Site!"
      Require valid-user
      AuthBasicProvider file
      AuthUserFile /home/gerrit/review_site/etc/passwd    #用户验证使用的文件,需要自行创建
    </Location>
    ProxyPass / http://127.0.1.1:10000/    #被代理地下
</VirtualHost>

AuthUserFile /home/gerrit/review_site/etc/passwd,我放在了这个目录下,可以自行指定,创建方法如下:

sudo touch /home/gerrit/review_site/etc/passwd  //创建文件
sudo htpasswd -b home/gerrit/review_site/etc/passwd  username passwd
//使用htpasswd帮助信息
htpasswd -h

后面登录的时候就可以使用useranme+passwd进行登录 了。

  1. 在/etc/apache2/apache2.conf中引入我们新建的httpd.conf
Include httpd.conf
  1. 配置/etc/apache2/ports.conf
Listen 80    #apache2默认监听的端口
Listen 9999  #新添加监听9999端口作为代理
  1. 重启apache2服务
sudo service apache2 restart

现在就可以访问服务器了,如下图:


image.png

登录成功后如图:


image.png

配置Gerrit开机启动

  1. 安装gerrit的时候,会有一个安装目录,在它的下面有个 bin/gerrit.sh文件,把这个文件拷贝到/etc/init.d下 改名叫做 gerrit
  2. 然后用sysv-rc-conf工具,这个没有可以直接apt-get install进行安装
  3. 运行sysv-rc-conf
    image.png

    找到gerrit的那一行,把2~5都X上。

搭建起来还是挺费劲的,欢迎各位交流指正!

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

推荐阅读更多精彩内容