项目介绍:HATEOAS

提取词

复杂权限体系 | 前后端重复维护判断逻辑 | 权限判断收敛服务端 | HATEOAS、HAL / HAL-FORMS | TypeScript 核心与 React Hooks | URI 资源复用与进行中请求去重 | 缓存更新、失效与事件订阅 | Schema 驱动表单与校验 | 工作区导航试点与端到端回归 | 系统 80 % 的功能模块 | 权限变化不再同步修改前端判断分支 | 相关任务成本降至原来的一半

项目介绍

这个项目是低代码平台里的 TypeScript 资源访问 SDK。平台有一套复杂的权限体系,前端为了判断入口和操作是否可用,需要维护一份与后端一致的逻辑判断。后端权限规则一旦变化,前端就必须同步修改、回归和部署。我负责资源契约设计、SDK 核心和 React 接入,并与后端共同落地接口改造。

我的思路是把权限判断收敛到服务端,前端直接使用服务端返回的判断结果。方案上采用 HATEOAS,通过 HAL / HAL-FORMS 表达当前可访问的资源关系和可执行动作;动作模板同时描述提交地址、HTTP 方法和字段约束。前端按契约展示入口、组织表单和提交数据,减少前后端重复维护判断逻辑。

SDK 分成框架无关的核心层和 React Hooks 适配层。核心层统一处理资源访问、协议解析、动作提交和缓存,React 层负责状态订阅与组件生命周期。同一资源按绝对 URI 复用,并对进行中的相同请求去重,避免多个消费方同时读取时重复发送请求。

写操作后,SDK 根据响应中的信息更新缓存或标记失效,再通过事件通知消费方。React 可以直接使用返回的新状态更新页面,也可以根据刷新配置重新读取,让缓存管理与页面呈现分开。

表单采用 Schema 驱动,根据服务端动作模板中的字段定义生成 Zod 校验规则和 JSON Schema 表单描述,让字段渲染与提交校验使用同一份定义,减少页面分别维护字段和校验逻辑。

落地时先以工作区导航为试点,在 API 表达层增量增加资源关系和动作模板,保持表结构、原有请求参数及保存接口响应不变。我用 AI 辅助生成工作流保存与发布的端到端测试,在改造后执行回归,验证已有业务链路的兼容性。

最终,系统 80 % 的功能模块,后端调整权限时,前端不再同步修改判断分支;相关任务成本直接降低至原来的一半。

简历描述

HATEOAS 资源契约与 TypeScript SDK 开发

  • 技术栈:TypeScript、React Hooks、HAL / HAL-FORMS、Zod、Nx + pnpm、Vite、Vitest、MSW、Testing Library、TypeDoc
  • 项目简介:面向低代码平台的复杂权限体系,设计 HATEOAS 资源契约与 SDK,将权限判断收敛到服务端,减少前端维护与后端一致的权限和操作可用性判断逻辑;PC 与移动端按服务端返回的资源关系、可执行动作及字段约束组织交互。
  • 工作内容与成果:
    • 资源契约与 SDK 分层:与后端协同设计 HAL / HAL-FORMS 资源关系和动作模板,实现框架无关的 TypeScript 核心与 React Hooks 适配层,分离资源定位、响应快照和动作提交,统一多端接入方式。
    • 请求去重与缓存管理:按绝对 URI 复用资源,对进行中的相同请求去重;根据写操作响应更新缓存或标记失效,通过事件订阅驱动 React 状态更新,并支持按配置刷新。
    • Schema 驱动表单:根据服务端动作模板的字段定义生成 Zod 校验规则与 JSON Schema 表单描述,在提交前验证数据,让字段渲染与校验使用同一份定义。
    • 增量迁移与兼容性回归:以工作区导航为试点,与后端协同在 API 表达层增量接入契约,保持表结构、原有请求参数和保存接口响应不变;用 AI 辅助生成工作流保存与发布的端到端用例,并在改造后执行回归。

可能追问

难点一:动态资源契约的统一消费

为什么不复用前端权限模块,或者只返回权限布尔值?

如果需求只是控制一个发布按钮,返回一个“是否允许发布”的布尔值就够了。但我们还需要统一提交地址、HTTP 方法、字段及必填约束,只给布尔值,前端仍要单独维护这些提交约定。所以协议采用 HAL / HAL-FORMS,用资源链接表达可访问的关系,用动作模板描述提交方式和字段约束,对应响应中的 _links 和 _templates。

SDK 怎样分层,资源访问、响应解析和动作提交如何协作?

我把 SDK 分成框架无关的 TypeScript 核心层和 React 适配层。核心层负责资源访问、网络请求、协议解析、动作提交与缓存,React 层负责状态订阅和组件生命周期。PC 和移动端共用协议处理逻辑,页面不用分别解析资源链接和动作模板。

核心内部先分开资源身份和响应快照。资源按绝对 URI 定位,提供读取、缓存访问和事件通知;同一资源从不同入口到达时,只要目标 URI 相同,就能复用。响应快照则保存某次读取取得的业务数据、资源关系和动作定义。

需要网络读取时,由统一请求接口发起调用,核心层再根据响应的 Content-Type 选择协议解析器。解析器把业务属性、资源链接、动作模板和嵌入资源整理为统一的响应快照,同时保留 URI 与响应头上下文。页面因此可以按同一套接口使用不同协议的响应,不必直接处理原始 JSON 结构。

动作提交单独封装目标地址、HTTP 方法、字段和序列化方式。页面从当前响应中取得动作后,只需提供提交数据,不必重新拼请求。比如工作区页面读取基本信息,通过资源关系访问工作流列表,再从动作模板取得工作区更新操作。

写操作后的缓存更新和失效通知也留在核心层,React 层订阅事件,更新组件或按配置刷新。这样资源身份、响应数据和提交行为各有职责,协议逻辑不会散落到页面中,React 生命周期也不会侵入核心实现。

动态契约怎样校验,哪些变化可以不用前端发版?

我把稳定的结构交给 TypeScript,把当前响应才知道的信息交给运行时。编译期约束资源数据、资源关系名称和目标资源类型;当前用户有没有入口,则要检查实际响应是否提供对应关系。访问响应中不存在的动作时,SDK 会报错,不能因为类型里描述过某个动作,就认为每次响应都会提供。

提交字段来自服务端动作模板。SDK 根据字段定义生成 Zod 校验规则,在发请求前检查提交数据;不符合约束时,抛出带字段信息的校验错误。业务页面也按同一份字段定义生成 JSON Schema 表单描述,让字段渲染和校验保持一致。

是否需要发版,要看现有渲染、校验和提交能力能否覆盖变化。例如服务端调整角色规则,工作流列表的资源关系随之出现或消失,导航直接按新响应渲染,不必同步维护角色分支。假设模板新增必填文本字段,而已有控件、校验和提交能力都支持,也可以沿用当前链路。

如果需要新控件、特殊交互,或者资源关系和动作的含义改变,就要调整前端。服务端能返回字段,不代表页面天然理解所有字段;访问地址可以跟随契约,入口和动作在产品中的意义仍需要双方约定。

我会沿资源关系识别、动作理解、字段渲染、校验和提交逐项判断。TypeScript 帮助开发时理解稳定结构,运行时按当前契约组织交互,服务端校验最终请求,分别处理不同阶段的问题。

难点二:写操作后的缓存与页面更新

多个入口怎样共享资源,React 何时重新请求?

共享先要统一资源身份。资源关系描述从哪里可以访问什么,目标绝对 URI 才用来定位具体资源。SDK 按 URI 复用资源对象,响应缓存也按 URI 管理;不同入口指向同一 URI 时,就能共享状态和事件,而不是每个页面各存一份。

这里有三种不同的复用:资源对象保留稳定的访问入口,响应缓存保存已经取得的快照,进行中请求去重则避免同一时间重复读取。例如两个组件同时读取同一资源,第一个请求还没完成,后一个复用这次读取;请求结束后清理进行中记录,后续再按缓存策略处理。

普通读取优先使用完整的响应缓存,主动刷新时则发起带 Cache-Control: no-cache 的请求。React 读取 Hook 订阅新状态和缓存失效两类通知:收到新状态时,直接使用已经取得的快照更新组件,不必再为同一份数据发 GET。

缓存失效通知只说明旧状态不能继续使用,并没有提供新数据。Hook 默认不会因此自动重新请求,只有开启失效后自动刷新配置时才主动读取,由调用方决定刷新策略。组件卸载时清理监听,把订阅生命周期留在 React 适配层。

比如一个组件完成修改,另一个组件能否立刻显示新状态,要看响应是否直接写回缓存,以及失效后是否开启刷新。排查时需要分别检查资源身份、缓存通知和 Hook 配置,不能把“缓存已失效”直接理解成“所有页面都已更新”。

动作执行后,哪些缓存失效,哪些能直接写回?

我把写操作后的处理分成两件事:哪些旧状态已经不能继续信任,以及这次响应有没有直接带回可用的新状态。一个动作可能同时影响当前资源和关联资源,只修改发起操作的组件,其他入口仍可能留着旧数据。

以成功的 POST 响应为例,SDK 会按规则处理请求 URI、Location,以及 HTTP Link 响应头中显式声明的失效目标,清理对应缓存并发出失效通知。缓存层还支持登记资源依赖,在某个资源失效时传播到依赖它的缓存。请求 URI 的默认失效可以通过专用标记控制。

失效通知并不提供新数据。能否直接写回,要看响应是否带 Content-Location,以及是否允许存储。满足条件时,SDK 把本次响应体解析为对应 URI 的资源快照,写入缓存并通知订阅方。这一步使用已经收到的响应,不额外发 GET;响应禁止存储时,也不会进行这类写回。

比如响应说明返回的就是某个资源的新状态,就可以直接更新该资源;另一个关联资源只有失效声明,就先清掉旧缓存,后续再按读取策略获取。在这套缓存策略中,Location 参与确定失效目标,Content-Location 标识响应体对应的资源,两者作用不同。

同一次响应可以先触发失效,再写回新状态。React 收到的是含义明确的两类通知:一个说明旧数据过期,另一个提供新快照,再据此更新页面或决定是否刷新。

难点三:存量系统的增量迁移与兼容性验证

怎样让后端协作方确认改造成本和风险可控?

推进时,后端关心的是改多少、是否影响原来的数据更新,以及出了问题有没有退路。我先把讨论落到具体接口范围:我负责模型、资源契约和客户端 SDK,与后端共同确认 API 表达方式,由后端完成服务端实现。

这次改造在资源响应中增加资源链接和动作模板,保持表结构、原有请求参数和工作流保存接口的响应不变。这里有两个范围:资源读取响应增加契约元数据,原来的保存接口继续保持原有输入输出。明确这两个范围,才能判断哪些地方需要修改、哪些兼容性需要回归。

落地先以工作区导航为试点,让 PC 和移动端从原有业务接口调用迁移到 SDK;架构上保留 BFF 作为渐进迁移退路。这样可以围绕一条实际访问路径讨论契约和适配,不要求所有业务页面一次迁完。

验证方面,我用 AI 辅助生成工作流保存与发布的端到端测试,前后端完成实现后重新回归。保存用例使用相同输入,对比原有的原始响应 JSON,把“原有行为是否保持”落实成断言。导航是否按资源关系渲染,则由另一组组件测试检查。

改造范围通过接口和数据结构来确认,兼容性通过相应场景的回归来判断。AI 帮助生成测试代码,实际验收仍看断言和回归结果,让双方能对照具体改动协作。

工作区试点怎样验证?

试点从工作区资源响应开始。我与后端协同增加资源链接和动作模板,由服务端按权限提供工作流列表入口和工作区基本信息更新操作。PC 侧边栏与移动端首页从原有业务接口调用迁移到 SDK,根据当前资源关系是否存在渲染导航,再沿关系访问目标。

导航组件测试用 MSW 模拟包含和不包含工作流列表关系的两种正常响应,在 Vitest 中渲染组件,通过 DOM 断言检查入口。它验证给定契约后的客户端表现;没有资源关系是正常状态,与请求失败分开处理。

工作流端到端回归针对保存与发布,我用 AI 辅助生成测试,在前后端实现后重新回归。保存用例使用相同输入,对比原有原始响应 JSON,检查已覆盖保存场景的输入输出兼容性。它验证的是存量业务链路,与导航组件测试的断言对象不同。