Cosmos 基础 -- Ignite CLI(二)Module basics: Blog

news2024/11/26 9:06:06

一、快速入门

Ignite CLI version: v0.26.1

在本教程中,我们将使用一个模块创建一个区块链,该模块允许我们从区块链中写入和读取数据这个模块将实现创建和阅读博客文章的功能,类似于博客应用程序。最终用户将能够提交新的博客文章,并查看区块链上现有文章的列表。本教程将指导您完成创建和使用此模块与区块链交互的过程。

本教程的目标是提供创建反馈循环的逐步说明,该反馈循环允许您向区块链提交数据并从区块链读取该数据。在本教程结束时,您将实现一个完整的反馈循环,并能够使用它与区块链进行交互。

首先,用Ignite CLI创建一个新的博客区块链:

$ ignite scaffold chain blog

为了创建使用区块链的博客应用程序,我们需要定义应用程序的需求。我们希望应用程序在区块链上存储Post类型的对象。这些对象应该有两个属性:titlebody

除了在区块链上存储文章外,我们还希望为用户提供对这些文章执行CRUD(创建、读取、更新和删除)操作的能力。这将允许用户创建新的文章,阅读现有文章,更新现有文章的内容,以及删除不再需要的文章。

Ignite CLI的特性之一是能够生成实现基本CRUD功能的代码。这是通过使用脚手架命令来实现的,可以使用这些命令快速生成在应用程序中创建、读取、更新和删除数据所需的代码。

Ignite CLI能够为存储在不同类型数据结构中的数据生成代码。这包括列表(lists,按递增整数索引的数据集合)、映射(maps,按自定义键索引的集合)和简单类型(singles,数据的单个实例)。通过使用这些不同的数据结构,可以定制应用程序以满足特定的需求。例如,如果您正在构建一个博客应用程序,您可能希望使用一个列表来存储所有文章,每个文章都用一个整数作为索引。或者,您也可以使用一个map根据每个文章的唯一标题来索引它,或者使用一个map来存储单个文章。数据结构的选择取决于应用程序的特定需求。

除了您选择的数据结构之外,Ignite CLI还要求您提供它将为其生成代码的数据类型的名称,以及描述数据类型的字段。例如,如果您正在创建一个博客应用程序,您可能想要创建一个名为Post的类型,其中包含文章的titlebody字段。Ignite CLI将使用这些信息生成必要的代码,用于在应用程序中创建、读取、更新和删除这种类型的数据。

进入blog 目录,执行ignite scaffold list命令:

$ cd blog
$ ignite scaffold type post title body creator id:uint

在这里插入图片描述
现在您已经使用Ignite CLI为您的应用程序生成了代码,让我们回顾一下它创建了什么。Ignite CLI 将为您指定的数据结构和数据类型生成代码,以及操作这些数据所需的基本CRUD操作的代码。这段代码将为您的应用程序提供坚实的基础,您可以进一步定制它以满足您的特定需求。通过检查 ignite CLI 生成的代码,您可以确保它满足您的需求,并更好地理解如何使用该工具构建应用程序

Ignite CLI在proto/blog/目录中生成了几个文件和修改。这些包括:

  • post.proto: 这是一个协议缓冲文件,定义Post类型,包含字段titlebodyidcreator

  • tx.proto: 该文件已被修改为包含三个RPCs (远程过程调用):CreatePostUpdatePostDeletePost每个 RPC 都对应一个Cosmos SDK消息,可用于在文章上执行相应的CRUD操作

  • query.proto:该文件已被修改为包含两个查询:PostPostAllPost查询可用于根据其ID检索单个文章,而PostAll查询可用于检索经过分页的文章列表。

  • genesis.proto: 该文件已被修改为在模块的起源状态中包含posts,该模块定义了区块链第一次启动时的初始状态。

Ignite CLI还在x/blog/keeper目录中生成了几个新文件,为应用程序实现了特定于CRUD的逻辑。这些包括:

  • msg_server_post.go: 这个文件实现了CreatePostUpdatePostDeletePost消息的keeper方法。这些方法在模块处理相应的消息时被调用,它们处理每个CRUD操作的特定逻辑。
  • query_post.go: 该文件实现PostPostAll查询,它们分别用于按ID检索单个文章或分页的文章列表。
  • post.go: 该文件实现了keeper方法所依赖的底层函数。这些功能包括将文章追加(添加)到商店、获取单个文章、获取文章数以及管理应用程序中的文章所需的其他操作。

总的来说,这些文件为博客应用程序的CRUD功能提供了必要的实现。它们处理每个CRUD操作的特定逻辑,以及这些操作所依赖的底层函数。

x/blog/types目录中创建和修改的文件:

  • messages_post.go: 这个新文件包含Cosmos SDK消息构造函数和相关方法,如Route(), Type(), GetSigners(), GetSignBytes(), 和ValidateBasic()

  • keys.go : 该文件已被修改为包含用于存储博客文章的key前缀。通过使用key前缀,我们可以确保博客文章的数据与数据库中的其他类型的数据是分开的,并且在需要时可以轻松访问。

  • genesis.go: 该文件被修改为定义博客模块的初始(创世)状态,以及验证初始状态的Validate()函数。这是设置区块链的重要步骤,因为它定义了初始数据,并确保它根据应用程序的规则有效。

  • codec.go: 修改此文件以向编码器注册我们的消息类型,允许它们在通过网络传输时正确地序列化和反序列化。

此外,*.pb.go文件由*.proto 生成。它们包含应用程序使用的消息、rpc和查询的类型定义。这些文件是使用协议缓冲区(protobuf)工具从*.proto生成的,它允许我们以语言无关的方式定义数据的结构。

Ignite CLI通过创建和修改几个文件,为x/blog/client/cli CLI目录添加了一些功能。

  • tx_post.go: 创建此文件是为了实现CLI命令,用于广播blog模块中包含消息的事务。这些命令允许用户使用Ignite CLI轻松地向区块链发送消息。
  • query_post.go :该文件用于实现CLI命令查询blog模块。这些命令允许用户从区块链检索信息,例如博客文章列表。
  • tx.go: 该文件被修改为向链的二进制文件中添加广播事务的CLI命令。
  • query.go : 该文件还被修改为将用于查询链的CLI命令添加到链的二进制中。

如您所见,ignite scaffold list命令已经生成并修改了许多源代码文件。这些文件定义了消息的类型、处理消息时执行的逻辑,以及将所有内容连接在一起的连接。这包括创建、更新和删除博客文章的逻辑,以及检索此信息所需的查询。

要查看实际生成的代码,我们需要启动区块链。我们可以通过使用ignite chain serve命令来做到这一点,该命令将为我们构建、初始化和启动区块链:

ignite chain serve

一旦区块链开始运行,我们就可以使用二进制文件与它交互,并查看代码如何处理创建、更新和删除博客文章。我们还可以看到它如何处理和响应查询。这将使我们更好地理解应用程序是如何工作的,并允许我们测试它的功能。

ignite chain service在一个终端窗口中运行时,打开另一个终端,并使用链的二进制文件在区块链上创建一个新的博客文章:

blogd tx blog create-post 'Hello, World!' 'This is a blog post' --from alice

在这里插入图片描述

使用--from标志指定将用于签署事务的帐户时,确保指定的帐户可用是很重要的。在开发环境中,您可以在ignite chain serve命令的输出中或在config.yml文件中看到可用帐户的列表。

同样值得注意的是,在广播事务时需要使用--from标志。该标志指定将用于签署事务的帐户,这是事务处理中的关键步骤。没有有效的签名,交易将不会被区块链接受。因此,确保使用--from标志指定的帐户可用是很重要的。

在事务成功广播之后,您可以查询区块链以获得博客文章列表。为此,您可以使用blogd q blog list-post命令,该命令将返回已添加到区块链的所有博客文章的分页列表。

在这里插入图片描述
通过查询区块链,您可以验证您的交易是否成功处理,博客文章是否已添加到链中。此外,您还可以使用其他查询命令检索关于区块链上其他数据的信息,例如帐户、余额和治理建议。

让我们通过改变body 内容来修改刚刚创建的博客文章。为此,我们可以使用blogd tx blog update-post 命令,该命令允许我们更新区块链上现有的博客文章。在运行这个命令时,我们需要指定我们想要修改的博客文章的ID,以及我们想要使用的新的正文内容。运行此命令后,交易将广播到区块链,博客文章将使用新的正文内容进行更新。

现在我们已经用新内容更新了博客文章,让我们再次查询区块链以查看更改。为此,我们可以使用blogd q blog list-post命令,该命令将返回区块链上所有博客文章的列表。通过再次运行此命令,我们可以在列表中看到更新的博客文章,并且可以验证我们所做的更改已成功应用于区块链。

在这里插入图片描述

让我们试着用Bob的账户删除一篇博客文章。但是,由于博客文章是使用Alice的帐户创建的,我们可以期望区块链检查用户是否被授权删除文章。在这种情况下,由于Bob不是文章的作者,他的交易应该被区块链拒绝。

要删除一篇博客文章,我们可以使用blogd tx blog delete-post命令,它允许我们删除区块链上现有的博客文章。在运行此命令时,我们需要指定要删除的博客文章的ID,以及要用于签署事务的帐户。在本例中,我们将使用Bob的帐户来签署交易。

运行此命令后,事务将广播到区块链。但是,由于Bob不是文章的作者,区块链应该拒绝他的交易,博客文章也不会被删除。这是区块链如何执行规则和权限的示例,它表明只有经过授权的用户才能对区块链进行更改。

在这里插入图片描述
现在,让我们再次尝试删除博客文章,但这次使用Alice的帐户。既然Alice是这篇博客文章的作者,她应该有权删除它。

在这里插入图片描述
为了检查Alice是否成功删除了博客文章,我们可以再次查询区块链以获得文章列表。
在这里插入图片描述
恭喜你成功完成了使用Ignite CLI创建博客的教程!按照说明,您已经学习了如何创建一个新的区块链,为具有CRUD功能的“post”类型生成代码,启动一个本地区块链,并测试您的博客的功能。

现在您已经有了一个简单应用程序的工作示例,您可以使用Ignite生成的代码进行实验,看看更改如何影响应用程序的行为。这是一项很有价值的技能,因为它将允许您定制应用程序以满足特定需求并改进应用程序的功能。您可以尝试更改数据结构或数据类型,或向代码添加其他字段或功能。

在接下来的教程中,我们将仔细研究Ignite生成的代码,以便更好地理解如何构建区块链。通过自己编写一些代码,我们可以更深入地了解Ignite的工作原理,以及如何使用它在区块链上创建应用程序。这将帮助我们更多地了解Ignite CLI的功能,以及如何使用它来构建健壮而强大的应用程序。请关注这些教程,并准备好与Ignite一起深入区块链的世界!

二、Blog – In-depth tutorial

在本教程中,您将学习如何从头开始使用Ignite CLI创建Cosmos SDK区块链的博客应用程序。这意味着您将负责设置必要的类型、消息和查询,并编写在区块链上创建、读取、更新和删除博客文章的逻辑。

您将构建的应用程序的功能将与Ignite CLI命令Ignite scaffold list post title body生成的功能相同,但您将手动执行,以便更深入地了解该过程。通过本教程,您将学习如何使用Ignite CLI在Cosmos SDK区块链上构建一个博客应用程序。

2.1 创建结构

使用以下命令创建一个新的区块链:

# blog 以 example 代替

ignite scaffold chain blog

这将创建一个名为blog/的新目录,其中包含区块链应用程序所需的文件和目录。接下来,通过运行以下命令导航到新创建的目录:

$ cd blog

因为你的应用程序将存储和操作博客文章,你将需要创建一个Post类型来表示这些文章。您可以使用以下Ignite CLI命令:

$ ignite scaffold type post title body creator id:uint

在这里插入图片描述

这将创建一个带有四个字段的Post类型:string类型的title, body, creatoruint类型的id

在使用Ignite的代码脚手架命令后,将更改提交到Git等版本控制系统是一个很好的实践。这将允许您区分Ignite自动做出的更改和开发人员手动做出的更改,还允许您在必要时回滚更改。您可以使用以下命令将更改提交到Git:
在这里插入图片描述

2.1.1 创建消息

接下来,您将为您的博客文章实现CRUD(创建、读取、更新和删除)操作。因为创建、更新和删除操作会改变应用程序的状态,所以它们被认为是写操作。在Cosmos SDK区块链中,通过广播包含触发状态转换的消息的交易(transactions)来改变状态。要创建广播和处理带有“create post”消息的交易的逻辑,您可以使用以下Ignite CLI命令:

ignite scaffold message create-post title body --response id:uint

在这里插入图片描述

这将创建一个“create post”消息,包含两个字段:titlebody,它们都是string类型。post将存储在类似于列表的数据结构的键值存储中,其中它们的索引是一个递增的整数ID。当一个新文章被创建时,它将被分配一个整数ID。--response标志用于返回uint类型的id,作为对“create post”消息的响应。

要在应用程序中更新特定的博客文章,需要创建一个名为“update post”的消息,该消息接受三个参数:titlebodyiduint类型的id参数对于指定要更新的博客文章是必要的。您可以使用Ignite CLI命令创建此消息:

ignite scaffold message update-post title body id:uint

在这里插入图片描述

要在应用程序中删除特定的博客文章,需要创建一个名为“delete post”的消息,该消息只接受要删除的文章的id。您可以使用Ignite CLI命令创建此消息:

ignite scaffold message delete-post id:uint

在这里插入图片描述

2.1.2 创建查询

查询(Queries)允许用户从区块链状态检索信息。在您的应用程序中,您将有两个查询:“show post”和“list post”。“show post”查询将允许用户通过ID检索特定的文章,而“list post”查询将返回所有存储的文章的分页列表。

创建“show post”查询,可以使用下面的Ignite CLI命令:

ignite scaffold query show-post id:uint --response post:Post

在这里插入图片描述

该查询将接受类型为uintid作为参数,并将返回类型为Post post作为响应。

要创建“list post”查询,您可以使用以下Ignite CLI命令:

ignite scaffold query list-post --response post:Post --paginated

该查询将在分页输出中返回Post类型的post--paginated标志表示查询应该以分页格式返回结果,允许用户一次检索特定页面的结果。

总结

祝贺您完成了区块链应用程序的初始设置!您已经成功地创建了一个“post”数据类型,并生成了处理三种类型的消息(创建、更新和删除)和两种类型的查询(列出和显示文章)所需的代码。

但是,此时,您所创建的消息将不会触发任何状态转换,并且您所创建的查询将不会返回任何结果。这是因为Ignite只为这些特性生成样板代码,而实现必要的逻辑以使它们发挥作用则取决于您。

在本教程的下一章中,您将学习如何实现消息处理和查询逻辑以完成区块链应用程序。这将涉及编写代码来处理您创建的消息和查询,并使用它们来修改或从区块链的状态中检索数据。在此过程结束时,您将在Cosmos SDK区块链上拥有一个功能齐全的博客应用程序。

2.2 创建文章

在本章中,我们将关注处理“create post”消息的过程。这涉及到使用一种特殊类型的函数,称为keeper方法Keeper方法负责与区块链交互,并根据消息中提供的指令修改其状态

当接收到“create post”消息时,将调用相应的keeper方法并将该消息作为参数传递。然后,keeper方法可以使用store对象提供的各种getter和setter函数来检索和修改区块链的当前状态。这允许keeper方法有效地处理“create post”消息,并对区块链进行必要的更新。

为了保持访问和修改store对象的代码整洁,并与keeper方法中实现的逻辑分离,我们将创建一个名为post.go的新文件。该文件将包含专门设计用于处理与在区块链中创建和管理文章相关的操作的函数。

2.2.1 添加文章到 store

# x/blog/keeper/post.go
package keeper

import (
    "encoding/binary"

    "blog/x/blog/types"

    "github.com/cosmos/cosmos-sdk/store/prefix"
    sdk "github.com/cosmos/cosmos-sdk/types"
)

func (k Keeper) AppendPost(ctx sdk.Context, post types.Post) uint64 {
    count := k.GetPostCount(ctx)
    post.Id = count
    store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostKey))
    appendedValue := k.cdc.MustMarshal(&post)
    store.Set(GetPostIDBytes(post.Id), appendedValue)
    k.SetPostCount(ctx, count+1)
    return count
}

这段代码定义了一个名为AppendPost的函数,它属于Keeper类型。Keeper类型负责与区块链交互,并修改其状态以响应各种消息。

AppendPost函数有两个参数:一个Context对象和一个Post对象。Context对象是Cosmos SDK中许多函数中的标准参数,用于提供有关区块链当前状态的上下文信息,例如当前块高度Post对象表示将添加到区块链的文章。

该函数首先使用GetPostCount方法检索当前的文章计数。您将在下一步中实现此方法,因为它还没有实现。该方法在Keeper对象上调用,并接受Context对象作为参数。它返回已添加到区块链的当前文章数。

接下来,该函数将新文章的ID设置为当前文章数,这样每个文章都有唯一的标识符。它通过将count的值分配给Post对象的Id字段来实现这一点。

函数然后使用prefix.NewStore创建一个新的store对象。prefix.NewStore函数接受两个参数:与所提供的上下文关联的KVStorePost对象的键前缀。KVStore是一个键值存储,用于在区块链上持久化数据,KeyPrefix用于将Post对象与可能存储在同一KVStore中的其他类型对象区分开来

该函数然后使用cdc.MustMarshal函数序列化Post对象,并使用store对象的Set方法将其存储在区块链中。cdc.MustMarshal函数是Cosmos SDK encoding/decoding (编码/解码库)的一部分,用于将Post对象转换为可以存储在KVStore中的字节片。Set方法在store对象上被调用,它有两个参数:一个键和一个值。在本例中,键是由GetPostIDBytes函数生成的字节片,值是序列化的Post对象。您将在下一步中实现此方法,因为它还没有实现。

最后,使用SetPostCount方法将 post 计数加 1,并更新区块链状态。您将在下一个步骤中实现该方法,因为它还没有实现。这个方法在Keeper 对象上被调用,并在Context 对象和一个新的post 计数作为参数。它更新了区块链中的当前 post 计数,成为新的 post 计数。

然后,函数返回新创建的post的ID,这是当前的post计数,然后再增加。这允许调用函数的调用者知道被添加到区块链的post的ID。

为了完成AppendPost的实现,需要执行以下任务:

  • 定义PostKey,它将用于存储并从数据库中检索 post。
  • 实现GetPostCount,它将检索存储在数据库中的当前存储数。
  • 实现GetPostIDBytes,它将将post ID转换为一个字节数组。
  • 实现SetPostCount,它将更新存储在数据库中的post计数。

Post key prefix

在文件keys.go中。我们来定义PostKey前缀如下:

# x/blog/types/keys.go

const (
    PostKey = "Post/value/"
)

这个前缀将用于在系统中唯一标识一个文章 。它将被用作每个 post 的键的开始,然后是 post 的ID,为每个文章创建一个唯一的键。

获得 post 计数

在文件post.go中。我们来定义GetPostCount函数如下:

# x/blog/keeper/post.go

func (k Keeper) GetPostCount(ctx sdk.Context) uint64 {
    store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte{})
    byteKey := types.KeyPrefix(types.PostCountKey)
    bz := store.Get(byteKey)
    if bz == nil {
        return 0
    }
    return binary.BigEndian.Uint64(bz)
}

这段代码定义了一个名为GetPostCount的函数,它属于Keeper结构体。该函数只接受一个参数,即sdk.Context的上下文对象ctx。,返回类型为uint64的值。

该函数首先使用上下文中的键值存储和空字节片作为前缀创建一个新store。然后,它使用types包中的KeyPrefix函数定义一个字节片byteKey,该函数接受PostCountKey。您将在下一步中定义PostCountKey。

然后,该函数使用storeGet方法检索 store 中键byteKey处的值,并将其存储在变量bz中。

接下来,该函数使用if语句检查byteKey的值是否为nil。如果它为nil,意味着该键在store中不存在,则函数返回0。这表明没有与该键关联的元素或文章。

如果byteKey的值不是nil,该函数使用二进制包的BigEndian类型解析bz中的字节,并返回结果的uint64值。BigEndian类型用于将bz中的字节解释为大端序编码的无符号64位整数。Uint64方法将字节转换为uint64值并返回。

GetPostCount函数用于检索键值store中存储的post的总数,以uint64值表示。

在文件keys.go中,让我们定义PostCountKey如下:

# x/blog/types/keys.go
const (
    PostCountKey = "Post/count/"
)

此key 将用于跟踪添加到 store 的最新文章的ID。

将文章ID转换为字节

现在,让我们实现GetPostIDBytes,它将post ID转换为字节数组。

# x/blog/keeper/post.go
func GetPostIDBytes(id uint64) []byte {
    bz := make([]byte, 8)
    binary.BigEndian.PutUint64(bz, id)
    return bz
}

GetPostIDBytes接收类型为uint64的值id,并返回类型为[]byte的值。

该函数首先使用make内置函数创建一个长度为8的新字节切片bz。然后,它使用binary 包的BigEndian类型将id的值编码为大端序编码的无符号整数,并使用PutUint64方法将结果存储在bz中。最后,函数返回得到的字节切片bz

此函数可用于将post ID(表示为uint64)转换为字节切片(byte slice),该字节切片可用作键值存储中的键。binary.BigEndian.PutUint64函数将iduint64值编码为大端序无符号整数,并将结果字节存储在[]字节片bz中。然后,生成的字节片可以用作存储中的键。

更新post count

post.go中实现SetPostCount,它将更新存储在数据库中的文章计数。

# x/blog/keeper/post.go

func (k Keeper) SetPostCount(ctx sdk.Context, count uint64) {
    store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte{})
    byteKey := types.KeyPrefix(types.PostCountKey)
    bz := make([]byte, 8)
    binary.BigEndian.PutUint64(bz, count)
    store.Set(byteKey, bz)
}

这段代码在Keeper结构中定义了一个函数SetPostCount。该函数接受类型为sdk.Contextctx。上下文和类型为uint64的值count ,并且不返回值。

该函数首先从前缀包调用NewStore函数,并从上下文传入键值store和一个空字节切片作为前缀,从而创建一个新 store。它将结果存储在名为store的变量中。

接下来,该函数使用types 包中的KeyPrefix函数定义一个字节切片byteKey,并传入PostCountKeyKeyPrefix函数返回一个以给定键作为前缀的字节切片。

然后,该函数使用make内置函数创建一个长度为8的新字节切片bz。然后,它使用binary 包的BigEndian类型将count的值编码为大端序编码的无符号整数,并使用PutUint64方法将结果存储在bz中。

最后,函数调用store变量的Set方法,将byteKeybz作为参数传入。这将store 中键byteKey处的值设置为值bz

这个函数可以用来更新存储在数据库中的文章的计数。它通过使用binary.BigEndian.PutUint64countuint64值转换为字节切片来实现这一点,然后使用Set方法将结果字节切片存储在store 中的键byteKey处。

现在您已经实现了创建博客文章的代码,接下来可以实现keeper方法,该方法在处理“create post”消息时被调用。

2.2.2 处理“create post”消息

# x/blog/keeper/msg_server_create_post.go

package keeper

import (
    "context"

    "blog/x/blog/types"

    sdk "github.com/cosmos/cosmos-sdk/types"
)

func (k msgServer) CreatePost(goCtx context.Context, msg *types.MsgCreatePost) (*types.MsgCreatePostResponse, error) {
    ctx := sdk.UnwrapSDKContext(goCtx)
    var post = types.Post{
        Creator: msg.Creator,
        Title:   msg.Title,
        Body:    msg.Body,
    }
    id := k.AppendPost(
        ctx,
        post,
    )
    return &types.MsgCreatePostResponse{
        Id: id,
    }, nil
}

CreatePost函数是MsgCreatePost消息类型的消息处理程序。它负责根据MsgCreatePost消息中提供的信息在区块链上创建一个新的文章。

该函数首先使用sdk.UnwrapSDKContext函数 从Go上下文检索Cosmos SDK context 。U然后,它使用MsgCreatePost消息中的CreatorTitleBody字段创建一个新的Post对象。

接下来,该函数调用msgServer对象(属于Keeper类型)上的AppendPost方法,并传入Cosmos SDK context 和新的Post对象作为参数。AppendPost方法负责将新文章添加到区块链并返回新文章的ID

最后,该函数返回一个MsgCreatePostResponse对象,其中包含新文章的ID。它还返回一个nil错误,表示操作成功。

测试

$ exampled tx example create-post 'Hello, world!' 'This is a blog post' --from alice

在这里插入图片描述

总结

伟大的工作!您已经成功实现了将博客文章写入区块链存储的逻辑,以及在处理“create post”消息时将调用的keeper方法。

AppendPost keeper方法检索当前的文章计数,将新文章的ID设置为当前的文章计数,序列化文章对象,并使用store对象的Set方法将其存储在区块链中。存储中post的键是由GetPostIDBytes函数生成的字节切片,值是序列化的post对象。然后,该函数将发布计数加1,并使用SetPostCount方法更新区块链状态。

CreatePost处理程序方法接收包含新文章数据的MsgCreatePost消息,使用该数据创建一个新的post对象,并将其传递给AppendPost keeper方法以添加到区块链。然后它返回一个MsgCreatePostResponse对象,其中包含新创建的文章的ID

通过实现这些方法,您已经成功地实现了处理“创建文章”消息和向区块链添加文章的必要逻辑。

2.3 更新文章

在本章中,我们将关注处理“update post”消息的过程。

要更新文章,您需要使用“Get”操作从存储中检索特定的文章,修改值,然后使用“Set”操作将更新的文章写回存储中。

让我们首先实现一个getter和一个setter逻辑。

2.3.1 获取文章

post.go中实现GetPost keeper方法:

# x/blog/keeper/post.go

func (k Keeper) GetPost(ctx sdk.Context, id uint64) (val types.Post, found bool) {
    store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostKey))
    b := store.Get(GetPostIDBytes(id))
    if b == nil {
        return val, false
    }
    k.cdc.MustUnmarshal(b, &val)
    return val, true
}

GetPost接受两个参数:一个上下文ctx和一个类型为uint64id,表示要检索的postid。它返回一个types.Post结构,包含文章的值,以及一个布尔值,指示是否在数据库中找到了文章。

该函数首先使用prefix.NewStore方法创建store ,从上下文类型中传入键值store 。types.KeyPrefix函数应用于类型的types.PostKey常量作为参数。然后,它尝试使用store.Get方法从存储中检索文章,将post的ID作为字节切片传入。如果在存储中没有找到post,则返回空types.Post类型结构和布尔值false

如果在存储(store)中找到post,该函数使用cdc.MustUnmarshal方法将检索到的字节切片解组为types.Post类型,传入val变量的指针作为参数。然后它返回val结构体和一个布尔值true,表示在数据库中找到了文章。

2.3.2 设置文章

post.go中实现SetPost keeper方法:

# x/blog/keeper/post.go

func (k Keeper) SetPost(ctx sdk.Context, post types.Post) {
    store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostKey))
    b := k.cdc.MustMarshal(&post)
    store.Set(GetPostIDBytes(post.Id), b)
}

SetPost接受两个参数:一个context ctx和一个types.Post 结构,其中包含Post的更新值。该函数不返回任何东西。

该函数首先使用prefix.NewStore方法创建存储,从上下文和类型中传入键值 store。应用于types.PostKey常量的types.KeyPrefix函数作为参数。然后它使用cdc.MustMarshal将更新后的post结构封送到字节切片中,将指向post结构体的指针作为参数传入。最后,它使用store.Set方法更新 store 中的文章,将post的ID作为字节切片传入,并将编码后的post结构作为参数传入。

2.3.3 更新文章

# x/blog/keeper/msg_server_update_post.go

package keeper

import (
    "context"
    "fmt"

    "blog/x/blog/types"

    sdk "github.com/cosmos/cosmos-sdk/types"
    sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

func (k msgServer) UpdatePost(goCtx context.Context, msg *types.MsgUpdatePost) (*types.MsgUpdatePostResponse, error) {
    ctx := sdk.UnwrapSDKContext(goCtx)
    var post = types.Post{
        Creator: msg.Creator,
        Id:      msg.Id,
        Title:   msg.Title,
        Body:    msg.Body,
    }
    val, found := k.GetPost(ctx, msg.Id)
    if !found {
        return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, fmt.Sprintf("key %d doesn't exist", msg.Id))
    }
    if msg.Creator != val.Creator {
        return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "incorrect owner")
    }
    k.SetPost(ctx, post)
    return &types.MsgUpdatePostResponse{}, nil
}

UpdatePost接受上下文和消息MsgUpdatePost作为输入,并返回响应MsgUpdatePostResponse和一个错误。该函数首先使用提供的msg.Id从数据库中检索帖子的当前值,并检查post是否存在以及msg.Creator是否与帖子的当前所有者相同。如果其中一个检查失败,它将返回一个错误。如果两个检查都通过,它就用msg中提供的新值更新数据库中的post,并返回一个没有错误的响应。

2.4 删除文章

在本章中,我们将关注处理“delete post”消息的过程。

2.4.1 删除文章

# x/blog/keeper/post.go

func (k Keeper) RemovePost(ctx sdk.Context, id uint64) {
    store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostKey))
    store.Delete(GetPostIDBytes(id))
}

RemovePost函数接受两个参数:上下文对象ctx和无符号整数id。该函数通过删除与给定id相关联的键值对来从键值sotre 中删除帖子。键值存储是使用store变量访问的,store变量是通过使用prefix包创建的,该前缀包使用上下文的键值存储和基于PostKey常量的前缀创建一个新的存储。然后在存储对象上调用Delete方法,使用GetPostIDBytes函数将id转换为字节切片作为要删除的键。

2.4.2 Deleting posts

# x/blog/keeper/msg_server_delete_post.go

package keeper

import (
    "context"
    "fmt"

    "blog/x/blog/types"

    sdk "github.com/cosmos/cosmos-sdk/types"
    sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

func (k msgServer) DeletePost(goCtx context.Context, msg *types.MsgDeletePost) (*types.MsgDeletePostResponse, error) {
    ctx := sdk.UnwrapSDKContext(goCtx)
    val, found := k.GetPost(ctx, msg.Id)
    if !found {
        return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, fmt.Sprintf("key %d doesn't exist", msg.Id))
    }
    if msg.Creator != val.Creator {
        return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "incorrect owner")
    }
    k.RemovePost(ctx, msg.Id)
    return &types.MsgDeletePostResponse{}, nil
}

DeletePost接受两个参数:类型为context的上下文goCtx。上下文和指向类型为*types.MsgDeletePost的消息的指针。该函数返回一个指向类型MsgDeletePostResponse消息的指针和一个错误。

2.5 展示一篇文章

在本章中,您将在您的博客应用程序中实现一个特性,使用户能够通过其唯一ID检索单个博客文章。当每篇博客文章创建并存储在区块链上时,这个 ID 被分配给它。通过添加这个查询功能,用户可以通过指定自己的 ID 轻松检索特定的博客文章。

2.5.1 Show post

让我们实现ShowPost keeper方法,当用户对区块链应用程序进行查询并指定所需帖子的ID时,将调用该方法。

# x/blog/keeper/query_show_post.go

package keeper

import (
    "context"

    "blog/x/blog/types"

    sdk "github.com/cosmos/cosmos-sdk/types"
    sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

func (k Keeper) ShowPost(goCtx context.Context, req *types.QueryShowPostRequest) (*types.QueryShowPostResponse, error) {
    if req == nil {
        return nil, status.Error(codes.InvalidArgument, "invalid request")
    }

    ctx := sdk.UnwrapSDKContext(goCtx)
    post, found := k.GetPost(ctx, req.Id)
    if !found {
        return nil, sdkerrors.ErrKeyNotFound
    }

    return &types.QueryShowPostResponse{Post: post}, nil
}

ShowPost是一个从区块链的状态中检索单个post对象的函数。它包含两个参数:名为goCtx的context.Context对象和指向types.QueryShowPostRequest类型的指针,称为req。它返回一个指向types.QueryShowPostResponse类型的指针和一个错误。

该函数首先检查req参数是否为nil。如果是,它将。使用google.golang.org/grpc/status包中的status.Error函数,返回一个带有code InvalidArgumenterror ,并状态返回消息“invalid request”

如果req参数不是nil,则该函数将使用sdk.UnwrapSDKContext函数 从context.Context unwrap sdk.Context对象。然后,它使用GetPost函数从区块链的状态中检索具有指定Idpost对象,并通过检查found 的布尔变量的值来检查是否找到了该post。如果没有找到文章,它将返回一个类型为sdkerrors.ErrKeyNotFound的错误。

如果找到post,该函数将创建一个新类型。QueryShowPostResponse对象,将检索到的post对象作为字段,并返回指向该对象的指针和nil错误。

2.5.2 Modify QueryShowPostResponse

QueryShowPostResponse消息的post字段中包含选项[(gogoproto.nullable) = false],以生成没有指针的字段。

# proto/blog/blog/query.proto

message QueryShowPostResponse {
  Post post = 1 [(gogoproto.nullable) = false];
}

运行命令从proto生成Go文件:

$ ignite generate proto-go

2.6 列出文章

在本章中,您将开发一个特性,使用户能够检索存储在区块链应用程序中的所有博客文章该功能将允许用户执行查询并接收分页响应,这意味着输出将被划分为更小的数据块或“页”。这将允许用户更容易地导航和浏览文章列表,因为他们将能够一次查看特定数量的帖子,而不必一次滚动一个可能很长的列表。

2.6.1 列出文章

让我们实现ListPost keeper方法,当用户对区块链应用程序进行查询,请求存储在chain上的所有文章的分页列表时,将调用该方法。

# x/blog/keeper/query_list_post.go

package keeper

import (
    "context"

    "blog/x/blog/types"

    "github.com/cosmos/cosmos-sdk/store/prefix"
    sdk "github.com/cosmos/cosmos-sdk/types"
    "github.com/cosmos/cosmos-sdk/types/query"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

func (k Keeper) ListPost(goCtx context.Context, req *types.QueryListPostRequest) (*types.QueryListPostResponse, error) {
    if req == nil {
        return nil, status.Error(codes.InvalidArgument, "invalid request")
    }

    var posts []types.Post
    ctx := sdk.UnwrapSDKContext(goCtx)

    store := ctx.KVStore(k.storeKey)
    postStore := prefix.NewStore(store, types.KeyPrefix(types.PostKey))

    pageRes, err := query.Paginate(postStore, req.Pagination, func(key []byte, value []byte) error {
        var post types.Post
        if err := k.cdc.Unmarshal(value, &post); err != nil {
            return err
        }

        posts = append(posts, post)
        return nil
    })

    if err != nil {
        return nil, status.Error(codes.Internal, err.Error())
    }

    return &types.QueryListPostResponse{Post: posts, Pagination: pageRes}, nil
}

ListPost有两个参数:一个上下文对象和一个QueryListPostRequest类型的请求对象。它返回一个QueryListPostResponse类型的响应对象和一个错误。

该函数首先检查请求对象是否为nil,如果为nil则返回一个带有InvalidArgument代码的错误。然后它初始化Post对象的一个空片并展开上下文对象。

它使用keeper结构的storeKey字段从上下文检索键值store ,并使用PostKey的前缀创建一个新的存储。然后,它从store 和请求对象中的分页信息上调用query Paginate函数。作为参数传递给Paginate的函数遍历存储中的键-值对,并将值解组到Post对象中,然后将这些值追加到posts片。

如果分页期间发生错误,该函数将返回一个带有错误消息的内部错误。否则,它返回一个QueryListPostResponse对象,其中包含文章列表和分页信息。

2.6.2 Modify QueryListPostResponse

添加一个repeated关键字来返回一个帖子列表,并包括选项[(gogoproto.nullable) = false]来生成不带指针的字段。

# proto/blog/blog/query.proto

message QueryListPostResponse {
  repeated Post post = 1 [(gogoproto.nullable) = false];
  cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

运行命令从proto生成Go文件:

$ ignite generate proto-go

2.7 Play

Create a blog post by Alice

$ exampled tx example create-post hello world --from alice
[root@localhost ~]# exampled tx example create-post hello world --from alice
auth_info:
  fee:
    amount: []
    gas_limit: "200000"
    granter: ""
    payer: ""
  signer_infos: []
  tip: null
body:
  extension_options: []
  memo: ""
  messages:
  - '@type': /example.example.MsgCreatePost
    body: world
    creator: cosmos19qhtq7ldt006laapts5t4v0tdhqf9turn56f2f
    title: hello
  non_critical_extension_options: []
  timeout_height: "0"
signatures: []
confirm transaction before signing and broadcasting [y/N]: y
code: 0
codespace: ""
data: 12280A262F6578616D706C652E6578616D706C652E4D7367437265617465506F7374526573706F6E7365
events:
- attributes:
  - index: true
    key: ZmVl
    value: ""
  - index: true
    key: ZmVlX3BheWVy
    value: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJm
  type: tx
- attributes:
  - index: true
    key: YWNjX3NlcQ==
    value: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJmLzI=
  type: tx
- attributes:
  - index: true
    key: c2lnbmF0dXJl
    value: RE1NVXdlcllZaHkyQ1U0c0NxRWF1MzlkbmxrZE5mQ1hhZHVNZThFT0RMWUhrakIzQnEzVUd3ZkRYSEJOZ1ZwcStMQUdoakE4U3o0QlkxTGtuSi9VUWc9PQ==
  type: tx
- attributes:
  - index: true
    key: YWN0aW9u
    value: L2V4YW1wbGUuZXhhbXBsZS5Nc2dDcmVhdGVQb3N0
  type: message
gas_used: "51338"
gas_wanted: "200000"
height: "570"
info: ""
logs:
- events:
  - attributes:
    - key: action
      value: /example.example.MsgCreatePost
    type: message
  log: ""
  msg_index: 0
raw_log: '[{"msg_index":0,"events":[{"type":"message","attributes":[{"key":"action","value":"/example.example.MsgCreatePost"}]}]}]'
timestamp: ""
tx: null
txhash: 402297082BED6F5C6F8488E655A254D3E20EC215ECC7F59CAA24E586918A3BA0

Show a blog post

在这里插入图片描述

Create a blog post by Bob

[root@localhost ~]# exampled tx example create-post foo bar --from bob
auth_info:
  fee:
    amount: []
    gas_limit: "200000"
    granter: ""
    payer: ""
  signer_infos: []
  tip: null
body:
  extension_options: []
  memo: ""
  messages:
  - '@type': /example.example.MsgCreatePost
    body: bar
    creator: cosmos1ydt87tge4du7rqzjlrjvd978jfctyxxxfldqt0
    title: foo
  non_critical_extension_options: []
  timeout_height: "0"
signatures: []
confirm transaction before signing and broadcasting [y/N]: y
code: 0
codespace: ""
data: 122C0A262F6578616D706C652E6578616D706C652E4D7367437265617465506F7374526573706F6E736512020801
events:
- attributes:
  - index: true
    key: ZmVl
    value: ""
  - index: true
    key: ZmVlX3BheWVy
    value: Y29zbW9zMXlkdDg3dGdlNGR1N3Jxempscmp2ZDk3OGpmY3R5eHh4ZmxkcXQw
  type: tx
- attributes:
  - index: true
    key: YWNjX3NlcQ==
    value: Y29zbW9zMXlkdDg3dGdlNGR1N3Jxempscmp2ZDk3OGpmY3R5eHh4ZmxkcXQwLzA=
  type: tx
- attributes:
  - index: true
    key: c2lnbmF0dXJl
    value: anB2UzQwZlRPQ3ZxazFQanljYkJFVVk4SW85QzRGa2Rxb1MxcGdWZnpZVjRwMDNTNUY3NWpvdERBZ041Vk4vNFZiQVFwRlZhM25oazJ1aUpZZDFkZHc9PQ==
  type: tx
- attributes:
  - index: true
    key: YWN0aW9u
    value: L2V4YW1wbGUuZXhhbXBsZS5Nc2dDcmVhdGVQb3N0
  type: message
gas_used: "61440"
gas_wanted: "200000"
height: "831"
info: ""
logs:
- events:
  - attributes:
    - key: action
      value: /example.example.MsgCreatePost
    type: message
  log: ""
  msg_index: 0
raw_log: '[{"msg_index":0,"events":[{"type":"message","attributes":[{"key":"action","value":"/example.example.MsgCreatePost"}]}]}]'
timestamp: ""
tx: null
txhash: 16559A53BB9C9E6C6D3C01A2E08B2A0020199D7EDF8CB3BAD31EFA6C43268099

List all blog posts with pagination

在这里插入图片描述

Update a blog post

[root@localhost ~]# exampled tx example update-post hello cosmos 0 --from alice
auth_info:
  fee:
    amount: []
    gas_limit: "200000"
    granter: ""
    payer: ""
  signer_infos: []
  tip: null
body:
  extension_options: []
  memo: ""
  messages:
  - '@type': /example.example.MsgUpdatePost
    body: cosmos
    creator: cosmos19qhtq7ldt006laapts5t4v0tdhqf9turn56f2f
    id: "0"
    title: hello
  non_critical_extension_options: []
  timeout_height: "0"
signatures: []
confirm transaction before signing and broadcasting [y/N]: y
code: 0
codespace: ""
data: 12280A262F6578616D706C652E6578616D706C652E4D7367557064617465506F7374526573706F6E7365
events:
- attributes:
  - index: true
    key: ZmVl
    value: ""
  - index: true
    key: ZmVlX3BheWVy
    value: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJm
  type: tx
- attributes:
  - index: true
    key: YWNjX3NlcQ==
    value: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJmLzM=
  type: tx
- attributes:
  - index: true
    key: c2lnbmF0dXJl
    value: OGk2RDczNzF3UHVBZkhlNTkwTFZmTnNYYVhaREFqdEtBcVRLUjJ2VlN4d2YwZlFxNmdvS1VEaisyci9rY0pXSVEwRGc4ZFBKeXcwZ0t4OFF6aTJPMWc9PQ==
  type: tx
- attributes:
  - index: true
    key: YWN0aW9u
    value: L2V4YW1wbGUuZXhhbXBsZS5Nc2dVcGRhdGVQb3N0
  type: message
gas_used: "49045"
gas_wanted: "200000"
height: "997"
info: ""
logs:
- events:
  - attributes:
    - key: action
      value: /example.example.MsgUpdatePost
    type: message
  log: ""
  msg_index: 0
raw_log: '[{"msg_index":0,"events":[{"type":"message","attributes":[{"key":"action","value":"/example.example.MsgUpdatePost"}]}]}]'
timestamp: ""
tx: null
txhash: 52C366D4587A039BCD6D5EF7105DF0CE19980A40DA46C4384844DB4FF252608F

在这里插入图片描述

Delete a blog post

[root@localhost ~]# exampled tx example delete-post 0 --from alice
auth_info:
  fee:
    amount: []
    gas_limit: "200000"
    granter: ""
    payer: ""
  signer_infos: []
  tip: null
body:
  extension_options: []
  memo: ""
  messages:
  - '@type': /example.example.MsgDeletePost
    creator: cosmos19qhtq7ldt006laapts5t4v0tdhqf9turn56f2f
    id: "0"
  non_critical_extension_options: []
  timeout_height: "0"
signatures: []
confirm transaction before signing and broadcasting [y/N]: y
code: 0
codespace: ""
data: 12280A262F6578616D706C652E6578616D706C652E4D736744656C657465506F7374526573706F6E7365
events:
- attributes:
  - index: true
    key: ZmVl
    value: ""
  - index: true
    key: ZmVlX3BheWVy
    value: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJm
  type: tx
- attributes:
  - index: true
    key: YWNjX3NlcQ==
    value: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJmLzQ=
  type: tx
- attributes:
  - index: true
    key: c2lnbmF0dXJl
    value: UUFBZkdzNDFmNmdJem9WWHJacWNFbm5UQzcwaTZ6dkpMajNUZDQzVXJDUWo4V0wwelBhUlBVT1F5d2JvQnFWVDJSS243UFZGNnVZOGtKTEhrTmdpdHc9PQ==
  type: tx
- attributes:
  - index: true
    key: YWN0aW9u
    value: L2V4YW1wbGUuZXhhbXBsZS5Nc2dEZWxldGVQb3N0
  type: message
gas_used: "45498"
gas_wanted: "200000"
height: "1084"
info: ""
logs:
- events:
  - attributes:
    - key: action
      value: /example.example.MsgDeletePost
    type: message
  log: ""
  msg_index: 0
raw_log: '[{"msg_index":0,"events":[{"type":"message","attributes":[{"key":"action","value":"/example.example.MsgDeletePost"}]}]}]'
timestamp: ""
tx: null
txhash: DF1327893E08BC3E26EC05C3C757620E2AEA8A5BA0472D57961354C7908128FD

在这里插入图片描述

Delete a blog post unsuccessfully

[root@localhost ~]# exampled tx example delete-post 1 --from alice
auth_info:
  fee:
    amount: []
    gas_limit: "200000"
    granter: ""
    payer: ""
  signer_infos: []
  tip: null
body:
  extension_options: []
  memo: ""
  messages:
  - '@type': /example.example.MsgDeletePost
    creator: cosmos19qhtq7ldt006laapts5t4v0tdhqf9turn56f2f
    id: "1"
  non_critical_extension_options: []
  timeout_height: "0"
signatures: []
confirm transaction before signing and broadcasting [y/N]: y
code: 4
codespace: sdk
data: ""
events:
- attributes:
  - index: true
    key: ZmVl
    value: ""
  - index: true
    key: ZmVlX3BheWVy
    value: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJm
  type: tx
- attributes:
  - index: true
    key: YWNjX3NlcQ==
    value: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJmLzU=
  type: tx
- attributes:
  - index: true
    key: c2lnbmF0dXJl
    value: QVJRbmlOS3NmdktMTDdwVEpMMUx2bFkwbWJmbDFmZjJJeFZDeU5EdGRsY3Y0ZS8yVFYrYTF1N09KaUlFZ29sUU5ya0hqL3BsL3VDbGRzc3FkdW05THc9PQ==
  type: tx
gas_used: "44509"
gas_wanted: "200000"
height: "1167"
info: ""
logs: []
raw_log: 'failed to execute message; message index: 0: incorrect owner: unauthorized'
timestamp: ""
tx: null
txhash: DBDE030CB7BB384666BD4AA5BB00D6DA85BC4690DDF04D1DDEF13A66DEB9864C

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/345523.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络学习笔记(一)

网络是由若干接点和连接这些结点的链路组成。 多个网络通过路由器互联起来构成覆盖范围更大的互联网。 普通用户通过ISP接入因特网。 基于ISP的三层结构因特网 相隔较远的两台主机间通信可能需要经过多个ISP。 有电路交换,报文交换,分组交换三种交换方…

【并发编程】LockSupport源码详解

目录 一、前言 1.1 简介 1.2 为什么说LockSupport是Java并发的基石? 二、LockSupport的用途 2.1 LockSupport的主要方法 2.2 使用案例 2.3 总结 三、LockSupport 源码分析 3.1 学习原理前的前置知识 3.1.1 Unsafe.park()和Unsafe.unpark() 3.1.2wait和notify/notify…

MyEclipse技术全面解析——EJB开发工具介绍(一)

MyEclipse v2022.1.0正式版下载1. MyEclipse EJB开发工具Enterprise Java Beans (EJB) 已经成为实现Java企业业务功能和与数据库资源接口的Java EE 5标准,MyEclipse EJB3工具支持Java EE 5简化的基于注释的POJO编程模型,这些工具使开发人员能够在几分钟内…

微信怎么群发消息给所有人?图文教学,快速弄懂

​微信作为很多小伙伴经常使用的工具,无论是学习、工作还是其他方面都会使用到。有些时候,需要将同一条消息发给通讯录里的大多数人,一条一条的转发太慢了,群发消息给所有人是个不错的办法。微信怎么群发消息给所有人?…

广东省基层就业补贴

基层就业补贴链接:https://www.gdzwfw.gov.cn/portal/v2/guide/11440309MB2D27065K4440511108001 一.申请条件: 1、劳动者到中小微企业、个体工商户、社会组织等就业,或到乡镇(街道)、村居社会管理和公共服务岗位就业…

spring cloud篇——什么是服务熔断?服务降级?服务限流?spring cloud有什么优势?

文章目录一、spring cloud 有什么优势二、服务熔断2.1、雪崩效应2.2、DubboHystrixCommand三、服务降级四、服务限流4.1、限流算法4.2、应用级限流4.3、池化技术4.4、分布式限流4.5、基于Redis 功能的实现限流4.6、基于令牌桶算法的实现4.6.1 、Java实现一、spring cloud 有什么…

GUI swing和awt

GUI(Graphical User Interface,简称 GUI,图形用户界面)是指采用图形方式显示的计算机操作用户界面,与早期计算机使用的命令行界面相比,图形界面对于用户来说在视觉上更易于接受。Java GUI主要有两个核心库&…

【计算机网络】传输层TCP协议

文章目录认识TCP协议TCP协议的格式字段的含义序号与确认号六个标志位窗口大小确认应答(ACK)机制超时重传机制连接管理机制三次握手四次挥手滑动窗口流量控制拥塞控制延迟应答捎带应答面向字节流粘包问题TCP异常情况总结认识TCP协议 传输控制协议 (TCP,T…

多边形网格算法笔记

本文是处理多边形和网格的各种笔记和算法。 推荐:使用 NSDT场景设计器 快速搭建 3D场景。 1、表面简化 下面描述了一种方法,用于减少构成表面表示的多边形数量,同时仍试图保留表面的基本形式。 如果正在为渲染和/或交互环境寻求性能改进&…

【CS224图机器学习】task1 图机器学习导论

前言:本期学习是由datawhale(公众号)组织,由子豪兄讲解的202302期CS224图机器学习的学习笔记。本次学习主要针对图机器学习导论做学习总结。1.什么是图机器学习?通过图这种数据结构,对跨模态数据进行整理。…

增减序列(差分)

分析:要想把整个数组变为同一个数,我们可以根据差分的思想来做。 差分定义:b[1]a[1] b[2]a[2]-a[1] ...... b[i]a[i]-a[i-1] 由定义可知,可以把b[2]~b[n]全部变为0,那么整个数组就一样了。现在问题转换为如何用最少的…

Seata-Server分布式事务原理加源码 (八) - Seata-XA模式

Seata-XA模式 Seata 1.2.0 版本重磅发布新的事务模式:XA 模式,实现对 XA 协议的支持。 我们从三个方面来深入分析: XA模式是什么?为什么支持XA?XA模式如何实现的,以及如何使用? XA模式 首先…

shell学习1

目录 一、echo 1.1 echo 1.2 打印彩色文本 1.3 打印彩色背景 二、printf 三、变量和环境变量 3.1 查看某个进程的环境变量 3.2给变量赋值。varvalue 3.3 给环境变量赋值 3.4 获取变量的长度 3.5 识别当前所使用的shell 3.6 检查是否为超级用户 四、数学运算 4.1 …

PHP新特性集合

php8新特性命名参数function foo(string $a, string $b, ?string $c null, ?string $d null) { /* … */ }你可以通过下面的方式传入参数进行调用foo(b: value b, a: value a, d: value d, );联合类型php7class Number {/** var int|float */private $number;/*** param f…

Vue|事件处理

事件处理1. 事件使用1.1 事件绑定1.2 事件参数2. 事件修饰符2.1 阻止默认事件2.2 阻止事件冒泡2.3 事件只允许触发一次2.4 事件捕获2.5 操作当前元素2.6 行为立即执行无需等待回调3. 键盘事件4. 本章小结4.1 事件使用小结4.2 事件修饰符小结4.3 键盘事件小结1. 事件使用 1.1 事…

C++STL剖析(八)—— unordered_set和unordered_multiset的概念和使用

文章目录前言1. unordered_set的介绍和使用🍑 unordered_set的构造🍑 unordered_set的使用🍅 insert🍅 find🍅 erase🍅 size🍅 empty🍅 clear🍅 swap🍅 count…

安全多方计算系列笔记1——前世今生

这一系列笔记参考了绿盟科技研究通讯的安全多方计算文章,及其他。 首先看定义:在不泄露参与方原始输入数据的前提下,允许分布式参与方合作计算任意函数,输出准确的计算结果。 起源 安全多方计算问题及解首先由姚期智&#xff08…

用大白话给你科普,到底什么是 API(应用程序编程接口)?

何为API?如果你在百度百科上搜索,你会得到如下结果:API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组…

Linux 安装Python3

python3 下载地址 python3 下载地址 https://www.python.org/downloads/ 选择自己需要的版本、 此文中选择3.10.9 下载源码压缩包 可下载到本地后上传至Linux服务器也可以复制下载地址 wget https://www.python.org/ftp/python/3.10.9/Python-3.10.9.tgzpython3 安装 yum…

python练习——简化路径

项目场景: 给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 /开头),请你将其转化为更加简洁的规范路径。在 Unix 风格的文件系统中,一个点(.)表示当前目录本…