关于 add-server-logic
用于创建和管理 Power Pages Server Logic 的技能:受 Web 角色和表权限保护的服务器端 JavaScript。它指导规划、实现、配置及前端集成,适用于 Dataverse 操作、外部 API 和自定义业务逻辑。
主要功能
- 01创建 Server Logic JavaScript 和元数据
- 02支持 get、post、put、patch、del 处理函数
- 03配置表权限和站点设置
- 04指导安全管理机密信息
- 05将端点集成到前端代码
- 06验证实现并提供测试指导
适用场景
- 01避免在浏览器中暴露外部 API 凭据
- 02在 Dataverse 写入时执行服务器端业务规则
- 03将 Power Pages SPA 连接到服务器端逻辑
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill add-server-logic安装后在 Claude Code 中输入/add-server-logic确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/serverlogic-plan.htmlreferences/frontend-integration-reference.mdserver-logic-docs.mdserver-logic-plan-data-format.mdscripts/create-serverlogic-metadata.jsvalidate-serverlogic.js详细介绍
插件检查:运行
node "${PLUGIN_ROOT}/scripts/check-version.js"——如果输出了消息,请在继续之前向用户展示。
添加 Server Logic
创建和管理一个或多个 Power Pages Server Logic 文件——在 Power Pages 运行时安全运行的服务器端 JavaScript,对浏览器隐藏,并受 Web 角色和表权限保护。Server Logic 无需向客户端暴露敏感代码或凭据,即可安全集成外部 API、执行 Dataverse 操作和实现自定义业务逻辑。
核心原则
- 以 Microsoft Learn 为准:编写代码前始终获取最新文档。Server Logic 功能处于预览阶段,SDK 可能会发生变化——切勿仅依赖缓存的知识。
- 无浏览器 API,无依赖项:Server Logic 在支持 ECMAScript 2023 的沙盒化服务器环境中运行。此环境不提供
fetch、XMLHttpRequest、setTimeout或任何 DOM API,也不提供 npm 包。 - 仅限五种函数:服务器逻辑文件只能导出以下顶层函数:
get、post、put、patch、del。delete是 JavaScript 保留字,不能使用。 - 始终返回字符串:每个函数都必须返回字符串。返回对象或数组时,请使用
JSON.stringify()。 - 使用 TaskCreate/TaskUpdate:在所有阶段持续跟踪进度——开始任何工作前,先创建包含所有阶段的待办列表。
- 保持前端完整性:如果阶段 9 会添加或更改 SPA UI,请先阅读
${PLUGIN_ROOT}/references/site-modification-integrity.md;在已配置的语言环境资源中同步所有可见状态,并确保 UI 支持双向操作且可安全展开。仅涉及后端的任务无需更改本地化内容。
前提条件:
- 已创建现有 Power Pages SPA 站点
- 站点必须至少部署过一次(必须存在
.powerpages-site文件夹)——服务器逻辑文件位于.powerpages-site/server-logic/中,因此必须先部署才能创建任何服务器逻辑
初始请求: $ARGUMENTS
工作流
- 验证站点是否存在——定位 Power Pages 项目、查看现有模式并验证前提条件
- 了解需求——确定用户意图,以及解决方案是否需要一个或多个服务器逻辑文件
- 获取最新文档——从 Microsoft Learn 查询最新的 Server Logic SDK 参考资料
- 审查实施计划——向用户展示计划,并在编写代码前取得确认
- 实施 Server Logic——在
.powerpages-site/server-logic/<name>/中创建获批的.js和.serverlogic.yml文件 - 配置表权限——*(有条件:仅在使用 Server.Connector.Dataverse 时)*为服务器逻辑访问的 Dataverse 表设置表权限
- 管理机密和环境变量——*(有条件:仅在服务器逻辑需要机密时)*使用 Azure Key Vault(推荐)或在 Dataverse 中直接配置环境变量,以安全地存储敏感值
- 配置站点设置——按需设置 ServerLogic 站点设置
- 客户端集成——帮助将服务器逻辑接入站点的前端代码
- 验证与测试指导——验证代码并提供测试说明
- 审查与部署——展示摘要并提供部署选项
阶段 1:验证站点是否存在
目标:定位 Power Pages 项目根目录并确认前提条件
操作:
- 创建包含全部 11 个阶段的待办列表(参见进度跟踪表)
1.1 定位项目
在当前目录或其直接子目录中查找 powerpages.config.json
如果未找到:告知用户先使用 /create-site 创建站点。
1.2 读取现有配置
读取 powerpages.config.json,获取站点名称和配置信息:
1.3 检测框架
读取 package.json,确定前端框架(React、Vue、Angular 或 Astro)。Phase 8(客户端集成指南)需要此信息。完整的框架检测映射请参阅 ${PLUGIN_ROOT}/references/framework-conventions.md。
1.4 探索现有服务端逻辑和前端代码
使用 Explore Agent(通过 Task 工具,设置 agent_type: "explore")分析站点中现有的服务端逻辑模式,以及可能调用或需要调用服务端逻辑端点的前端代码。
发给 Explore Agent 的提示词:
"分析此 Power Pages SPA 站点的服务端逻辑上下文。请检查:
.powerpages-site/server-logic/是否存在?如果存在,请列出所有子目录及其中的 .js 文件。概述每项服务端逻辑的作用(实现了哪些函数、使用了哪些 SDK 功能)。同时阅读对应的 .serverlogic.yml 文件,检查 Web 角色分配情况。- 在前端源代码(
src/**/*.{ts,tsx,js,jsx,vue,astro})中搜索对/_api/serverlogics/的现有调用——这表示已有服务端逻辑端点正在被使用。- 查找 CSRF 令牌处理模式(
__RequestVerificationToken、_layout/tokenhtml)——这些可展示站点当前如何进行经过身份验证的 API 调用。- 检查是否有提及服务端逻辑、后端或服务端处理的 TODO/FIXME 注释。
- 查找可能需要替换为服务端逻辑调用的硬编码 API URL、模拟数据或占位 fetch 调用。
- 检查
src/shared/、src/services/或类似目录中是否有可复用的现有服务层或 API 工具文件,以便集成服务端逻辑。- 阅读
.powerpages-site/web-roles/*.webrole.yml文件,列出可用的 Web 角色及其 GUID——创建服务端逻辑元数据 YAML 时需要这些信息。- 对于每项现有服务端逻辑,评估能否复用或安全扩展以实现所请求的功能,而不是新建服务端逻辑文件。指出可优先复用的候选项并说明原因。 汇总所有发现,帮助我们避免重复工作并遵循现有模式。"
根据 Explore Agent 的发现,记录:
- 现有服务端逻辑文件——已实现的内容,以及适合复用或扩展的文件
- 前端调用模式——站点如何调用 API(Phase 9 中应遵循此模式)
- 现有服务/工具文件——添加客户端集成时复用这些文件
- 缺口——前端代码引用了尚不存在的服务端逻辑端点
1.5 检查部署状态(必做)
查找 .powerpages-site 文件夹:
如果未找到:必须先部署站点,才能创建服务端逻辑——服务端逻辑文件存放在 .powerpages-site/server-logic/ 中。告知用户:
"未找到
.powerpages-site文件夹。服务端逻辑文件存放在此文件夹中,因此必须至少部署一次站点,才能创建服务端逻辑。现在要部署吗?"
🚦 **关卡(计划 · add-server-logic:1.5.deploy-first):**缺少
.powerpages-site——服务端逻辑文件存放在其中。请先部署,否则停止。**触发条件:**Phase 1.5 未找到
.powerpages-site目录。 **询问原因:**写入不存在路径的服务端逻辑.js/.yml文件无法部署。 **取消后保留:**无——尚未写入任何服务端逻辑文件。
使用 AskUserQuestion:
| 问题 | 选项 |
|---|---|
服务端逻辑需要 .powerpages-site 文件夹。现在要部署站点吗? | 是,现在部署(必需),取消 |
如果选择“是,现在部署”:先调用 /deploy-site,然后继续 Phase 2。
如果选择“取消”:停止工作流——没有 .powerpages-site 就无法创建服务端逻辑。
输出:确认项目根目录、.powerpages-site 是否存在、现有服务端逻辑(如有)、可用 Web 角色
阶段 2:理解需求
目标:确定用户意图,判断是否需要一个或多个服务器逻辑文件,并记录每项所需的 HTTP 方法
操作:
2.1 分析用户请求
根据用户的请求,确定:
- 意图类型:该请求对应一个还是多个服务器逻辑?
- 复用机会:现有服务器逻辑能否满足请求的部分需求,或在安全的前提下扩展以满足需求?
- 服务器逻辑清单:针对每项所需的服务器逻辑,记录其用途、建议的端点名称,以及应复用、扩展还是新建
- 所需的 HTTP 方法:每项服务器逻辑需要实现 5 个函数中的哪些函数(
get、post、put、patch、del)
如果现有服务器逻辑在领域、安全模型和生命周期方面已经符合所需能力,应优先复用或安全地扩展。只有在复用会让现有文件变得难以理解、范围过宽或不安全时,才创建新的服务器逻辑。
如果用例涉及不同职责、安全边界或生命周期方面的问题,应优先拆分为多个服务器逻辑文件。例如:
- 读写工作流所需的 Web 角色不同,应分别处理
- 涉及不同外部系统或站点设置的集成,应分别处理
- 独立的业务能力若合并到同一端点会更难测试或理解,应分别处理
2.1.1 识别验证并执行模式
对于计划中的每项服务器逻辑,确定它是否应采用验证并执行模式——即服务器逻辑既验证业务规则,也执行相应的 Dataverse 写入操作,而不是仅返回验证结果供客户端处理。
满足以下任一条件时,服务器逻辑应采用验证并执行模式:
| 条件 | 示例 |
|---|---|
| 强制执行状态机或生命周期 | 订单状态:草稿 → 已提交 → 已批准 |
| 写入操作取决于必须防篡改的业务规则 | “仅允许在截止时间前提交竞标” |
| 操作需要跨多个表原子执行 | 授予竞标 + 拒绝所有其他竞标 + 更新活动状态 |
| 写入涉及计算或派生值 | 服务器计算分数并写入 |
| 客户端不应拥有字段的直接写入权限 | 严格遵循转换规则的状态字段 |
对于每项采用验证并执行模式的服务器逻辑,请记录:
- 服务器逻辑将执行哪些 Dataverse 写入操作(UpdateRecord、CreateRecord 等)
- 将写入哪些字段——客户端不应通过 Web API 写入这些字段
- 服务器逻辑向客户端返回什么——通常是包含操作前后状态的成功/失败结果,而不是供客户端据此操作的验证标志
应避免的反模式:服务器逻辑仅进行验证并返回 { valid: true/false },然后由客户端单独调用 Web API 执行写入操作。这样客户端就可以绕过验证,直接写入。
2.1.2 发现 Dataverse 自定义操作
如果任何计划中的服务器逻辑项涉及 Dataverse 操作,请检查用户的 Dataverse 环境中是否已有可直接利用的自定义操作(Custom API 或 Custom Process Action),而不是从头编写逻辑。
步骤 1 — 获取自定义操作:
node "${PLUGIN_ROOT}/scripts/list-custom-actions.js" "<ENV_URL>"
脚本会输出一个 JSON 对象,其中包含:
customApis— 现代 Custom API,包含完整的请求参数和响应属性customProcessActions— 旧版 Custom Process Action(仅限已激活的)total— 两种类型的总数
每个条目均包含:name、displayName、description、type(action 或 function)、binding(unbound、entity 或 entityCollection)、boundEntity 和 source(customApi 或 customProcessAction)。Custom API 还包含 requestParameters 和 responseProperties 数组。
步骤 2 — 展示并询问用户:
如果发现了自定义操作(total > 0),请按绑定类型(无绑定与实体绑定)向用户展示摘要,并询问是否要使用其中任何操作:
🚦 关卡(计划 · add-server-logic:2.1.2.use-custom-actions): 已发现自定义操作 — 决定是封装现有 Dataverse Custom API/Process Action,还是从头编写服务器逻辑。此选择会影响第 5 阶段的实现形式。
触发条件:
list-custom-actions.js返回至少一个条目。 询问原因: 自动封装可能会选错操作;自动跳过则可能导致重复实现 Dataverse 中已有的逻辑。 取消后保留: 无 — 尚未写入任何服务器逻辑文件。
使用 AskUserQuestion:
| 问题 | 选项 |
|---|---|
您的 Dataverse 环境中有 <total> 个自定义操作。您想在服务器逻辑中使用其中的任何操作,而不是从头编写逻辑吗? | 是,让我选择要使用的操作;否,全部从头编写 |
清楚地展示列表 — 对每个操作显示:名称、描述、类型(action/function)、绑定类型,以及绑定的实体(如适用)。为便于查看,请分为 无绑定 和 实体绑定 两组。
如果用户选择 否,请跳至第 2.2 阶段。
步骤 3 — 将自定义操作映射到服务器逻辑项:
如果用户选择 是,请针对每个要创建的服务器逻辑项,询问应封装哪个自定义操作(如有):
对每个服务器逻辑项使用 AskUserQuestion:
| 问题 | 上下文 |
|---|---|
对于 <server-logic-name> 端点,应使用哪个自定义操作? | 展示自定义操作列表及其名称、描述和绑定类型。将 “无 — 从头编写” 作为一个选项。 |
记录每个服务器逻辑项的映射。对于封装自定义操作的项,请注明:
- 自定义操作名称(用于
InvokeCustomApi调用) - 它是函数(
GET)还是操作(POST) - 绑定类型和绑定的实体(如适用)
- 请求参数和响应属性(如果 Custom API 提供)
此映射将在第 5.3 阶段生成服务器逻辑代码时使用,并会显示在 HTML 计划(第 4 阶段)中,以标明哪些项封装了现有的自定义操作。
2.2 确定所需的 SDK 功能
根据每项计划中的服务端逻辑的用途,确定需要哪些 Server SDK 功能:
| 功能 | 使用场景 |
|---|---|
Server.Connector.HttpClient | 调用外部 REST API(不包括 Dataverse) |
Server.Connector.Dataverse | 读取/写入 Dataverse 记录(CRUD + InvokeCustomApi,用于 Dataverse 自定义 API) |
Server.Context | 访问请求参数、标头和正文 |
Server.User | 执行用户范围内的操作、检查角色 |
Server.Logger | 始终使用 — 每个函数都应记录进入/退出和错误 |
Server.Sitesetting | 读取站点设置配置值 |
Server.EnvironmentVariable | 通过 Server.EnvironmentVariable.get(name) 直接读取 Dataverse 环境变量值 — 对于非机密配置,可替代 Server.Sitesetting |
Server.Website | 访问站点元数据 |
2.3 确定机密值
确定是否有任何服务端逻辑项需要不应硬编码的机密或敏感配置值。常见示例:
| 场景 | 所需机密 |
|---|---|
| 调用经过身份验证的外部 API | API 密钥、客户端机密、Bearer 令牌 |
| 连接到第三方服务 | 连接字符串、访问令牌 |
| OAuth2 客户端凭据流 | 客户端 ID + 客户端机密 |
| Webhook 验证 | 签名机密、共享密钥 |
对于每项已确定的机密,请记录:
- 机密名称:描述性名称(例如,
ExchangeRateApiKey、PaymentGatewaySecret) - 用途:说明为何需要该机密
- 站点设置名称:服务端逻辑将通过
Server.Sitesetting.Get()使用的名称(例如,ExternalApi/ExchangeRateApiKey) - 环境变量架构名称:Dataverse 环境变量架构名称(例如,
cr5b4_ExchangeRateApiKey)
这些值将在第 7 阶段用于创建环境变量和站点设置。
2.3.1 Key Vault 决策
如果在第 2.3 阶段确定了机密,请现在询问用户是否要使用 Azure Key Vault。必须在第 4 阶段之前作出此决定,以便实现计划显示所选的机密管理方式。
🚦 关卡(计划 · add-server-logic:2.3.1.keyvault): 选择机密存储机制(Key Vault 或普通环境变量)。此选择会影响第 4 阶段呈现的计划和第 7 阶段的实施流程。
触发条件: 第 2.3 阶段确定了至少一个机密值。 询问原因: 创建普通环境变量可能会在解决方案导出文件中暴露机密;自动选择 Key Vault 则需要额外的 Azure 设置。 取消后状态: 无 — 尚未写入任何环境变量定义。
使用 AskUserQuestion:
| 问题 | 选项 |
|---|---|
| 此服务端逻辑需要机密值(例如,API 密钥、客户端机密)。Azure Key Vault 是安全存储机密的推荐方式。您想使用 Azure Key Vault 吗? | 是,使用 Azure Key Vault(推荐);否,直接存储为环境变量 |
记录用户的选择 — 该选择会显示在 HTML 计划中(第 4 阶段),并在第 7 阶段执行。
2.4 与用户确认
如果需求不明确,请使用 AskUserQuestion 进行澄清:
| 问题 | 上下文 |
|---|---|
| 这个服务端逻辑整体上应该实现什么功能? | 如果用途不明确 |
| 应该使用一个还是多个服务端逻辑? | 如果该请求合理地可以建模为一种或多种方式 |
| 每个服务端逻辑需要使用哪些 HTTP 方法? | 如果未指定,请根据用例提出建议(例如,只读 = GET,表单处理 = POST) |
| 每个服务端逻辑是否需要调用外部 API、Dataverse,或两者都调用? | 用于确定要使用哪些连接器 |
| 每个服务端逻辑应该叫什么名称? | 根据职责建议便于用于 URL 的名称 |
| 服务端逻辑是否需要任何机密或敏感值(API 密钥、客户端机密、令牌)? | 如果服务端逻辑需要调用经过身份验证的外部 API 或服务 |
输出:清楚了解总体意图、要复用/扩展/创建的服务端逻辑列表、它们的 HTTP 方法、所需的 SDK 功能以及任何必需的机密
第 3 阶段:获取最新文档
目标:在编写任何代码之前,从 Microsoft Learn 查找并阅读所有最新的 Server Logic 文档
这一步至关重要,因为 Server Logic 是预览功能,SDK 的接口可能会发生变化。Microsoft Learn 文档是权威来源。
操作:
3.1 按照文档参考进行操作
使用以下参考文档作为查找、分类、获取和核对 Server Logic 文档的准则:
参考:
${PLUGIN_ROOT}/skills/add-server-logic/references/server-logic-docs.md
遵循该参考文档,以便:
- 动态搜索 Microsoft Learn,查找所有最新的 Server Logic 文档
- 获取核心参考页面和任何相关的特定场景页面
- 搜索最新代码示例
- 将发现的文档与参考中已知的 SDK 基线进行核对
3.2 提取特定于任务的备注
从获取的文档中提取并记录与当前任务相关的内容:
- 所有 SDK 方法签名、参数类型和返回类型
- 当前支持的 HTTP 方法和函数签名
- 站点设置及其默认值
- 安全模型(Web 角色、表权限、CSRF)
- 客户端调用模式和响应格式
- 在 Microsoft Learn 中发现的任何新方法或破坏性更改
输出:已根据所有相关 Microsoft Learn 文档页面核实的最新 SDK 参考
第 4 阶段:审核实施计划
目标:向用户展示实施计划,并在编写任何代码之前获得确认
操作:
4.1 准备计划数据
构建服务端逻辑计划数据,并在请求批准前生成 HTML 计划。
参考:
${PLUGIN_ROOT}/skills/add-server-logic/references/server-logic-plan-data-format.md
生成的计划应概述:
- 要创建或复用的服务端逻辑数量
- 每个端点的名称、API URL 以及要创建的文件
- 将要实现的函数及其各自的作用
- 每个项目涉及的 SDK 功能、外部服务和 Dataverse 表
- 适用于每个项目的 Web 角色、安全限制和站点设置
- 将作为环境变量存储的任何机密或敏感值(无论是否使用 Azure Key Vault)。如果用户在第 2.3.1 阶段选择了 Azure Key Vault,则将
SECRETS_DATA设置为useKeyVault: true,并提供机密列表 — HTML 计划将显示 Key Vault 横幅,说明其安全优势,并显示每个服务端逻辑依赖的机密。如果不需要机密,则将SECRETS_DATA设为null。 - 获得批准后的预期后续步骤
4.2 渲染 HTML 计划
根据模板生成 HTML 计划文件,并在征求用户批准前,在用户的默认浏览器中打开该文件。
在 Power Pages 项目中工作时,将计划写入:
<PROJECT_ROOT>/docs/serverlogic-plan.html
如果 docs/ 文件夹尚不存在,请创建该文件夹。将此 HTML 文件保留在仓库中,以便与其余服务器逻辑工作一并审查和提交。
不要手动编写 HTML。请使用渲染脚本:
node "${PLUGIN_ROOT}/scripts/render-serverlogic-plan.js" --output "<OUTPUT_PATH>" --data "<DATA_JSON_PATH>"
渲染脚本不会覆盖现有文件。调用脚本前,检查默认输出路径(<PROJECT_ROOT>/docs/serverlogic-plan.html)是否已存在。如果已存在,请根据上下文选择一个新的描述性文件名,例如 serverlogic-plan-exchange-rate.html、serverlogic-plan-apr-2026.html。通过 --output 传入所选文件名。
4.3 呈现计划摘要
不要在 CLI 中再次呈现详细计划。HTML 文件是唯一的详细计划文档。
在 CLI 中,只提供指向浏览器中已打开的 HTML 计划的简短摘要。摘要仅包含:
- 服务器逻辑的总数
- 计划将创建、更新还是复用项目
- 是否涉及 Web 角色、表权限或站点设置
- 渲染脚本返回的实际输出路径
- 说明浏览器中打开的 HTML 文件包含完整详情
除非用户明确要求文本版本,否则不要在 CLI 中重述每项服务器逻辑的具体内容、理由、角色分配或函数详情。告知用户详细 HTML 计划文件的保存位置,以及该文件已在浏览器中打开以供审查;同时说明,除非用户要求丢弃该文件,否则仓库中的计划副本将与实现产物一并提交。
4.4 向用户确认
🚦 关卡(计划 · add-server-logic:4.4.plan-approval): 在第 5 阶段写入任何
.serverlogic.yml/.js文件或第 7 阶段创建环境变量前,对渲染后的 HTML 计划进行最终确认。触发条件: 第 4.2 阶段已渲染 HTML 计划;第 4.3 阶段已提供 CLI 摘要。 询问原因: 避免服务器逻辑文件以错误名称 / 错误角色提交;避免针对错误的密钥存储模式创建环境变量定义。 取消后状态: 无事发生——尚未写入任何服务器逻辑文件。
使用 AskUserQuestion:
| 问题 | 选项 |
|---|---|
| 这是此服务器逻辑工作的实施计划。看起来正确吗? | 批准并实施(推荐)、请求修改、取消 |
如果选择“请求修改”:询问需要修改哪些内容,更新计划后再次呈现。
如果选择“取消”:停止工作流。
输出:用户批准的实施计划
第 5 阶段:实施服务器逻辑
目标:为每项获批的服务器逻辑创建 .js 文件和元数据 YAML,并遵循第 3 阶段验证的约束
操作:
5.1 创建服务器逻辑文件夹
针对每项获批的服务器逻辑:
- 如果获批计划注明
reuse:不要创建新文件夹。原样复用现有服务器逻辑,并仅在必要时更新周边集成工作。 - 如果获批计划注明
update/ extend:复用现有文件夹,并更新现有.js/.serverlogic.yml文件,而不是创建重复文件。 - 如果获批计划注明
create:在.powerpages-site/server-logic/中创建文件夹(注意:使用单数server-logic,末尾不带“s”)。确保文件夹名称与该端点名称完全一致。
5.2 读取或创建 Web 角色
使用 Create Web Role 技能,确定已批准的服务端逻辑计划需要哪些 Web 角色,并在编写元数据前创建任何缺少的角色。
不要假设每项服务端逻辑都需要所有可用角色。应根据每项服务端逻辑的用途、安全模型和已批准的计划,确定所需的最少角色集合。
Web 角色文件内容示例:
adx_anonymoususersrole: false
adx_authenticatedusersrole: true
description: Role for authenticated users
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
name: Authenticated Users
在技能工作流中,遇到以下情况时,请明确调用 Create Web Role 技能:
- 站点没有合适的现有 Web 角色
- 已批准的计划包含尚不存在的拟议角色
- 创建元数据前需要完善角色分配
Create Web Role 技能完成后,读取生成的 Web 角色 YAML 文件,并收集每项服务端逻辑的元数据 YAML 所需的 id 和 name 值。
5.3 创建服务端逻辑文件
对每项已批准的服务端逻辑重复此步骤。根据已批准的计划状态(create、update 或 reuse),创建或更新 <PROJECT_ROOT>/.powerpages-site/server-logic/<name>/<name>.js,并遵循以下强制模式:
结构规则
- 仅限顶层函数:文件顶层只能包含以下 5 个函数:
get、post、put、patch、del。只包含用户需要的函数。 - 每个函数都返回字符串:对对象/数组使用
JSON.stringify()。 - 每个函数都包含 try/catch:每个函数都必须用 try/catch 块包装其逻辑。
- 每个函数都记录日志:在入口处使用
Server.Logger.Log(),在 catch 块中使用Server.Logger.Error()。 - 不使用导入或 require:不使用
import、require或外部依赖。 - 不使用浏览器 API:不使用
fetch、XMLHttpRequest、setTimeout、setInterval、console.log或 DOM API。 - 需要时使用 async:仅在使用
await(HttpClient 调用)时将函数标记为async。Dataverse 连接器方法(Server.Connector.Dataverse.*)是同步的——不要对它们使用async/await。
禁止的脚本模式
Power Pages 服务端脚本验证器会在运行时拒绝包含特定模式的脚本。违规会在诊断信息中显示为 RTSL01: Script validation failed: prohibited pattern found - Pattern: <regex>,且函数会静默跳过,不执行用户代码。
| 模式 | 正则表达式 | 注意事项 |
|---|---|---|
JavaScript with 语句 | with\s*\( | 正则表达式会匹配文件中任何位置的子串 with(——包括字符串字面量和其他标识符中的内容。OData 筛选函数(如 startswith(、endswith( 和 groupwith()会触发该规则,因为它们都以 with( 结尾。 |
OData 函数的规避方法——拆分字面量,使源码中不出现连续的 with(:
// ❌ Triggers validator: "startswith(" contains the substring "with("
var query = "$filter=startswith(name,'INV-')";
// ✅ Split the literal — server still receives "startswith(name,...)"
var query = "$filter=startswith" + "(name,'INV-')";
同样的方法也适用于 endswith(、groupwith( 以及任何其他以 with( 结尾的标识符。
代码模板
// Server Logic: <name>
// Purpose: <description>
// API URL: https://<site-url>/_api/serverlogics/<name>
function get() {
try {
Server.Logger.Log("<name> GET called");
// Access query parameters
// const id = Server.Context.QueryParameters["id"];
// Your logic here...
return JSON.stringify({
status: "success",
method: "GET",
data: null // replace with actual data
});
} catch (err) {
Server.Logger.Error("<name> GET failed: " + err.message);
return JSON.stringify({
status: "error",
method: "GET",
message: err.message
});
}
}
验证并执行模板
当某项服务端逻辑被确定为验证并执行型逻辑时(参见阶段 2.1.1),请使用此模式。主要区别在于:服务端逻辑会在一次调用中读取当前状态、验证业务规则,并将结果写入 Dataverse。客户端绝不会直接写入受保护字段。
// Server Logic: <name>
// Purpose: Validate and execute <describe the operation>
// Pattern: Validate-and-execute — this endpoint both validates the business rule
// and performs the Dataverse write. The client should NOT write <protected fields>
// via Web API — all writes to those fields go through this endpoint.
// API URL: https://<site-url>/_api/serverlogics/<name>
function post() {
try {
Server.Logger.Log("<name> POST called");
const body = JSON.parse(Server.Context.Body);
const entityId = body.entityId;
const targetStatus = body.targetStatus;
// 1. Read the current record from Dataverse
const current = Server.Connector.Dataverse.RetrieveRecord("<table-name>", entityId, "?$select=<status-field>");
const currentStatus = current["<status-field>"];
// 2. Validate the transition
const allowedTransitions = {
"Draft": ["Submitted"],
"Submitted": ["Approved", "Rejected"],
"Approved": ["Fulfilled"]
};
const allowed = allowedTransitions[currentStatus] || [];
if (!allowed.includes(targetStatus)) {
return JSON.stringify({
status: "error",
message: "Invalid transition: " + currentStatus + " → " + targetStatus + " is not allowed",
currentStatus: currentStatus,
targetStatus: targetStatus,
allowedTargets: allowed
});
}
// 3. Execute the write — server performs the Dataverse update
const updateData = {};
updateData["<status-field>"] = targetStatus;
Server.Connector.Dataverse.UpdateRecord("<table-name>", entityId, JSON.stringify(updateData));
Server.Logger.Log("<name> transition executed: " + currentStatus + " → " + targetStatus);
// 4. Return the result — client receives confirmation, not a validation flag
return JSON.stringify({
status: "success",
previousStatus: currentStatus,
newStatus: targetStatus,
entityId: entityId
});
} catch (err) {
Server.Logger.Error("<name> POST failed: " + err.message);
return JSON.stringify({
status: "error",
message: err.message
});
}
}
与基本模板的主要区别:
- 服务端逻辑从 Dataverse 读取当前状态(不信任客户端看到的状态)
- 在服务端验证业务规则
- 通过
Server.Connector.Dataverse.UpdateRecord将结果写入 Dataverse - 返回成功/失败结果——而不是让客户端据此采取操作的
{ valid: true/false }标志 - 客户端调用这一个端点——不会另外发起 Web API PATCH 调用
自定义操作包装模板
当服务器逻辑项包装 Dataverse 自定义操作(在 Phase 2.1.2 中映射)时,请使用此模式调用 Server.Connector.Dataverse.InvokeCustomApi。服务器逻辑充当适用于门户的包装器,通过 /_api/serverlogics/<name> 端点公开自定义操作,并进行适当的 Web 角色授权。
无绑定操作:
// Server Logic: <name>
// Purpose: Wraps Dataverse custom action "<custom-action-name>" for portal consumption
// Custom Action: <custom-action-name> (unbound, action)
// API URL: https://<site-url>/_api/serverlogics/<name>
function post() {
try {
Server.Logger.Log("<name> POST called — invoking custom action <custom-action-name>");
const body = JSON.parse(Server.Context.Body);
// Build the request payload matching the custom action's input parameters
const payload = JSON.stringify({
// "<ParameterName>": body.<clientFieldName>
});
const result = Server.Connector.Dataverse.InvokeCustomApi(
"POST",
"<custom-action-name>",
payload
);
Server.Logger.Log("<name> custom action completed successfully");
return JSON.stringify({
status: "success",
data: result
});
} catch (err) {
Server.Logger.Error("<name> POST failed: " + err.message);
return JSON.stringify({
status: "error",
message: err.message
});
}
}
实体绑定操作:
function post() {
try {
Server.Logger.Log("<name> POST called — invoking bound action <custom-action-name>");
const body = JSON.parse(Server.Context.Body);
const entityId = body.entityId;
const payload = JSON.stringify({
// "<ParameterName>": body.<clientFieldName>
});
// Include the entity set and record ID, followed by the fully qualified action name
const result = Server.Connector.Dataverse.InvokeCustomApi(
"POST",
"<entity-set-name>(" + entityId + ")/Microsoft.Dynamics.CRM.<custom-action-name>",
payload
);
Server.Logger.Log("<name> bound action completed for entity " + entityId);
return JSON.stringify({
status: "success",
data: result,
entityId: entityId
});
} catch (err) {
Server.Logger.Error("<name> POST failed: " + err.message);
return JSON.stringify({
status: "error",
message: err.message
});
}
}
无绑定函数(只读,GET):
function get() {
try {
Server.Logger.Log("<name> GET called — invoking custom function <custom-function-name>");
// Pass parameters as query string for functions
const param1 = Server.Context.QueryParameters["param1"];
const queryString = "<custom-function-name>(Param1='" + param1 + "')";
const result = Server.Connector.Dataverse.InvokeCustomApi(
"GET",
queryString,
null
);
Server.Logger.Log("<name> custom function completed successfully");
return JSON.stringify({
status: "success",
data: result
});
} catch (err) {
Server.Logger.Error("<name> GET failed: " + err.message);
return JSON.stringify({
status: "error",
message: err.message
});
}
}
要点:
- 无绑定操作:将操作名称用作 URL,并以 JSON 正文传递参数
- 实体绑定操作:在 URL 路径中包含实体集和记录 ID,后接
Microsoft.Dynamics.CRM.<action-name> - 函数(GET):使用
"GET"作为 HTTP 方法,并使用 OData 函数调用语法在 URL 中内联传递参数 - 操作(POST):使用
"POST"作为 HTTP 方法,并以 JSON 正文负载传递参数 InvokeCustomApi是同步的——不得使用async/await- 服务器逻辑可以在自定义操作调用前后添加额外的验证、转换或日志记录——不必只是透传
- 当已知 Custom API 响应属性(来自 Phase 2.1.2)时,将其映射到响应对象,以提高清晰度
Dataverse 响应形状(对前端集成至关重要)
当函数返回 Server.Connector.Dataverse.* 方法的结果时,客户端会看到双重包装的负载——这是前端集成出错最常见的原因。编写函数前,请从以下三种响应形状中选择一种,并在 Phase 9 记录选择:方法 A——原始透传(按原样返回连接器结果)、方法 B——包装连接器结果的信封(返回 { status, data: result },不解包 Body),或方法 C——完全规范化(在服务器端解析 Body 并返回特定于功能的形状——推荐用于非通用端点)。
请参阅 ${PLUGIN_ROOT}/skills/add-server-logic/references/frontend-integration-reference.md → “Dataverse Connector Response Format”,了解双重包装的说明、CreateRecord / entityid 标头行为,以及每种形状的服务器端和客户端示例。
在代码中引用机密
当服务器逻辑需要使用在 Phase 2.3 中确定的机密值时,切勿硬编码该值。而应在运行时从由环境变量提供值的网站设置中读取:
const apiKey = Server.Sitesetting.Get("ExternalApi/ExchangeRateApiKey");
使用 Phase 2.3 中规划的网站设置名称。实际的环境变量和网站设置 YAML 将在 Phase 7 中创建。
SDK 使用指南
不要在此技能中内联重复 Microsoft Learn 的 SDK 使用模式。使用 Phase 3 中获取的文档作为连接器方法、签名和支持模式的权威来源,然后仅应用已记录在获批计划中的任务专属说明。
5.4 创建元数据 YAML
对于计划状态为 create 的每个已批准服务器逻辑项,使用确定性写入脚本生成元数据文件,而不是手动编写 YAML。脚本会生成 UUID,按正确顺序写入字段,并以 JSON 格式返回创建的文件路径。对于 update / reuse 项,请跳过此步骤——YAML 已存在,如有需要应手动更新。
node "${PLUGIN_ROOT}/skills/add-server-logic/scripts/create-serverlogic-metadata.js" --projectRoot "<PROJECT_ROOT>" --name "<name>" --displayName "<human-readable display name>" --description "<description of what this server logic does>" --webRoleIds "<uuid1,uuid2,uuid3>"
生成的 <PROJECT_ROOT>/.powerpages-site/server-logic/<name>/<name>.serverlogic.yml 文件具有以下结构:
adx_serverlogic_adx_webrole:
- <web-role-guid-1>
- <web-role-guid-2>
- <web-role-guid-3>
description: <description of what this server logic does>
display_name: <human-readable display name>
id: <generated-uuid>
name: <name>
关键要求:
id字段为必填项——脚本会生成新的 UUID(v4)。如果缺少此字段,PAC CLI 会因Expected Guid for primary key 'id'而崩溃。adx_serverlogic_adx_webrole——步骤 5.2 中的 Web 角色 GUID 数组。仅包含该服务器逻辑项所需的角色。name——必须与文件夹名称和.js文件名一致(在/_api/serverlogics/<name>中使用的 URL 友好名称)。display_name——人类可读的名称(例如,“汇率 API”、“订单处理器”)。- 字段按字母顺序排列——字段必须按字母顺序排序:
adx_serverlogic_adx_webrole、description、display_name、id、name。
5.5 验证代码
保存前,请根据以下约束检查代码:
| 约束 | 检查项 |
|---|---|
| 仅允许使用指定的顶层函数 | 不得使用 get、post、put、patch、del 以外的函数 |
| 每个函数都返回字符串 | 所有代码路径都返回字符串(包括 catch 块) |
| 每个函数都使用 try/catch | 每个函数体都包含在 try/catch 中 |
| 每个函数都使用 Server.Logger | 在入口处记录日志,在 catch 中记录错误 |
| 不得使用外部依赖 | 不得使用 import、require、module.exports |
| 不得使用浏览器 API | 不得使用 fetch、XMLHttpRequest、setTimeout、console.log、document、window |
| 仅在需要时使用 Async | 仅将使用 await 的函数标记为 async |
| 符合 ECMAScript 2023 标准 | 仅使用标准 JS 特性(可选链、空值合并等均可) |
5.6 Git 提交
创建已批准的服务器逻辑文件后,提交服务器逻辑更改。
如果 HTML 计划是在项目内部生成的,请将其包含在同一次提交中(使用渲染脚本 JSON 响应中的实际输出路径)。
输出:已创建、验证并提交服务器逻辑 .js 和 .serverlogic.yml 文件
第 6 阶段:配置表权限(条件执行)
目标:为服务器逻辑代码中通过 Server.Connector.Dataverse 访问的 Dataverse 表设置表权限
仅当服务器逻辑使用 Server.Connector.Dataverse 时才运行此阶段。 如果服务器逻辑仅使用 Server.Connector.HttpClient(外部 API),或完全不访问 Dataverse,请完全跳过此阶段并继续第 7 阶段。
Server.Connector.Dataverse 不会绕过表权限——它会遵循表权限配置。未配置表权限时,Dataverse 连接器会静默返回 0 条记录,而不是实际数据。这是一个常见的困惑来源。
操作:
6.1 检测 Dataverse 表和所需权限
解析第 5 阶段创建的服务器逻辑 .js 文件,识别访问了哪些 Dataverse 表以及执行了哪些 CRUD 操作:
| Dataverse SDK 方法 | 所需表权限 |
|---|---|
RetrieveMultipleRecords("tablename", ...) | 读取 |
RetrieveRecord("tablename", ...) | 读取 |
CreateRecord("tablename", ...) | 创建 |
UpdateRecord("tablename", ...) | 写入 |
DeleteRecord("tablename", ...) | 删除 |
从每个方法调用中提取实体集名称(第一个参数)。构建映射:
| 表(实体集名称) | 读取 | 创建 | 写入 | 删除 |
|---|---|---|---|---|
accounts | 是 | — | — | — |
contacts | 是 | 是 | — | — |
6.2 使用 Table Permissions Architect
当任何已批准的服务端逻辑项使用 Server.Connector.Dataverse 时,调用 ${PLUGIN_ROOT}/agents/table-permissions-architect.md 中的 table-permissions-architect Agent,以确定并创建所需的表权限。
提示词:
“分析此 Power Pages SPA 站点,并为已批准的服务端逻辑计划所访问的 Dataverse 表提出表权限方案。以下表需要配置权限:
[列出步骤 6.1 中每个表所需的 CRUD 权限,并按服务端逻辑项分组]
上下文:
- 由于服务端逻辑使用
Server.Connector.Dataverse,而该连接器会遵循表权限,因此需要配置这些权限;若未配置,连接器会静默返回 0 条记录。- 对于获取所有记录的服务端逻辑,权限范围通常应为 Global;如果服务端逻辑按当前用户筛选,则使用 Contact 范围。
- 分配给这些服务端逻辑项的 Web 角色为:[列出阶段 5.2 中的 Web 角色名称和 GUID]
- 项目根目录:[路径]
检查现有的表权限和 Web 角色。如果需要新建 Web 角色,请使用 create-web-role.js 脚本创建。提出方案,然后在获得批准后使用确定性脚本创建表权限 YAML 文件。”
该 Agent 将:
- 查找现有的表权限和 Web 角色
- 如有需要,通过
create-web-role.js创建缺失的 Web 角色 - 提出表权限方案(含 HTML 可视化)
- 通过计划模式向用户展示方案,以供批准
- 获得批准后,使用
create-table-permission.js在.powerpages-site/table-permissions/中创建表权限 YAML 文件
6.3 Git 提交
创建表权限(以及任何新建的 Web 角色)后,提交表权限变更。
输出:为服务端逻辑访问的所有 Dataverse 表配置表权限(以及新建的 Web 角色,如有)
阶段 7:管理机密和环境变量
目标:将服务端逻辑所需的任何机密值(API 密钥、客户端机密、连接字符串)作为 Dataverse 中的环境变量安全存储,并可选择由 Azure Key Vault 提供支持
仅当服务端逻辑需要机密或敏感配置值时,才执行此阶段(在阶段 2.3 中识别)。如果不需要机密,请跳过此阶段并继续阶段 8。
操作:
7.1 确认 Key Vault 决策
用户已在阶段 2.3.1 中(展示计划之前)选择是否使用 Azure Key Vault。在此处使用该决策,不要再次询问。
7.2a Azure Key Vault 路径
如果用户在阶段 2.3.1 中选择了 Azure Key Vault:
步骤 1 — 列出可用的 Key Vault:
node "${PLUGIN_ROOT}/scripts/list-azure-keyvaults.js"
脚本会从用户的 Azure 订阅中输出一个 Key Vault 的 JSON 数组(包含 name、resourceGroup、location)。
步骤 2 — 选择或创建 Key Vault:
如果找到了 Key Vault,请展示列表并询问用户要使用哪一个:
使用 AskUserQuestion:
| 问题 | 上下文 |
|---|---|
| 你想使用哪个 Azure Key Vault 来存储机密? | 展示脚本输出中的名称 |
如果未找到 Key Vault,请询问用户要如何继续:
🚦 关卡(计划 · add-server-logic:7.2a.no-vaults): 用户的订阅中未找到 Key Vault — 创建一个或改用普通环境变量。此选项会分支机密存储流程。
触发条件: 阶段 2.3.1 选择了 Key Vault,但
list-azure-keyvaults.js返回空列表。 询问原因: 自动创建 Key Vault 会在未经明确同意的情况下预配 Azure 资源;自动回退会在用户明确选择使用 Key Vault 后,将机密存储为普通环境变量。 取消后的状态: 无 — 尚未执行任何 Azure 或 Dataverse 写入操作。
使用 AskUserQuestion:
| 问题 | 选项 |
|---|---|
| 在你的订阅中未找到 Azure Key Vault。你想创建一个,还是改为将机密直接存储为环境变量? | 创建新的 Key Vault(推荐),直接存储为环境变量 |
如果选择“创建新的 Key Vault”:询问 Key Vault 名称、资源组和位置,然后创建:
使用 AskUserQuestion:
| 问题 | 上下文 |
|---|---|
| 你想为新的 Key Vault 指定什么名称、资源组和 Azure 区域? | Key Vault 名称必须为 3-24 个字符、在全球范围内唯一、以字母开头,且只能包含字母数字字符和连字符。根据项目/站点名称建议一个名称。 |
node "${PLUGIN_ROOT}/scripts/create-azure-keyvault.js" \
--name "<vault-name>" \
--resourceGroup "<resource-group>" \
--location "<location>"
脚本会输出一个包含 name、resourceGroup 和 location 的 JSON 对象。后续步骤中使用创建的 Key Vault。
如果选择“直接存储为环境变量”:跳过阶段 7.2a 的其余部分,并继续阶段 7.2b(直接环境变量路径)。
步骤 3 — 提供在 Key Vault 中存储各个机密的说明:
对于阶段 2.3 中识别出的每个机密,向用户说明如何自行存储其值。不要询问机密值 — 机密值绝不能通过对话传递。
同时提供两种方法(CLI 和 Azure Portal),供用户选择:
选项 A — Azure CLI(推荐用于自动化):
将命令列为编号列表,方便用户复制并运行。使用 stdin 形式,以免机密值出现在进程列表中:
For each secret, run the following command (replacing <YOUR_SECRET_VALUE> with the actual value):
1. <secret-name>:
printf '%s' '<YOUR_SECRET_VALUE>' | node "${PLUGIN_ROOT}/scripts/store-keyvault-secret.js" \
--vaultName "<selected-vault>" \
--secretName "<secret-name>"
告知用户,每条命令都会输出一个包含 secretUri 的 JSON 对象,并请用户分享输出(其中仅包含 URI,不包含机密),以便工作流继续。
选项 B — Azure Portal:
针对每个机密提供以下步骤:
1. Go to the Azure Portal (https://portal.azure.com)
2. Search for "Key vaults" in the top search bar and select it
3. Select the Key Vault: <selected-vault>
4. In the left menu under "Objects", click "Secrets"
5. Click "+ Generate/Import" at the top
6. Fill in the fields:
- Upload options: Manual
- Name: <secret-name>
- Secret value: paste your secret value here
- Leave other fields as defaults
7. Click "Create"
8. After creation, click on the secret name, then click the current version
9. Copy the "Secret Identifier" URI and share it here so the workflow can continue
告知用户,机密标识符 URI 的格式为 https://<vault-name>.vault.azure.net/secrets/<secret-name>/<version>,并说明应分享此 URI(而非机密值)。
步骤 4 — 在 Dataverse 中创建环境变量:
用户分享每条命令输出的 secretUri 后,在 Dataverse 中创建一个引用 Key Vault 机密的环境变量定义。使用 secret 类型:
node "${PLUGIN_ROOT}/scripts/create-environment-variable.js" "<ENV_URL>" \
--schemaName "<prefix_SecretName>" \
--displayName "<Secret Display Name>" \
--type "secret" \
--value "<secretUri-from-step-3>"
步骤 5 — 为环境变量创建站点设置:
针对每个环境变量,创建一个映射到它的站点设置 YAML:
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
--projectRoot "<PROJECT_ROOT>" \
--name "<SiteSetting/Name>" \
--envVarSchema "<schemaName-from-step-4>"
这会创建一个包含 envvar_schema 和 source: 1 的站点设置,告知 Power Pages 从 Dataverse 环境变量(由 Key Vault 提供支持)解析值。
7.2b 直接环境变量路径
如果用户选择不使用 Azure Key Vault:
步骤 1 — 使用占位值创建环境变量:
对于第 2.3 阶段中识别出的每个机密,在 Dataverse 中创建环境变量,并设置占位值:
node "${PLUGIN_ROOT}/scripts/create-environment-variable.js" "<ENV_URL>" \
--schemaName "<prefix_SecretName>" \
--displayName "<Secret Display Name>" \
--value "PLACEHOLDER_SET_ACTUAL_VALUE"
步骤 2 — 为环境变量创建站点设置:
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
--projectRoot "<PROJECT_ROOT>" \
--name "<SiteSetting/Name>" \
--envVarSchema "<schemaName-from-step-1>"
步骤 3 — 告知用户如何设置实际的机密值:
不要索要机密值——它们绝不能通过对话传递。请告知用户使用以下任一方法,将每个占位值更新为实际值:
- Power Apps maker portal(make.powerapps.com)——转到 Solutions → Default Solution → Environment variables → 按显示名称查找变量 → 更新值
列出需要更新的环境变量(每个变量都提供显示名称和架构名称),让用户确切知道需要设置哪些变量。
7.3 验证环境变量配置
创建所有环境变量和站点设置后:
- 确认每个站点设置 YAML 文件均已在
.powerpages-site/site-settings/中创建 - 验证每个 YAML 文件都包含
envvar_schema和source: 1 - 确认服务器逻辑代码通过
Server.Sitesetting.Get("<SiteSetting/Name>")引用了正确的站点设置名称
7.4 Git 提交
为环境变量站点设置更改执行一次 git 提交。
输出:已在 Dataverse 中创建环境变量(由 Azure Key Vault 支持或不由其支持),已配置站点设置,服务器逻辑引用了正确的设置名称
第 8 阶段:配置站点设置
目标:为服务器逻辑功能设置站点设置
操作:
8.1 配置服务器逻辑站点设置
此时 .powerpages-site 文件夹保证已存在(已在第 1.5 阶段验证)。
以下站点设置用于控制服务器逻辑行为。仅创建与默认值不同或有特定需求的设置:
| 设置 | 说明 | 默认值 | 配置时机 |
|---|---|---|---|
ServerLogic/Enabled | 启用/禁用服务器逻辑功能 | true | 仅在明确禁用且需要重新启用时 |
ServerLogic/AllowedDomains | 限制 HttpClient 可调用的外部域名 | 所有域名 | 服务器逻辑调用外部 API,且出于安全考虑需要限制为特定域名时 |
ServerLogic/TimeoutInSeconds | 最大执行时间 | 120 | 平台会将此值限制为 120 秒——超过 120 的值会被静默截断。仅在需要缩短超时时配置,不要尝试延长。 |
ServerLogic/AllowNetworkingToAllDomains | 允许跨域网络连接 | true | 通过 AllowedDomains 限制域名时,设为 false |
使用现有的站点设置创建脚本:
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" --projectRoot "<PROJECT_ROOT>" --name "ServerLogic/AllowedDomains" --value "api.example.com,api.other.com" --description "Restrict server logic external API calls to these domains"
8.2 Git 提交
如果创建了任何设置:
为站点设置更改执行一次 git 提交。
输出:站点设置已配置并提交(如果不需要或已部署,则跳过)
第 9 阶段:客户端集成
目标:帮助用户从其站点的前端代码调用服务器逻辑端点,并与第 1 阶段发现的现有模式保持一致
服务器逻辑负责创建后端,但如果没有调用它的前端代码,这些端点就不会被使用。本阶段会创建或更新前端代码,以调用服务器逻辑 API,并沿用代码库中已建立的模式和约定。
操作:
9.1 询问用户集成范围
🚦 Gate(规划 · add-server-logic:9.1.frontend-scope): 决定是否还要将服务端逻辑接入前端 UI,还是仅完成后端部分。
触发条件: 阶段 8 已完成(服务端逻辑已部署就绪)。 询问原因: 自动集成会修改用户希望自行处理的 UI 文件;自动跳过则会导致应用无法访问这些端点。 取消后保留: 无 — 服务端逻辑后端已写入磁盘;此提示仅用于决定是否继续集成前端。
使用 AskUserQuestion:
| 问题 | 选项 |
|---|---|
| 我已创建服务端逻辑后端。您希望我也将其完整集成到前端 UI 中吗? | 是,完整集成到 UI 中(推荐),否,我会自行处理前端 |
如果选择“否”:跳至阶段 10,但请提供 API URL 和可供用户复制的代码片段。
9.2 遵循前端集成参考
使用下方参考了解前端集成方法、示例和特定框架的模式:
参考:
${PLUGIN_ROOT}/skills/add-server-logic/references/frontend-integration-reference.md
根据 Explore Agent 在阶段 1.4 的发现和已批准的计划,从该参考中选择集成方法,并将其一致地应用于所有接入前端的服务端逻辑端点。
9.3 创建或更新前端集成
遵循参考:
- 如果网站已有服务层或 API 工具,请复用它
- 仅当网站没有既定的 API 客户端模式时,才创建轻量级、支持 CSRF 的辅助工具
- 如果将多个服务端逻辑端点归入同一个服务模块能提升可维护性,则将它们归组
- 仅当代码库已采用特定框架的模式时,才添加对应的钩子、组合式函数或服务
- 将服务端逻辑完整集成到实际 UI 流程中 — 不要止步于创建服务或辅助代码
- 更新相关页面、组件、表单、按钮或用户流程,使界面能够访问新的后端功能
- 如果占位数据、模拟处理程序或临时操作应由新的服务端逻辑端点提供支持,则将其替换
- 添加或保留加载、成功、空状态和错误状态,使 UI 表现得像一个完成的功能
- 对于验证并执行端点:前端必须为受保护的操作(例如状态转换)调用服务端逻辑端点 — 不得针对同一字段另行发送 Web API PATCH 请求。确保该操作对应的 UI(例如“提交”或“批准”按钮)调用服务端逻辑服务函数,而不是 Web API 服务
- 对于由 Dataverse 支持的端点:前端解析必须与阶段 5.3 选定的响应格式相匹配。具体格式对应的解析方式,请参阅前端集成参考中的“Dataverse Connector Response Format”
- 如果响应格式不明确:不要猜测。网站部署后,针对线上网站调用
/test-site,以便从网络选项卡捕获实际的服务端逻辑响应,并据此完成集成
9.4 Git 提交
如果创建了前端集成代码:
为前端集成变更执行 git 提交。
输出:按需创建前端服务或钩子,完整集成 UI 组件或页面,并提交变更
阶段 10:验证与测试指导
目标:验证代码,并为用户提供测试服务端逻辑所需的一切
操作:
10.1 最终代码验证
重新阅读每个已创建的 .js 文件并确认:
- 仅包含允许的顶层函数(get、post、put、patch、del)
- 每个函数都返回字符串
- 每个函数中都有 try/catch
- 每个函数中都有 Server.Logger 调用
- 不包含
import、require或外部依赖 - 不包含浏览器 API(
fetch、XMLHttpRequest、setTimeout、console.log、document、window) - 仅在使用 await 的函数上使用 Async
- SDK 方法使用正确(已根据第 3 阶段文档验证)
- HttpClient 仅用于外部 API(不用于 Dataverse)
- Dataverse 操作使用 Dataverse 连接器
重新阅读每个 .serverlogic.yml 文件并确认:
- 存在
id字段,且其值为有效的 UUID -
adx_serverlogic_adx_webrole数组非空(至少包含一个 Web 角色) -
name与文件夹名称和.js文件名称一致 - 已填写
display_name和description - 字段已按字母顺序排序
- 文件名一致:文件夹名称、
.js名称、.serverlogic.yml名称和name字段均使用相同的值
10.1b 最终站点完整性检查
服务器逻辑及任何可选的客户端集成完成后,运行:
node "${PLUGIN_ROOT}/scripts/validate-site-integrity.js" --projectRoot "<PROJECT_ROOT>"
在报告完成或请求部署前,修复所有阻塞性错误。双向检查并报告审查结果,且提供扩展内容。仅后端运行仍须执行此检查;如果未更改 SPA 源代码,则对可见源代码完整性的检查不执行任何操作。之后若有任何源代码更改,请重新运行此检查。
10.2 提供 API URL
告知用户每个端点的 URL:
https://<site-url>/_api/serverlogics/<server-logic-name>
10.3 测试指南
提供测试说明:
- 先部署站点 — 必须先通过
/deploy-site部署服务器逻辑,之后才能调用 - 非 GET 请求需要 CSRF 令牌 — 对服务器逻辑端点发出的 POST、PUT、PATCH 和 DELETE 请求都需要 CSRF 令牌。从
/_layout/tokenhtml获取令牌,并将其作为__RequestVerificationToken标头传入。GET 请求豁免反伪造验证,执行只读调用时无需令牌。 - 身份验证 — 服务器逻辑遵循站点的身份验证设置。来自已验证身份会话的调用会自动使用基于 Cookie 的身份验证。匿名访问取决于治理设置。
- 从浏览器控制台测试:
使用第 9 阶段的前端集成参考,查找与站点技术栈相符的准确调用方式。
- 检查诊断信息 — 可在 Power Pages 设计工作室的诊断中查看 Server.Logger 输出
- 如果端点返回错误或意外响应 — 请参阅服务器逻辑执行错误排查,了解 Playwright +
X-Ms-UserTrace调试流程
输出:代码已验证,已提供 API URL 和测试指南
第 11 阶段:审查与部署
目标:总结已完成的所有工作并提供部署选项
操作:
11.1 记录 Skill 使用情况
参考:
${PLUGIN_ROOT}/references/skill-tracking-reference.md
按照参考文档中的 Skill 跟踪说明记录此 Skill 的使用情况。使用 --skillName "AddServerLogic"。
11.2 汇总
汇总已完成的所有工作:
| 步骤 | 状态 | 详情 |
|---|---|---|
| Server Logic JS | 已创建 | 列出每个已创建的 .powerpages-site/server-logic/<name>/<name>.js 文件 |
| Server Logic YAML | 已创建 | 列出每个已创建的 .powerpages-site/server-logic/<name>/<name>.serverlogic.yml 文件 |
| HTML 计划 | 已创建/已更新 | render script 输出中的实际路径 |
| 函数 | 已实现 | 汇总每个 server logic 项目中实现的方法 |
| 使用的 SDK 功能 | — | 汇总每个 server logic 项目中使用的功能 |
| 表权限 | 已创建/已跳过 | accounts(读取)、contacts(读取、创建)等 |
| 密钥和环境变量 | 已创建/已跳过 | 环境变量(由 Key Vault 支持或直接设置),以及带有 envvar_schema 的站点设置 |
| 站点设置 | 已创建/已跳过 | ServerLogic/AllowedDomains 等 |
| 客户端服务 | 已创建/已跳过 | 列出已创建或更新的前端服务文件 |
| UI 集成 | 已创建/已跳过 | 已完整连接到 server logic 端点的页面、组件、表单或操作 |
| API URL | — | 列出每个 /_api/serverlogics/<name> URL |
11.3 询问是否部署
🚦 关卡(计划 · add-server-logic:11.3.deploy): 实现完成后的部署提示——部署之前无法访问 server logic 端点。
触发条件: 所有 server logic 构件均已写入并提交。 询问原因: 自动部署可能会选错环境。 取消后: 无——构件会保留在磁盘上;不会触发部署。
使用 AskUserQuestion:
| 问题 | 选项 |
|---|---|
| Server logic 工作已就绪。要使其生效,需要部署站点。现在要部署吗? | 是,现在部署(推荐);不,我稍后部署 |
🚦 关卡(计划 · add-server-logic:11.3.test): 部署后的验证提示——调用
/test-site,对线上新端点进行测试。触发条件: 上一个关卡中的部署成功。 询问原因: 跳过不会造成影响(仍可手动测试);自动调用
/test-site会增加运行时间。 取消后: 无——部署已完成。
如果选择“是,现在部署”:调用 /deploy-site 技能来部署站点。
部署成功后,使用 AskUserQuestion:
| 问题 | 选项 |
|---|---|
站点已部署。现在要我运行 /test-site 进行验证吗? | 是,运行 /test-site(推荐);不,跳过测试 |
如果选择“是,运行 /test-site”:调用 /test-site 技能。
如果选择“不,我稍后部署”:确认并提醒:
“没问题!准备好后,请记得使用
/deploy-site部署站点。部署站点之前,无法访问 server logic 端点。”
11.4 部署后注意事项
部署完成后(或跳过部署时),提醒用户:
- 测试端点:使用适当的 HTTP 方法调用每个
/_api/serverlogics/<name>URL(非 GET 请求需包含 CSRF 令牌) - 建议进行完整的网站验证:部署后,询问是否运行
/test-site,以便对线上网站进行端到端验证 - 检查日志:使用 Power Pages 设计工作室诊断中的 Server.Logger 输出排查问题
- 表权限:已为此服务器逻辑使用的 Dataverse 表配置表权限。如果之后添加了新的 Dataverse 表,请再次运行表权限设置;没有权限时,
Server.Connector.Dataverse会静默返回 0 条记录 - 超时:默认执行超时为 120 秒,这也是平台上限(超过 120 秒的值会被静默限制为 120 秒)
- 匿名访问:如果网站的治理控制禁用了匿名访问,匿名用户将无法调用与外部系统集成的服务器逻辑
- 预览功能:Server Logic 目前处于预览阶段,请关注 Microsoft Learn 以获取更新
- 值为占位符的环境变量:如果第 7 阶段创建的环境变量使用了占位符值,请提醒用户在测试前将其更新为实际的机密值。用户可以通过以下方式操作:
- Power Platform 管理中心 — 环境 → 选择环境 → 环境变量 → 按显示名称查找 → 更新值
- Power Apps 创作者门户 — 解决方案 → 打开解决方案 → 环境变量 → 编辑值
输出:已提供摘要,部署已完成或推迟,并已提供部署后指导
排查服务器逻辑执行错误
部署的服务器逻辑端点返回错误或意外响应时,根本原因通常隐藏在 X-Ms-UserTrace 响应标头中——这是一个经过 base64 编码、包含运行时诊断日志的数据块。Power Pages Edge 浏览器扩展会显示相同的数据,但在针对线上网站进行迭代时,检查响应标头是最快的方法。
每当服务器逻辑调用失败或返回的响应与预期不同时,请使用以下流程:
1. 使用 Playwright 在浏览器中打开线上网站
使用 Playwright MCP 工具操作网站:
- 导航到已部署的网站 URL(
/activate-site返回的websiteUrl,或 Power Pages 管理中心中显示的网站 URL)。 - 如果端点需要身份验证,请让用户登录并等待确认。
- 触发会调用失败的服务器逻辑端点的操作(点击按钮、提交表单等),或者直接使用
fetch()调用端点。
2. 捕获网络响应
使用 mcp__plugin_power-pages_playwright__browser_network_requests 列出网络活动,然后找到对 /_api/serverlogics/<name> 的请求。记录以下信息:
- HTTP 状态码(例如,200、400、500)
- 响应正文(验证失败时通常是通用错误或空负载)
- 最重要的是:
X-Ms-UserTrace响应标头——实际诊断日志就在这里
如果 browser_network_requests 未直接显示响应标头,则改用 mcp__plugin_power-pages_playwright__browser_evaluate,通过 fetch() 调用读取标头:
const res = await fetch('/_api/serverlogics/<name>', { method: 'GET', credentials: 'include' });
const trace = res.headers.get('X-Ms-UserTrace');
return { status: res.status, body: await res.text(), trace };
3. 解码 X-Ms-UserTrace 标头
标头值是经过 base64 编码的 JSON。对其进行解码。
解码后的负载包含诊断日志条目,包括实际错误消息和被禁止的模式(如果脚本验证失败)。
修复后,通过 /deploy-site 重新部署并重启网站,以便立即应用更改。
重要说明
在所有阶段中
- 使用
TaskCreate/TaskUpdate在每个阶段跟踪进度 - 在编写代码之前,务必在第 3 阶段获取 Microsoft Learn 文档——文档是权威依据
- 在关键决策点征求用户确认
- 在里程碑处提交代码——服务器逻辑代码、表权限(如有)、机密/环境变量(如有)、站点设置以及前端集成(如有)完成后提交
- 彻底验证——服务器逻辑有严格的约束,违反约束会导致运行时错误
关键决策点(等待用户确认)
- 在第 1.5 阶段:立即部署还是取消(如果缺少
.powerpages-site——必须确认) - 在第 2.1.2 阶段:使用现有 Dataverse 自定义操作还是从头构建(如果找到自定义操作)
- 在第 2 阶段:确认需求(用途、名称、HTTP 方法、机密)
- 在第 4 阶段:在编写代码前批准实现计划
- 在第 6.2 阶段:审查并批准
table-permissions-architect计划(如果使用 Dataverse 连接器) - 在第 2.3.1 阶段:选择 Azure Key Vault 或直接使用环境变量(如果需要机密)
- 在第 7.2a 阶段第 2 步:新建 Key Vault,或改用直接环境变量(如果未找到保管库)
- 在第 9.1 阶段:创建前端集成,或跳过
- 在第 11.3 阶段:立即部署,或稍后部署
SDK 模式的权威依据
不要将此技能文件视为 SDK 的权威参考。第 3 阶段获取的 Microsoft Learn 文档是 SDK 用法模式、支持的方法、签名和连接器行为的权威依据。计划和实现说明中只保留任务特定的决策。
进度跟踪
开始第 1 阶段之前,使用 TaskCreate 创建包含所有阶段的任务列表:
| 任务主题 | activeForm | 说明 |
|---|---|---|
| 验证站点是否存在 | 正在验证站点前置条件 | 定位项目根目录,检测框架,检查现有服务器逻辑和前端模式,并验证 .powerpages-site 是否存在(必须) |
| 了解需求 | 正在收集需求 | 确定用户意图、是否需要一个或多个服务器逻辑文件、每项的所需方法/功能、发现 Dataverse 自定义操作,以及所需的任何机密 |
| 获取最新文档 | 正在获取 Microsoft Learn 文档 | 查询 Microsoft Learn,获取最新的 Server Logic SDK 参考和示例 |
| 审查实现计划 | 正在与用户审查计划 | 呈现计划(服务器逻辑清单、函数、SDK 功能、外部 API、机密),并在编写代码前确认 |
| 实现服务器逻辑 | 正在编写服务器逻辑代码 | 确定/创建所需的 Web 角色,创建已批准的 .js 和 .serverlogic.yml 文件,并验证代码 |
| 配置表权限 | 正在设置 Dataverse 表权限 | (有条件)解析 .js 文件中的 Dataverse 表,启动 table-permissions-architect,创建权限 YAML 文件 |
| 管理机密和环境变量 | 正在配置机密和环境变量 | (有条件)推荐 Azure Key Vault,列出保管库,存储机密,在 Dataverse 中创建环境变量,并使用 envvar_schema 创建站点设置 |
| 配置站点设置 | 正在配置站点设置 | 按需设置 ServerLogic/* 站点设置 |
| 客户端集成 | 正在连接前端与服务器逻辑 | 遵循前端集成参考,按需创建/更新服务文件,并将 UI 完整连接到服务器逻辑端点 |
| 验证和测试指导 | 正在验证并提供测试指导 | 最终验证、API URL、CSRF 令牌说明和测试指南 |
| 审查并部署 | 正在审查摘要并部署 | 呈现摘要,询问是否部署,并提供部署后指导 |
开始每项任务时,通过 TaskUpdate 将其标记为 in_progress;完成时标记为 completed。在阶段转换之间以及最终摘要之前,使用 TaskList 确认没有未完成的工作项。
从第 1 阶段开始:验证站点是否存在