第六课 技术小白如何开发一个DAPP区块链应用(以宠物商店为例)

1. 文章摘要

【本文目标】

通过逐步的指导和截图举证,一步步带领一个技术小白完成一个宠物商店DAPP应用的开发和部署。

【环境前置条件】

参考《第一课 如何在WINDOWS环境下搭建以太坊开发环境》,已完成Ubuntu的安装,已完成TRUFFLE,Ganache-cli,lite-server的安装;
本案例是通过WINDOWS的XSHELL客户端同本机的Ubuntu命令操作进行的。
已在本地WIDOWS环境完成MetaMask轻钱包客户端的安装。
最好遵循从头开始的课程学习顺序。不过如果你想半途插入实操学习,问题也不大,遇到障碍时反向找对应文章的指导内容即可完成。

【技术收获】

从本实践中,你可以学习到:
搭建智能合约开发环境
创建Truffle项目
编写智能合约
编译和部署智能合约到区块链
如何通过Web3和智能合约交互
MetaMask 的使用

【实操课程列表】

第一课 如何在WINDOWS环境下搭建以太坊开发环境
第二课 如何实现以太坊最简智能合约“Hello World”的运行
第四课 以太坊开发框架Truffle从入门到实战
第六课 技术小白如何开发一个DAPP区块链应用(以宠物商店为例)
第七课 技术小白如何在45分钟内发行通证(TOKEN)并上线交易
第八课 如何调试以太坊官网的智能合约众筹案例
【说明】未列出的课程为知识普及的非实操类课程,所有区块链文章参考“区块链入口”专栏。

2. 下载/编写TRUFFLE框架的智能合约

项目背景

Pete有一个宠物店,有16只宠物狗,他想开发一个去中心化应用,让大家来领养宠物。
在truffle box中,已经提供了pet-shop的网站部分的代码,我们只需要编写合约及交互部分。
【官网原始参考】
代码框架下载:http://truffleframework.com/boxes/pet-shop
开发指导:http://truffleframework.com/tutorials/pet-shop

环境搭建

环境需要NodeJS, Truffle,Ganache-Cli, Lite-Server, Meta-Mask等程序,按照步骤可以参考《第一课 如何在WINDOWS环境下搭建以太坊开发环境》 搜索对应关键字在已安装的UBUNTU操作系统完成安装。

  1. 安装Node:

sudo apt-get install curl
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash
sudo apt-get install -y nodejs

  1. 安装 Truffle :

npm install -g truffle

  1. 安装Ganache

sudo npm install -g ganache-cli

【说明】Ganache(或Ganache CLI)已经取代了 testrpc。

4.Lite-Server, Meta-Mask安装

本文后面章节描述。

创建项目

  1. 建立项目目录并进入
 mkdir dapp-guide-pet-shop
 cd dapp-guide-pet-shop

  1. 使用truffle unbox 创建项目
    下载官方程序代码

truffle unbox pet-shop

输出成功结果:

Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!

Commands:

  Compile:        truffle compile
  Migrate:        truffle migrate
  Test contracts: truffle test
  Run dev server: npm run dev

项目目录结构

contracts/ 智能合约的文件夹,所有的智能合约文件都放置在这里,里面包含一个重要的合约Migrations.sol(稍后再讲)
migrations/ 用来处理部署(迁移)智能合约 ,迁移是一个额外特别的合约用来保存合约的变化。
test/ 智能合约测试用例文件夹
truffle.js/ 配置文件
其他代码可以暂时不用管
【说明】如果想了解TRUFFLE框架更详细的内容,可参考文章《第四课 以太坊开发框架Truffle从入门到实战》

编写智能合约

智能合约承担着分布式应用的后台逻辑和存储。智能合约使用solidity编写。

在contracts目录下,添加合约文件Adoption.sol

pragma solidity ^0.4.17;

contract Adoption {

  address[16] public adopters;  // 保存领养者的地址

    // 领养宠物
  function adopt(uint petId) public returns (uint) {
    require(petId >= 0 && petId <= 15);  // 确保id在数组长度内

    adopters[petId] = msg.sender;        // 保存调用这地址 
    return petId;
  }

  // 返回领养者
  function getAdopters() public view returns (address[16]) {
    return adopters;
  }

}

编译部署智能合约

Truffle集成了一个开发者控制台,可用来生成一个开发链用来测试和部署智能合约。

编译

Solidity是编译型语言,需要把可读的Solidity代码编译为EVM字节码才能运行。
进入dapp的根目录dapp-guide-pet-shop执行命令,

> truffle compile

输出

Compiling ./contracts/Adoption.sol...
Writing artifacts to ./build/contracts

编写部署代码

编译之后,就可以部署到区块链上。
在migrations文件夹下已经有一个1_initial_migration.js部署脚本,用来部署Migrations.sol合约。
Migrations.sol 用来确保不会部署相同的合约。

现在我们来创建一个自己的部署脚本2_deploy_contracts.js

var Adoption = artifacts.require("Adoption");

module.exports = function(deployer) {
  deployer.deploy(Adoption);
};

安装启动Ganache测试环境

在执行部署之前,需要确保有一个区块链运行, 可以使用
Ganache来开启一个私链来进行开发测试。
参考文章《第一课 如何在WINDOWS环境下搭建以太坊开发环境》 对应的章节,完成ganache-cli的安装。

sudo npm install -g ganache-cli

在新的命令行窗口运行ganache-cli程序:

cd /usr/work/Ganache
ganache-cli >> trace.log

这个>>目的是把ganache-cli的结果输出到文件,便于后面复制钱包地址用于查看ETH余额。作者实践时把这2行命令合并成一行执行。

ganache-cli >> /usr/work/Ganache/trace.log

这个命令窗口用于运行Ganache程序输出,不可关闭。需要新开一个命令窗口用于执行TRUFFLE的命令。
输出截图

打开trace.log文件,可查看默认启动的10个钱包账户和对应的私钥地址:

Available Accounts
==================
(0) 0x7554cc8c721712adde43e67a5669225bbe8f21f6
(1) 0x3e6ce6a43c1fa565b5b90963bef090625d3edc6c
(2) 0xeb2eb6ed4b325e77f5a597497ec5ffaa2f5c2650
(3) 0x1de061d5f225533f7e3c38a5905a6ca2ecb3e55a
(4) 0x3ec2e1ed3f47fc7ab9cca1fe09afe9fd1feb789b
(5) 0xf27a303880b73a0a287a2e5dc1286098fb49ed63
(6) 0xf3c007932e1de894503166aee3cbf85b4aff0188
(7) 0xe7655733659c14c7c83fb71bd40dc51796592d96
(8) 0x507722000223ca96ac646198709b9ae3f7f49a5a
(9) 0x82b087a83f72cc7dd5ca6ac7787a366b2c3ff143

Private Keys
==================
(0) 629551aa45c594ce822c5b4a378d01cf46fb57c15b69a61eb400a4867ffab002
(1) a90103a95ed805acc52782eaa29eb061f6c2a9431fed3ac18a683ea3143a29b6
(2) c2b13ce370fb235997dc4783ce591a22e5b3909e934dc7c0f61797ce57d5059d
(3) 841d0e1d3dc658ad2f308c7292b4d5c40da158d170cf3ed9001c64b8352cd0c2
(4) 255225aedba340b6ec62e6c86a6202535d7382704b129d022461c69b8341d2dd
(5) c3fd784fc7a46edb5e7cba9e90120e51152a3fbe2d8b97a0c3106791d3bbe87e
(6) f8f90b056c419464d48dcd12ac8e326a31ebe300ea5e245876fe0308c511fbac
(7) 09e20553971b044c1fda8ac88f2ed1dcdff6af0889fc05c2ad4374c52d4f52d7
(8) 142643d0fb6f4a78e0fd291a568b784869927d8f8eee87bf3fbdbf493ec3f425
(9) 8f89d4a05582193cc32f800ecf9419f35b5384abc5db42afd4ec016a54d27716

配置以太坊客户端本地环境

ruffle.js是truffle的配置文件,位于dapp-guide-pet-shop目录下,启动好以太坊本地结点以后,我们需要让truffle去识别它并使用它,这就需要在truffle.js中配置相关属性:

module.exports = {
  // See <http://truffleframework.com/docs/advanced/configuration>
  // for more about customizing your Truffle configuration!
  networks: {
    development: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "*" // Match any network id
    }
  }
};

执行部署命令

接下来在新的命令窗口执行部署命令

cd /usr/work/dapp-guide-pet-shop
truffle migrate

执行后,有一下类似的输出,

Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x29612ceea67bc946cc6ae82afbedc546f9a53ba8cab5d804f9025fb8f15e48f8
  Migrations: 0x8af912046664ba26738b811c34068d42216528c0
Saving successful migration to network...
  ... 0x6c968a3c492439ab22028e1956360a6b73a02716c436b15234cd29804dac7298
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying Adoption...
  ... 0x27448e4f75b608015f3670e3650cf607c882fe7f1a32f98e8a94bf7a406c871c
  Adoption: 0xe42f434105a7e0eacf4f4229c76e1e135d536db6
Saving successful migration to network...
  ... 0x9375cd6fa143d01a520c5ed0d46cfe859f482b01e37cf678d751c5db6a278e5f
Saving artifacts...

查看Ganache-cli的输出文件:trace.log,可以看到区块链状态的变化,现在产生了4个区块。

net_version
eth_accounts
eth_accounts
net_version
net_version
eth_sendTransaction

  Transaction: 0x29612ceea67bc946cc6ae82afbedc546f9a53ba8cab5d804f9025fb8f15e48f8
  Contract created: 0x8af912046664ba26738b811c34068d42216528c0
  Gas usage: 268407
  Block Number: 1
  Block Time: Tue Apr 10 2018 09:10:18 GMT+0800 (CST)

eth_newBlockFilter
eth_getFilterChanges
eth_getTransactionReceipt
eth_getCode
eth_uninstallFilter
eth_sendTransaction

  Transaction: 0x6c968a3c492439ab22028e1956360a6b73a02716c436b15234cd29804dac7298
  Gas usage: 41981
  Block Number: 2
  Block Time: Tue Apr 10 2018 09:10:19 GMT+0800 (CST)

eth_getTransactionReceipt
eth_accounts
net_version
net_version
eth_sendTransaction

  Transaction: 0x27448e4f75b608015f3670e3650cf607c882fe7f1a32f98e8a94bf7a406c871c
  Contract created: 0xe42f434105a7e0eacf4f4229c76e1e135d536db6
  Gas usage: 247573
  Block Number: 3
  Block Time: Tue Apr 10 2018 09:10:19 GMT+0800 (CST)

eth_newBlockFilter
eth_getFilterChanges
eth_getTransactionReceipt
eth_getCode
eth_uninstallFilter
eth_sendTransaction

  Transaction: 0x9375cd6fa143d01a520c5ed0d46cfe859f482b01e37cf678d751c5db6a278e5f
  Gas usage: 26981
  Block Number: 4
  Block Time: Tue Apr 10 2018 09:10:20 GMT+0800 (CST)

eth_getTransactionReceipt

这时说明已经智能合约已经部署好了。

测试

现在我们来测试一下智能合约,测试用例可以用 JavaScript or Solidity来编写,这里使用Solidity。

test目录下新建一个TestAdoption.sol,编写测试合约

pragma solidity ^0.4.17;

import "truffle/Assert.sol";   // 引入的断言
import "truffle/DeployedAddresses.sol";  // 用来获取被测试合约的地址
import "../contracts/Adoption.sol";      // 被测试合约

contract TestAdoption {
  Adoption adoption = Adoption(DeployedAddresses.Adoption());

  // 领养测试用例
  function testUserCanAdoptPet() public {
    uint returnedId = adoption.adopt(8);

    uint expected = 8;
    Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
  }

  // 宠物所有者测试用例
  function testGetAdopterAddressByPetId() public {
    // 期望领养者的地址就是本合约地址,因为交易是由测试合约发起交易,
    address expected = this;
    address adopter = adoption.adopters(8);
    Assert.equal(adopter, expected, "Owner of pet ID 8 should be recorded.");
  }

    // 测试所有领养者
  function testGetAdopterAddressByPetIdInArray() public {
  // 领养者的地址就是本合约地址
    address expected = this;
    address[16] memory adopters = adoption.getAdopters();
    Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
  }
}

Assert.sol 及 DeployedAddresses.sol是Truffle框架提供,在test目录下并不提供truffle目录。

TestAdoption合约中添加adopt的测试用例

运行测试用例

在终端中,执行

truffle test

如果测试通过,则终端输出:

Using network 'development'.

Compiling ./contracts/Adoption.sol...
Compiling ./test/TestAdoption.sol...
Compiling truffle/Assert.sol...
Compiling truffle/DeployedAddresses.sol...

Compilation warnings encountered:

truffle/Assert.sol:1563:9: Warning: Use of the "var" keyword is deprecated.
        var nstr = _itoa(value, 10);
        ^------^
,truffle/Assert.sol:1580:9: Warning: Use of the "var" keyword is deprecated.
        var nstr = _utoa(value, 10);
        ^------^
,truffle/Assert.sol:1597:9: Warning: Use of the "var" keyword is deprecated.
        var nstr = _ltoa(value);
        ^------^
,truffle/Assert.sol:1347:13: Warning: Invoking events without "emit" prefix is deprecated.
            TestEvent(true, "");
            ^-----------------^
,truffle/Assert.sol:1349:13: Warning: Invoking events without "emit" prefix is deprecated.
            TestEvent(false, message);
            ^-----------------------^

  TestAdoption
    ✓ testUserCanAdoptPet (246ms)
    ✓ testGetAdopterAddressByPetId (231ms)
    ✓ testGetAdopterAddressByPetIdInArray (287ms)


  3 passing (2s)

3. 创建用户接口和智能合约交互

我们已经编写和部署及测试好了我们的合约,接下我们为合约编写UI,让合约真正可以用起来。

在Truffle Box pet-shop里,已经包含了应用的前端代码,代码在src/文件夹下。

在编辑器中打开src/js/app.js
可以看到用来管理整个应用的App对象,init函数加载宠物信息,就初始化web3.
web3是一个实现了与以太坊节点通信的库,我们利用web3来和合约进行交互。

初始化web3

接下来,我们来编辑app.js修改initWeb3():
删除注释,修改为:

  initWeb3: function() {
    // Is there an injected web3 instance?
    if (typeof web3 !== 'undefined') {
      App.web3Provider = web3.currentProvider;
    } else {
      // If no injected web3 instance is detected, fall back to Ganache
      App.web3Provider = new Web3.providers.HttpProvider('http://localhost:8545');
    }
    web3 = new Web3(App.web3Provider);

    return App.initContract();
  }

代码中优先使用 MetaMask提供的web3实例,如果没有则从本地环境创建一个。

实例化合约

使用truffle-contract会帮我们保存合约部署的信息,就不需要我们手动修改合约地址,修改initContract()代码如下:

  initContract: function() {
    // 加载Adoption.json,保存了Adoption的ABI(接口说明)信息及部署后的网络(地址)信息,它在编译合约的时候生成ABI,在部署的时候追加网络信息
    $.getJSON('Adoption.json', function(data) {
      // Get the necessary contract artifact file and instantiate it with truffle-contract
       // 用Adoption.json数据创建一个可交互的TruffleContract合约实例。
      var AdoptionArtifact = data;
      App.contracts.Adoption = TruffleContract(AdoptionArtifact);
    
      // Set the provider for our contract
      App.contracts.Adoption.setProvider(App.web3Provider);
    
      // Use our contract to retrieve and mark the adopted pets
      return App.markAdopted();
    });

    return App.bindEvents();
  },

处理领养

修改markAdopted()代码:

  markAdopted: function(adopters, account) {
    var adoptionInstance;

    App.contracts.Adoption.deployed().then(function(instance) {
      adoptionInstance = instance;

      // 调用合约的getAdopters(), 用call读取信息不用消耗gas
      return adoptionInstance.getAdopters.call();
    }).then(function(adopters) {
      for (i = 0; i < adopters.length; i++) {
        if (adopters[i] !== '0x0000000000000000000000000000000000000000') {
          $('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true);
        }
      }
    }).catch(function(err) {
      console.log(err.message);
    });
  }

修改handleAdopt()代码:

  handleAdopt: function(event) {
    event.preventDefault();

    var petId = parseInt($(event.target).data('id'));

    var adoptionInstance;

    // 获取用户账号
    web3.eth.getAccounts(function(error, accounts) {
      if (error) {
        console.log(error);
      }

      var account = accounts[0];

      App.contracts.Adoption.deployed().then(function(instance) {
        adoptionInstance = instance;

        // 发送交易领养宠物
        return adoptionInstance.adopt(petId, {from: account});
      }).then(function(result) {
        return App.markAdopted();
      }).catch(function(err) {
        console.log(err.message);
      });
    });
  }

4. 安装和配置lite-server

【定义】lite-server 是一个全功能的网站架设工具软件包轻量级的,仅适用于开发 的 node 服务器, 它仅支持 web app。 它能够为你打开浏览器, 当你的html或是JavaScript文件变化时,它会识别到并自动帮你刷新浏览器, 还能使用套接字自动注入变化的CSS, 当路由没有被找到时,它将自动后退页面。
参考文章《第一课 如何在WINDOWS环境下搭建以太坊开发环境》的"(8)安装 lite-server 【可选】”章节完成lite-server的安装。

bs-config.json文件指示了lite-server的工作目录。

{
  "server": {
    "baseDir": ["./src", "./build/contracts"]
  }
}

./src 是网站文件目录
./build/contracts 是合约输出目录

以此同时,在package.json文件的scripts中添加了dev命令:

{
  "name": "pet-shop",
  "version": "1.0.0",
  "description": "",
  "main": "truffle.js",
  "directories": {
    "test": "test"
  },
  "scripts": {
    "dev": "lite-server",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "lite-server": "^2.3.0"
  }
}

当在新的命令窗口运行npm run dev的时候,就会启动lite-server

cd /usr/work/dapp-guide-pet-shop
npm run dev

正常的运行结果有如下类似输出内容:

> pet-shop@1.0.0 dev /usr/work/dapp-guide-pet-shop
> lite-server

** browser-sync config **
{ injectChanges: false,
  files: [ './**/*.{html,htm,css,js}' ],
  watchOptions: { ignored: 'node_modules' },
  server: 
   { baseDir: [ './src', './build/contracts' ],
     middleware: [ [Function], [Function] ] } }
[Browsersync] Access URLs:
 ---------------------------------------
       Local: http://localhost:3000
    External: http://192.168.80.144:3000
 ---------------------------------------
          UI: http://localhost:3001
 UI External: http://192.168.80.144:3001
 ---------------------------------------
[Browsersync] Serving files from: ./src
[Browsersync] Serving files from: ./build/contracts
[Browsersync] Watching files...

5. 安装 MetaMask和配置区块链网络

安装 MetaMask

【定义】MetaMask 是一款插件形式的以太坊轻客户端,开发过程中使用MetaMask和我们的dapp进行交互是个很好的选择。
作者是在本地WINDOWS的CHROME浏览器上安装MetaMask钱包工具,具体的安装方法参考文章《第一课 如何在WINDOWS环境下搭建以太坊开发环境》的“(7)安装 MetaMask 【可选】”章节。
说明下,最详细的MetaMask安装/配置文章可参考欧阳哥哥的《以太坊钱包MetaMask使用教程》

配置钱包

1. 接受隐私条款
点击浏览器地址栏右侧,MetaMask的狐狸头图标,第一次使用时,会出一个隐私提示,如下图:

隐私提示

2. 接受服务条款
点击Accept按钮,显示的是MetaMask的服务条款,如下图2-2:

image.png

Accept按钮默认是灰色的,将滚动条拉到底部,就可以点击Accept按钮了

3、创建新账号

  • 创建新账号:输入一串8位以上的密码,再次重复输入,点击CREATE按钮,即可完成。
    下面以创建新账号为例:
    作者启动大写键使用自己的通用密码设置

在点击CREATE按钮后,MetaMask会为用户创建12个英文助记词,一定要保存好这些助记词,点击SAVE SEED WORDS AS FILE可以将助词词以文件的形式保存到本地,建议使用纸笔手工记录并收藏于安全的地方。

SEED

连接开发区块链网络

默认连接的是以太坊主网(左上角显示),选择Custom RPC,添加Ubuntu的IP地址作为客户自定义RPC网络

http://192.168.80.144:8545

链接测试机以太坊客户端成功,菜单有链接成功的橙色√提示。

连接成功

这是左上角显示为Private Network,此时显示的是默认的Account 1空账号,ETH数量为0。

导入Ganache-cli的第一个钱包账号

查看“trace.log”文件,可以Ganache-cli的第一个默认钱包地址为

(0) 629551aa45c594ce822c5b4a378d01cf46fb57c15b69a61eb400a4867ffab002
(1) a90103a95ed805acc52782eaa29eb061f6c2a9431fed3ac18a683ea3143a29b6

选择MetaMask的“Import Account”输入私钥,获取账号,可以查看对应ETH余额。


输入私钥

第一个钱包账户的余额:
99.211ETH,智能合约运行已消耗

第二个钱包账户的余额:
100ETH,未消耗过

至此MetaMask的安装,配置已经完成。

6. 领养宠物

修改.\dapp-guide-pet-shop\src\index.html文件引用jquery的地址

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <!--
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
     -->
     <script src="http://libs.baidu.com/jquery/2.1.1/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->

在WINDOWS浏览器输入测试网站地址
http://192.168.80.144:3000/

可以看到宠物商店的入口界面:
可爱的狗狗

点击“Adopt”按钮,领养这个最漂亮的狗狗宝贝。MetaMask会提示我们交易的确认,如图:
要从当前账号6扣除交易费用

点击“SUBMIT”按钮成功后,这个狗狗的按钮变为"SUCCESS",该狗狗已被包养了。
领养成功

查看当前打开的账号ACCOUNT6,发现金额不再是100ETH,而是99.999ETH了。
账户余额消耗

恭喜你,开发并成功部署了一个DAPP区块链应用程序,并且领养了几只小狗狗。

6. 知识对接服务

我们在知识星球开通了区块链入门专栏,用于存放本项目的工程源码等内容,并建立专项微信群用于技术交流,欢迎加入。
分享码

7. 参考

1),Truffle官网文档
2),一步步教你开发、部署第一个Dapp应用:宠物商店
3),web3.js 1.0中文手册-接口详细涉及web3.eth.personal函数

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

推荐阅读更多精彩内容