接手宏天架构的低代码平台之后,我第一件被叫去处理的事,就是API设计规范。低代码平台和普通业务系统最大的区别,在于它自己就是一个“业务系统的生成器”,用户在设计器里拖出来的每一个业务对象,最终都要被一套API暴露给前端代码生成器、外部系统、移动端甚至BI工具消费。如果API设计沿袭传统后端“一个表配一套Controller”的思路,平台很快就会失控。RESTful最佳实践放在这里,不是教你怎么写CRUD,而是要解决动态实体、多租户、权限模型、元数据自描述这一连串低代码平台特有的问题。这篇文章我把宏天架构落地过程中沉淀下来的设计思路和踩坑记录完整梳理一遍,希望能给正在做低代码、通用业务中台或动态建模系统的团队一些参考。
1. 低代码平台的API设计,为什么不能照搬传统业务后端
1.1 传统RESTful设计解决的是“固定模型”问题
普通业务系统里,User、Order、Product都是编译期就写死的实体。你为User写一个UserController,里面GetMapping("/api/users")、PostMapping("/api/users"),Swagger按注解自动生成文档,前端根据文档写死调用代码,这套链路非常成熟。RESTful规范的核心假设是:资源是稳定且可枚举的,每一个资源类型都有独立的URL、独立的Service、独立的存储映射。
这种设计在传统项目里是良性的。模型固定意味着URL可以固定,URL固定意味着客户端可以放心缓存、编排、做类型推导。可一旦把它放到低代码平台里,第一个冲突就出现了:业务对象不是在代码里定义的,而是用户在界面上创建的。今天客户建了一个“合同管理”对象,明天又建了一个“巡检记录”对象,后天可能给某个对象加了三个字段。你不可能每一次都去改代码并重新发布一个Controller,如果这么干,平台就不是低代码,而是高频发版平台。
1.2 低代码平台真正的特殊点:模型不在代码里,而在数据库里
我在宏天架构里把低代码平台的资源分成两大类:一类是平台内置资源,比如用户、角色、权限、菜单、流程、表单、数据源;另一类是用户在业务建模器里创建的动态业务对象,在运行时以元数据记录的形式存在数据库里,但又要像普通实体一样支持增删改查、关联查询、字段校验、数据权限。
这对API架构的影响是决定性的。你面对的不再是几十张写死的表,而是成百上千个结构未知、字段动态变化的业务对象。API层必须在运行时读取元数据,动态生成查询和写入逻辑。设计一个通用数据服务接口,让它能处理任意业务对象,这才是低代码平台API设计与传统RESTful实践的主要分水岭。
1.3 宏天架构给出的三条设计主线
经历过早期版本“URL里塞表名、参数直接拼SQL、错误码随手写”的混乱之后,我最终把宏天架构的API设计收敛成三条主线:
- 资源可识别:任何一条API,不用看实现,只凭URL就能知道它操作的是平台系统资源、元数据还是业务数据。
- 语义可预测:同样的HTTP方法,在列表、详情、创建、更新、删除上永远保持一致的语义,不搞特例。
- 错误可编程:响应结构统一,错误码分段管理,前端代码生成器和外部系统可以依据错误体里的结构化字段做自动处理,而不是靠解析中文提示文本。
这三条主线后面会反复出现在具体设计里。先想清楚约束条件,再看RESTful哪些部分能直接继承,哪些必须改造,才不会在设计时左右摇摆。
2. 资源命名与URL规划:让动态实体也有稳定的“门牌号”
2.1 集合资源与单一资源的命名规范
宏天架构里所有资源统一使用小写复数名词,单词之间用连字符分隔。为什么不用下划线?因为下划线在URL里容易被部分旧系统当成特殊字符处理,连字符更安全,也符合大多数API网关的路径规则。集合资源后面不加动词,比如GET /api/v1/sys/users是用户列表,POST /api/v1/sys/users是创建用户,GET /api/v1/sys/users/{userId}是获取单个用户。
对于业务数据,不能像传统系统那样写死对象名,所以URL里用对象编码做占位符。业务对象编码是设计器里创建对象时生成的唯一标识,一旦生成不再允许修改,这个编码就是业务对象的稳定“门牌号”。URL结构类似:
/api/v1/data/{entityCode} /api/v1/data/{entityCode}/{recordId}这样无论业务对象怎么增加,API层的入口只有一套。同一个数据服务接口,通过不同的entityCode加载对应的元数据模型,再动态生成查询与校验逻辑。前端代码生成器只要拼接URL,不需要为每个对象单独写一套API方法。
2.2 动态业务对象与内置系统资源的URL分层
我见过有的低代码平台把用户、角色、权限和动态业务数据全部混在同一个/data路径下,结果网关不好做权限分流,前端也不好判断某个接口到底是平台能力还是业务能力。宏天架构一开始就做了明确分层:
| 资源类型 | URL前缀 | 用途 |
|---|---|---|
| 系统资源 | /api/v1/sys | 用户、角色、菜单、权限点、参数配置等平台内置能力 |
| 元数据资源 | /api/v1/meta | 业务对象定义、字段定义、校验规则、对象关系 |
| 业务数据 | /api/v1/data | 用户创建的动态业务对象的记录读写 |
| 流程与表单 | /api/v1/flow、/api/v1/form | 流程实例、待办、表单实例等扩展能力 |
注意我刻意没有把数据源、数据库连接这类底层配置暴露到/api/v1下面。它们属于平台管理面,应该走独立管理端口或专用接口,和运行面分离。低代码平台的API面向的是业务操作,不应该让调用方感知物理存储结构。
2.3 版本策略:兼容性不是靠URL版本号堆出来的
低代码平台API的版本管理比普通系统更容易翻车。原因有两个:第一,业务对象的结构在变,同一个接口今天返回5个字段,明天可能返回7个字段;第二,前端页面也是平台生成的,一旦页面发布出去,背后API的演进必须尽量平滑。
宏天架构的做法是:URL里统一带主版本号/api/v1,只在做破坏性契约变更时升到/api/v2,平时的小变化通过兼容策略消化。业务对象的字段新增、字段描述调整、校验规则加强,都属于兼容演进,不需要升版本。真正需要升版本的场景只有URL资源语义变化,比如从“按对象编码定位资源”改成“按数据源+对象编码定位资源”,这种改了地址含义的,才必须升版本。
还有一个容易忽略的细节:业务对象自身的元数据也有版本。元数据字段里保存metaVersion,数据API请求时可以不带,默认使用当前生效版本;确有必要查询历史结构数据时,显式传?metaVersion=xxx,但这种情况很少,不建议开放给普通前端。
3. 动词之外的语义设计:HTTP方法、状态码与错误体在低代码平台的落地
3.1 五个方法在动态模型上的完整语义
RESTful的HTTP方法本身很简洁,麻烦在于落到动态模型上时,很容易产生语义分裂。比如更新操作,有人用PUT全量替换,有人用PATCH部分更新,有人干脆统一POST。宏天架构里的约定是:
- GET:查询列表或详情,不允许有任何副作用。
- POST:创建资源,返回201和Location头。
- PUT:全量更新,提交的字段将覆盖资源所有可写字段,适合表单整体保存。
- PATCH:部分更新,提交哪些字段就更新哪些字段,适合多用户协作时的局部保存。
- DELETE:删除资源,默认走逻辑删除,返回204或200。
为什么把PATCH单独拎出来强调?低代码平台的页面往往是由多个区块组成的,不同人可能同时编辑同一份记录的不同字段。如果都用PUT,前面编辑的内容会被后来者覆盖;PATCH携带?fields参数可以只更新本次涉及的字段,这才是协作场景的正确姿势。
在数据服务内部,PUT和PATCH语义差异不只是SQL语句的差异,更是乐观锁校验的差异。PUT因为覆盖整个对象,必须校验版本号;PATCH因为只改局部,也要校验版本号,但只需要对被修改字段做冲突检测。这个细节我们后面在Vue前端接入部分还会提到。
3.2 批量操作:低代码场景里绕不开的POST /batch
很多严格RESTful的布道者会告诉你不要做批量删除,用DELETE逐条删就行。但在低代码平台里这不现实。业务人员经常在列表页勾选上百条记录一键删除、一键审批、批量导入,逐个请求会让浏览器直接卡死,网关也会因为连接数过高报警。
宏天架构的做法是提供一个专用的批量操作端点:POST /api/v1/data/{entityCode}/batch。请求体里用action区分是create、update、delete还是自定义动作:
{ "action": "delete", "items": ["id1", "id2", "id3"], "transactional": false }注意我特意不在DELETE请求里带body。DELETE带body在HTTP语义上是允许的,但很多网关、代理、浏览器开发工具会直接吃掉body,兼容性很差。统一用POST + batch动作反而是工程上最稳的方案。transactional字段也很关键,默认false,每条记录独立执行、独立返回结果;只有明确要求全部成功或全部失败时才传true,后端会开启事务包裹,但这会明显拉长锁持有时间,所以默认绝不能开。
批量接口的响应也不一样。因为是逐条执行,可能一部分成功一部分失败,响应体里会返回每条记录的执行状态、错误码和生成的ID。前端拿到这个结构,才能在列表页精确标记“哪三行失败了、为什么失败”。
3.3 错误码体系:给前端生成代码一个可编程的契约
低代码平台的前端多数是代码生成器或页面设计器自动产出的,它处理错误不能靠人类看文案判断,必须有结构化信息。宏天架构错误体统一结构如下:
{ "code": "DATA-42203", "message": "字段 age 超出允许范围 0~120", "requestId": "88f9c2a1-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "details": [ { "field": "age", "code": "VALIDATION_RANGE_EXCEEDED", "message": "年龄不能大于120" } ] }错误码分段管理,前缀表达错误来源:AUTH-表示认证授权、DATA-表示业务数据校验、META-表示元数据错误、PLAT-表示平台系统错误、FLOW-表示流程错误。后面四位里,前两位代表HTTP状态码,后两位代表具体场景。比如DATA-42203表示数据校验失败,422是HTTP状态码,03是具体规则编号。
这套设计的价值在接入方那里尤其明显。外部系统对接时,不需要为“更新失败”写一堆if-else猜原因,直接看code前缀就能定位问题,看details里的field就能在表单里高亮错误字段。前端代码生成器也能根据code自动决定是弹错误提示、刷新登录状态还是跳转无权限页。HTTP状态码表达“这一层出错了”,业务错误码表达“具体哪里错了”,两层各司其职,不要混在一起。
4. 元数据端点与Schema动态变化:API的“自描述”能力
4.1 /meta端点:让前端代码生成器知道资源长什么样
低代码平台的前端页面不是手写字段的,而是根据元数据自动渲染的。所以平台必须有一个API告诉前端:这个业务对象有哪些字段、什么类型、多长、是否必填、有哪些枚举、值域范围是多少。这就是元数据端点的作用。
GET /api/v1/meta/entities/{entityCode}返回对象基本信息、字段列表、字段校验规则、关联关系、默认视图配置。字段列表是核心:
{ "entityCode": "contract", "entityName": "合同", "metaVersion": 42, "fields": [ { "field": "contractNo", "label": "合同编号", "type": "STRING", "maxLength": 64, "required": true, "unique": true }, { "field": "amount", "label": "合同金额", "type": "DECIMAL", "precision": 18, "scale": 2, "min": 0 } ] }这个端点是所有前端能力的基石。列表页要展示哪些列、表单页要渲染哪些控件、校验规则是什么、哪些字段不可见,全部从这里推导。宏天架构要求所有数据API的响应字段顺序、字段编码都和元数据里的定义严格一致,前端拿到meta之后再取数就不会出现“接口返回了,但我不知道这个字段要怎么渲染”的问题。
4.2 字段变更后的API兼容策略
低代码平台有一个高频操作:对象建好、页面已经在用了,然后业务人员说“我要加一个字段”“这个字段要改成必填”“这个字段改名了”。API层必须对这类变更给出明确的兼容策略。
默认规则是:新增字段不破坏已有客户端。旧客户端不传新字段,数据服务按元数据里的默认值填充,响应里新字段会出现在JSON里,但旧客户端不认识就直接忽略,不影响运行。真正危险的是字段属性变更:把某个字段从非必填改成必填,旧客户端仍然不传,此时请求会返回422,并且details里明确指出是哪个字段、哪条规则。宏天架构的元数据里专门有一个状态机,字段可以有active、deprecated、removed三种状态。字段改名时不允许直接删除字段,而是创建新字段、把旧字段标记为deprecated,并在deprecated配置里写明替代字段。API层遇到deprecated字段的写入请求时,会返回警告信息但不会拒绝写入,给前端一个平滑迁移期。
这里有一个容易被忽略的坑:元数据变更后,必须让所有运行中的前端重新拉取元数据。否则前端本地还在用旧字段定义去渲染表单,提交时就会莫名其妙地校验失败。宏天架构的解法是在所有数据API响应头里返回X-Meta-Version,前端每次请求都对比本地版本,不一致时自动刷新元数据缓存。这个机制比WebSocket推送更简单可靠,也不需要额外维护长连接。
4.3 查询、过滤、分页的query参数规范
低代码平台的列表查询需求极其复杂,动辄需要组合条件、范围过滤、模糊匹配、排序、只看特定字段。如果每个接口都自定义一套query参数,前端代码生成器根本没法统一处理。宏天架构把查询参数收敛成四个:
- page与pageSize:基础的页码分页参数。
- sort:排序参数,支持多个排序字段,用逗号分隔,字段前加-表示倒序,比如sort=createTime,-amount。
- filter:结构化过滤条件,JSON格式,支持and/or组合。
- fields:返回字段裁剪,逗号分隔,控制在不需要大量字段时的传输体积。
filter参数的实际格式如下:
{ "logic": "and", "conditions": [ { "field": "status", "op": "eq", "value": "ACTIVE" }, { "field": "amount", "op": "gt", "value": 10000 }, { "field": "createTime", "op": "between", "value": ["2025-01-01", "2025-12-31"] } ] }为什么不用OData那套复杂协议?那套能力确实很强,但学习成本太高,低代码平台的调用方有相当一部分是业务集成人员,他们更习惯“能看懂、能手写”的条件格式。filter保持JSON结构,既能让前端用规则引擎可视化生成,也能让外部系统自己拼JSON,够用就好。
5. 安全模型在API层的映射:认证、权限和数据范围
5.1 认证信息透传与租户上下文
低代码平台几乎都是多租户架构,一个平台实例同时服务多个企业或部门。租户隔离如果只靠业务逻辑里的where条件,迟早会漏数据。宏天架构的底线是:租户信息在网关层强制注入,绝不允许客户端通过参数指定租户ID。API层从认证token或域名解析里取得租户上下文,再透传到数据服务层。
认证方式统一走OAuth2/JWT。API网关校验JWT签名,把解析出的principalId、tenantId、部门Id、角色列表放到请求上下文里,后续任何一层都不再解析原始凭据。这样设计的好处是,业务服务不用关心token从哪来、怎么验签,只需要从可信上下文里读取身份信息。外部系统接入时,给一个clientCredentials模式的专用clientId,通过clientId绑定租户,从根上避免跨租户访问。
实测中最容易出问题的是“内网调用”。很多团队为了省事,内部服务之间直接传递userInfo字符串,结果网关校验形同虚设。宏天架构在早期也踩过这个坑,后来强制规定:服务内所有调用必须通过API网关,未携带token的请求一律401。低代码平台的API一旦被内部直连绕过,数据权限就是一层窗户纸。
5.2 功能权限与字段级权限的API表达
RESTful资源URL天然适合做功能权限粒度的锚点。宏天架构在RBAC模型里定义一个权限点对应一组URL模式,角色绑定权限点,用户通过角色获得权限。比如“合同管理-查看”权限点对应GET /api/v1/data/contract/**,“合同管理-删除”权限点对应DELETE /api/v1/data/contract/*。
真正需要花心思的是字段级权限。同一个合同对象,销售代表只能看客户名称和金额,财务专员还能看成本利润率,业务运营则要看到完整的审批备注。数据API只会返回完整字段列表,不做裁剪的话,前端虽然可以不展示,但调用方直接调接口还是能拿到敏感字段。
宏天架构在元数据里为每个字段增加了权限标记,数据服务返回结果前,根据当前用户的角色字段权限进行裁剪。更重要的是,这个字段级权限同样作用于元数据端点。前端代码生成器从meta端点拿到的字段列表,本来就是当前用户可用的过滤后版本,这样页面上压根不会渲染无权限字段,而不是渲染出来再隐藏。数据写入时也一样,提交里包含用户无写权限的字段时,直接返回403或忽略该字段,具体策略取决于字段的auditFlag配置。
5.3 数据权限的过滤条件注入
行级数据权限是低代码平台绕不开的硬骨头。同一个列表接口,普通员工只能看自己创建的记录,部门主管能看到本部门所有记录,管理层能看到全部租户内数据。数据服务不可能为每种角色写不同查询,必须由权限引擎统一注入过滤条件。
宏天架构的实现思路是:数据服务的标准查询流程里,在解析用户query之前,先执行数据权限解析器。解析器根据当前用户的角色、部门、数据范围规则,生成一组追加过滤条件对象,再与用户传入的filter合并。这样无论前端要求查什么,最终执行的SQL里都一定会带上权限过滤。
这里有个安全指标,我称之为“权限过滤覆盖率”:所有数据API必须100%经过数据权限引擎,不允许任何接口绕过。实现上可以通过框架层强制,给所有数据操作入口统一加注解,注解里配置是否启用数据权限过滤。新加入的接口如果没有显式配置,默认启用放开功能的,不对,默认启用严格模式,这样即使开发人员忘了处理,权限引擎也会自动加最保守的过滤条件,避免未授权数据泄露。
6. Vue技术栈接入体验:让低代码生成的API层真正“好用”
6.1 API编排层:把RESTful响应包装成前端友好的结构
宏天架构的前端默认技术栈是Vue 3 + TypeScript,页面和页面里的API调用代码,大部分由平台代码生成器产出。RESTful接口设计得再标准,如果前端拿到的数据还要做各种手工转换,生成出来的代码就会很啰嗦。所以前端应有一个统一的API编排层,负责处理HTTP状态码、错误体解析、loading、取消重复请求。
axios拦截器里我建议做这几件事:请求头自动带token、超时设置、响应体统一解包。宏天架构的响应设计是“HTTP状态码表达语义,body里直接放资源数据”,所以拦截器里不需要解包{code, data}这种外壳,而是直接返回response.data对应资源:
const http = axios.create({ baseURL: '/api/v1', timeout: 15000 }) http.interceptors.request.use(config => { const token = getAccessToken() if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) http.interceptors.response.use( response => response.data, error => { const status = error.response?.status const errorBody = error.response?.data || {} if (status === 401) { redirectToLogin() } else if (status === 403) { showMessage('没有操作权限') } else if (errorBody.code) { showMessage(errorBody.message) if (errorBody.details?.length) { highlightFormFields(errorBody.details) } } return Promise.reject(errorBody) } )这段代码不长,但它把错误处理的规则收口在一处,后面生成的业务代码可以保持非常干净,不需要每个页面都写异常判断。
6.2 缓存、幂等与乐观锁在前端交互中的配合
低代码平台前端最常见的性能问题是重复请求。列表页每次切换筛选条件就发一次请求,表单页打开详情后又马上要关联数据,如果不做缓存,网关压力会很大。但缓存不能乱加,因为低代码平台的数据权限是按用户维度区分的,同一个URL,不同用户返回的数据范围本来就不同。宏天架构的缓存策略是:列表数据不缓存或短TTL缓存,字典数据、元数据、权限点这类低频变化数据可以缓存,并且缓存key要带上租户ID和用户角色指纹。
另一件必须在前端配合的事是幂等创建。用户在网络卡顿时习惯性点两次提交按钮,如果后端没有幂等保护,就会生成两条重复记录。宏天架构要求POST创建接口支持Idempotency-Key请求头,前端在表单提交时用UUID生成一个幂等键,第一次请求成功后本地记录这个键,后续重复提交直接跳过。
乐观锁则主要用在编辑场景。列表数据拿到手时有version字段,打开编辑页、修改字段、保存提交时带上version。如果这期间有其他用户先保存了,后端返回412 Precondition Failed,错误体里的details会带上最新数据版本。前端拿到412后不要直接替换用户输入内容,而是提示“当前记录已被其他人修改”,并提供“覆盖保存”或“刷新后重试”两个操作。
6.3 生成代码的可维护性设计
低代码平台的代码生成器最容易生成出“一次性代码”——生成之后没人敢改,因为改一处可能和平台下次重新生成互相覆盖。要解决这个矛盾,生成的API调用层必须做到:结构简单、命名可读、类型完整。
以Vue项目为例,宏天架构的生成器会为每个业务对象生成一个API模块,比如contract.ts,内容不是一大坨请求函数,而是围绕元数据端点生成的类型定义和统一数据服务函数的组合:
export interface ContractField { contractNo: string amount: number status: 'DRAFT' | 'ACTIVE' | 'FINISHED' version: number } export function getContractList(params: ListQuery) { return http.get<PageResult<ContractField>>('/data/contract', { params }) } export function createContract(data: Partial<ContractField>) { return http.post<ContractField>('/data/contract', data, { headers: { 'Idempotency-Key': createIdempotencyKey() } }) }生成的类型字段完全来自meta端点的字段定义,字段类型、枚举值、是否可选都能在编译期检查出来。普通开发人员即使完全不看后端代码,也能在Vue组件里获得完整的智能提示。这是低代码平台“低代码”体验的最后一块拼图:不是说不用写代码,而是写代码时不需要再猜接口长什么样。
7. 宏天架构落地中的几个真实教训
7.1 N+1查询在通用列表接口里被放大了
传统系统里N+1查询通常是某个列表页需要显示关联对象名称,开发人员偷懒在循环里查了子表。低代码平台的通用数据服务更容易踩这个坑,因为业务对象关系是动态配置的,前端列表想显示关联字段,会通过expand参数请求关联数据。如果数据服务按“遍历每条记录查询关联表”的方式实现,一个100条记录的列表,可能瞬间产生几百条SQL。
宏天架构的教训是:关联查询必须批量预加载。服务端拿到expand参数后,先查主表记录,再根据主表记录的外键值集合,对关联对象做一次IN查询,最后在内存里完成映射组装。代码层面对开发人员透明,但对数据服务实现有硬性要求。实测优化前后,同一个包含三跳关联的列表接口,响应时间从3秒降到120毫秒,差距非常大。这里建议在数据服务的性能监控里专门对expand堡垒做SQL条数统计,超过主表条数一半就要告警。
7.2 元数据缓存与数据变更的一致性问题
低代码平台的数据API每次请求都要读取元数据来决定字段类型、校验规则、关联关系,如果每次查库,数据库压力会非常大。所以元数据必须做缓存。但缓存会带来一致性问题:业务人员在设计器里改了字段定义,运行中的API实例可能还持着旧缓存,导致新接口访问报错,前端也拿到老结构。
我踩过的坑是单实例缓存没问题,一旦多实例部署,一个实例收到元数据变更通知清掉缓存,另外几个实例还是旧数据。后来宏天架构的解法是给元数据增加全局版本号,任何变更都会递增版本号,数据服务每次处理请求时读一次本地缓存的版本号,再和Redis里的全局版本号做比对,不一致就重新加载。这个比对开销极小,但能彻底解决多实例缓存漂移。另外元数据缓存必须设置兜底TTL,即使版本号机制出问题,缓存也最多脏30秒,不会永久不一致。
7.3 批量事务边界必须显式声明
低代码平台的导入、批量审批、批量删除极其常见,开发人员常常理所当然地认为“批量操作必须是一个事务”。但一次批量操作几百上千条记录,如果都包在一个事务里,数据库行锁会长时间不释放,高并发下很容易拖垮整个租户。宏天架构做了一个偏激但实用的取舍:批量接口默认非事务,逐条执行,逐条返回结果;只有客户端显式传transactional: true时才启用事务。
这么做之后,批量接口的响应设计也要相应调整,不能只返回“成功/失败”,而要返回条目级别的结果数组。前端生成器拿到部分失败结果,可以精准提示“第3行、第7行失败,原因是……”,业务人员再针对性修改,而不是整批回滚后重新来一遍。事务模式仍然提供,给了那些确实要求强一致性的场景一条路,只是默认不开启,防止被人滥用。
7.4 删除操作必须明确“逻辑删”还是“物理删”
低代码平台里,列表页的删除按钮到底对应DELETE还是软删除,一直容易扯皮。宏天架构的默认策略是:所有前端触发的DELETE都是逻辑删除,记录不会真正消失,列表默认过滤已删除记录,但可以通过includeDeleted=true参数查看。物理删除只保留给两个场景:管理员在回收站里的彻底清理,以及外部系统通过专用接口且携带hard=true参数时。
这个策略的API表达是:DELETE /api/v1/data/{entityCode}/{recordId}默认逻辑删除,返回204;带?hard=true且当前用户拥有物理删除权限时,才执行真正DELETE。逻辑删除的代价是查询语句都要多加一个默认过滤条件,这个条件对上层透明,但对下层的索引设计和查询性能有影响。宏天架构在实现时把这个过滤条件也纳入数据权限引擎统一管理,避免每个查询接口各自维护一条软删除逻辑。
踩过这么多坑之后,我自己最大的体会是:低代码平台的API设计,本质上是在设计一套“给所有业务系统共用的语言”。语言本身的语法要简单,但语义必须精确;资源URL可以动态表达,但行为不能动态漂移;字段可以灵活变化,但兼容边界必须预先画好。如果你也在做类似平台,建议把API规范当成一等产品来维护,而不是等出问题后靠口头约定修正。宏天架构现在每个新接口上线前都会过一遍自动规范扫描,检查URL命名、错误码分段、幂等支持、权限过滤覆盖率这几项硬指标,扫描不过直接拦截发布。这套约束看起来烦琐,但恰恰是它,让上百个业务对象可以安心地跑在同一套API体系上,也让那些后来接入平台的Vue项目、外部系统、移动端,都少了一大批“为什么这个接口这么难用”的困惑。