AI地牢游戏
模块 1: 单体仓库设置
Section titled “模块 1: 单体仓库设置”我们将从创建一个新的单体仓库开始。在您选择的目录中运行以下命令:
npx create-nx-workspace@21.4.1 dungeon-adventure --pm=pnpm --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip
npx create-nx-workspace@21.4.1 dungeon-adventure --pm=yarn --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip
npx create-nx-workspace@21.4.1 dungeon-adventure --pm=npm --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip
npx create-nx-workspace@21.4.1 dungeon-adventure --pm=bun --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip
这将在 dungeon-adventure
目录中设置一个 NX 单体仓库,您可以在 vscode 中打开它。其结构应如下所示:
文件夹.nx/
- …
文件夹.vscode/
- …
文件夹node_modules/
- …
文件夹packages/ 此处将存放您的子项目
- …
- .gitignore
- .npmrc
- .prettierignore
- .prettierrc
- nx.json 配置 Nx CLI 和单体仓库默认设置
- package.json 所有 Node 依赖项在此定义
- pnpm-lock.yaml 或 bun.lock、yarn.lock、package-lock.json(取决于包管理器)
- pnpm-workspace.yaml(如果使用 pnpm)
- README.md
- tsconfig.base.json 所有基于 Node 的子项目继承此配置
- tsconfig.json
- aws-nx-plugin.config.mts 配置 AWS 的 Nx 插件
现在,我们已准备好使用 @aws/nx-plugin
开始创建不同的子项目。
游戏 API
Section titled “游戏 API”首先创建我们的游戏 API。为此,按照以下步骤创建一个名为 GameApi
的 tRPC API:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#trpc-api
- 填写必需参数
- name: GameApi
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
yarn nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
npx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
bunx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
您应该看到文件树中出现了一些新文件。
ts#trpc-api 更新文件
以下是 ts#trpc-api
生成器生成的所有文件列表。我们将检查文件树中突出显示的一些关键文件:
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹app/ 应用特定的 CDK 构造
文件夹apis/
- game-api.ts 用于创建 tRPC API 的 CDK 构造
- index.ts
- …
- index.ts
文件夹core/ 通用 CDK 构造
文件夹api/
- rest-api.ts API Gateway Rest API 的基础 CDK 构造
- trpc-utils.ts tRPC API CDK 构造的实用工具
- utils.ts API 构造的实用工具
- index.ts
- runtime-config.ts
- index.ts
- project.json
- …
文件夹types/ 共享类型
文件夹src/
- index.ts
- runtime-config.ts 被 CDK 和网站共同使用的接口定义
- project.json
- …
文件夹game-api/ tRPC API
文件夹src/
文件夹client/ 通常用于机器间调用的原生客户端
- index.ts
- sigv4.ts
文件夹middleware/ Powertools 工具
- error.ts
- index.ts
- logger.ts
- metrics.ts
- tracer.ts
文件夹schema/ API 输入和输出的定义
- echo.ts
文件夹procedures/ API 过程/路由的具体实现
- echo.ts
- index.ts
- init.ts 设置上下文和中间件
- local-server.ts 本地运行 tRPC 服务器时使用
- router.ts Lambda 处理程序的入口点,定义所有过程
- project.json
- …
- eslint.config.mjs
- vitest.workspace.ts
查看几个关键文件:
import { awsLambdaRequestHandler, CreateAWSLambdaContextOptions,} from '@trpc/server/adapters/aws-lambda';import { echo } from './procedures/echo.js';import { t } from './init.js';import { APIGatewayProxyEvent } from 'aws-lambda';
export const router = t.router;
export const appRouter = router({ echo,});
export const handler = awsLambdaRequestHandler({ router: appRouter, createContext: ( ctx: CreateAWSLambdaContextOptions<APIGatewayProxyEvent>, ) => ctx, responseMeta: () => ({ headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', }, }),});
export type AppRouter = typeof appRouter;
路由器定义了 tRPC API 的入口点,是声明所有 API 方法的地方。如上所示,我们有一个名为 echo
的方法,其实现位于 ./procedures/echo.ts
文件中。
import { publicProcedure } from '../init.js';import { EchoInputSchema, EchoOutputSchema,} from '../schema/echo.js';
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ result: opts.input.message }));
此文件是 echo
方法的实现,通过声明其输入和输出数据结构进行强类型化。
import { z } from 'zod';
export const EchoInputSchema = z.object({ message: z.string(),});
export type IEchoInput = z.TypeOf<typeof EchoInputSchema>;
export const EchoOutputSchema = z.object({ result: z.string(),});
export type IEchoOutput = z.TypeOf<typeof EchoOutputSchema>;
所有 tRPC 模式定义均使用 Zod 定义,并通过 z.TypeOf
语法导出为 TypeScript 类型。
import { Construct } from 'constructs';import * as url from 'url';import { Code, Runtime, Function, FunctionProps, Tracing,} from 'aws-cdk-lib/aws-lambda';import { AuthorizationType, Cors, LambdaIntegration,} from 'aws-cdk-lib/aws-apigateway';import { Duration, Stack } from 'aws-cdk-lib';import { PolicyDocument, PolicyStatement, Effect, AccountPrincipal, AnyPrincipal,} from 'aws-cdk-lib/aws-iam';import { IntegrationBuilder, RestApiIntegration,} from '../../core/api/utils.js';import { RestApi } from '../../core/api/rest-api.js';import { Procedures, routerToOperations } from '../../core/api/trpc-utils.js';import { AppRouter, appRouter } from ':dungeon-adventure/game-api';
// 所有 API 操作名称的字符串联合类型type Operations = Procedures<AppRouter>;
/** * 创建 GameApi 构造的属性 * * @template TIntegrations - 操作名称到其集成的映射 */export interface GameApiProps< TIntegrations extends Record<Operations, RestApiIntegration>,> { /** * 操作名称到 API Gateway 集成的映射 */ integrations: TIntegrations;}
/** * 一个 CDK 构造,用于创建和配置专为 GameApi 设计的 AWS API Gateway REST API。 * @template TIntegrations - 操作名称到其集成的映射 */export class GameApi< TIntegrations extends Record<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { /** * 为所有操作创建默认集成,将每个操作实现为独立的 Lambda 函数。 * * @param scope - CDK 构造作用域 * @returns 包含默认 Lambda 集成的 IntegrationBuilder */ public static defaultIntegrations = (scope: Construct) => { return IntegrationBuilder.rest({ operations: routerToOperations(appRouter), defaultIntegrationOptions: { runtime: Runtime.NODEJS_LATEST, handler: 'index.handler', code: Code.fromAsset( url.fileURLToPath( new URL( '../../../../../../dist/packages/game-api/bundle', import.meta.url, ), ), ), timeout: Duration.seconds(30), tracing: Tracing.ACTIVE, environment: { AWS_CONNECTION_REUSE_ENABLED: '1', }, } satisfies FunctionProps, buildDefaultIntegration: (op, props: FunctionProps) => { const handler = new Function(scope, `GameApi${op}Handler`, props); return { handler, integration: new LambdaIntegration(handler) }; }, }); };
constructor( scope: Construct, id: string, props: GameApiProps<TIntegrations>, ) { super(scope, id, { apiName: 'GameApi', defaultMethodOptions: { authorizationType: AuthorizationType.IAM, }, defaultCorsPreflightOptions: { allowOrigins: Cors.ALL_ORIGINS, allowMethods: Cors.ALL_METHODS, }, policy: new PolicyDocument({ statements: [ // 此处授予部署项目账户中的任何 AWS 凭证调用 API 的权限。 // 如果需要,可以在此定义更细粒度的机器到机器访问权限,使用更具体的委托人(如角色或用户)和资源(如哪些 API 路径可由哪些委托人调用)。 new PolicyStatement({ effect: Effect.ALLOW, principals: [new AccountPrincipal(Stack.of(scope).account)], actions: ['execute-api:Invoke'], resources: ['execute-api:/*'], }), // 开放 OPTIONS 以允许浏览器进行未经身份验证的预检请求 new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/OPTIONS/*'], }), ], }), operations: routerToOperations(appRouter), ...props, }); }}
这是定义我们 GameApi 的 CDK 构造。如您所见,它提供了一个 defaultIntegrations
方法,自动为 tRPC API 中的每个过程创建 Lambda 函数,指向已打包的 API 实现。这意味着在 cdk synth
时不会发生打包(与使用 NodeJsFunction 不同),因为我们已经将其作为后端项目构建目标的一部分进行了打包。
故事 API
Section titled “故事 API”现在创建我们的故事 API。为此,按照以下步骤创建一个名为 StoryApi
的 Fast API:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - py#fast-api
- 填写必需参数
- name: StoryApi
- moduleName: story_api
- 点击
Generate
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
您应该看到文件树中出现了一些新文件。
py#fast-api 更新文件
以下是 py#fast-api
生成器生成的所有文件列表。我们将检查文件树中突出显示的一些关键文件:
文件夹.venv/ 单体仓库的单一虚拟环境
- …
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹app/ 应用特定的 CDK 构造
文件夹apis/
- story-api.ts 用于创建 Fast API 的 CDK 构造
- index.ts 更新以导出新的 story-api
- project.json 更新以添加对 story_api 的构建依赖
文件夹types/ 共享类型
文件夹src/
- runtime-config.ts 更新以添加 StoryApi
文件夹story_api/
文件夹story_api/ Python 模块
- init.py 设置 Powertools、FastAPI 和中间件
- main.py 包含所有路由的 Lambda 入口点
文件夹tests/
- …
- .python-version
- project.json
- pyproject.toml
- project.json
- .python-version 固定的 uv Python 版本
- pyproject.toml
- uv.lock
import { Construct } from 'constructs';import * as url from 'url';import { Code, Runtime, Function, FunctionProps, Tracing,} from 'aws-cdk-lib/aws-lambda';import { AuthorizationType, Cors, LambdaIntegration,} from 'aws-cdk-lib/aws-apigateway';import { Duration, Stack } from 'aws-cdk-lib';import { PolicyDocument, PolicyStatement, Effect, AccountPrincipal, AnyPrincipal,} from 'aws-cdk-lib/aws-iam';import { IntegrationBuilder, RestApiIntegration,} from '../../core/api/utils.js';import { RestApi } from '../../core/api/rest-api.js';import { OPERATION_DETAILS, Operations,} from '../../generated/story-api/metadata.gen.js';
/** * 创建 StoryApi 构造的属性 * * @template TIntegrations - 操作名称到其集成的映射 */export interface StoryApiProps< TIntegrations extends Record<Operations, RestApiIntegration>,> { /** * 操作名称到 API Gateway 集成的映射 */ integrations: TIntegrations;}
/** * 一个 CDK 构造,用于创建和配置专为 StoryApi 设计的 AWS API Gateway REST API。 * @template TIntegrations - 操作名称到其集成的映射 */export class StoryApi< TIntegrations extends Record<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { /** * 为所有操作创建默认集成,将每个操作实现为独立的 Lambda 函数。 * * @param scope - CDK 构造作用域 * @returns 包含默认 Lambda 集成的 IntegrationBuilder */ public static defaultIntegrations = (scope: Construct) => { return IntegrationBuilder.rest({ operations: OPERATION_DETAILS, defaultIntegrationOptions: { runtime: Runtime.PYTHON_3_12, handler: 'story_api.main.handler', code: Code.fromAsset( url.fileURLToPath( new URL( '../../../../../../dist/packages/story_api/bundle', import.meta.url, ), ), ), timeout: Duration.seconds(30), tracing: Tracing.ACTIVE, environment: { AWS_CONNECTION_REUSE_ENABLED: '1', }, } satisfies FunctionProps, buildDefaultIntegration: (op, props: FunctionProps) => { const handler = new Function(scope, `StoryApi${op}Handler`, props); return { handler, integration: new LambdaIntegration(handler) }; }, }); };
constructor( scope: Construct, id: string, props: StoryApiProps<TIntegrations>, ) { super(scope, id, { apiName: 'StoryApi', defaultMethodOptions: { authorizationType: AuthorizationType.IAM, }, defaultCorsPreflightOptions: { allowOrigins: Cors.ALL_ORIGINS, allowMethods: Cors.ALL_METHODS, }, policy: new PolicyDocument({ statements: [ // 此处授予部署项目账户中的任何 AWS 凭证调用 API 的权限。 // 如果需要,可以在此定义更细粒度的机器到机器访问权限,使用更具体的委托人(如角色或用户)和资源(如哪些 API 路径可由哪些委托人调用)。 new PolicyStatement({ effect: Effect.ALLOW, principals: [new AccountPrincipal(Stack.of(scope).account)], actions: ['execute-api:Invoke'], resources: ['execute-api:/*'], }), // 开放 OPTIONS 以允许浏览器进行未经身份验证的预检请求 new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/OPTIONS/*'], }), ], }), operations: OPERATION_DETAILS, ...props, }); }}
这是定义我们 StoryApi 的 CDK 构造。如您所见,它提供了一个 defaultIntegrations
方法,自动为 FastAPI 中的每个操作创建 Lambda 函数,指向已打包的 API 实现。这意味着在 cdk synth
时不会发生打包(与使用 PythonFunction 不同),因为我们已经将其作为后端项目构建目标的一部分进行了打包。
from .init import app, lambda_handler, tracer
handler = lambda_handler
@app.get("/")@tracer.capture_methoddef read_root(): return {"Hello": "World"}
这是定义所有 API 方法的地方。如您在此处所见,我们有一个映射到 GET /
路由的 read_root
方法。您可以使用 Pydantic 声明方法的输入和输出以确保类型安全。
游戏 UI: 网站
Section titled “游戏 UI: 网站”现在创建用于与游戏交互的 UI。为此,按照以下步骤创建一个名为 GameUI
的网站:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#react-website
- 填写必需参数
- name: GameUI
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
yarn nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
npx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
bunx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
您应该看到文件树中出现了一些新文件。
ts#react-website 更新文件
以下是 ts#react-website
生成器生成的所有文件列表。我们将检查文件树中突出显示的一些关键文件:
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹app/ 应用特定的 CDK 构造
文件夹static-websites/
- game-ui.ts 用于创建游戏 UI 的 CDK 构造
文件夹core/
- static-website.ts 通用静态网站构造
文件夹game-ui/
文件夹public/
- …
文件夹src/
文件夹components/
文件夹AppLayout/
- index.ts 整体页面布局:页眉、页脚、侧边栏等
- navitems.ts 侧边栏导航项
文件夹hooks/
- useAppLayout.tsx 允许动态设置通知、页面样式等
文件夹routes/ @tanstack/react-router 基于文件的路由
- index.tsx 根 ’/’ 页面重定向到 ‘/welcome’
- __root.tsx 所有页面使用此组件作为基础
文件夹welcome/
- index.tsx
- config.ts
- main.tsx React 入口点
- routeTree.gen.ts 此文件由 @tanstack/react-router 自动更新
- styles.css
- index.html
- project.json
- vite.config.ts
- …
import * as url from 'url';import { Construct } from 'constructs';import { StaticWebsite } from '../../core/index.js';
export class GameUI extends StaticWebsite { constructor(scope: Construct, id: string) { super(scope, id, { websiteName: 'GameUI', websiteFilePath: url.fileURLToPath( new URL( '../../../../../../dist/packages/game-ui/bundle', import.meta.url, ), ), }); }}
这是定义我们 GameUI 的 CDK 构造。如您所见,它已配置了指向生成的 Vite UI 捆绑包的文件路径。这意味着在 build
时,打包发生在 game-ui 项目的构建目标内,其输出在此处使用。
import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';
import '@cloudscape-design/global-styles/index.css';
const router = createRouter({ routeTree });
// 为类型安全注册路由器实例declare module '@tanstack/react-router' { interface Register { router: typeof router; }}
const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RouterProvider router={router} /> </I18nProvider> </React.StrictMode>, );
这是挂载 React 的入口点。如所示,它最初仅配置了基于文件路由的 @tanstack/react-router
。这意味着,只要开发服务器在运行,您只需在 routes
文件夹中创建文件,@tanstack/react-router
将为您创建样板文件设置并更新 routeTree.gen.ts
文件。此文件以类型安全的方式维护所有路由,这意味着当您使用 <Link>
时,to
选项仅显示有效路由。有关更多信息,请参阅 @tanstack/react-router
文档。
import { ContentLayout, Header, SpaceBetween, Container,} from '@cloudscape-design/components';import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/welcome/')({ component: RouteComponent,});
function RouteComponent() { return ( <ContentLayout header={<Header>Welcome</Header>}> <SpaceBetween size="l"> <Container>欢迎来到您的新 Cloudscape 网站!</Container> </SpaceBetween> </ContentLayout> );}
当导航到 /welcome
路由时将渲染的组件。只要开发服务器在运行,@tanstack/react-router
将在您创建/移动此文件时管理 Route
。这将在本教程的后续部分中展示。
游戏 UI: 认证
Section titled “游戏 UI: 认证”现在配置我们的游戏 UI,要求通过 Amazon Cognito 进行认证访问。按照以下步骤操作:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#react-website#auth
- 填写必需参数
- cognitoDomain: game-ui
- project: @dungeon-adventure/game-ui
- allowSignup: true
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
yarn nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
npx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
bunx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
您应该看到文件树中出现了一些新文件或变更。
ts#react-website#auth 更新文件
以下是 ts#react-website#auth
生成器生成/更新的所有文件列表。我们将检查文件树中突出显示的一些关键文件:
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹core/
- user-identity.ts 创建用户/身份池的 CDK 构造
文件夹types/
文件夹src/
- runtime-config.ts 更新以添加 cognitoProps
文件夹game-ui/
文件夹src/
文件夹components/
文件夹AppLayout/
- index.tsx 在页头添加已登录用户/注销功能
文件夹CognitoAuth/
- index.ts 管理 Cognito 登录
文件夹RuntimeConfig/
- index.tsx 获取
runtime-config.json
并通过上下文提供给子组件
- index.tsx 获取
文件夹hooks/
- useRuntimeConfig.tsx
- main.tsx 更新以添加 Cognito
import CognitoAuth from './components/CognitoAuth';import RuntimeConfigProvider from './components/RuntimeConfig';import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import '@cloudscape-design/global-styles/index.css';const router = createRouter({ routeTree });// 为类型安全注册路由器实例declare module '@tanstack/react-router' { interface Register { router: typeof router; }}const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RuntimeConfigProvider> <CognitoAuth> <RouterProvider router={router} /> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
通过 AST 转换,main.tsx
文件已更新,添加了 RuntimeConfigProvider
和 CognitoAuth
组件。这使得 CognitoAuth
组件能够通过获取包含所需 Cognito 连接配置的 runtime-config.json
来与 Amazon Cognito 进行认证,从而正确调用后端服务。
游戏 UI: 连接到故事 API
Section titled “游戏 UI: 连接到故事 API”现在配置我们的游戏 UI 连接到之前创建的故事 API:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - api-connection
- 填写必需参数
- sourceProject: @dungeon-adventure/game-ui
- targetProject: dungeon_adventure.story_api
- 点击
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
您应该看到文件树中出现了一些新文件或变更。
UI -> FastAPI api-connection 更新文件
以下是 api-connection
生成器生成/更新的所有文件列表。我们将检查文件树中突出显示的一些关键文件:
文件夹packages/
文件夹game-ui/
文件夹src/
文件夹hooks/
- useSigV4.tsx StoryApi 用于签名请求
- useStoryApiClient.tsx 构建 StoryApi 客户端的钩子
- useStoryApi.tsx 使用 TanStack Query 与 StoryApi 交互的钩子
文件夹components/
- QueryClientProvider.tsx TanStack Query 客户端提供者
- StoryApiProvider.tsx StoryApi TanStack Query 钩子的提供者
- main.tsx 注入 QueryClientProvider 和 StoryApiProvider
- .gitignore 忽略生成的客户端文件
- project.json 更新以添加生成 OpenAPI 钩子的目标
- …
文件夹story_api/
文件夹scripts/
- generate_open_api.py
- project.json 更新以生成 openapi.json 文件
import { StoryApi } from '../generated/story-api/client.gen';import { useSigV4 } from './useSigV4';import { useRuntimeConfig } from './useRuntimeConfig';import { useMemo } from 'react';
export const useStoryApi = (): StoryApi => { const runtimeConfig = useRuntimeConfig(); const apiUrl = runtimeConfig.apis.StoryApi; const sigv4Client = useSigV4(); return useMemo( () => new StoryApi({ url: apiUrl, fetch: sigv4Client, }), [apiUrl, sigv4Client], );};
此钩子可用于向 StoryApi
发起认证的 API 请求。如实现所示,它使用构建时生成的 StoryApi
,因此在构建代码之前 IDE 中会显示错误。有关客户端生成或如何消费 API 的更多细节,请参考 React 到 FastAPI 指南。
import { createContext, FC, PropsWithChildren, useMemo } from 'react';import { useStoryApiClient } from '../hooks/useStoryApiClient';import { StoryApiOptionsProxy } from '../generated/story-api/options-proxy.gen';
export const StoryApiContext = createContext<StoryApiOptionsProxy | undefined>( undefined,);
export const StoryApiProvider: FC<PropsWithChildren> = ({ children }) => { const client = useStoryApiClient(); const optionsProxy = useMemo( () => new StoryApiOptionsProxy({ client }), [client], );
return ( <StoryApiContext.Provider value={optionsProxy}> {children} </StoryApiContext.Provider> );};
export default StoryApiProvider;
上述提供者组件使用 useStoryApiClient
钩子,并实例化 StoryApiOptionsProxy
,用于构建 TanStack Query 钩子的选项。您可以使用相应的钩子 useStoryApi
访问此选项代理,从而以与 tRPC API 一致的方式与您的 FastAPI 交互。
由于 useStoryApiClient
为我们的流式 API 提供了异步迭代器,本教程中将直接使用原生客户端。
游戏 UI: 连接到游戏 API
Section titled “游戏 UI: 连接到游戏 API”现在配置我们的游戏 UI 连接到之前创建的游戏 API:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - api-connection
- 填写必需参数
- sourceProject: @dungeon-adventure/game-ui
- targetProject: @dungeon-adventure/game-api
- 点击
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
您应该看到文件树中出现了一些新文件或变更。
UI -> tRPC api-connection 更新文件
以下是 api-connection
生成器生成/更新的所有文件列表。我们将检查文件树中突出显示的一些关键文件:
文件夹packages/
文件夹game-ui/
文件夹src/
文件夹components/
- GameApiClientProvider.tsx 设置 GameAPI 客户端
文件夹hooks/
- useGameApi.tsx 调用 GameApi 的钩子
- main.tsx 注入 trpc 客户端提供者
- package.json
import { GameApiTRCPContext } from '../components/GameApiClientProvider';
export const useGameApi = GameApiTRCPContext.useTRPC;
此钩子使用 tRPC 最新的 React Query 集成,允许用户直接与 @tanstack/react-query
交互,无需额外的抽象层。有关如何调用 tRPC API 的示例,请参考 使用 tRPC 钩子指南。
import GameApiClientProvider from './components/GameApiClientProvider';import QueryClientProvider from './components/QueryClientProvider';import CognitoAuth from './components/CognitoAuth';import RuntimeConfigProvider from './components/RuntimeConfig';import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import '@cloudscape-design/global-styles/index.css';const router = createRouter({ routeTree });// 为类型安全注册路由器实例declare module '@tanstack/react-router' { interface Register { router: typeof router; }}const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RuntimeConfigProvider> <CognitoAuth> <QueryClientProvider> <GameApiClientProvider> <RouterProvider router={router} /> </GameApiClientProvider> </QueryClientProvider> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
main.tsx
文件已通过 AST 转换更新,注入了 tRPC 提供者。
游戏 UI: 基础设施
Section titled “游戏 UI: 基础设施”现在需要创建的最后一个子项目是 CDK 基础设施。按照以下步骤创建:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#infra
- 填写必需参数
- name: infra
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
您应该看到文件树中出现了一些新文件或变更。
ts#infra 更新文件
以下是 ts#infra
生成器生成/更新的所有文件列表。我们将检查文件树中突出显示的一些关键文件:
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹core/
- checkov.ts
- index.ts
文件夹infra
文件夹src/
文件夹stages/
- application-stage.ts 此处定义 CDK 堆栈
文件夹stacks/
- application-stack.ts 此处定义 CDK 资源
- index.ts
- main.ts 定义所有阶段的入口点
- cdk.json
- project.json
- …
- package.json
- tsconfig.json 添加引用
- tsconfig.base.json 添加别名
import { ApplicationStage } from './stacks/application-stage.js';import { App } from ':dungeon-adventure/common-constructs';
const app = new App();
// 使用此部署您自己的沙盒环境(假设您的 CLI 凭证)new ApplicationStage(app, 'dungeon-adventure-infra-sandbox', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, },});
app.synth();
这是 CDK 应用程序的入口点。
import * as cdk from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props);
// 定义堆栈的代码在此处 }}
这是我们将实例化 CDK 构造以构建地牢冒险游戏的地方。
更新基础设施
Section titled “更新基础设施”让我们更新 packages/infra/src/stacks/application-stack.ts
以实例化一些已生成的构造:
import { GameApi, GameUI, StoryApi, UserIdentity,} from ':dungeon-adventure/common-constructs';import { Stack, StackProps } from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props);
// The code that defines your stack goes here const userIdentity = new UserIdentity(this, 'UserIdentity');
const gameApi = new GameApi(this, 'GameApi', { integrations: GameApi.defaultIntegrations(this).build(), }); const storyApi = new StoryApi(this, 'StoryApi', { integrations: StoryApi.defaultIntegrations(this).build(), });
// grant our authenticated role access to invoke our APIs [storyApi, gameApi].forEach((api) => api.grantInvokeAccess(userIdentity.identityPool.authenticatedRole), );
// Ensure this is instantiated last so our runtime-config.json can be automatically configured new GameUI(this, 'GameUI'); }}
注意,此处我们为两个 API 提供了默认集成。默认情况下,API 中的每个操作都映射到处理该操作的独立 Lambda 函数。
Nx 命令
单目标与多目标
Section titled “单目标与多目标”run-many
命令将在多个列出的子项目上运行目标(--all
将针对所有项目)。它将确保依赖项按正确顺序执行。
您还可以通过直接在项目上运行目标来触发单个项目目标的构建(或任何其他任务)。例如,如果我们想构建 @dungeon-adventure/infra
项目,可以运行以下命令:
pnpm nx run @dungeon-adventure/infra:build
yarn nx run @dungeon-adventure/infra:build
npx nx run @dungeon-adventure/infra:build
bunx nx run @dungeon-adventure/infra:build
可视化依赖关系
Section titled “可视化依赖关系”您还可以通过以下命令可视化依赖关系:
pnpm nx graph
yarn nx graph
npx nx graph
bunx nx graph

Nx 依赖缓存以便重用先前构建的工件,从而加速开发。需要一些配置才能正确工作,有时您可能希望在不使用缓存的情况下执行构建。为此,只需在命令后附加 --skip-nx-cache
参数。例如:
pnpm nx run @dungeon-adventure/infra:build --skip-nx-cache
yarn nx run @dungeon-adventure/infra:build --skip-nx-cache
npx nx run @dungeon-adventure/infra:build --skip-nx-cache
bunx nx run @dungeon-adventure/infra:build --skip-nx-cache
如果出于某种原因需要清除缓存(存储在 .nx
文件夹中),可以运行以下命令:
pnpm nx reset
yarn nx reset
npx nx reset
bunx nx reset
pnpm nx run-many --target build --all
yarn nx run-many --target build --all
npx nx run-many --target build --all
bunx nx run-many --target build --all
您应该会看到以下提示:
NX The workspace is out of sync
[@nx/js:typescript-sync]: Some TypeScript configuration files are missing project references to the projects they depend on or contain outdated project references.
This will result in an error in CI.
? Would you like to sync the identified changes to get your workspace up to date? …Yes, sync the changes and run the tasksNo, run the tasks without syncing the changes
此消息表示 NX 检测到一些可以自动更新的文件。在本例中,它指的是未设置对引用项目的 TypeScript 引用的 tsconfig.json
文件。选择 Yes, sync the changes and run the tasks 选项继续。您应该注意到所有与 IDE 相关的导入错误都会自动解决,因为同步生成器会自动添加缺失的 TypeScript 引用!
所有构建工件现在都位于单体仓库根目录的 dist/
文件夹中。这是使用 @aws/nx-plugin
生成的项目时的标准做法,因为它不会用生成的文件污染文件树。如果您想清理文件,只需删除 dist/
文件夹,而无需担心生成的文件散落在文件树中。
恭喜!您已创建了开始实现地牢冒险游戏核心所需的所有子项目。🎉🎉🎉