AI地牢游戏
模块 1:Monorepo 设置
Section titled “模块 1:Monorepo 设置”我们将从创建一个新的 monorepo 开始。在目标目录中运行以下命令:
npx create-nx-workspace@~21.4.1 dungeon-adventure --pm=pnpm --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.4.1 dungeon-adventure --pm=yarn --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.4.1 dungeon-adventure --pm=npm --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.4.1 dungeon-adventure --pm=bun --preset=@aws/nx-plugin --ci=skip
这将在 dungeon-adventure
目录中创建一个 NX monorepo,之后你可以在 vscode 中打开它。其文件结构应如下所示:
文件夹.nx/
- …
文件夹.vscode/
- …
文件夹node_modules/
- …
文件夹packages/ 此处将存放你的子项目
- …
- .gitignore
- .npmrc
- .prettierignore
- .prettierrc
- nx.json 配置 Nx CLI 和 monorepo 默认值
- 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 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/ 用于 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';
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 // 如需细粒度控制,可在此定义更具体的权限(如角色/用户)和资源(如允许特定主体调用特定 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 // 如需细粒度控制,可在此定义更具体的权限(如角色/用户)和资源(如允许特定主体调用特定 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 创建 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, { websiteName: 'GameUI', websiteFilePath: url.fileURLToPath( new URL( '../../../../../../dist/packages/game-ui/bundle', import.meta.url, ), ), }); }}
这是定义我们 GameUI 的 CDK 构造体。它已配置了指向 Vite 构建产出的文件路径,这意味着在 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 { 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/
文件夹cfn-guard-rules/
- *.guard
- cfn-guard.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, CfnGuardValidator, RuleSet,} from ':dungeon-adventure/common-constructs';
const app = new App({ policyValidationBeta1: [new CfnGuardValidator(RuleSet.AWS_PROTOTYPING)],});
// 用于部署你自己的沙盒环境(假设已配置 CLI 凭证)new ApplicationStage(app, 'dungeon-adventure-infra-sandbox', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, },});
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 { 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 命令
单目标 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 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 引用!
所有构建产物现在都位于 monorepo 根目录的 dist/
文件夹中。这是使用 @aws/nx-plugin
生成项目的标准做法,避免生成文件污染文件树。如需清理文件,只需删除 dist/
文件夹即可。
恭喜!你已成功创建了实现地牢冒险游戏核心功能所需的所有子项目。🎉🎉🎉