在NESTJS中配置微服务:初学者指南

在NESTJS中配置微服务:初学者指南
2024年10月12日 11:18 云云众生s

开始使用微服务:按照本教程使用 NestJS、MySQL、Prisma、NATS 和 Postman 设置一个基本的博客网站。

译自Configure Microservices in NestJS: A Beginner’s Guide,作者 Zziwa Raymond Ian。

在 2011 年之前,单体架构是后端开发的主要方法。在这种模型中,整个应用程序被构建为一个单一的、统一的代码库,其中所有组件和服务紧密耦合,并作为一个模块一起部署。单体方法将所有业务逻辑、数据访问、用户界面 (UI) 和其他功能封装在一个可执行文件或应用程序中。

虽然单体方法在开发和部署方面提供了简单性,但它在应用程序扩展时带来了重大挑战。使用单个代码库,即使是微小的更改也需要重建和重新部署整个应用程序,从而导致更长的开发周期和更高的引入错误风险。此外,扩展单体应用程序通常效率低下,因为它通常需要扩展整个系统,即使只有单个组件的需求增加。组件之间的紧密耦合也会导致相互依赖,随着团队和代码库的增长,系统变得更加脆弱,更难维护。

然而,单体架构最关键的缺点是其广泛的故障影响,通常被称为“爆炸半径”。一个组件的故障会导致整个系统崩溃,从而导致重大停机。这种相互关联增加了广泛停机的风险,并使故障排除和恢复变得复杂。单个问题可能会级联到整个系统,使其难以隔离和解决,而不会影响其他部分。因此,组织经常面临长时间停机,这会对业务运营和整体用户体验产生严重影响。

尽管存在这些缺点,但由于其简单性和缺乏替代方案,单体方法多年来一直是标准。然而,微服务和其他新的架构范式提供了更灵活、更可扩展的解决方案。

什么是微服务?

在微服务架构中,应用程序由小型、独立的服务组成,这些服务通过定义明确的 API 相互通信。微服务将应用程序划分为不同的、松散耦合的服务。每个服务负责特定的功能;例如,在电子商务后端应用程序中,用户身份验证、支付处理、库存管理和其他服务可以独立开发、部署和扩展。这提供了许多优势,包括:

  • 可扩展性: 微服务允许独立扩展单个服务。当一个服务遇到高需求时,它可以被扩展,而不会影响整个应用程序。这优化了资源利用率,提高了整体性能。
  • 技术灵活性: 在微服务架构中,每个服务可以使用最适合其特定需求的技术、语言或框架进行开发。这种灵活性允许开发团队为每个任务选择最佳工具。
  • 故障隔离: 由于微服务的独立性,一个服务的故障不太可能影响整个系统。这最大限度地减少了故障的影响,增强了系统弹性,减少了停机时间。
  • 开发和部署速度: 微服务允许团队同时处理不同的服务,从而加快开发流程。持续集成和部署实践更容易实施,从而实现更快的更新和更频繁的改进。
  • 简化维护和更新: 微服务的模块化结构使维护和更新应用程序更加直观。可以对单个服务进行更改,而不会影响其他服务,从而降低错误风险并简化测试过程。
  • 组织对齐: 微服务促进了围绕特定业务能力组织团队。每个团队可以完全拥有一个服务,从开发到部署和支持,从而提高自主权、问责制和效率。
  • 增强敏捷性: 微服务的模块化设计支持迭代开发,允许更灵活地适应不断变化的业务需求,并促进快速创新。

单体与微服务:结构差异

在单体应用程序中,所有客户端请求都由单个通用控制器处理。该控制器负责处理请求、执行必要的命令或操作并将响应返回给客户端。本质上,所有业务逻辑和请求处理都是集中式的,这简化了开发过程。 与之相反,微服务架构通过引入应用程序网关增加了额外的复杂性。应用程序网关在微服务设置中充当至关重要的中间层。以下是它的工作原理:

  1. 请求处理: 网关接收来自客户端的所有传入请求。
  2. 路由: 然后,它根据其路由规则确定应该处理每个请求的适当微服务或控制器。
  3. 服务交互: 选定的控制器与相应的微服务交互以处理请求。
  4. 响应聚合: 微服务完成其任务后,它将结果发送回控制器,然后控制器将其转发到网关。
  5. 客户端响应: 最后,网关将处理后的响应返回给客户端。

这种分层方法将请求路由和业务逻辑的关注点分离,使每个微服务能够专注于其特定功能,而网关则管理请求分发和响应聚合。如果这听起来很复杂,别担心 - 我将详细介绍每个组件,并解释它们如何协同工作。

使用 NestJS 实现微服务

NestJS 是一个渐进式 Node.js 框架,它利用TypeScript,提供了现代JavaScript功能、面向对象编程和函数式编程范式的强大组合。它旨在提供一个原生应用程序架构,帮助开发人员构建高度可测试、可扩展和可维护的应用程序。

在本教程中,我将向您展示如何使用 NestJS 作为主要技术、NATS作为通信媒介、Prisma 作为对象关系映射 (ORM) 技术、MySQL 作为数据库以及最后使用 Postman 测试端点来实现微服务。

这种方法将演示如何有效地管理微服务,确保它们无缝通信、易于扩展,并且可以在生产环境中可靠地部署。在此过程中,我将介绍设置微服务架构、管理依赖项和保护部署的最佳实践,为构建健壮高效的分布式系统奠定坚实的基础。

设置基础 NestJS 应用程序

在开始之前,请确保已安装 Node.js。Node.js 对于在服务器端运行 JavaScript 代码和管理包至关重要。如果您尚未安装 Node.js,可以从官方 Node.js 网站下载。接下来,使用 npm(与 Node.js 捆绑在一起)安装 Nest 命令行界面 (CLI),这是一个简化 NestJS 应用程序创建和管理的工具。

安装完 Nest CLI 后,设置您的基础 NestJS 应用程序作为您的网关,并将其命名为api-gateway:

npm install -g @nestjs/cli //--this command is to install the nest clinest new api-gateway //--this command is to scaffold our base application..you can change `api-gateway` to any name you want

启动您喜欢的文本编辑器(例如,VS Code、Sublime Text)并打开 NestJS 应用程序的父目录(包含您基础应用程序父文件夹的目录)。导航到基础应用程序文件夹(即api-gateway)并打开一个新的终端实例。大多数现代文本编辑器都具有内置的终端功能。对于 VS Code,您可以通过从顶部菜单中选择Terminal,然后选择New Terminal来打开终端。对于 Sublime Text,您可能需要使用 Terminus 等插件在编辑器中打开终端。

cd api-gateway //-- to navigate into the parent folder of my base applicationnpm run start:dev //--to start the development server

构建后端应用程序

您的基础应用程序成功启动并运行后,下一步是为博客网站构建一个基础后端应用程序。您将在本教程中实现两个独立的服务:一个用于管理读者,另一个用于处理博客文章的创建、读取、更新和删除 (CRUD) 操作。如果您以前使用过 NestJS,那么项目结构将很熟悉且简单。但是,如果您不确定如何组织,我将简要概述一下结构。

当您构建一个新的 NestJS 项目时,默认结构通常包括:

src: 这是大多数应用程序代码所在的目录。

  • app.module.ts: 将应用程序的不同部分联系在一起的根模块。
  • app.controller.ts: 负责处理传入请求并返回响应的控制器。
  • app.service.ts: 包含业务逻辑的服务;可以注入到控制器中。
  • main.ts: 应用程序的入口点,在这里引导 NestJS 应用程序。

test: 此目录包含应用程序的测试文件。

  • app.e2e-spec.ts: 端到端测试文件。
  • jest-e2e.json: 使用 Jest 进行端到端测试的配置文件。

node_modules: 此目录包含项目的所有已安装依赖项。

package.json: 此文件列出了项目的依赖项和脚本。

tsconfig.json: TypeScript 配置文件。

nest-cli.json: NestJS CLI 的配置文件。

创建微服务和网关

下一步是创建两个额外的应用程序,它们将充当微服务,并分别命名为reader-mgt和article-mgt。这些应用程序将在架构中充当独立的微服务。之后,安装@nestjs/microservices和nats库以启用服务之间的通信。然后配置这两个应用程序以通过 NATS 监听请求,确保它们能够相应地处理传入的消息。

因此,在同一个文件夹中,运行以下命令:

nest new reader-mgt //--create reader management microservicenest new article-mgt //-- create article management service

现在,这两个服务已经搭建好了,配置您的网关来处理客户端请求并将它们路由到相应的服务。首先,安装@nestjs/microservices和nats依赖项。然后创建一个 NATS 模块,该模块将在API 网关的应用程序模块中注册,以实现网关和微服务之间的正常通信:

npm install @nestjs/microservices nats //--to install the dependencies

如果您不在gateway文件夹中,请使用cd命令导航到该文件夹。到达那里后,转到src文件夹并创建一个名为nats-client的新目录,该目录将用作 NATS 客户端配置的位置。之后,在nats-client文件夹中创建一个名为nats.module.ts的文件,并添加以下代码:

import{Module}from'@nestjs/common';import{ClientsModule,Transport}from'@nestjs/microservices';@Module({ imports: [ ClientsModule.register([ { name: 'NATS_SERVICE', transport: Transport.NATS, options: { servers: ['nats://localhost:4222'], }, }, ]), ], exports: [ ClientsModule.register([ { name: 'NATS_SERVICE', transport: Transport.NATS, options: { servers: ['nats://localhost:4222'], }, }, ]), ],})exportclassNatsClientModule{}

此代码创建了一个NatsClientModule,您将在稍后将其注册到 API 网关的应用程序模块中。首先,它从之前安装的@nestjs/microservices库中导入Module装饰器以及ClientsModule和Transport声明。接下来,它注册NATS_SERVICE并将传输指定为Transport.NATS。NestJS 默认支持各种传输客户端,但对于本示例,请坚持使用 NATS。

然后它定义了一个options对象,该对象指定了servers属性并将 NATS 服务器地址设置为nats://localhost:4222。最后,它导出注册的 NATS 客户端以使它们可供其他模块访问,如果您在网关中有多个模块,这将很有用。例如,您可能有一个用户模块、文章模块、读者模块等等。但是,本教程使用单个控制器和模块来处理读者和文章。

完成此操作后,您现在可以继续到app.module.ts文件并注册NatsClientModule:

import{Module}from'@nestjs/common';import{AppController}from'./app.controller';import{NatsClientModule}from'./nats-client/nats.module';@Module({ imports: [NatsClientModule], controllers: [AppController], providers: [],})exportclassAppModule{}

此时,您已经完成了大约 80% 的 API 网关配置。最后一步是在app.controller.ts文件中定义 API 路由。导航到该文件并添加以下代码:

import{Controller,Get,Inject,Post,Req}from'@nestjs/common';import{ClientProxy}from'@nestjs/microservices';@Controller('api/')exportclassAppController{ constructor( @Inject('NATS_SERVICE')privatereadonlynatsClient: ClientProxy, ){} @Post('/save-reader') saveReader(@Req()req: Request){ returnthis.natsClient.send({cmd: 'SAVE_READER'},req.body); } @Get('get-all-readers') getReaders(@Req()req: Request){ returnthis.natsClient.send({cmd: 'GET_ALL_READERS'},req.body); } @Post('/save-article') saveArticle(@Req()req: Request){ returnthis.natsClient.send({cmd: 'SAVE_ARTICLE'},req.body); } @Get('/get-all-articles') getAllArticles(@Req()req: Request){ returnthis.natsClient.send({cmd: 'GET_ALL_ARTICLES'},req.body); } @Post('delete-article') deleteArticle(@Req()req: Request){ returnthis.natsClient.send({cmd: 'DELETE_ARTICLE'},req.body); }}

这定义了 API 路由,这些路由将处理传入的 HTTP 请求并将它们转发到 NATS 服务以进行处理。AppController类使用@Controller装饰器来指定所有端点的基本路由,即'api/'。在此控制器中,使用@Inject装饰器注入 NATS 客户端,将其与'NATS_SERVICE'令牌关联。控制器包含几个端点:POST /save-reader和GET /get-all-readers用于管理读者,以及POST /save-article、GET /get-all-articles和POST /delete-article用于管理文章。每个端点方法都使用natsClient.send方法将命令发送到 NATS 服务,并将请求主体作为有效负载传递。此设置允许 API 网关通过 NATS 将客户端请求中继到相应的微服务。

最后,执行npm run start:dev命令启动 API 网关应用程序。这将验证应用程序是否顺利运行且没有任何错误。

图 1:api-gateway 应用程序

配置通信服务

接下来,配置您的服务以处理来自正在运行的 API 网关的请求,处理它们并将响应发送回。但是,在继续之前,有一个重要的步骤:在本地设置 NATS 服务器。由于您将 NATS 服务器地址指定为nats://localhost:4222,因此网关和服务都将期望在您的本地机器上运行 NATS 服务器。

出于开发目的,在本地安装 NATS 服务器。虽然您可以在Docker容器中运行它,但为了简单起见,请使用本地设置。对于 Linux 和 macOS 用户,使用brew install nats-server安装 NATS 服务器,并运行nats-server启动服务。对于 Windows 用户,请使用choco install nats-server。如果choco未被识别,请确保通过运行以下命令安装 Chocolatey:

Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))

然后通过运行choco --version验证安装。如果您需要进一步的指导,请咨询NATS 文档

图 2:本地运行的 NATS 服务器

配置您的第一个服务

现在您可以配置您的第一个服务,article-mgt。转到main.ts文件,它是此服务的入口点,并将默认代码替换为:

import{NestFactory}from'@nestjs/core';import{AppModule}from'./app.module';import{MicroserviceOptions,Transport}from'@nestjs/microservices';asyncfunctionbootstrap(){ constapp=awaitNestFactory.createMicroservice

( AppModule, { transport: Transport.NATS, options: { servers: ['nats://localhost:4222'], }, }, ); awaitapp.listen();}bootstrap();

此代码将article-mgt从一个独立的应用程序转换为一个 NestJS 微服务实例,并将其配置为使用 NATS 作为传输机制,指定服务器地址 (nats://localhost:4222) 连接到 NATS 服务器。

接下来,在src文件夹中创建一个名为dto的新目录,然后创建一个名为dto.ts的文件,该文件将包含预期的有效负载结构。DTO 代表数据传输对象,它们是用于在应用程序的不同层之间传输数据的简单对象,尤其是在网络请求期间。在这种情况下,DTO 有助于定义后端应用程序从客户端请求中期望的有效负载的结构和类型。如果需要,您可以使用class-validator依赖项实现进一步的验证。但是,为了使本文重点突出,我们不会在这里使用它。如果您有兴趣,可以在 NestJS 官方文档中了解更多关于class-validator的信息。

exportclasssaveArticleDto{ title: string; content: string;}exportclassdeleteArticleDto{ id: string;}

此代码定义了两个 DTO 用于处理article-mgt服务中的数据。saveArticleDto类指定了保存文章的结构,需要一个title和content。而deleteArticleDto类定义了删除文章的结构,它需要一个id来标识要删除的文章。这些 DTO 有助于确保在应用程序的不同部分之间传递的数据定义明确、一致且符合预期类型。文章有三个路由,但只定义了两个 DTO 类。这是因为第三个路由,它检索所有文章,不需要任何有效负载。

现在转到app.controller.ts文件并修改代码。

图 3:app.controller.ts中的代码

您可能会注意到控制器方法中函数名称下方的红色波浪线;这是因为您还没有在app.service.ts中定义这些函数。在解决这个问题之前,让我解释一下代码:它导入 DTO 以对有效负载执行类型检查,确保传递给函数的数据符合预期结构。@MessagePattern装饰器指定了如何处理消息。它接受一个对象,其中cmd属性定义一个命令字符串。此字符串必须与之前在 API 网关中指定的命令匹配。API 网关使用此命令来确定对给定 API 请求调用哪个函数,在将请求转发之前将命令附加到请求中。

使用 Prisma 与您的数据库交互

要使用 Prisma 与您的数据库交互,请创建一个 Prisma 模块和服务,您可以在app.service.ts文件中使用它。首先,在src目录中创建一个名为prisma的文件夹。然后,在这个文件夹中,创建两个文件:prisma.module.ts和prisma.service.ts。PrismaService在prisma.service.ts中扩展了 Prisma 的PrismaClient类,并通过使用DATABASE_URL环境变量配置数据库连接 URL 来定制 Prisma 客户端。PrismaModule在prisma.module.ts中定义了一个提供PrismaService的模块,允许它被注入并在微服务的其他部分中用于数据库操作。

//prisma.service.ts fileimport{Injectable}from'@nestjs/common';import{PrismaClient}from'@prisma/client';import{env}from'process';@Injectable()exportclassPrismaServiceextendsPrismaClient{ constructor(){ super({ datasources: { db: { url: env.DATABASE_URL, }, }, }); }}//prisma.module.ts fileimport{Module}from'@nestjs/common';import{PrismaService}from'./prisma.service';@Module({ providers: [PrismaService],})exportclassPrismaModule{}

现在,转到app.service.ts并添加下面的代码来定义必要的函数。简要解释一下:saveArticle函数以data作为参数,它必须是saveArticleDto类型,如前所述。该函数使用 try-catch 块来处理该过程。首先,它尝试将数据插入数据库。之后,它调用getAllArticles函数来检索更新后的文章列表。由于getAllArticles是一个异步函数,它使用await关键字。完成此操作后,该函数返回一个对象作为响应,其中包含HttpCode,其中包括statusCode、一条消息和一个字符串。此外,getAllArticles函数从数据库返回所有文章,而deleteArticle函数根据提供的 ID 处理文章的删除。

//app.service.ts fileimport{Injectable}from'@nestjs/common';import{deleteArticleDto,saveArticleDto}from'./dto/dto';import{PrismaService}from'./prisma/prisma.service';@Injectable()exportclassAppService{ constructor(privatereadonlyprismaService: PrismaService){} asyncsaveArticle(data: saveArticleDto){ try{ awaitthis.prismaService .$queryRaw`INSERT INTO article (title, content) VALUES (${data.title}, ${data.content})`; constarticles=awaitthis.getAllArticles(); return{ HttpCode: 201, message: 'Article saved successfully', data: articles.data, }; }catch(error){ console.log(error); return{ HttpCode: 400, message: 'Error saving article', data: null, }; } } asyncgetAllArticles(){ try{ constarticles=awaitthis.prismaService .$queryRaw`SELECT * FROM article`; return{ HttpCode: 201, message: 'Article saved successfully', data: articles, }; }catch(error){ console.log(error); return{ HttpCode: 400, message: 'Error while fetching articles', data: null, }; } } asyncdeleteArticle(data: deleteArticleDto){ try{ awaitthis.prismaService .$queryRaw`DELETE FROM Article WHERE id = ${data.id}`; constarticles=awaitthis.getAllArticles(); return{ HttpCode: 200, message: 'Article deleted successfully', data: articles, }; }catch(error){ console.log(error); return{ HttpCode: 400, message: 'Error while deleting article', data: null, }; } }}

启动您的第一个服务

完成这些操作后,您现在可以启动您的articles-mgt服务并检查它是否能顺利运行,没有任何错误。为此,只需执行命令'npm run start:dev'。

图 4:article-mgt服务

配置您的第二个服务

现在您已经完成了article-mgt微服务的配置,接下来配置reader-mgt服务。该服务将处理两个主要操作:注册读者和检索所有已注册读者。由于设置过程与我之前介绍的非常相似,为了节省时间,我将跳过详细的解释。实现本质上是相同的,只是在不同的服务上下文中。

要设置reader-mgt服务,首先导航到reader-mgt目录。由于此服务中的main.ts文件将与article-mgt服务中的代码相同,因此您可以简单地将article-mgt main.ts文件中的内容复制并粘贴到reader-mgt中的相应文件中。接下来,将article-mgt服务中的整个prisma目录复制到reader-mgt服务中。但是,这不会立即生效;您需要在reader-mgt服务中安装并初始化 Prisma。运行npm install Prisma @prisma/client来安装 Prisma,然后执行npx prisma generate来初始化它。此外,定义读者的模式并执行迁移。不要忘记从article-mgt中的.env文件中复制数据库连接字符串,因为没有它,reader-mgt微服务将无法连接到数据库。

图 5:读者和文章模型

定义读者模式后,运行npx prisma migrate dev将迁移应用到数据库,这将向 MySQL 数据库添加reader表。

最后一步是配置reader-mgt服务中的app.controller.ts和app.service.ts文件。此过程类似于您在article-mgt服务中所做的操作。在控制器中,定义路由,然后将这些路由映射到服务中的相应函数。您可以使用article-mgt微服务配置作为参考来指导您完成此过程。

//app.service.tsimport{Injectable}from'@nestjs/common';import{saveReaderDto}from'./dto/dto';import{PrismaService}from'./prisma/prisma.service';@Injectable()exportclassAppService{ constructor(privatereadonlyprismaService: PrismaService){} asyncsaveReader(data: saveReaderDto){ try{ awaitthis.prismaService .$queryRaw`INSERT INTO reader (email) VALUES (${data.email})`; constreaders=awaitthis.getAllReaders(); return{ HttpCode: 201, message: 'Reader saved successfully', data: readers.data, }; }catch(error){ console.log(error); return{ HttpCode: 400, message: 'Error saving reader', data: null, }; } } asyncgetAllReaders(){ try{ constreaders=awaitthis.prismaService.$queryRaw`SELECT * FROM reader`; return{ HttpCode: 201, message: 'Readers fetched successfully', data: readers, }; }catch(error){ console.log(error); return{ HttpCode: 400, message: 'Error while fetching articles', data: null, }; } }}//app.controller.tsimport{Controller}from'@nestjs/common';import{AppService}from'./app.service';import{MessagePattern,Payload}from'@nestjs/microservices';import{saveReaderDto}from'./dto/dto';@Controller()exportclassAppController{ constructor(privatereadonlyappService: AppService){} @MessagePattern({cmd: 'SAVE_READER'}) asyncsaveReader(@Payload()data: saveReaderDto){ returnthis.appService.saveReader(data); } @MessagePattern({cmd: 'GET_ALL_READERS'}) asyncgetAllReaders(){ returnthis.appService.getAllReaders(); }}//app.module.tsimport{Module}from'@nestjs/common';import{AppController}from'./app.controller';import{AppService}from'./app.service';import{PrismaService}from'./prisma/prisma.service';@Module({ imports: [], controllers: [AppController], providers: [AppService,PrismaService],})exportclassAppModule{}

运行您的微服务

配置完reader-mgt服务的app.service.ts、app.module.ts和app.controller.ts文件后,最后一步是运行微服务以确保一切正常运行且没有错误。这包括验证控制器中的路由是否正确映射到服务中的函数,以及微服务是否可以按预期处理请求。

确认所有配置到位后,您可以使用npm run start:dev命令启动reader-mgt服务。这将在开发模式下启动服务,允许您检查任何问题并确保服务无缝运行。

图 6:reader-mgt 微服务

测试您的应用程序

如果您已经完成了这一步,恭喜您!项目的编码部分已完成,您的api-gateway、reader-mgt和article-mgt服务已启动并运行,没有任何错误。下一步是使用 Postman 测试应用程序,并确保它按预期执行。使用 Postman 向 API 网关发送请求,并验证操作是否由微服务正确处理。这将有助于确认应用程序的所有部分都无缝地协同工作。

图 7:/save-reader端点

图 8:/get-all-readers

该图像说明了save-reader和get-all-readers端点通过 API 网关到达reader-mgt微服务的流程。API 网关首先接收请求,识别正确的命令,并通过 NATS 将它们转发到reader-mgt服务。然后,reader-mgt服务通过创建新读者或检索所有读者来处理请求。请求成功后,服务将返回相应的响应。

接下来,通过发送请求来创建、删除和检索文章来测试article-mgt端点。首先,向/save-article端点发送三个创建请求,以将三篇文章添加到数据库中,如图 9 所示。然后,向/delete-article端点发送一个请求,以删除 ID 为 2 的文章。最后,向/get-all-articles端点发出一个 GET 请求,以检索更新后的文章列表,确认删除成功,并且剩余的文章已正确列在数据库中。

图 9:/save-article端点

图 10:/delete-article端点

图 11:/get-all-articles端点

结论

恭喜您完成了本篇全面设置指南!您已经成功地完成了使用 NestJS、Prisma、MySQL 和 NATS 配置健壮的微服务架构的复杂过程。虽然您已经成功地设置了功能性的微服务架构,但始终有改进的空间。

在继续开发应用程序时,请考虑实施其他功能,例如健壮的错误处理、安全措施和全面的日志记录。探索 Docker 进行容器化和Kubernetes进行编排可以进一步简化您的开发和部署流程。

感谢您跟随本指南。您对掌握这些技术的奉献精神无疑将为创建复杂且有弹性的应用程序铺平道路。如果您需要本博文的代码,请在我的GitHub 仓库中找到它。祝您编码愉快,并祝您在持续开发中一切顺利!

本文在云云众生https://yylives.cc/)首发,欢迎大家访问。

财经自媒体联盟更多自媒体作者

新浪首页 语音播报 相关新闻 返回顶部