纯js实现区块链核心模型

想直接看code的同学可移步第四节

前言

区块链技术是近几年最火爆的风口之一,不仅仅是比特币,手机、物流业、金融业、供应链。。。各行各业都在使用区块链技术,甚至有国家在使用区块链货币作为法定货币(瑞典、迪拜试行,委内瑞拉2017年发布石油币)。

如此神奇的技术,即使不是技术从业者,也该了解一番。何况是身为程序员的你?

本文将以尽量通俗易懂的方式介绍区块链的前世今生,以及使用前端纯js实现区块链的核心模型。


一、概念

维基百科:

区块链是借由密码学串接并保护内容的串连交易记录(又称区块)。

百度百科:

区块链是分布式数据存储、加密算法、共识机制、智能合约等计算机技术的新型应用模式。

这是网上两个比较官方的解释,二者从不同维度,使用一些黑话(行业术语)来描述了区块链,但我们仍然无法理解区块链的本质。以下将对这些黑话做详细介绍:

1.1 分布式数据存储

也称分布式账本,即去中心化,每个节点记录完整账目并保持独立。

1.2 加密算法

采用安全的非对称加密算法(sha256)和授权技术。
存储在区块链上的交易信息是公开的,但是账户身份信息是高度加密的,只有在数据拥有者授权的情况下才能访问到,从而保证了数据的安全和个人的隐私。

1.3 共识机制

少数服从多数,人人平等。
以比特币为例,采用的是工作量证明:只有在控制了全网超过51%的记账节点的情况下,才有可能伪造出一条不存在的记录。当加入区块链的节点足够多的时候,这基本上不可能,从而杜绝了造假的可能。

1.4 智能合约

智能合约是基于这些可信的不可篡改的数据,可以自动化的执行一些预先定义好的规则和条款。
以保险为例,如果说每个人的信息(包括医疗信息和风险发生的信息)都是真实可信的,那就很容易的在一些标准化的保险产品中,去进行自动化的理赔。

简而言之,区块链主要解决交易的信任和安全问题,因此它针对这个问题提出了以上四个技术创新。

二、比特币

比特币单独拿出来作为一节介绍,是因为它是目前区块链最有价值的应用场景,大部分人也是因为比特币而知晓和想要了解区块链。

首先我们来看看比特币有多疯狂,下面是截止发稿日期的比特币价格曲线:


比特币价格曲线

可以看到,当前一个比特币价格是4万7美刀,也就是30万人民币!比一手茅台还贵。请注意,这是一个比特币的价格。

另外,比特币发行之初,一直都无人问津,价格低廉,没有人信任这个虚幻的概念。
直到2017年开始腾飞,之后一路大起大落,2020年也就是去年底,开始指数增长!(主要原因是全球疫情肆虐,加上美国1.5万亿美元刺激)

比特币的疯涨产生了无数造富神话,许多网友也后悔不已:


网友评论.png

茅台是中国的造富神话,但和比特币来比只是弟弟:

  • 2009年投资10万买茅台,2021年1月你的资产是386万;
  • 2009年投资10万买比特币,20201年1月你的资产是4700亿!

值得注意的是,比特币已经进入美国银行的官方资产回报名单,也就是说,曾经饱受争议的加密货币,已经越来越得到社会的认可了,相信其风险也会得到控制。

让我们先冷静下来,看看比特币的前世今生:

2.1 货币的演变

比特币的本质是货币,货币的本质是一般等价物。
先来聊聊比特币的前世——货币的演变:
以物易物 --> 贝壳 --> 金属 --> 纸币 --> 信用卡 --> 数字货币 --> 加密货币

货币的特性,从数量稀有可控、牢固或易于存储,到今日的价值共识,安全可靠(如国家法定货币以政府信用背书)。
货币的理念越发先进,只要有人认可,就能够充当货币。

实际上,比特币已经在很多地方能够用来交易,并且只是世界上小部分人认可。

2.2 比特币的历史

接下来是比特币的今生:

  • 2008年10月,全球金融危机,大量银行破产。乱世出英雄,日本天才程序员、密码学爱好者中本聪发明区块链电子现金系统、去中心化数字货币——比特币。当时各国政府信用降低,而比特币不依赖任何政府或第三方,但未受到重视;
  • 2009年1月,中本聪挖出第一个创世区块(Genesis Block),并获得50个比特币的奖励。创世区块诞生(第四节会介绍),但此时比特币没有任何价值;
  • 2010年5月,一个程序员用1万个比特币买了一块价值25美元的披萨,宣告比特币正式有了第一个公允汇率(很出名的梗,这块史上最贵披萨今天的价格是5亿美金);
  • 2011年2月,比特币首次达1美元,与美元等价,随后与英镑等其他货币兑换交易开张;
  • 2013年1月,塞浦路斯债务危机,比特币单枚价值265美元;货币危机来临,传统金融信用下降,人们采用数字货币来规避短期的风险;
  • 2017年12月,全年涨幅高达1700%,价格远超黄金(1200美元/盎司);
  • 今天,比特币价格站上47000美金,达到巅峰!投资者们为之疯狂。

比特币基于可靠的区块链技术,让全球共识,跨越一切第三方,以技术为背书。
其算法决定比特币总量只有2100万个,符合了货币数量可控的特性。

2.3 世界矿都

全球约有70%比特币产自中国,中国大多数比特币产自四川---丰富的水电资源和良好的气候条件,挖矿成本低于全球大多数地区。
挖矿,实际上就是依靠计算机大量的计算,得出与区块匹配的hash,进而获得相应比特币的奖励。
挖矿已经成为一门产业,电费是矿场最大成本70%以上,依靠水电站、火电厂发电,所以川西高原深山成为矿场的绝佳选址。许多人投资购买矿机、雇佣矿工(维护机房),成立矿场。
只要比特币价格持续上涨,这门生意的利润就会越高,涌入的人就会越多。

三、应用

除了比特币,区块链技术已经应用在各行各业。

  • 金融业:
    银行、保险、虚拟货币甚至国家法定区块链货币;通过完全改变交易流程和记录保存的方式,大幅降低交易成本,显著提升效率,并且安全可靠。
  • 联络与物联网:
    三星和IBM正在开发“ADEPT”区块链技术,一个智能设备的去中心化网络。
    大数据管理,安全和透明性,以及对基于相互连接的智能设备之间服务交换的
    微交易带来的便利。
  • 供应链管理:
    利用‘去中心化’特性,解决传统的依靠单一‘链主’的协调机制局限性。
  • 共享经济:
    区块链作为一个去中心化的一致性。共享数据账本,在此架构下,整个系统
    的运作都是公开透明的,它将让共享经济变得更加容易。
  • 云存储:
    区块链技术可以支持加密数据的分布式存储,是协同云存储的核心。
  • 艺术与生活:
    解决音乐、画作等艺术品的版权问题;HTC已发布区块链手机。
    。。。。。。

四、code模型

终于来到了本文的核心——code环节。
具体分为以下三个步骤:

  1. 实现一个基本的区块链;
  2. 实现 POW(proof-of-work工作量证明);
  3. 交易与挖矿奖励。

4.1 实现一个基本区块链

4.1.1 区块链

区块链是由一个个任何人都可以访问的区块构成的公共数据库。这好像没什么特别的,不过它们有一个有趣的属性:它们是不可变的。

一旦一个区块被添加到区块链中,除非让剩余的其余区块失效,否则它是不会再被改变的。

这就是为什么加密货币是基于区块链的原因。你肯定不希望人们在交易完成后再变更交易!

4.1.2 创造一个区块

区块链是由许许多多的区块链接在一起的。链上的区块通过某种方式允许我们检测到是否有人操纵了之前的任何区块。

那么我们如何确保数据的完整性呢?每个区块都包含一个基于其内容计算出来的 hash。同时也包含了前一个区块的 hash。

下面是一个区块类用 JavaScript 写出来大致的样子:

const SHA256 = require("crypto-js/sha256");
class Block {
  constructor(index, timestamp, data, previousHash = '') {
    this.index = index;
    this.previousHash = previousHash;
    this.timestamp = timestamp;
    this.data = data;
    this.hash = this.calculateHash();
  }
  calculateHash() {
    return SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.data)).toString();
  }
}

这里引入了上文提到的sha256算法:

  • 安全散列算法(英语:Secure Hash Algorithm,缩写为SHA)是一个密码散列函数家族,是FIPS(美国联邦信息处理标准)所认证的安全散列算法。
  • SHA256长度为256bits,安全系数也高于其他散列算法,特性:
    • 1.不可以从计算结果中复原信息;
    • 2.两个不同的消息不会产生同样的计算结果

因为 JavaScript 中并不支持 sha256 所以我引入了 crypto-js 库。然后我定义了一个构造函数来初始化区块的属性。

每一个区块上都被赋予了 index 属性来告知我们这个区块在整个链上的位置。我们同时也生成了一个时间戳,以及需要在区块里存储的一些数据。最后是前一个区块的 hash。

4.1.3 创造一个链
现在我们可以在 Blockchain 类中将区块链接起来了。下面是用 JavaScript 实现的代码:

class Blockchain{
  constructor() {
    this.chain = [this.createGenesisBlock()];
  }
  // 创世区块
  createGenesisBlock() {
    return new Block(0, "01/01/2017", "Genesis block", "0");
  }
  getLatestBlock() {
    return this.chain[this.chain.length - 1];
  }
  addBlock(newBlock) {
    newBlock.previousHash = this.getLatestBlock().hash;
    newBlock.hash = newBlock.calculateHash();
    this.chain.push(newBlock);
  }
  isChainValid() {
    for (let i = 1; i < this.chain.length; i++){
      const currentBlock = this.chain[i];
      const previousBlock = this.chain[i - 1];
      if (currentBlock.hash !== currentBlock.calculateHash()) {
        return false;
      }
      if (currentBlock.previousHash !== previousBlock.hash) {
        return false;
      }
    }
    return true;
  }
}

在构造函数里,我通过创建一个包含创世块的数组来初始化整个链。第一个区块是特殊的,因为它不能指向前一个区块。

我还添加了下面两个方法:

  • getLatestBlock()返回我们区块链上最新的区块。
  • addBlock()负责将新的区块添加到我们的链上。
    为此,我们将前一个区块的 hash 添加到我们新的区块中。这样,我们就可以保持整个链的完整性。

因为只要我们变更了最新区块的内容,我们就需要重新计算它的 hash。当计算完成后,我将把这个区块推进链里(一个数组)。

最后,我创建一个 isChainValid() 来确保没有人篡改过区块链。它会遍历所有的区块来检查每个区块的 hash 是否正确。

它会通过比较 previousHash 来检查每个区块是否指向正确的上一个区块。如果一切都没有问题,它会返回 true 否则会返回 false。

4.1.4 使用区块链
我们的区块链类已经写完啦,可以真正的开始使用它了。

let testCoin = new Blockchain();
testCoin.addBlock(new Block(1, "20/07/2017", { amount: 4 }));
testCoin.addBlock(new Block(2, "20/07/2017", { amount: 8 }));

在这里我仅仅是创建了一个区块链的实例,并且命名它为 testCoin。之后我在链上添加了一些区块。

区块里可以包含任何你想要放的数据,不过在上面的代码里,我选择添加了一个带有 amount 属性的对象。

试着操作吧!
在介绍里我曾说过区块链是不可变的。一旦添加,区块就不可能再变更了。让我们试一下。

// 检查是否有效
console.log('Blockchain valid? ' + testCoin.isChainValid()); // true
// 现在尝试操作变更数据
testCoin.chain[1].data = { amount: 100 };
// 再次检查是否有效
console.log("Blockchain valid? " + testCoin.isChainValid()); // false

我会在一开始通过运行 isChainValid() 来验证整个链的完整性。我们操作过任何区块,所以它会返回 true。

之后我将链上的第一个(索引为 1)区块的数据进行了变更。之后我再次检查整个链的完整性,发现它返回了 false。我们的整个链不再有效了。

结论:
这个小栗子还远未达到完成的程度。它还没有实现 POW(工作量证明机制)或 P2P 网络来与其他矿工来进行交流。

但它确实证明了区块链的工作原理。许多人认为原理会非常复杂,但这篇文章证明了区块链的基本概念是非常容易理解和实现的。

4.2 实现POW

在上文中我们用 JavaScript 创建了一个简单的区块链来演示区块链的工作原理。

不过这个实现并不完整,很多人发现依旧可以篡改该系统。没错!我们的区块链需要另一种机制来抵御攻击。让我们来看看我们该如何做到这一点。

现在我们可以很快的创造区块,然后非常迅速的将它们添加进我们的区块链中。

不过这导致了三个问题:

  • 人们可以快速创建区块,然后在我们的链里塞满垃圾。大量的区块会导致我们区块链过载并让它无法使用。
  • 因为创建一个有效的区块太容易了,人们可以篡改链中的某一个区块,然后重新计算所有区块的 hash。即使它们已经篡改了区块,他们仍然可以以有效的区块来作为结束。
  • 你可以通过结合上述两个破绽来有效控制区块链。区块链由 P2P 网络驱动,其中节点会将区块添加到可用的最长链中。
    所以你可以篡改区块,然后计算所有其他的区块,最后添加任意多你想要添加的区块。你最后会得到一个最长的链,所有的其他节点都会接受它,然后往上添加自己的区块。

4.2.1 什么是POW(proof-of-work:工作量证明)
POW 是在第一个区块链被创造之前就已经存在的一种机制。这是一项简单的技术,通过一定数量的计算来防止滥用。

工作量是防止垃圾填充和篡改的关键。如果它需要大量的算力,那么填充垃圾就不再值得。

比特币通过要求 hash 以特定 0 的数目来实现 POW。这也被称之为难度,不过等一下!一个区块的 hash 怎么可以改变呢?

在比特币的场景下,一个区块包含有各种金融交易信息。我们肯定不希望为了获取正确的 hash 而混淆了那些数据。

为了解决这个问题,区块链添加了一个 Nonce 值。Nonce 是用来查找一个有效 hash 的次数。

而且,因为无法预测 hash 函数的输出,因此在获得满足难度条件的 hash 之前,只能大量组合尝试。寻找到一个有效的 hash(创建一个新的区块)在圈内称之为挖矿。

在比特币的场景下,POW 确保每 10 分钟只能添加一个区块。你可以想象垃圾填充者需要多大的算力来创造一个新区块,他们很难欺骗网络,更不要说篡改整个链。

4.2.2 实现 POW
该如何实现呢?我们先来修改我们区块类并在其构造函数中添加 Nonce 变量。我会初始化它并将其值设置为 0。

constructor(index, timestamp, data, previousHash = '') {
  this.index = index;
  this.previousHash = previousHash;
  this.timestamp = timestamp;
  this.data = data;
  this.hash = this.calculateHash();
  this.nonce = 0;
}

我们还需要一个新的方法来增加 Nonce,直到我们获得一个有效 hash。强调一下,这是由难度决定的。所以我们会收到作为参数的难度。

mineBlock(difficulty) {
    while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {
        this.nonce++;
        this.hash = this.calculateHash();
    }
    console.log("BLOCK MINED: " + this.hash);
}

最后,我们还需要更改一下 calculateHash() 函数。因为目前它还没有使用 Nonce 来计算 hash。

calculateHash() {
  return SHA256(this.index +
    this.previousHash +
    this.timestamp +
    JSON.stringify(this.data) +
    this.nonce
  ).toString();
}

将它们结合在一起,你会得到如下所示的区块类:

class Block {
  constructor(index, timestamp, data, previousHash = '') {
    this.index = index;
    this.previousHash = previousHash;
    this.timestamp = timestamp;
    this.data = data;
    this.hash = this.calculateHash();
    this.nonce = 0;
  }
  calculateHash() {
    return SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.data) + this.nonce).toString();
  }
  mineBlock(difficulty) {
    while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {
      this.nonce++;
      this.hash = this.calculateHash();
    }
    console.log("BLOCK MINED: " + this.hash);
  }
}

现在,我们的区块已经拥有 Nonce 并且可以被开采了,我们还需要确保我们的区块链支持这种新的行为。

让我们先在区块链中添加一个新的属性来跟踪整条链的难度。我会将它设置为 2(这意味着区块的 hash 必须以 2 个 0 开头)。

constructor() {
  this.chain = [this.createGenesisBlock()];
  this.difficulty = 2;
}

现在剩下要做的就是改变 addBlock() 方法,以便在将其添加到链中之前确保实际挖到该区块。下面我们将难度传给区块。

addBlock(newBlock) {
  newBlock.previousHash = this.getLatestBlock().hash;
  newBlock.mineBlock(this.difficulty);
  this.chain.push(newBlock);
}

大功告成!我们的区块链现在拥有了 POW 来抵御攻击了。

4.2.3 测试
现在让我们来测试一下我们的区块链,看看在 POW 下添加一个新区块会有什么效果。

我将会使用之前的代码,我们将创建一个新的区块链实例,然后往里添加 2 个区块。

let testCoin = new Blockchain();
console.log('Mining block 1');
testCoin.addBlock(new Block(1, "20/07/2017", { amount: 4 }));
console.log('Mining block 2');
testCoin.addBlock(new Block(2, "20/07/2017", { amount: 8 }));

如果你运行了上面的代码,你会发现添加新区块依旧非常快。这是因为目前的难度只有 2(或者你的电脑性能非常好)。

如果你创建了一个难度为 5 的区块链实例,你会发现你的电脑会花费大概 10 秒钟来挖矿。随着难度的提升,你的防御攻击的保护程度越高。

就像之前说的:这绝不是一个完整的区块链。它仍然缺少很多功能(像 P2P 网路)。这只是为了说明区块链的工作原理。

并且:由于单线程的原因,用 JavaScript 来挖矿并不快。

4.3 交易与挖矿奖励

在前面两部分我们创建了一个简单的区块链,并且加入了 POW 来抵御攻击。

然而我们在途中也偷了懒:我们的区块链只能在一个区块中存储一笔交易,而且矿工没有奖励。现在,让我们解决这个问题!

4.3.1 重构区块类
现在一个区块拥有 index,previousHash,timestamp,data,hash 和 nonce 属性。

这个 index 属性并不是很有用,事实上我甚至不知道为什么开始我要将它添加进去。

所以我把它移除了,同时将 data 改名为 transactions 来更语义化。

class Block{
  constructor(timestamp, transactions, previousHash = '') {
    this.previousHash = previousHash;
    this.timestamp = timestamp;
    this.transactions = transactions;
    this.hash = this.calculateHash();
    this.nonce = 0;
  }
}

当我们改变区块类时,我们也必须更改 calculateHash()函数。现在它还在使用老旧的 index 和 data 属性。

calculateHash() {
  return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.transactions) + this.nonce).toString();
}

4.3.2 交易类
在区块内,我们将可以存储多笔交易。因此我们还需要定义一个交易类,这样我们可以锁定交易应当具有的属性:

class Transaction{
  constructor(fromAddress, toAddress, amount){
    this.fromAddress = fromAddress;
    this.toAddress = toAddress;
    this.amount = amount;
  }
}

这个交易例子非常的简单,仅仅包含了发起方(fromAddress)和接受方(toAddress)以及数量。如果有需求,你也可以在里面加入更多字段,不过这个只是为了最小实现。

4.3.3 交易存储
当前的最大任务:调整我们的区块链来适应这些新变化。我们需要做的第一件事就是存储待处理交易的地方。

正如你所知道的,由于 POW,区块链可以稳定的创建区块。在比特币的场景下,难度被设置成大约每 10 分钟创建一个新区块。但是可以在创造两个区块之间提交新的交易。

为了做到这一点,首先需要改变我们区块链的构造函数,以便他可以存储待处理的交易。

我们还将创造一个新的属性,用于定义矿工获得多少钱作为奖励:

class Blockchain{
  constructor() {
    this.chain = [this.createGenesisBlock()];
    this.difficulty = 5;
    // 在区块产生之间存储交易的地方
    this.pendingTransactions = [];
    // 挖矿回报
    this.miningReward = 100;
  }
}

下一步,我们将调整我们的 addBlock()方法。不过我的调整是指删掉并重写它!我们将不再允许人们直接为链上添加区块。相反,他们必须将交易添加至下一个区块中。

而且我们将 addBlock()更名为 createTransaction(),这看起来更语义化:

createTransaction(transaction) {
  // 这里应该有一些校验!
  // 推入待处理交易数组
  this.pendingTransactions.push(transaction);
}

4.3.4 挖矿奖励
人们现在可以将新的交易添加到待处理交易的列表中。但无论如何,我们需要将他们清理掉并移入实际的区块中。

为此,我们来创建一个 minePendingTransactions()方法。这个方法不仅会挖掘所有待交易的新区块,而且还会向采矿者发送奖励。

minePendingTransactions(miningRewardAddress) {
  // 用所有待交易来创建新的区块并且开挖..
  let block = new Block(Date.now(), this.pendingTransactions);
  block.mineBlock(this.difficulty);
  // 将新挖的看矿加入到链上
  this.chain.push(block);
  // 重置待处理交易列表并且发送奖励
  this.pendingTransactions = [
      new Transaction(null, miningRewardAddress, this.miningReward)
  ];
}

请注意,该方法采用了参数 miningRewardAddress。如果你开始挖矿,你可以将你的钱包地址传递给此方法。

一旦成功挖到矿,系统将创建一个新的交易来给你挖矿奖励(在这个栗子里是 100 枚币)。

有一点需要注意的是,在这个栗子中,我们将所有待处理交易一并添加到一个区块中。但实际上,由于区块的大小是有限制的,所以这是行不通的。

在比特币里,一个区块的大小大概是 2MB。如果有更多的交易能够挤进一个区块,那么矿工可以选择哪些交易达成哪些交易不达成(通常情况下费用更高的交易容易获胜)。

4.3.5 地址的余额
在测试我们的代码前让我们再做一件事!如果能够检查我们区块链上地址的余额将会更好。

getBalanceOfAddress(address){
  let balance = 0; // you start at zero!
  // 遍历每个区块以及每个区块内的交易
  for(const block of this.chain){
    for(const trans of block.transactions){
      // 如果地址是发起方 -> 减少余额
      if(trans.fromAddress === address){
        balance -= trans.amount;
      }
      // 如果地址是接收方 -> 增加余额
      if(trans.toAddress === address){
        balance += trans.amount;
      }
    }
  }
  return balance;
}

4.3.6 测试
好吧,我们已经完成并可以正常工作。为此,我们创建了一些交易:

let testCoin = new Blockchain();
console.log('Creating some transactions...');
testCoin.createTransaction(new Transaction('address1', 'address2', 100));
testCoin.createTransaction(new Transaction('address2', 'address1', 50));

这些交易目前都处于等待状态,为了让他们得到证实,我们必须开始挖矿:

console.log('Starting the miner...');
testCoin.minePendingTransactions('my-address');

当我们开始挖矿,我们也会传递一个我们想要获得挖矿奖励的地址。在这种情况下,我的地址是 my-address。

之后,让我们检查一下 my-address 的账户余额:

console.log('Balance of My address is', testCoin.getBalanceOfAddress('my-address'));
// 输出: 0

我的账户输出竟然是 0?!等等,为什么?难道我不应该得到我的挖矿奖励么?如果你仔细观察代码,你会看到系统会创建一个交易,然后将您的挖矿奖励添加为新的待处理交易。

这笔交易将会包含在下一个区块中。所以如果我们再次开始挖矿,我们将收到我们的 100 枚硬币奖励!

console.log('Starting the miner again!');
testCoin.minePendingTransactions("my-address");
console.log('Balance of My address is', testCoin.getBalanceOfAddress('my-address'));
// 输出: 100

4.3.7 局限性与结论
现在我们的区块链已经可以在一个区块上存储多笔交易,并且可以为矿工带来回报。

不过,还是有一些不足:发送货币时,我们不检查发起人是否有足够的余额来实际进行交易。

然而,这其实是一件容易解决的事情。我们也没有创建一个新的钱包和签名交易(传统上用公钥/私钥加密完成)。

至此,我们的区块链核心模型已基本实现!

结语

本文介绍了区块链的概念、应用场景,以及比特币的前世今生,最后使用js实现了一个基本模型。
每一个新兴技术或概念,都有时代赋予它的意义。
社会在进步,技术在创新,让我们跟上时代的步伐,感受浪潮之美!

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

推荐阅读更多精彩内容

  • 我不时有一种状态,就是翻遍了所有课程、书籍,就是不知道要做学什么好,像只无头苍蝇。每当有这种感觉的时候,我告诉自己...
    ceido阅读 3,877评论 0 1
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,471评论 28 53
  • 信任包括信任自己和信任他人 很多时候,很多事情,失败、遗憾、错过,源于不自信,不信任他人 觉得自己做不成,别人做不...
    吴氵晃阅读 6,133评论 4 8
  • 步骤:发微博01-导航栏内容 -> 发微博02-自定义TextView -> 发微博03-完善TextView和...
    dibadalu阅读 3,095评论 1 3
  • 人工智能是什么?什么是人工智能?人工智能是未来发展的必然趋势吗?以后人工智能技术真的能达到电影里机器人的智能水平吗...
    ZLLZ阅读 3,733评论 0 5