AI地牢游戏
模块一:Monorepo 配置
我们将从创建一个新的 monorepo 开始。在您选择的目录下运行以下命令:
npx create-nx-workspace@~20.6.3 dungeon-adventure --pm=pnpm --preset=ts --ci=skip --formatter=prettier
npx create-nx-workspace@~20.6.3 dungeon-adventure --pm=yarn --preset=ts --ci=skip --formatter=prettier
npx create-nx-workspace@~20.6.3 dungeon-adventure --pm=npm --preset=ts --ci=skip --formatter=prettier
npx create-nx-workspace@~20.6.3 dungeon-adventure --pm=bun --preset=ts --ci=skip --formatter=prettier
这将在 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
的组件添加到 monorepo 中,我们需要将其作为开发依赖项安装。在 dungeon-adventure
monorepo 的根目录下运行以下命令:
pnpm add -Dw @aws/nx-plugin
yarn add -D @aws/nx-plugin
npm install --legacy-peer-deps -D @aws/nx-plugin
bun install -D @aws/nx-plugin
现在我们可以使用 @aws/nx-plugin
开始创建不同的子项目了。
游戏 API
首先创建我们的游戏 API。为此,按照以下步骤创建一个名为 GameApi
的 tRPC API:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#trpc-api
- 填写必需参数
- apiName: GameApi
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive
yarn nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive
npx nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive
bunx nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive --dry-run
您应该看到文件树中出现了一些新文件。
ts#trpc-api 更新文件
以下是 ts#trpc-api
生成器生成的所有文件列表。我们将检查文件树中突出显示的一些关键文件:
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹app/ 应用特定的 CDK 构造
文件夹http-apis/
- game-api.ts 创建 tRPC API 的 CDK 构造
- index.ts
- …
- index.ts
文件夹core/ 通用 CDK 构造
- http-api.ts HTTP API 的基础 CDK 构造
- index.ts
- runtime-config.ts
- index.ts
- project.json
- …
文件夹types/ 共享类型
文件夹src/
- index.ts
- runtime-config.ts 由 CDK 和前端共同使用的接口定义
- project.json
- …
文件夹game-api/
文件夹backend/ tRPC 实现代码
文件夹src/
文件夹client/ 通常用于 TS 机器间调用的客户端
- index.ts
- sigv4.ts
文件夹middleware/ Powertools 工具集
- error.ts
- index.ts
- logger.ts
- metrics.ts
- tracer.ts
文件夹procedures/ API 方法/路由的具体实现
- echo.ts
- index.ts
- init.ts 设置上下文和中间件
- local-server.ts 本地运行 tRPC 服务器时使用
- router.ts Lambda 处理程序的入口点,定义所有方法
- project.json
- …
文件夹schema/
文件夹src/
文件夹procedures/
- echo.ts
- index.ts
- 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 { APIGatewayProxyEventV2WithIAMAuthorizer } from 'aws-lambda';
export const router = t.router;
export const appRouter = router({ echo,});
export const handler = awsLambdaRequestHandler({ router: appRouter, createContext: ( ctx: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2WithIAMAuthorizer>, ) => ctx,});
export type AppRouter = typeof appRouter;
路由器定义了 tRPC API 的入口点,是声明所有 API 方法的地方。如上所示,我们有一个名为 echo
的方法,其实现位于 ./procedures/echo.ts
文件中。
import { publicProcedure } from '../init.js';import { EchoInputSchema, EchoOutputSchema,} from ':dungeon-adventure/game-api-schema';
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ result: opts.input.message }));
该文件是 echo
方法的实现,通过声明输入和输出数据结构进行强类型化。它从 :dungeon-adventure/game-api-schema
项目中导入这些定义,这是 schema 项目的 别名。
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 schema 定义均使用 Zod 定义,并通过 z.TypeOf
语法导出为 TypeScript 类型。
import { Construct } from 'constructs';import * as url from 'url';import { HttpApi } from '../../core/http-api.js';import { HttpIamAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';import { Runtime } from 'aws-cdk-lib/aws-lambda';
export class GameApi extends HttpApi { constructor(scope: Construct, id: string) { super(scope, id, { defaultAuthorizer: new HttpIamAuthorizer(), apiName: 'GameApi', runtime: Runtime.NODEJS_LATEST, handler: 'index.handler', handlerFilePath: url.fileURLToPath( new URL( '../../../../../../dist/packages/game-api/backend/bundle', import.meta.url, ), ), }); }}
这是定义 GameApi 的 CDK 构造。如您所见,它已配置处理程序文件路径指向 tRPC 后端实现的生成包。这意味着在 cdk synth
时不会发生捆绑(与使用 NodeJsFunction 不同),因为我们已在后端项目的构建目标中完成了捆绑。
故事 API
现在创建我们的故事 API。按照以下步骤创建一个名为 StoryApi
的 Fast API:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - py#fast-api
- 填写必需参数
- name: StoryApi
- 点击
Generate
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
您应该看到文件树中出现了一些新文件。
py#fast-api 更新文件
以下是 py#fast-api
生成器生成的所有文件列表。我们将检查文件树中突出显示的一些关键文件:
文件夹.venv/ monorepo 的单一虚拟环境
- …
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹app/ 应用特定的 CDK 构造
文件夹http-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 { HttpApi } from '../../core/http-api.js';import { HttpIamAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';import { Runtime } from 'aws-cdk-lib/aws-lambda';
export class StoryApi extends HttpApi { constructor(scope: Construct, id: string) { super(scope, id, { defaultAuthorizer: new HttpIamAuthorizer(), apiName: 'StoryApi', runtime: Runtime.PYTHON_3_12, handler: 'story_api.main.handler', handlerFilePath: url.fileURLToPath( new URL( '../../../../../../dist/packages/story_api/bundle', import.meta.url, ), ), }); }}
这是定义 StoryApi 的 CDK 构造。如您所见,它已配置处理程序文件路径指向 Fast 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 { httpApis: { 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:网站
现在创建用于与游戏交互的 UI。按照以下步骤创建一个名为 GameUI
的网站:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#cloudscape-website
- 填写必需参数
- name: GameUI
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
yarn nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
npx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
bunx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
您应该看到文件树中出现了一些新文件。
ts#cloudscape-website 更新文件
以下是 ts#cloudscape-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>欢迎</Header>}> <SpaceBetween size="l"> <Container>欢迎来到您的新 Cloudscape 网站!</Container> </SpaceBetween> </ContentLayout> );}
当导航到 /welcome
路由时,将渲染此组件。@tanstack/react-router
将在您创建/移动此文件时管理 Route
(只要开发服务器运行)。这将在本教程的后续部分中展示。
游戏 UI:身份验证
现在通过 Amazon Cognito 配置我们的 Game UI 要求身份验证访问。按照以下步骤操作:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#cloudscape-website#auth
- 填写必需参数
- cognitoDomain: game-ui
- project: @dungeon-adventure/game-ui
- allowSignup: true
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
yarn nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
npx nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
bunx nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
您应该看到文件树中出现/更改了一些新文件。
ts#cloudscape-website#auth 更新文件
以下是 ts#cloudscape-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 转换将 RuntimeConfigProvider
和 CognitoAuth
组件添加到 main.tsx
文件。这允许 CognitoAuth
组件通过获取包含所需 Cognito 连接配置的 runtime-config.json
来与 Amazon Cognito 进行身份验证,从而正确调用后端。
游戏 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.httpApis.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
访问此选项代理,提供与您的 FastAPI 交互的一致方式。
由于 useStoryApiClient
为我们的流式 API 提供了异步迭代器,在本教程中我们将直接使用原生客户端。
游戏 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-backend
- 点击
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive --dry-run
您应该看到文件树中出现/更改了一些新文件。
UI -> tRPC api-connection 更新文件
以下是 api-connection
生成器生成/更新的所有文件列表。我们将检查文件树中突出显示的一些关键文件:
文件夹packages/
文件夹game-ui/
文件夹src/
文件夹components/
文件夹TrpcClients/
- index.tsx
- TrpcApis.tsx 所有配置的 tRPC API
- TrpcClientProviders.tsx 为每个 tRPC API 创建客户端提供者
- TrpcProvider.tsx
文件夹hooks/
- useGameApi.tsx 调用 GameApi 的钩子
- main.tsx 注入 trpc 客户端提供者
- package.json
import { TrpcApis } from '../components/TrpcClients';
export const useGameApi = () => TrpcApis.GameApi.useTRPC();
此钩子使用 tRPC 的最新 React Query 集成,允许用户直接与 @tanstack/react-query
交互而无需额外抽象层。有关如何调用 tRPC API 的示例,请参考 使用 tRPC 钩子指南。
import TrpcClientProviders from './components/TrpcClients';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> <TrpcClientProviders> <RouterProvider router={router} /> </TrpcClientProviders> </QueryClientProvider> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
通过 AST 转换更新 main.tsx
文件以注入 tRPC 提供者。
游戏 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 构造以构建地牢冒险游戏的地方。
更新基础设施
让我们更新 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);
// 定义堆栈的代码在此处 const userIdentity = new UserIdentity(this, 'UserIdentity');
const gameApi = new GameApi(this, 'GameApi'); const storyApi = new StoryApi(this, 'StoryApi');
// 授予已验证角色调用 API 的权限 [storyApi, gameApi].forEach((api) => api.grantInvokeAccess(userIdentity.identityPool.authenticatedRole), );
// 确保最后实例化,以便自动配置 runtime-config.json new GameUI(this, 'GameUI'); }}
构建代码
Nx 命令
单目标 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 引用!
所有构建产物现在可在 monorepo 根目录的 dist/
文件夹中找到。这是使用 @aws/nx-plugin
生成的项目时的标准做法,因为它不会用生成的文件污染您的文件树。如果您想清理文件,只需删除 dist/
文件夹,无需担心生成的文件散落各处。
恭喜!您已创建了开始实现地牢冒险游戏核心所需的所有子项目。🎉🎉🎉