Webiny `ai-powerups` 架构重构实战:AI Personas 分支的 7 项非破坏性改造计划全解析

发布时间:2026/9/29 5:34:53
Webiny `ai-powerups` 架构重构实战:AI Personas 分支的 7 项非破坏性改造计划全解析 CMS后端前端【免费下载链接】webiny-jsOpen-source, self-hosted CMS platform on AWS serverless (Lambda, DynamoDB, S3). TypeScript framework with multi-tenancy, lifecycle hooks, GraphQL API, and AI-assisted development via MCP server. Built for developers at large organizations.项目地址https://gitcode.com/gh_mirrors/we/webiny-js点击查看免费下载导读本文基于仓库内 ai-context/plans/ai-personas-refactor.md 这一架构重构计划文档完整解析 Webiny 在feat/ai-personas分支上围绕 AI Projects项目预设、Reader/Writer Personas读者/作者人设与增强内容生成功能暴露出的 5 类架构违规绕过 DI 直接使用 S3、Personas 间近乎全量的代码重复、用例职责膨胀、硬编码配置以及静默吞错。计划以 7 个互有依赖的重构项P0–P3逐一修复并给出分 4 个阶段的无阻塞执行顺序与可落地的验证命令。读完本文你将掌握该计划的核心抽象划分GetFileContentsUseCase、ProjectFileLoader、TokenBudgetConfig、GenerationContextResolver、IContentGenerationOptionsProvider、工厂模式收敛重复代码的手法以及如何在当前仓库源码中印证每项改造的现状与目标。一、背景feat/ai-personas分支的架构债从何而来计划文档开篇即点明问题来源feat/ai-personas分支引入了 AI Projects、Reader/Writer Personas 以及增强内容生成能力功能本身可以工作但快速迭代过程中积累了多项架构违规主要包括直接使用 S3 绕过依赖注入DIloadProjectFiles.ts直接new S3()、读取process.env.S3_BUCKET并伸手进入 FileManager 的内部 KV 结构FileManager/File/${id}/Metadata把 S3 拉取、KV 缓存、MIME 过滤、元数据查询混在一个纯函数里无法参与 DI。Persona 类型间近乎全量的代码重复Reader 与 Writer 两类 Persona 之间有 6 个文件几乎一模一样——API handler、类型定义与管理端设置。再加第三种 Persona 就得再把这一切复制一遍。用例职责持续膨胀WbGeneratePageContentUseCase拥有 5 个构造依赖同时承担设置查询、Persona 解析、项目加载、文件组装、Prompt 构建与 AI 调用。配置硬编码ProjectsHandler.ts把TOKEN_BUDGET 150_000与CHARS_PER_TOKEN 4写死不同模型的上下文窗口不同却无法调整。静默错误吞噬loadProjectFiles.ts第 91、104、150 行与GenerateContentPresenter.ts第 133 行存在空 catch 块生产环境排障几乎不可能。计划的目标是以**具体的、非破坏性non-breaking**的重构逐一解决上述问题。计划还明确给出了每个重构项的优先级P0–P3、工作量XS–L、关键文件与依赖关系。需要说明的是本计划文档描述的是目标态对照当前仓库源码可以发现其中部分抽象如AiPromptContext下的ProjectFileCache、ProjectFileAssembler、AiPromptContextBuilder已经落地而另一部分如ProjectsHandler.ts中的硬编码预算仍保持计划中描述的原始状态。下文的“现状”均基于当前仓库代码核实。二、Item 1P0Size: L提取GetFileContentsUseCase让ai-powerups彻底告别裸 S32.1 问题本质计划指出loadProjectFiles.ts是一个无法参与 DI 的纯函数它把 S3 对象读取、KV 缓存、MIME 过滤与 FileManager 元数据查询耦合在一起。对api-file-manager系列包而言S3 是存储层的实现细节本应被抽象在“用例Use Case”之后而ai-powerups作为消费方不应知道FileManager/File/${id}/Metadata这种内部 KV 结构。2.2 计划的三步走步骤所在包动作1. 定义抽象packages/api-file-manager新建src/features/file/GetFileContents/abstractions.ts声明GetFileContentsUseCase方法签名为execute(fileId: string): PromiseResult{ buffer: Buffer; contentType: string }, Error并从包索引导出2. 实现 S3 版本packages/api-file-manager-s3新建src/features/GetFileContents/GetFileContentsUseCaseImpl.ts注入GlobalKeyValueStore复用该包既有的MetadataReader模式与 S3 client读取process.env.S3_BUCKET该包的既定约定并在 S3 feature 中注册3. 消费方收敛packages/ai-powerups把loadProjectFiles.ts改造成可注入的ProjectFileLoader类ProjectFileLoader注入GetFileContentsUseCase与GlobalKeyValueStoreMIME 过滤与项目级缓存留在ai-powerups属于它的关切WbGeneratePageContentUseCase改为注入ProjectFileLoader2.3 仓库现状印证抽象的“已落地版本”在当前仓库中packages/api-file-manager已存在与之同构的抽象 GetFileContentsById/abstractions.ts它通过createAbstraction声明GetFileContentsByIdUseCase返回ResultFileContents, GetFileContentsByIdError其中FileContents { buffer: Buffer; contentType: string }与计划中 Item 1 的签名{ buffer; contentType }完全一致错误类型则收敛为FileNotFoundError与FilePersistenceError两种。S3 一侧的实现 GetFileContentsByIdUseCase.ts 正是计划描述的实现范式构造时只注入GlobalKeyValueStore内部实例化MetadataReader见 MetadataReader.ts其读取的 KV 键为FileManager/File/${fileId}/Metadata——这正是计划中点名的“FileManager 内部 KV 结构”如今被封装在存储包内部不再泄漏给上层执行时先读元数据拿不到即返回FileNotFoundError随后new S3()并读取process.env.S3_BUCKET通过metadata.bucketKey发起getObject成功则返回{ buffer, contentType }失败则包装为FilePersistenceError注册方式为GetFileContentsByIdUseCase.createImplementation({ dependencies: [GlobalKeyValueStore] })。也就是说Item 1 的“抽象 存储实现 DI 注册”三段式在当前仓库中已经以GetFileContentsByIdUseCase的名称落地而ai-powerups侧对应loadProjectFiles.ts的职责如今由 AiPromptContext 下的ProjectFileAssembler负责 MIME 过滤、批量拉取、组装ProjectFileContent与ProjectFileCache负责项目级上下文缓存承担详见下文 Item 4。计划中 Item 1 的依赖标注为“无依赖”是 Item 4 与 Item 5file loader 部分的前置基础。三、Item 2P1Size: S用工厂模式统一 Reader/Writer Personas3.1 问题本质计划指出Reader 与 Writer 两类 Persona 之间存在 6 个近重复文件API handler、类型、管理端设置。增加第三种 Persona如 SEO Persona就意味着再复制一遍全套。3.2 计划的 API 侧改造在packages/ai-powerups/src/api/features/Personas/新建types.ts共享的PersonaPreset { id, name, description, style? }与PersistedPersonas类型createPersonasHandler.ts工厂函数入参为{ name: string }返回一个 handler 类内部持有共享的 Zod schema 与映射逻辑ReaderPersonasHandler.ts与WriterPersonasHandler.ts退化为一行工厂调用保留每种类型各自的declare module增强——因为它们在IAiPowerUpsSettings上挂的是不同 keyreaderPersonas/writerPersonas。3.3 计划的 Admin 侧改造新建packages/ai-powerups/src/admin/presentation/createPersonasSettingsGroup.ts工厂入参为配置对象{ name, label, description, addItemLabel, itemTitlePrefix, descriptionHint, styleHint }ReaderPersonasSettings.ts与WriterPersonasSettings.ts各自退化为薄包装。3.4 仓库现状印证重复确实存在对照当前仓库API 侧 ReaderPersonasHandler.ts 与 WriterPersonasHandler.ts 的正文几乎逐字相同同样的inputSchema{ presets: z.array(z.object({ id, name, description, style? })) }、同样的mapFromStorage空值回退{ presets: [] }、同样的mapToStorage字段透传仅namereaderPersonas/writerPersonas不同。管理端 ReaderPersonasSettings.ts 与 WriterPersonasSettings.ts 同样高度同构都用AiPowerUpsSettingsGroup抽象定义见 settingsGroup.ts都通过fields.object().renderer(objectAccordionMultiple, ...)构建presets列表字段均为id隐藏、generateAlphaNumericId(10)生成、name、descriptiontextarea8 行、styletextarea3 行差异仅在于label、description与提示文案。这恰好印证了计划对“6 个文件近重复、扩展第三种类型需复制全套”的判断。计划中 Item 2 的依赖标注为“无依赖、独立”可并行执行。类型层面两种 Persona 的 settings 增强分别见 ReaderPersonas/types.ts 与 WriterPersonas/types.ts均通过declare module ~/api/types.js合并进 api/types.ts 中的空接口IAiPowerUpsSettings {}——这也是“保持各自独立增强”这一设计要点的来源IAiPowerUpsSettings是插件通过声明合并扩展的开放接口。四、Item 3P2Size: S让 Token 预算可配置4.1 问题本质计划指出ProjectsHandler.ts将TOKEN_BUDGET 150_000与CHARS_PER_TOKEN 4写死。不同模型的上下文窗口不同Claude 与 GPT 系列对“每 token 对应字符数”的经验估算也不同不改代码就无法调整。4.2 计划方案在ai-powerups/src/api/features/Projects/abstractions.ts新建TokenBudgetConfig抽象接口{ getTokenBudget(): number; getCharsPerToken(): number }默认实现返回现值150K 与 4ProjectsHandler改为注入TokenBudgetConfig而非使用常量消费方可为不同模型通过container.register()覆盖默认实现。4.3 仓库现状印证硬编码依然存在当前 ProjectsHandler.ts 第 18–20 行仍是计划描述的原始状态const TOKEN_BUDGET 150_000; const CHARS_PER_TOKEN 4; const MAX_CONTEXT_BYTES TOKEN_BUDGET * CHARS_PER_TOKEN;这些常量被用在presetSchema的superRefine校验里第 33–56 行当某项目预设的instructions字节数加上所有文件字节数之和超过MAX_CONTEXT_BYTES即 600_000 字节时按totalBytes / CHARS_PER_TOKEN估算 token 数找出最大的 3 个文件产出形如Project xxx exceeds the 150,000 token budget (~N tokens). Largest files: ...的 Zod 自定义错误。这正是“不同模型上下文窗口不同却无法调整”的具象场景预算既是校验阈值也间接决定了模型能一次吃下的项目上下文量。按计划落地后该校验与后续上下文组装即可随TokenBudgetConfig的实现而缩放。该项目的持久化结构含version自增逻辑见 Projects/types.ts。五、Item 4P2Size: M从用例中提取GenerationContextResolver5.1 问题本质计划指出WbGeneratePageContentUseCase有 5 个构造依赖同时处理设置查询、Persona 解析、项目加载、文件组装、Prompt 构建与 AI 调用其中第 50–78 行是纯上下文解析逻辑应当独立。5.2 计划方案在WbGeneratePageContent/abstractions.ts新建GenerationContextResolver抽象接口resolve(params): PromiseGenerationContext其中GenerationContext { project?, readerPersona?, writerPersona?, files }实现注入GetSettingsUseCase与ProjectFileLoader用例依赖收敛为 3 个GenerationContextResolver、Ai、Encryption外加AiSdkTools。5.3 仓库现状印证上下文解析已由AiPromptContext承载计划中 Item 4 依赖 Item 1ProjectFileLoader。当前仓库中ai-powerups已经演化出与计划同构的落地形态——AiPromptContext featureAiPromptContextBuilder.execute(params: AiPromptContextParams): PromiseAiPromptContext其中AiPromptContext { project?, readerPersona?, writerPersona?, allProjectFiles, additionalFiles, excludedFileIds, cacheHit, warnings, toString() }与计划的GenerationContext { project?, readerPersona?, writerPersona?, files }一一对应allProjectFilesadditionalFiles即files的超集另增excludedFileIds与warnings透传实现类 AiPromptContextBuilder.ts 注入GetSettingsUseCase、ProjectFileAssembler、GetFileContentsByIdUseCase、GetFileUseCase、TextExtractor五个依赖——这正是“设置查询 项目加载 文件组装”的职责组合与计划中GenerationContextResolver注入GetSettingsUseCaseProjectFileLoader的划分一一对应。AiPromptContextBuilder内部的关键逻辑与计划描述完全吻合resolvePersonas()第 20–52 行Persona 解析遵循“请求参数 项目默认”的优先级——params.readerPersonaId ?? project?.defaultReaderPersonaId ?? undefined再从settings.readerPersonas.presets/settings.writerPersonas.presets中按 id 查找项目上下文第 118–139 行有projectPreset时调用ProjectFileAssembler组装文件再按excludedFileIds过滤计算totalTokens附加文件第 163–213 行对additionalFileIds逐个执行GetFileContentsByIdUseCaseGetFileUseCase的并发读取经TextExtractor.canExtract(mimeType)白名单过滤后转为ProjectFileContenttoken 数按content.length / 4估算formatContext()第 54–70 行依次拼装 Writer Persona 段、Reader Persona 段、Project 段。各段的文案由 WriterPersonaSection.ts、ReaderPersonaSection.ts 与 ProjectSection.ts 提供——Project 段会列出每个参考文件的id / name / description / tokens并提示模型“仅在需要时用read_project_file工具读取不要预先全读”。组装与缓存由两个配套实现完成ProjectFileAssembler.tsMIME 白名单过滤text/*前缀 application/json、application/csv命中缓存直接返回{ files, cacheHit: true }否则逐文件并发拉取、utf-8 解码、估算 token 并回写缓存单个文件失败会console.warn并追加到warnings而非中断整体ProjectFileCache.ts基于GlobalKeyValueStoregzip 压缩后以 base64 存储TTL 30 天缓存键为AiProjectContext/${projectId}/v${version}——版本号变化即天然失效正对应ProjectsHandler中的version自增逻辑。回到用例侧当前 WbGeneratePageContentUseCase.ts 的职责边界已经比计划描述的“5 依赖巨类”清晰很多它注入AiPromptContextBuilder、ResolveAiCapabilityUseCase、Ai、AiSdkTools、ListTagsUseCase五个依赖但上下文解析已委托给AiPromptContextBuilder.execute()第 47–53 行自身聚焦于能力解析WB_GENERATE_PAGE_CAPABILITY、把项目文件注册为read_project_file工具ReadProjectFileTool.ts、拉取图片标签供提示词枚举、调用ai.generateText、解析 LLM JSONLlmJsonResponse.ts、用ComponentFilter过滤幻觉组件并产出GenerationTelemetryfilesRead、cacheHit、toolCallsMade、totalSteps、toolsAvailable、imageTagsInPrompt。六、Item 5P1Size: S消灭静默错误吞噬6.1 问题本质计划点名两处空 catchloadProjectFiles.ts第 91、104、150 行的空 catch 块GenerateContentPresenter.ts第 133 行的空 catch 块。后果是生产环境排障几乎不可能——缓存反序列化失败、缓存写入失败、S3 拉取失败都被无声吞掉用户侧只看到“生成没反应”。6.2 计划方案ProjectFileLoader依赖 Item 1 完成后失败场景处理方式缓存反序列化失败console.warn携带缓存 key 与 error缓存写入失败console.warn携带缓存 key 与 errorS3/文件拉取失败console.warn携带文件 id、bucket key 与 error返回类型Result{ files: ProjectFileContent[], warnings: string[] }GenerateContentPresenter.ts独立项在 ViewModel 中新增error?: stringcatch 块中不再只是console.error而是写入该字段供 UI 展示。6.3 仓库现状印证warnings 机制已成型Presenter 仍未补 error在项目加载链路中计划的“warnings 透传”模式已经落地AiPromptContext的execute返回warnings: string[]ProjectFileAssembler对每个失败文件执行console.warn(ProjectFileAssembler:, msg)并warnings.push(msg)见 ProjectFileAssembler.ts 第 63–79 行AiPromptContextBuilder对“设置加载失败”“附加文件加载失败”“类型不支持”也分别 push warning第 100–104、178–193 行ProjectFileCache对缓存损坏与写入失败均console.warn并携带 key见 ProjectFileCache.ts 第 26–31、46–48 行。需要注意一个风格细节仓库的代码风格指南 no-console-in-backend.md 明确规定后端api-*代码禁止使用console.log/warn/error应注入webiny/api-core/features/logger的 DILoggerpino 支撑结构化上下文作为首参。因此若将计划中的“console.warn携带结构化错误”落地产出更贴合仓库现状的做法是改为logger.warn({ key, error }, message)。这是阅读计划时值得注意的、与仓库既定规范衔接的落地细节。Presenter 侧当前 GenerateContentPresenter.ts 第 100–105 行的 catch 块依然只做clearTimeout与_submitting false复位未暴露错误信息对应的 ViewModel 定义 abstractions.ts 中IGenerateContentVm也尚无error字段——即计划中 Item 5 的 presenter 部分独立、无依赖在当前仓库中仍未实施是可立即动手的改造点。七、Item 6P3Size: S让 Presenter 与原始 settings 形状解耦7.1 问题本质计划指出GenerateContentPresenter在 5 个私有方法里直接导航this._settings?.projects?.presets。一旦 settings 形状变化例如projects.presets改名或嵌套层级调整每个方法都会一起崩。7.2 计划方案提取IContentGenerationOptionsProvider接口含 5 个方法getProjectOptions()getProjectFileOptions(projectId)getPersonaOptions(type)getProjectDefaults(projectId)getAllFileIds(projectId)实现类包装IAiPowerUpsSettingsPresenter 改为注入该 provider 而非裸 settings。依赖标注为“无依赖但低优先级”放在 Phase 4。对照当前 GenerateContentPresenter.ts 的实现它的输入实际上来自表单AiPromptFormFactory生成的表单在submit()中产出{ prompt, project?, includedFiles?, readerPersona?, writerPersona?, additionalFiles? }并借用ToolRegistry/ToolPipelineRunner做工具注册与 AI 响应解析若按计划引入 provider职责边界将变为“Present 层只面向 provider 语义方法settings 的形状变化被隔离在 provider 内部”。八、Item 7P3Size: XS导出buildPrompt辅助函数以支持独立测试计划指出buildPrompt.ts中的buildPersonaSections()与buildProjectSection()是模块私有函数但它们承载非平凡的字符串组装逻辑应当可独立测试——方案就是给两个函数加上export关键字。依赖标注为“无依赖”因此排入 Phase 1 与 Item 2、3、5 并行。对照当前 buildPrompt.ts可见本文件已演进为单一导出buildDomainPrompt(components, tools, availableImageTags)它内联了 Rich Text 内容策略、图片选择铁律“必须先调用listImagesByTag再发出resolveImage绝不臆造图片 ID”、SEO 内容结构最佳实践单h1、层级不跳级、倒金字塔结构、自然关键词布局、组件目录 JSON、工具信封{ tool: ..., params: ... }、页面 Schema 类型定义以及Webiny/Grid GridColumn children的嵌套结构示例与“只能用目录内组件”的硬约束。而 Persona 段与项目段的分段文案已由 AiPromptContext 下的WriterPersonaSection、ReaderPersonaSection、ProjectSection三个静态格式化类承担并由formatContext()负责拼接。因此计划中“导出分段 helper 以独立测试”的诉求在当前仓库中相当于对上述字符串组装单元各 Section 的format做直接的单测覆盖。现有的相关测试范例如 ComponentFilter.test.ts已覆盖组件目录白名单过滤、深层嵌套清理、工具信封保留等场景可作为新增 prompt 组装单测的同目录参照。九、执行顺序4 个阶段的无阻塞推进计划给出了明确的执行顺序核心原则是“最大化并行、最小化阻塞”Phase 1并行无依赖: [ ] Item 7: Export buildPrompt helpers [XS] [ ] Item 2: Unify Personas via factories [S] [ ] Item 3: Token budget config [S] [ ] Item 5 (presenter part): Fix GenerateContentPresenter error [XS] Phase 2体量最大无阻塞者: [ ] Item 1: GetFileContentsUseCase ProjectFileLoader [L] Phase 3依赖 Item 1: [ ] Item 5 (file loader part): Fix error logging in ProjectFileLoader [S] [ ] Item 4: Extract GenerationContextResolver [M] Phase 4低优先级: [ ] Item 6: Decouple presenter from settings shape [S]解读Phase 1 四件事互不依赖、体量都小可多人并行Phase 2 只有 Item 1 这个 L 级大头是后续两项的“地基”必须先行Phase 3 中的 Item 5 文件加载部分与 Item 4 都依赖 Item 1 产出的ProjectFileLoaderPhase 4 的 Item 6 优先级最低可最后处理。计划特别标注了每项的依赖关系“Item 1 是 Item 4 和 Item 5file loader 部分的基础”“Item 2/3 无依赖、独立”“Item 5 的 presenter 部分独立”“Item 6 无依赖但低优先级”。十、验证每项改造完成后的回归检查计划要求每个 Item 完成后执行以下验证以webiny/ai-powerups为主要对象Item 1 额外涉及 FileManager 两个包命令验证目标yarn check -p webiny/ai-powerupsai-powerups 类型检查yarn check -p webiny/api-file-managerFileManager 抽象包类型检查Item 1yarn check -p webiny/api-file-manager-s3S3 存储包类型检查Item 1yarn build -p webiny/ai-powerups 21 \| tail -30构建成功查看尾部日志yarn lint无新增 lint 错误yarn test packages/ai-powerups 21 \| tail -50既有测试全部通过对照仓库根 package.json 的脚本定义check对应tsx scripts/checkPackagesbuild对应tsx scripts/buildPackageslint为oxlint --deny-warningstest为vitest --config testing/vitest.config.ts --run——计划中的命令与仓库实际脚本语义一致可直接套用。需要说明的是这些验证命令针对的是 Webiny 的 monorepo 包管理脚本适用前提是本仓库已安装依赖yarn工作区。十一、小结本计划的架构方法论将ai-personas-refactor.md的 7 个 Item 放在一起看可以提炼出几条贯穿始终的架构原则也对应到仓库既有的抽象惯例存储细节永远藏在 Use Case 之后Item 1ai-powerups不碰 S3、不碰FileManager/File/${id}/Metadata只面向GetFileContentsUseCase语义存储包负责process.env.S3_BUCKET与 KV 元数据仓库以GetFileContentsByIdUseCase印证了这一模式。用工厂消灭跨类型重复Item 2一类配置一个 handler/settings group交给“配置驱动的工厂”产出新增类型只写薄包装。配置走抽象、可被 DI 覆盖Item 3预算类常量收敛为TokenBudgetConfig默认值不变、扩展点开放container.register()即可按模型覆盖。用例瘦身上下文解析独立成抽象Item 4GenerationContextResolver把设置/Persona/项目/文件从“生成”中剥离用例只保留编排与 AI 调用仓库的AiPromptContextBuilder已是该思路的成熟形态。失败必须可见Item 5、7warnings 透传 结构化日志 ViewModel 错误字段同时辅助函数导出以便单测锁定字符串组装逻辑。表示层面向语义接口而非原始形状Item 6Presenter 只依赖IContentGenerationOptionsProvider的 5 个方法settings 形状变化被隔离。计划通篇强调“非破坏性non-breaking”所有抽象都以默认实现保留现状行为如 Token 预算默认仍为 150K/4、缓存键与版本自增策略不变只新增扩展点与职责边界。对于想要把ai-powerups的生成链路接入更多模型、更多 Persona 类型或更多存储后端的开发者这份计划的抽象划分抽象包 / 存储实现包 / 消费包本身就是一个可以直接照搬的分层模板。赞分享CMS后端前端【免费下载链接】webiny-jsOpen-source, self-hosted CMS platform on AWS serverless (Lambda, DynamoDB, S3). TypeScript framework with multi-tenancy, lifecycle hooks, GraphQL API, and AI-assisted development via MCP server. Built for developers at large organizations.项目地址https://gitcode.com/gh_mirrors/we/webiny-js点击查看免费下载相关推荐CesiumJS 3D Tiles 入门从第一行代码到可交互地球CesiumJS 3D Tiles 入门从第一行代码到可交互地球 你给客户演示一栋扫描出来的建筑模型单个 glb 文件 800MB浏览器转了 6 秒才加载前端3D渲染图形学数据可视化Workbench无缝自动同步你的Dotfiles到iCloud的终极解决方案Workbench无缝自动同步你的Dotfiles到iCloud的终极解决方案 Workbench是一款专为开发者打造的高效工具它能够实现Dotfiles到Flink 1.5 升级指南部署重构、网络栈改造与 REST API 破坏性变更全解析Flink 1.5 升级指南部署重构、网络栈改造与 REST API 破坏性变更全解析 Flink 1.5 对集群与作业部署组件、网络栈进行了整体重构引入基后端大数据流处理批处理上一篇DBeaver数据库权限管理角色与用户的可视化配置下一篇vue-vben-admin大型项目目录结构模块化与可扩展性设计创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询