AI地牢游戏
模块 1: 单仓库设置
Section titled “模块 1: 单仓库设置”我们将从创建新的单仓库开始。在目标目录中运行以下命令:
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=pnpm --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=yarn --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=npm --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=bun --preset=@aws/nx-plugin --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
开始创建不同的子项目。
游戏 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 基础构造
- 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/ 用于 TS 机器间调用的客户端
- 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/v4';
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;}
/** * 用于创建和配置 AWS API Gateway REST API 的 CDK 构造(专为 GameApi 设计) * @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 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;}
/** * 用于创建和配置 AWS API Gateway REST API 的 CDK 构造(专为 StoryApi 设计) * @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 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 不同),因为我们已经通过后端项目的构建目标完成了打包。
export type ApiUrl = string;// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interfaceexport interface IRuntimeConfig { apis: { GameApi: ApiUrl; StoryApi: ApiUrl; };}
这是生成器执行 AST 转换的示例,保留现有代码并进行更新。此处 StoryApi
被添加到 IRuntimeConfig
定义中,当前端消费时将强制执行类型安全!
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 创建 Game 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, { 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:认证”现在通过以下步骤配置 Game 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”现在配置 Game 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”现在配置 Game 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 { useGameApi as useClient } from '../components/GameApiClientProvider';
export const useGameApi = useClient;
此钩子使用 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>, );
通过 AST 转换更新了 main.tsx
文件以注入 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/
文件夹cfn-guard-rules/
- *.guard
- cfn-guard.ts
- index.ts
文件夹infra
文件夹src/
文件夹stacks/
- application-stack.ts 定义 CDK 资源
- index.ts
- main.ts 定义所有堆栈的入口点
- cdk.json
- project.json
- …
- package.json
- tsconfig.json 添加引用
- tsconfig.base.json 添加别名
import { ApplicationStack } from './stacks/application-stack.js';import { App, CfnGuardValidator, RuleSet,} from ':dungeon-adventure/common-constructs';
const app = new App({ policyValidationBeta1: [new CfnGuardValidator(RuleSet.AWS_PROTOTYPING)],});
// 部署自己的沙盒环境(假设已配置 CLI 凭证)new ApplicationStack(app, 'dungeon-adventure-infra-sandbox', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, }, crossRegionReferences: true,});
app.synth();
这是 CDK 应用的入口点。
配置使用 cfn-guard
根据配置的规则集进行基础设施验证。该验证在合成后执行。
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 * 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);
// 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 命令
单目标 vs 多目标
Section titled “单目标 vs 多目标”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
可通过以下命令可视化依赖:
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 工作区不同步
[@nx/js:typescript-sync]: 某些 TypeScript 配置文件缺少项目引用或包含过时的引用。
这将在 CI 中导致错误。
? 是否要同步变更以使工作区保持最新? …是,同步变更并运行任务否,不同步直接运行任务
此消息表示 NX 检测到可自动更新的文件。此处指未设置 Typescript 引用的 tsconfig.json
文件。选择 是,同步变更并运行任务 继续。所有 IDE 导入错误将自动解决,因为同步生成器会自动添加缺失的 Typescript 引用!
所有构建产物现在位于单仓库根目录的 dist/
文件夹中。这是使用 @aws/nx-plugin
生成项目的标准实践,避免生成文件污染文件树。如需清理,直接删除 dist/
文件夹即可。
恭喜!您已成功创建地牢冒险游戏所需的所有子项目,可以开始核心功能开发了。🎉🎉🎉