当前位置: 首页 > news >正文

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

一、快速入门

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 keeperimport ("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 = countstore := 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.goconst (PostKey = "Post/value/"
)

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

获得 post 计数

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

# x/blog/keeper/post.gofunc (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.gofunc (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.gopackage keeperimport ("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.gofunc (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.gofunc (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.gopackage keeperimport ("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.gofunc (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.gopackage keeperimport ("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.gopackage keeperimport ("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.protomessage 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.gopackage keeperimport ("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.Postctx := 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.Postif 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.protomessage 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.MsgCreatePostbody: worldcreator: cosmos19qhtq7ldt006laapts5t4v0tdhqf9turn56f2ftitle: hellonon_critical_extension_options: []timeout_height: "0"
signatures: []
confirm transaction before signing and broadcasting [y/N]: y
code: 0
codespace: ""
data: 12280A262F6578616D706C652E6578616D706C652E4D7367437265617465506F7374526573706F6E7365
events:
- attributes:- index: truekey: ZmVlvalue: ""- index: truekey: ZmVlX3BheWVyvalue: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJmtype: tx
- attributes:- index: truekey: YWNjX3NlcQ==value: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJmLzI=type: tx
- attributes:- index: truekey: c2lnbmF0dXJlvalue: RE1NVXdlcllZaHkyQ1U0c0NxRWF1MzlkbmxrZE5mQ1hhZHVNZThFT0RMWUhrakIzQnEzVUd3ZkRYSEJOZ1ZwcStMQUdoakE4U3o0QlkxTGtuSi9VUWc9PQ==type: tx
- attributes:- index: truekey: YWN0aW9uvalue: L2V4YW1wbGUuZXhhbXBsZS5Nc2dDcmVhdGVQb3N0type: message
gas_used: "51338"
gas_wanted: "200000"
height: "570"
info: ""
logs:
- events:- attributes:- key: actionvalue: /example.example.MsgCreatePosttype: messagelog: ""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.MsgCreatePostbody: barcreator: cosmos1ydt87tge4du7rqzjlrjvd978jfctyxxxfldqt0title: foonon_critical_extension_options: []timeout_height: "0"
signatures: []
confirm transaction before signing and broadcasting [y/N]: y
code: 0
codespace: ""
data: 122C0A262F6578616D706C652E6578616D706C652E4D7367437265617465506F7374526573706F6E736512020801
events:
- attributes:- index: truekey: ZmVlvalue: ""- index: truekey: ZmVlX3BheWVyvalue: Y29zbW9zMXlkdDg3dGdlNGR1N3Jxempscmp2ZDk3OGpmY3R5eHh4ZmxkcXQwtype: tx
- attributes:- index: truekey: YWNjX3NlcQ==value: Y29zbW9zMXlkdDg3dGdlNGR1N3Jxempscmp2ZDk3OGpmY3R5eHh4ZmxkcXQwLzA=type: tx
- attributes:- index: truekey: c2lnbmF0dXJlvalue: anB2UzQwZlRPQ3ZxazFQanljYkJFVVk4SW85QzRGa2Rxb1MxcGdWZnpZVjRwMDNTNUY3NWpvdERBZ041Vk4vNFZiQVFwRlZhM25oazJ1aUpZZDFkZHc9PQ==type: tx
- attributes:- index: truekey: YWN0aW9uvalue: L2V4YW1wbGUuZXhhbXBsZS5Nc2dDcmVhdGVQb3N0type: message
gas_used: "61440"
gas_wanted: "200000"
height: "831"
info: ""
logs:
- events:- attributes:- key: actionvalue: /example.example.MsgCreatePosttype: messagelog: ""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.MsgUpdatePostbody: cosmoscreator: cosmos19qhtq7ldt006laapts5t4v0tdhqf9turn56f2fid: "0"title: hellonon_critical_extension_options: []timeout_height: "0"
signatures: []
confirm transaction before signing and broadcasting [y/N]: y
code: 0
codespace: ""
data: 12280A262F6578616D706C652E6578616D706C652E4D7367557064617465506F7374526573706F6E7365
events:
- attributes:- index: truekey: ZmVlvalue: ""- index: truekey: ZmVlX3BheWVyvalue: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJmtype: tx
- attributes:- index: truekey: YWNjX3NlcQ==value: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJmLzM=type: tx
- attributes:- index: truekey: c2lnbmF0dXJlvalue: OGk2RDczNzF3UHVBZkhlNTkwTFZmTnNYYVhaREFqdEtBcVRLUjJ2VlN4d2YwZlFxNmdvS1VEaisyci9rY0pXSVEwRGc4ZFBKeXcwZ0t4OFF6aTJPMWc9PQ==type: tx
- attributes:- index: truekey: YWN0aW9uvalue: L2V4YW1wbGUuZXhhbXBsZS5Nc2dVcGRhdGVQb3N0type: message
gas_used: "49045"
gas_wanted: "200000"
height: "997"
info: ""
logs:
- events:- attributes:- key: actionvalue: /example.example.MsgUpdatePosttype: messagelog: ""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.MsgDeletePostcreator: cosmos19qhtq7ldt006laapts5t4v0tdhqf9turn56f2fid: "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: truekey: ZmVlvalue: ""- index: truekey: ZmVlX3BheWVyvalue: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJmtype: tx
- attributes:- index: truekey: YWNjX3NlcQ==value: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJmLzQ=type: tx
- attributes:- index: truekey: c2lnbmF0dXJlvalue: UUFBZkdzNDFmNmdJem9WWHJacWNFbm5UQzcwaTZ6dkpMajNUZDQzVXJDUWo4V0wwelBhUlBVT1F5d2JvQnFWVDJSS243UFZGNnVZOGtKTEhrTmdpdHc9PQ==type: tx
- attributes:- index: truekey: YWN0aW9uvalue: L2V4YW1wbGUuZXhhbXBsZS5Nc2dEZWxldGVQb3N0type: message
gas_used: "45498"
gas_wanted: "200000"
height: "1084"
info: ""
logs:
- events:- attributes:- key: actionvalue: /example.example.MsgDeletePosttype: messagelog: ""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.MsgDeletePostcreator: cosmos19qhtq7ldt006laapts5t4v0tdhqf9turn56f2fid: "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: truekey: ZmVlvalue: ""- index: truekey: ZmVlX3BheWVyvalue: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJmtype: tx
- attributes:- index: truekey: YWNjX3NlcQ==value: Y29zbW9zMTlxaHRxN2xkdDAwNmxhYXB0czV0NHYwdGRocWY5dHVybjU2ZjJmLzU=type: tx
- attributes:- index: truekey: c2lnbmF0dXJlvalue: 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

相关文章:

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

一、快速入门 Ignite CLI version: v0.26.1 在本教程中,我们将使用一个模块创建一个区块链,该模块允许我们从区块链中写入和读取数据。这个模块将实现创建和阅读博客文章的功能,类似于博客应用程序。最终用户将能够提交新的博客文章&#x…...

Quartz 快速入门案例,看这一篇就够了

前言 Quartz 是基于 Java 实现的任务调度框架,对任务的创建、修改、删除、触发以及监控这些操作直接提供了 api,这意味着开发人员拥有最大的操作权,也带来了更高的灵活性。 什么是任务调度? 任务调度指在将来某个特定的时间、固…...

图解LeetCode——1233. 删除子文件夹(难道:中等)

一、题目 你是一位系统管理员,手里有一份文件夹列表 folder,你的任务是要删除该列表中的所有 子文件夹,并以 任意顺序 返回剩下的文件夹。 如果文件夹 folder[i] 位于另一个文件夹 folder[j] 下,那么 folder[i] 就是 folder[j] …...

Doris--简单使用

一、数据表的创建与数据导入 1.1、创建表 1.1.1、单分区 CREATE TABLE table1 (siteid INT DEFAULT 10,citycode SMALLINT,username VARCHAR(32) DEFAULT ,pv BIGINT SUM DEFAULT 0 -- 聚合模型, value column 使用sum聚合 ) AGGREGATE KEY(siteid, citycode, …...

使用GPT让你的RStudio如虎添翼

API的的调用目前来说不限制地区,但是OpenAI的API的申请限制了地区。运行的时候,如果出现了429,意味着你被限流了,需要等一会才行。 前提是,你需要注册一个OpenAI的账户,然后在https://openai.com/api/ 里申…...

Python 算法交易实验45 再探量化

说明 去年大部分精力都在构建底层架构和工具了,一直都没有时间搞量化。目前底层的数据库服务(ADB)和清洗(衍生 AETL) 工具已经好了,我想尽快的把量化启动起来。 内容 1 思想 作为交易来说,只有买卖。通过数据分析与模型,我们获得的增强点是决策。在合适的时候进行买卖的…...

Dubbo加载配置文件方式,加载流程,加载配置文件源码解析

配置方法 API配置 以Java编码的方式组织配置&#xff0c;Dubbo3配置API详解 &#xff1a;https://dubbo.apache.org/zh/docs3-v2/java-sdk/reference-manual/config/api/#bootstrap-api public static void main(String[] args) throws IOException {ServiceConfig<Greet…...

十大开源测试工具和框架,一定有你需要的

目录 前言 Katalon Studio Selenium Appium JMeter SOAP UI Robot Framework Watir JUnit Robotium Citrus 总结 前言 免费的开源框架和工具由于其开源特性&#xff0c;现在逐渐成为自动化测试的首选解决方案。区别在于&#xff0c;你是喜欢使用类库编写一个全新的…...

加密技术在android中的应用

1、算法基础 算法基础参照linux的全盘加密与文件系统加密在android中的应用 消息摘要算法 对称加密算法 非对称加密算法...

备战蓝桥杯【一维前缀和】

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…...

研报精选230214

目录 【行业230214艾瑞股份】中国增强现实&#xff08;AR&#xff09;行业研究报告【行业230214国信证券】信息安全深度剖析5&#xff1a;密评和信创双催化&#xff0c;密码产业开启从1到N【行业230214民生证券】磁性元器件深度报告&#xff1a;乘新能源之风&#xff0c;磁性元…...

【SSL/TLS】准备工作:证书格式

证书格式1. 格式说明1.1 文件编码格式1.2 文件后缀格式2. xca导出格式1. 格式说明 1.1 文件编码格式 1. PEM格式: 使用Base 64 ASCII进行编码的纯文本格式。后缀为“.pem”, ".cer", ".crt", ".key" 2. DER格式 二进制编码格式&#xff0c;文件…...

Linux常用命令---系统常用命令

Linux系统常用命令场景一&#xff1a; 查看当前系统内核版本相关信息场景二&#xff1a; sosreport 命令场景三&#xff1a; 如何定位并确定命令&#xff1f;场景四&#xff1a;查看当前系统运行负载怎场景五&#xff1a; 查看当前系统的内存可用情况场景六&#xff1a;查看网卡…...

C 结构体

C 数组允许定义可存储相同类型数据项的变量&#xff0c;结构是 C 编程中另一种用户自定义的可用的数据类型&#xff0c;它允许您存储不同类型的数据项。结构用于表示一条记录&#xff0c;假设您想要跟踪图书馆中书本的动态&#xff0c;您可能需要跟踪每本书的下列属性&#xff…...

手语检测识别

论文&#xff1a;Real-Time Sign Language Detection using Human Pose Estimation Github&#xff1a;https://github.com/google-research/google-research/tree/master/sign_language_detection SLRTP 2020 手语识别任务包括手语检测&#xff08;Sign language detection&a…...

android fwk模块之Sensor架构

本文基于Android 12源码整理&#xff0c;包含如下内容&#xff1a; 通信架构应用层实现使用方式SensorManager抽象接口具体实现fwk层的实现native中的SensorManager的初始化流程native中的消息队列初始化与数据读取sensorservice实现HAL层的实现通信架构 应用层实现 涉及代码&…...

安装less-loader5出现webpack版本不兼容

今天遇到一个问题&#xff1a; 安装less-loader5之后其它包提示peerDependencies WARNING&#xff0c;意思是包版本不兼容。 【难题】 虽然NPM已经很自动化了&#xff0c;但依赖问题真的是一个难题&#xff0c;无法自动解决&#xff0c;需要人工干预调整。 【解决办法】 去查…...

Java 网络编程

1.UDP和TCPUDP和TCP是传输层协议中最核心的两种协议他们的特点分别是UDP: 无连接,不可靠传输,面向数据报,全双工TCP: 有连接,是可靠传输,面向字节流,全双工有无连接有连接:就好比两个人打电话,打电话的一方发出连接请求,被打电话的一方选择确认连接,此时双方才能进行通话无连接…...

BEV学习记录

近期可能要经常性的开展BEV工作&#xff0c;打算把自己觉着不错的网站拿出来记录一下。 首先贴上来我还没有细读的一篇觉着不错的文章。 自动驾驶感知新范式——BEV感知经典论文总结和对比&#xff08;上&#xff09;_苹果姐的博客-CSDN博客_bev视角 开山之作--LSS ECCV 202…...

Webrtc Native C++切换音频输入源

modules/audio_device/audio_device_impl.cc #include “api/audio_options.h” #include “modules/audio_device/include/factory.h” // 创建一个 AudioDeviceModule 对象 auto audio_device_module = webrtc::AudioDeviceModule::Create( webrtc::AudioDeviceModule::kPl…...

裸辞5个月,面试了37家公司,终于找到理想工作了

上半年裁员&#xff0c;下半年裸辞&#xff0c;有不少人高呼裸辞后躺平真的好快乐&#xff01;但也有很多人&#xff0c;裸辞后的生活五味杂陈。 面试37次终于找到心仪工作 因为工作压力大、领导PUA等各种原因&#xff0c;今年2月下旬我从一家互联网小厂裸辞&#xff0c;没想…...

Mybatis-plus@DS实现动态切换数据源应用

目录1 DS实现动态切换数据源原理2 不可在事务中切换数据库分析解决3 原因解析1 DS实现动态切换数据源原理 首先mybatis-plus使用com.baomidou.dynamic.datasource.AbstractRoutingDataSource继承 AbstractDataSource接管数据源&#xff1b;具体实现类为com.baomidou.dynamic.d…...

SpringBoot的创建和使用

SpringBoot是什么&#xff1f;SpringBoot诞生的目的就是为了简化Spring开发&#xff0c;而相对于Spring&#xff0c;SpringBoot算是一个很大的升级&#xff0c;就如同汽车手动挡变成了自动挡。Spring&#xff1a;SpringBoot&#xff1a;SpringBoot的优点SpringBoot让Spring开发…...

居家电话客服宝典

客服分类从销售的流程来分&#xff0c;客服分为售前和售后。售前一般都带有销售性质&#xff0c;工资主要靠提成&#xff0c;售后一般是解答问题&#xff0c;工资主要看服务质量和差评量。从工作模式来分&#xff0c;客服分为在线客服和热线客服。在线客服以打字聊天为主&#…...

开发方案设计

1、开发流程产品需求设计-->需求粗评-->做设计方案-->粗估时-->需求细评-->排期-->开发-->提测、修bug-->code review-->上线设计方案主要是写实现思路、模块划分code review&#xff1a;完善代码&#xff0c;发现未考虑到的边界问题2、具体实现方案…...

文件路径模块pathlib

文件路径模块pathlib 文章目录文件路径模块pathlib1.概述2.创建路径2.1.创建非windos平台路径2.2.动态拼接路径joinpath2.3.替换文件名称 with_name2.4.创建固定目录2.5.创建文件夹和文件1.创建多级目录mkdir2.创建空文件3.路径解析3.1.根据路径分隔符解析路径parts3.2.获取父级…...

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 有什么…...

Tomcat构建

软件架构C/S:Client/Server.需要安装才能使用。B/S:Brower/Server。有浏览器就可以。资源分类动态资源&#xff1a;每个用户访问相同的资源后&#xff0c;得到的结果可能不一样&#xff0c;称为动态资源。动态资源被访问后&#xff0c;先转换为静态资源&#xff0c;再被浏览器解…...

入门深度学习——基于全连接神经网络的手写数字识别案例(python代码实现)

入门深度学习——基于全连接神经网络的手写数字识别案例&#xff08;python代码实现&#xff09; 一、网络构建 1.1 问题导入 如图所示&#xff0c;数字五的图片作为输入&#xff0c;layer01层为输入层&#xff0c;layer02层为隐藏层&#xff0c;找出每列最大值对应索引为输…...

预算砍砍砍,IT运维如何降本增效

疫情短暂过去&#xff0c;一个乐观的共识正在蔓延&#xff1a;2023年的互联网&#xff0c;绝对不会比2022年更差。 “降本”是过去一年许多公司的核心策略&#xff0c;营销大幅缩水、亏损业务大量撤裁&#xff0c;以及层出不穷的裁员消息。而2023年在可预期的经济复苏下&#…...

wordpress滑动解锁/合肥seo网站管理

1.Java语言中负责并发管理机制的是多线程 &#xff08;这道题来自牛客网&#xff0c;但是感觉不太对劲&#xff09;&#xff0c;Java中并发的实现机制是多线程&#xff0c;而不是管理机制&#xff0c;算了。自己理解就好。 2.Java中的线程池具有什么作用&#xff1f; &#xff…...

小说网站制作开源/今日新闻头条官网

英文 / 518M矢量应变下载英文 / 141M星际争霸下载英文 / 164.8M极品飞车5下载英文 / 3.90G野蛮的复活下载中文 / 0KBGTFO下载英文 / 43.5M警察故事下载英文 / 43.2M邪恶失灵下载中文 / 1.76G极品飞车9下载繁体中文 / 5.30G极品飞车17下载中文 / 1.27G火炬之…...

网站开发教程视频百度云资源/搜索引擎优化seo是什么

今天终于得空&#xff0c;去影院贡献了一波&#xff0c;《前任3》 &#xff0c;看完后&#xff0c;总想说点什么&#xff0c;那就用文字记下吧。和基友去看&#xff0c;气氛并不如我所料那般尬&#xff0c;反倒被余飞和丁点这对活宝逗得停不下来&#xff0c;然而在他们衬托下&a…...

dreamweaver可以做手机网站吗/百度贴吧广告投放

努力 奋斗 人生总要有这么一段时光 纯粹而简单 疯狂而热烈 拼搏而勇敢 若干年后 回忆起来 让自己感动 惊艳了时光 温柔了岁月 丰富了记忆 成为你人生宝贵的一笔财富...

网站的301重定向怎么做/google下载安卓版下载

我们将介绍使用 function_score 的基础知识&#xff0c;并介绍一些 function core 技术非常有用和有效的用例。 介绍 评分的概念是任何搜索引擎&#xff08;包括 Elasticsearch&#xff09;的核心。评分可以粗略地定义为&#xff1a;找到符合一组标准的数据并按相关性顺序将其…...

wordpress反应很慢/济南计算机培训机构哪个最好

方法一&#xff1a; 选择添加——现有项&#xff0c;将排除的文件添加进来。 方法二&#xff1a; 在解决方案资源管理器上方有一排小按钮&#xff0c;找到那个显示所有文件&#xff0c;点击后&#xff0c;将显示你被排除的文件&#xff0c;然后再在这个文件上点击右键&#xff…...