使用Hyperledger Fabric和Composer实现区块链应用程序

目前无法绕过技术领域的是区块链话题。但除了加密货币之外,还有更多有趣的应用程序可以带来许多激动人心的软件生态系统。这也适用于Hyperledger项目,该项目提供了一个非常模块化的区块链框架。让我们看看使用Hyperledger Fabric和Composer实现区块链应用程序是多么容易。

关于项目Hyperledger

Hyperledger是一个umbrella项目的名称,在该项目下开源区块链方法和工具是协同开发的。它由Linux基金会于2015年推出,并享有IBM,英特尔和富士通等软件巨头以及大型社区的热烈参与。Hyperledger的GitHub存储库目前比以往更加活跃。任何人都可以参与开发。

在Hyperledger中,不仅开发了单个区块链框架(或平台)。相反,重点是并行采用多种方法,创造协同效应,可重复使用的组件和灵活性。从Hyperledger概念的角度来看,区块链网络与比特币或以太网等加密货币的代表无法比较。相反,Hyperledger网络的节点分布在参与组织中,这使得私有,许可或联盟区块链网络特别有趣。首先,我们可以忘记公共区块链的工作证明,股权证明和其他共识机制。所涉及的组织从应用程序业务价值和所涉及的信任中作为联合体验证彼此的交易和利益。这也很大程度上解决了可扩展性问题(我们从比特币网络中了解到)并且可以实现高交易吞吐量。

image

项目Hyperledger的不同区块链方法是Fabric,Burrow,Iroha,Indy和Sawtooth。私有,许可和联合区块链可以与所有这些区块链一起开发,但每种实现都遵循不同的方法。

我们将在本文中详细介绍Fabric,因为它拥有最活跃的社区,并且是最灵活的变体。由于其强大的模块化,fabric是普遍可用的。 “你可以将Hyperledger Fabric视为类似于Apache Web Server”,Linux基金会Hyperledger执行董事Brian Behlendorf说。其他方法更多用于在有限的环境中实施特殊情况。

Hyperledger Fabric ——灵活的区块链应用平台

使用Fabric作为平台,可以开发完全独立的分布式分类帐解决方案。Fabric包含可以尽可能自由实现的概念。区块链网络的基础是对所需组织结构的建模。每个参与者都有固定的身份,可以通过颁发的证书来识别自己。除了身份验证之外,还包括授权。使用这种基于角色的系统,可以获得许可区块链中隐私和机密性的灵活方面。对于证书和参与者的管理,可以使用结构证书颁发机构(1.0版之前的成员服务提供者)。

资产的定义(要在区块链上管理的项目)完全取决于区块链应用程序。这些资产,例如来自汽车行业的引擎块由JSON和/或二进制格式的键值对模型定义。

链代码的概念旨在基于资产及其所有者实现业务逻辑。这可用于实现Go,Java或Node.js等语言中的规则,这些规则定义读取权限或资产修改。执行链代码功能可以读取和返回资产和/或创建和修改资产并将它们存储在本地分类帐数据库中。在节点上的本地持久性更改之后,将更改提交给网络(“认可”)并在其他组织接受后插入到区块链中。在以太坊或其他公共区块链平台的背景下,可以将链码与智能合约进行比较。

image

通道用于实现隐私领域。在最简单的场景中,整个链代码部署在所有参与者加入的单个通道上。但是,为了创建封装区域并仅允许选定的参与者在其中进行通信,可以配置具有受限参与者组的通道。每个通道可以部署不同的链代码,从而可以实现功能隔离。此外,可以使用AES部分或完全加密通道中的通信。

结果,在每个通道中维护一个分布式分类帐,这可以被想象为链接交易的现金簿。每个参与者为他们所属的每个通道保留一份分类帐副本。这为网络中的每个现有信道创建了区块链数据结构。与区块链一样,交易存储在块中,这些块在单个连接列表中成为加密链。

但是,为了向客户端应用程序提供分类帐数据的单独视图,甚至可以执行针对网络的复杂读取请求。由于使用了像CouchDB这样的面向文档的数据库,这是可能的。这为连接到Fabric网络的客户端提供了灵活的数据访问。

使用Composer添加更简单的概念

Hyperledger-Composer是Hyperledger生态系统中的工具之一。你可以将其视为Fabric的框架。如果你想开发,构建和管理Fabric网络,那么即使不是强制性的,也是实用的。它引入了基于Fabric的进一步概念,以提供精美的抽象概念。

除资产外,还可以在Composer建模语言中定义网络参与者,交易和事件的方案。每种交易类型的流都通过JavaScript代码在简单的API上实现。访问控制文件可用于限制参与者对某些资源的访问权限。可以在Composer Query Language中定义对分类帐中数据的常用查询,这是一种类似SQL的语言。

然后,必须将所有必需文件打包到.bna文件中的BND(业务网络定义)。然后,可以将此存档安装在现有Fabric网络上。BND的源代码当然可以在我们首选的编辑器中进行本地开发和测试,因此可以通过Git进行版本控制。对于原型设计和演示目的,有Composer Playground。这提供了一个现代,清晰且直观可用的Web界面,可访问Composer CLI的本地配置。使用Playground,你可以轻松创建,安装,测试,编辑,导入和导出BND。

image

在Composer Playground中,你可以以用户友好的方式安装,修改和测试新的业务网络,而无需先前的样本区块链应用知识(例如车辆生命周期,汽车拍卖或农场动物跟踪)。在设置工具之后,可以在本地完成相同的操作,这样我们就可以在短时间玩游戏后离开托管游乐场。这个游乐场非常适合使用原型验证想法并了解底层的Composer和Fabric模型。

使用案例:引擎块的供应链跟踪

为了使用Hyperledger-Fabric和Composer实现私有区块链网络,以汽车行业的发动机组跟踪为例。在这种情况下,有制造商和经销商作为网络参与者。发动机及其安装的车辆显示为资产。制造商和经销商的公司被引入并被识别为网络中的组织。

Fabric链代码应提供以下功能:

  • 1.生产具有唯一序列号的发动机缸体。
  • 2.生产后将发动机缸体传送给经销商。
  • 3.跟踪车辆的序列号。
  • 4.将发动机缸体安装到注册车辆中。

下一步是安装所需的工具和设置项目。

开发环境设置和项目创建

首先,需要安装文档中列出的Fabric的所有要求。然后我们安装Composer和Composer及其相关工具本身的要求。

然后,最好让自己熟悉新环境。如果我们完全按照上一个链接的说明操作,则fabric-tools现在位于我们的主目录中。通过描述的脚本,我们可以在Docker-Compose中启动一个简单的Fabric网络,获得对等管理员访问权限并停止并再次删除它。首先,我们下载1.1版的Docker镜像并启动网络:

export FABRIC_VERSION=hlfv11 && ./downloadFabric.sh && ./startFabric.sh

在网络运行时,composer-playground web-UI可以通过composer-playground启动。它使用composer-cli的所有托管配置并访问正在运行的Fabric网络。从现在开始,我们将Fabric视为可配置的平台/基础架构,其状态通过合适的工具进行更改。我们不直接使用Fabric概念开发链代码,权限或任何模型,因为Composer提供了更多优势。

实施功能

现在我们在我们选择的目录中创建我们的BND项目。对于Yeoman(使用模板设置项目的代码生成器,如Maven Archtypes),有一个模板(hyperledger-composer:businessnetwork。但是,我已经准备了一个存储库,我们现在也可以使用JavaScript ES6和一些很好的工具。我们应该从开始分支“初始”开始。master分支具有最终版本和工作版本。我们首先克隆存储库的初始分支。

git clone -b initial git@github.com:jverhoelen/fabric-composer-engine-supplychain.git

现在我们在我们选择的编辑器中打开文件夹。Visual Studio Code非常适合Composer,因为它具有可安装的语法高亮扩展。稍作修改后你会发现它是一个NPM项目,所以我们从npm install开始安装所有依赖项。使用npm test我们可以运行单元测试,使用npm run lint我们可以测试代码样式,并且使用npm run createArchive我们可以创建the.bna文件,我们以打包格式完成业务网络定义。让我们马上试试看是否一切正常。

然后我们熟悉项目结构。lib文件夹包含实现交易处理器功能的JS文件。当然,我们想测试这个业务逻辑并将我们的单元测试存储在test/文件夹中。模型定义(参与者,资产,交易等)在models/中。

我们想首先为所需的区块链网络建模。为此,我们删除模型文件的内容,并在第一行为其指定一个新的命名空间:

namespace org.acme.enginesupplychain

我们为参与者制造商和经销商建模,并使用Composer建模语言的继承。我们还希望每个参与者除了姓名外还有一个可选地址。我们将这些属性放入一个概念中:

participant Member identified by memberId {
    o String memberId
    o String name
    o Address address optional
}
 
participant Manufacturer extends Member {
}
 
participant Merchant extends Member {
}
 
concept Address {
    o String country
    o String city
    o String street
    o String streetNo
}

然后我们介绍我们网络的资产:引擎块和稍后安装引擎的汽车。在这里,我们了解资产和参与者可以互相参考。引用始终指向任何类型的现有资源。我们以小“o”开头的属性总是存在于资源本身中。

asset Engine identified by engineId {
    o String engineId
    o EngineProperties data
 
    --> Manufacturer manufacturer
    --> Car currentCar optional
    --> Merchant merchant optional
}
 
asset Car identified by carId {
    o String carId
    o String legalDocumentId
}
 
concept EngineProperties {
    o String brand
    o String model
    o Double horsePower
    o Double cubicCapacity
    o Integer cylindersAmount
}

在对系统建模之后,我们定义了可以在现有资产和参与者之上执行的所有可用操作。这些是交易资源。之后,我们为以下每个交易模型测试并实现基础交易逻辑。

transaction EngineMerchantTransfer {
    --> Engine engine
    --> Merchant merchant
}
 
transaction EngineCarInstallation {
    --> Engine engine
    --> Car car
}
 
transaction EngineCreation {
    --> Manufacturer manufacturer
    o EngineProperties data
}
 
transaction CarCreation {
    o String legalIdDocument
}

现在我们已经定义了可能发生的事情,我们可以在提交交易时开始实现它如何影响分类帐状态。首先,我们致力于创建引擎资产。引擎应该以UUID格式获得随机生成的ID,并且应该从一开始就始终属于制造商。所以我们清空logic.js文件并从头开始。我们定义常量modelsNamespace和函数uuid,因为我们将更频繁地需要它们。接下来是createEngineAsset函数。函数上方的文档块非常重要,以便Composer可以识别打包代码时实现的交易类型。

/* global getAssetRegistry getFactory */
 
const modelsNamespace = 'org.acme.enginesupplychain'
function uuid() {
    const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1)
    return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`
}
 
/**
* Creation of a Engine asset triggered by physical production.
* @param {org.acme.enginesupplychain.EngineCreation} tx - the transaction to create an engine
* @transaction
*/
async function createEngineAsset(tx) { // eslint-disable-line no-unused-vars
    const engineRegistry = await getAssetRegistry(modelsNamespace + '.Engine')
    const engine = getFactory().newResource(modelsNamespace, 'Engine', uuid())
    const engineData = getFactory().newConcept(modelsNamespace, 'EngineProperties')
 
    engine.data = Object.assign(engineData, tx.data)
    engine.manufacturer = tx.manufacturer
 
    await engineRegistry.add(engine)
}

通过这种方式,我们还实现了其他交易类型EngineMerchantTransferEngineCarInstallationCarCreation

/**
* An engine is transfered to a merchant.
* @param {org.acme.enginesupplychain.EngineMerchantTransfer} tx - the engine transfer transaction
* @transaction
*/
async function transferEngineToMerchant(tx) { // eslint-disable-line no-unused-vars
    const engineRegistry = await getAssetRegistry(modelsNamespace + '.Engine')
    tx.engine.merchant = tx.merchant
 
    await engineRegistry.update(tx.engine)
}
 
/**
* An engine is installed in a car.
* @param {org.acme.enginesupplychain.EngineCarInstallation} tx - the engine into car installation transaction
* @transaction
*/
async function installEngineToCar(tx) { // eslint-disable-line no-unused-vars
    const engineRegistry = await getAssetRegistry(modelsNamespace + '.Engine')
    if (tx.car) {
        tx.engine.currentCar = tx.car
        await engineRegistry.update(tx.engine)
    } else {
        return Promise.reject('No target car was set on the transaction!')
    }
}
 
/**
* A car is created.
* @param {org.acme.enginesupplychain.CarCreation} tx - transaction to create a new car
* @transaction
*/
async function createCar(tx) { // eslint-disable-line no-unused-vars
    const carRegistry = await getAssetRegistry(modelsNamespace + '.Car')
    const factory = getFactory()
    const carId = uuid()
    const car = factory.newResource(modelsNamespace, 'Car', carId)
    car.legalDocumentId = tx.legalIdDocument
 
    await carRegistry.add(car)
}

功能本身的单元测试相对简单,如果我们有经验,我们不需要了解更多。只有对此所需的对象的boostrapping仍然有点超载样板代码。测试首先启动内存中的Fabric网络,在其上安装业务网络,然后以默认管理员身份对其进行身份验证。因为这个Composer提供了库composer-admincomposer-clientcomposer-commoncomposer-connector-embedded。在测试设置之后,我们现在可以针对嵌入式网络编写测试用例。由于其长度,设置代码未包含在列表中,但可以在test/EngineSupplychainSpec.js中的主分支上查看和测试。

用于测试交易类型的单元测试用例通常具有类似的模式。他们使用其属性和关系重新创建交易,针对网络执行交易,然后检查所涉及的资产和参与者的数据状态。我们来看看createEngineAsset的现有测试用例。

describe(‘EngineSupplychainSpec’, () => {
 
   // setup is done in the before and beforeEach hook
   // results are the bnc (BusinessNetworkConnection), target namespace
   // as well as test assets, participants and required registries
   describe('createEngineAsset', () => {
       it('should create an Engine by submitting a valid EngineCreation transaction', async () => {
           const factory = bnc.getBusinessNetwork().getFactory()
 
           const engineCreationTrans = factory.newTransaction(namespace, 'EngineCreation')
           engineCreationTrans.data = factory.newConcept(namespace, 'EngineProperties')
           engineCreationTrans.data.brand = 'Audi'
           engineCreationTrans.data.model = 'Fancy engine model'
           engineCreationTrans.data.horsePower = 400
           engineCreationTrans.data.cubicCapacity = 4000
           engineCreationTrans.data.cylindersAmount = 10
 
           const manufacturerRegistry = await bnc.getParticipantRegistry(namespace + '.Manufacturer')
           await manufacturerRegistry.addAll([])
           engineCreationTrans.manufacturer = factory.newRelationship(namespace, 'Manufacturer', testManufacturer.$identifier)
 
           await bnc.submitTransaction(engineCreationTrans)
 
           const allEngines = await engineRegistry.getAll()
           allEngines.length.should.equal(2)
       })
   })
})

在Hyperledger Composer中实现业务网络定义的方法应该通过这些见解变得清晰。此外,BND可以为我们定义更多的东西。在permissions.acl中,你可以使用访问控制语言为给定简单条件的参与者定义访问限制。对于许多应用程序,事件和查询功能也非常有用和有趣。

最后,我们来看看主分支上的解决方案。所有这些要求都已在其中实施和测试。我们现在用npm run createArchive生成完成的.bna文件,然后在dist/文件夹中。我们现在可以将它导入到我们在控制台中启动的Composer Playground中,以便在我们的本地Fabric网络上进行尝试。通过Web UI的方式应该是不言自明的,但它也是正式记录的。

总结和展望

我们已经了解了Hyperledger项目的重要部分。具体来说,我们现在知道Fabric作为具有基本概念的区块链平台。Composer添加了许多重要概念,使开发人员可以非常方便地实施和管理区块链网络。通过实施的关于发动机缸体生产和跟踪的区块链应用案例,我们了解了一个简单但功能强大的私人/联盟区块链用例。

最终的区块链网络最初只在本地执行。我们还没有扩展同行组织和订购服务的配置。但我们可以轻松添加更多组织并通过多个主机分发对等节点。对于由真正的组织联盟跨越的区块链网络,我们仍然有一些问题需要解决:

我们如何管理组织和对等节点?组织如何自动将新的对等节点添加到网络中?我们如何获得一个可以抵御失败的公平和同质的网络?客户如何与网络通信?

这个仍然年轻的平台已经提供了很多功能和舒适性。但是,仍有许多任务需要完成。从开发人员的角度来看,单元测试代码看起来仍然非常臃肿。很快就会出现库,通过它可以更容易地实现通常的测试模式。我们迫切希望看到Hyperledger等项目将如何继续推动业务中分布式账本技术的适应性。

故障排除

确保所有工具都与Fabric 1.1兼容。这意味着必须下载此版本的所有docker镜像。应安装最新版本的Composer和Composer Playground。目前是v0.19.1。本文中Fabric和Composer文档的所有链接都是故意修复到Fabric 1.1和最新的Composer版本。

======================================================================

分享一些Hyperledger fabric、以太坊、比特币、EOS、Tendermint等区块链相关的交互式在线编程实战教程:

  • Hyperledger Fabric 区块链开发详解,本课程面向初学者,内容即包含Hyperledger Fabric的身份证书与MSP服务、权限策略、信道配置与启动、链码通信接口等核心概念,也包含Fabric网络设计、nodejs链码与应用开发的操作实践,是Nodejs工程师学习Fabric区块链开发的最佳选择。
  • Hyperledger Fabric java 区块链开发详解,课程面向初学者,内容即包含Hyperledger Fabric的身份证书与MSP服务、权限策略、信道配置与启动、链码通信接口等核心概念,也包含Fabric网络设计、java链码与应用开发的操作实践,是java工程师学习Fabric区块链开发的最佳选择。
  • java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。
  • 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • ERC721以太坊通证实战,课程以一个数字艺术品创作与分享DApp的实战开发为主线,深入讲解以太坊非同质化通证的概念、标准与开发方案。内容包含ERC-721标准的自主实现,讲解OpenZeppelin合约代码库二次开发,实战项目采用Truffle,IPFS,实现了通证以及去中心化的通证交易所。
  • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
  • java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
  • php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
  • c#比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在C#代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是C#工程师不可多得的比特币开发学习课程。
  • EOS入门教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
  • 深入浅出玩转EOS钱包开发,本课程以手机EOS钱包的完整开发过程为主线,深入学习EOS区块链应用开发,课程内容即涵盖账户、计算资源、智能合约、动作与交易等EOS区块链的核心概念,同时也讲解如何使用eosjs和eosjs-ecc开发包访问EOS区块链,以及如何在React前端应用中集成对EOS区块链的支持。课程内容深入浅出,非常适合前端工程师深入学习EOS区块链应用开发。
  • tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。

汇智网原创翻译,转载请标明出处。这里是原文使用Hyperledger Fabric和Composer实现区块链应用程序

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

推荐阅读更多精彩内容

  • 我们为希望尽快学习Hyperledger fabric 的java工程师上线了一门教程Fabric java 区块...
    编程狂魔阅读 3,399评论 0 2
  • Hyperledger Hyperledger is an open source collaborative e...
    黑马程序员小华华阅读 1,455评论 1 4
  • ❤️感赏我的孩子们给我买了生日蛋糕,上面写着十年育树百年育人。我好感动,第一次当班主任和老师,能收到那么多学生的爱...
    幸福萍宝阅读 206评论 0 3
  • 它躲在我们每个人的心里 很多时候他会不自觉的跑出来 很多人都不敢面对 不断逃避 却发现它和和我们越来越近 孤独充满...
    董辉_e155阅读 162评论 0 0
  • 每天坚持感恩一切都会非常美好,时刻觉察自己的意识。 感恩儿子的脾气让我看到我自己对他的控制欲,现在用六时书觉察自己...
    生活就该甜甜蜜蜜阅读 120评论 0 0