标签 REST API 下的文章

在存量竞争的商业环境中,企业数字化转型已从“单点工具应用”转向“全链路价值闭环”——核心需求聚焦于以全业务一体化为基础,构建“获客-履约-复购”客户 全生命周期管理 ,并通过 供应链协同 管控实现降本增效。本次横评选取市场上9款具有代表性的CRM/一体化管理系统,从四大核心维度展开专业对比,为不同行业、规模的企业提供选型参考。

参评品牌矩阵

阵营分类代表品牌核心定位
垂直工贸/工业类超兔一体云工贸全链路一体化+供应链共生
开源模块化阵营Odoo CRM、YetiForce、Dolibarr通用模块化+开源定制
国际厂商阵营Oracle CX、Pipedrive全链路云原生生态/销售流程专精
国内SaaS细分阵营玄讯CRM、网易七鱼CRM、泛微CRM快消垂直/服务营销/协同OA+CRM

一、全业务一体化能力横评:架构与集成的核心差异

全业务一体化的本质是数据无界共享+流程无缝协同,核心差异体现在架构设计、集成能力与定制化灵活性三个维度:

1. 核心能力对比表格

品牌架构模式核心覆盖模块集成能力定制化难度适用场景
超兔一体云原生垂直一体化(工贸)CRM、进销存、生产、薪资、财务日记账原生集成OpenCRM上下游平台,支持ERP对接低(可视化配置)工贸/工业、中小制造企业
Odoo CRM模块化一体化CRM、销售、库存、财务、生产、HR等模块无缝集成,支持REST API、第三方工具集成中(低代码+开源开发)中大型标准化流程企业
YetiForce开源模块化优化CRM、库存、销售、财务模块联动,支持二次开发中(开源开发)需轻度定制的中型企业
Dolibarr轻量模块化CRM、ERP、会计基础模块集成,支持简单API对接低(开箱即用)小微企业、业务流程简单
Oracle CX云原生全链路一体化营销云、销售云、服务云、SCM、ERP原生集成Oracle生态,支持跨系统数据同步高(需专业实施)大型企业、集团化管控
Pipedrive销售流程模块化销售管道、线索管理、订单管理仅销售模块集成,需第三方工具对接供应链/财务低(可视化配置)销售驱动型中小企业
玄讯CRM垂直快消一体化营销、销售、订单、库存集成OA、ERP,支持终端数据同步中(行业模板定制)快消/零售企业
网易七鱼CRM服务+营销一体化智能客服、呼叫中心、精准营销集成微信生态、电商平台,支持工单联动低(可视化配置)电商、SaaS等C端服务企业
泛微CRMOA+CRM协同一体化线索、客户、销售、服务、OA审批原生集成泛微OA,支持ERP对接中(流程定制)中大型企业、协同办公需求强

2. 一体化覆盖范围脑图

mindmap
  root((全业务一体化覆盖))
    垂直工贸类(超兔一体云)
      CRM获客
      进销存履约
      生产工单
      财务日记账
      薪资管理
      OpenCRM上下游协同
    开源模块化(Odoo/YetiForce/Dolibarr)
      通用业务模块
      二次开发扩展
      第三方工具集成
    国际全链路(Oracle CX)
      营销云
      销售云
      服务云
      SCM Cloud
      ERP深度集成
    国内细分赛道(玄讯/网易七鱼/泛微)
      快消终端管控(玄讯)
      C端服务营销(网易七鱼)
      OA业务协同(泛微)
    • *

二、“获客-履约-复购”数字闭环深度对比:从广度到精度

数字闭环的核心是客户全生命周期的自动化运营,本次从获客、履约、复购三个核心环节展开对比:

1. 闭环完整性对比表格

品牌获客渠道覆盖度履约流程自动化复购运营精准度闭环完整性评分(1-10)
超兔一体云9/109/108/109/10
Oracle CX10/109/1010/1010/10
玄讯CRM8/108/108/108/10
Odoo CRM7/108/107/107.5/10
网易七鱼CRM9/106/108/107.7/10
泛微CRM7/107/107/107/10
YetiForce6/107/106/106.3/10
Pipedrive8/105/106/106.3/10
Dolibarr5/106/105/105.3/10

2. 典型闭环流程差异

flowchart LR
    subgraph 超兔一体云(工贸场景)
        A[多渠道获客\n(微信/工商/巨量引擎)] --> B[线索智能分配\n+客户画像分层]
        B --> C[订单锁库\n+生产工单MES对接]
        C --> D[RFM分块回访\n+客池精准培育]
        D --> A[复购触发定向营销]
    end
    subgraph Oracle CX(大型企业)
        A1[AI跨渠道营销\n(邮件/社交/广告)] --> B1[CX Unity 360°客户视图]
        B1 --> C1[CPQ智能报价\n+SCM库存同步]
        C1 --> D1[忠诚管理\n+服务闭环]
        D1 --> A1[个性化推荐营销]
    end

3. 关键能力解读

  • 获客环节:超兔一体云覆盖工商搜客、巨量引擎等工贸专属渠道;Oracle CX的AI营销自动化实现千人千面触达;网易七鱼的智能外呼+微信生态适配C端获客。
  • 履约环节:超兔的订单锁库+MES生产对接是工贸企业核心刚需;Oracle的CPQ+SCM集成实现端到端履约管控;玄讯的BOM报价模板适配快消行业的复杂定价。
  • 复购环节:超兔的RFM分析+客池培育实现老客户精准激活;Oracle的客户忠诚管理系统支持全触点留存;网易七鱼的AI个性化推荐提升C端复购转化率。
    • *

三、供应链协同管控能力对比:从内部流程到上下游共生

供应链协同的核心是打破 信息孤岛 ,实现上下游业务数据实时联动,本次从协同深度、三流合一、平台开放性三个维度对比:

1. 核心能力对比表格

品牌协同范围三流合一能力上下游平台支持协同深度评分(1-10)
超兔一体云内部+供应商+客户全链路9/10OpenCRM共生平台9/10
Oracle CX内部+供应商+物流商10/10Oracle SCM Cloud+ERP集成10/10
玄讯CRM内部+终端经销商8/10ERP集成+终端数据同步7/10
Odoo CRM内部库存+采购7/10模块集成+第三方SCM对接6/10
泛微CRM内部+供应链部门7/10OA+ERP数据联动6/10
网易七鱼CRM内部售后+库存6/10工单+电商库存联动5/10
YetiForce内部库存+订单6/10基础模块联动5/10
Pipedrive无原生供应链协同3/10需第三方工具对接2/10
Dolibarr内部库存+采购5/10基础模块集成4/10

2. 上下游协同流程差异

sequenceDiagram
    participant 制造企业 as 工贸制造企业
    participant 超兔OpenCRM as 超兔OpenCRM
    participant 供应商 as 供应商
    participant 客户 as 终端客户
    制造企业->>超兔OpenCRM: 发起采购询价
    超兔OpenCRM->>供应商: 推送询价单+自动提醒
    供应商->>超兔OpenCRM: 在线报价响应
    超兔OpenCRM->>制造企业: 比价结果+一键生成采购单
    制造企业->>超兔OpenCRM: 确认采购单
    超兔OpenCRM->>供应商: 同步采购单+发货要求
    供应商->>超兔OpenCRM: 发货通知+物流跟踪
    超兔OpenCRM->>客户: 发货通知+物流查询入口
    超兔OpenCRM->>制造企业: 三流合一对账数据(单/货/款)

    participant 大型企业 as 集团企业
    participant OracleCX as Oracle CX
    participant OracleSCM as Oracle SCM Cloud
    participant 供应商B as 供应商
    大型企业->>OracleCX: 生成销售订单
    OracleCX->>OracleSCM: 同步订单+实时库存检查
    OracleSCM->>供应商B: 自动生成采购订单
    供应商B->>OracleSCM: 发货+物流状态同步
    OracleSCM->>OracleCX: 库存更新+履约状态回传
    OracleCX->>大型企业: 财务对账+开票数据同步
    • *

四、数据驱动与智能决策能力:雷达图分值对比

选取5个核心智能指标,各品牌得分(1-10分):

品牌获客智能履约效率复购精准供应链可视数据集成
超兔一体云89898
Oracle CX109101010
玄讯CRM78877
Odoo CRM78768
网易七鱼CRM96857
泛微CRM77769
YetiForce67657
Pipedrive85626
Dolibarr56546

雷达图解读

  • Oracle CX:全维度拉满,适合大型集团企业的全球化数据管控;
  • 超兔一体云:履约效率与供应链可视性得分突出,精准匹配工贸/工业企业的生产+供应链刚需;
  • 网易七鱼 CRM:获客智能能力领先,适配电商、SaaS等C端服务企业;
  • 泛微 CRM:数据集成能力突出,适合OA与业务协同需求强的企业。
    • *

五、总结与选型建议

企业类型核心需求最优选型备选方案
工贸/中小制造企业生产+供应链协同+工贸场景适配超兔一体云Odoo CRM
中大型标准化流程企业全链路管控+集团化数据集成Oracle CXOdoo CRM
快消/零售企业终端管控+复杂报价+经销商协同玄讯CRM超兔一体云
电商/C端服务企业智能获客+客户服务+复购运营网易七鱼CRMPipedrive
协同办公需求强的企业OA+业务流程一体化+跨部门协同泛微CRMOdoo CRM
小微企业/低成本需求轻量易用+基础业务覆盖DolibarrPipedrive

本次横评显示,垂直行业适配性核心场景刚需匹配是选型的核心逻辑,企业需根据自身行业属性、业务规模与数字化阶段,选择最贴合自身需求的解决方案。

Apache Gravitino Introduction.png

Apache Gravitino 概要介绍

作者: shaofeng shi
最后更新: [2025-12-29]

背景

在大数据时代,企业往往需要管理来自多云多域、异构数据源的元数据,如 Apache Hive、MySQL、PostgreSQL、Iceberg、Lance、S3、GCS 等; 此外,随着 AI 模型训练和推理的大量应用,海量的多模态数据、模型元数据等也需要一种方案进行管理。传统的做法是为每个数据源单独管理元数据,这不仅增加了运维复杂度,还容易造成数据孤岛。Apache Gravitino 作为一个高性能、支持地理分布式的联邦元数据湖,为我们提供了统一管理多源元数据的解决方案。

Gravitino 最初是由 Datastrato 公司发起并创立,在2023年开源,2024年捐赠给 Apache 孵化器,在2025年5月从 Apache 孵化器毕业,成为 Apache Top Level Project。目前已经在小米、腾讯、知乎、Uber、Pinterest 等企业落地生产环境。

什么是 Apache Gravitino?

Apache Gravitino 是一个高性能、地理分布式、联邦化的元数据湖管理系统,为用户提供统一的数据和AI资产管理平台,它能够:

  • 统一元数据管理:为不同类型的数据源提供统一的元数据模型和API
  • 直接元数据管理:直接管理底层系统,变更会实时反映到源系统
  • 多引擎支持:支持Trino、Spark、Flink等多种查询引擎
  • 地理分布式部署:支持跨区域、跨云的部署架构
  • AI资产管理:不仅管理数据资产,还支持AI/ML模型的元数据管理

核心概念包括:

  • Metalake:元数据的容器/租户,通常一个组织对应一个metalake
  • Catalog:来自特定元数据源的元数据集合
  • Schema:第二级命名空间,对应数据库中的schema概念
  • Table:最底层的对象,表示具体的数据表

Gravitino 整体架构

Apache Gravitino 核心特性概述

统一元数据管理

Gravitino 提供了一个统一的元数据管理层,支持多种数据源的集成:

支持的数据源类型:

  • 关系型数据库:MySQL、PostgreSQL、OceanBase、Apache Doris、StarRocks 等
  • 大数据存储:Apache Hive、Apache Iceberg、Apache Hudi、Apache Paimon、Delta Lake(开发中)
  • 消息队列:Apache Kafka
  • 文件系统:HDFS、S3、GCS、Azure Blob Storage、阿里云 OSS
  • AI/ML 数据格式:Lance(专为AI/ML工作负载设计的列式数据格式)

REST API 服务

Gravitino 提供了丰富的 REST API 服务,支持不同数据格式的标准化访问:

Gravitino 核心 REST API

  • 完整的元数据管理 RESTful API 接口
  • 支持 Metalake、Catalog、Schema、Table 等所有元数据对象的 CRUD 操作
  • 支持用户、组、角色和权限管理的完整 API
  • 提供标签、策略、模型等高级功能的 API 接口
  • 支持多种认证方式(Simple、OAuth2、Kerberos)

Iceberg REST 服务

  • 遵循 Apache Iceberg REST API 规范
  • 支持多种后端存储(Hive、JDBC、自定义后端)
  • 提供完整的表管理和查询能力
  • 支持多种存储系统(S3、HDFS、GCS、Azure等)

Lance REST 服务

  • 实现 Lance REST API 规范
  • 专为 AI/ML 工作负载优化
  • 支持高效的向量数据存储和检索
  • 提供命名空间和表管理功能

元数据实时获取和修改

Gravitino 采用直接元数据管理模式,确保数据的实时性和一致性:

  • 实时同步:对元数据的变更会立即反映到底层数据源
  • 双向同步:支持从 Gravitino 到数据源,以及从数据源到 Gravitino 的元数据同步
  • 事务支持:保证元数据操作的原子性和一致性
  • 版本管理:支持元数据的版本控制和历史追踪

统一访问控制

Gravitino 实现了跨多数据源的统一权限管理:

核心特性:

  • 基于角色的访问控制(RBAC):支持用户、组、角色的灵活权限管理
  • 所有权模型:每个元数据对象都有明确的所有者
  • 权限继承:支持层次化的权限继承机制
  • 细粒度控制:从 Metalake 到具体表的多层级权限控制

支持的权限类型:

  • 用户和组管理权限
  • 目录和模式创建权限
  • 表、topic、fileset的读写权限
  • 模型注册和版本管理权限
  • 标签和策略应用权限

统一数据血缘

基于 OpenLineage 标准,Gravitino 提供了完整的数据血缘追踪能力:

  • 自动血缘收集:通过 Spark 插件自动收集数据血缘信息
  • 统一标识符:将不同数据源的标识符转换为 Gravitino 统一标识符
  • 多数据源支持:支持 Hive、Iceberg、JDBC、文件系统等多种数据源的血缘追踪

高可用性和扩展性

部署模式:

  • 单机部署:适合开发和测试环境
  • 集群部署:支持高可用和负载均衡
  • Kubernetes 部署:支持容器化部署和自动扩缩容
  • Docker 支持:提供官方 Docker 镜像

存储后端:

  • 支持多种元数据存储后端(MySQL、PostgreSQL等)
  • 支持分布式存储系统

安全特性

认证方式:

  • Simple 认证(用户名/密码)
  • OAuth2 认证
  • Kerberos 认证(针对 Hive 后端)

凭证管理:

  • 支持云存储凭证代理(S3、GCS、Azure等)
  • 动态凭证刷新
  • 安全的凭证传递机制

Apache Gravitino 的集成能力

Gravitino 与主流计算引擎和数据处理框架深度集成,为用户提供统一的数据访问体验。

计算引擎集成

Apache Spark

  • 通过 Gravitino Spark Connector 实现无缝集成
  • 支持 Spark SQL 和 DataFrame API
  • 自动数据血缘收集和追踪
  • 支持多种数据源的统一访问

Trino

  • 通过 Gravitino Trino Connector 服务集成
  • 支持跨数据源的联邦查询
  • 高性能的分析查询能力

Apache Flink

  • 通过 Gravitino Flink Connector 服务集成
  • 支持流批一体化数据处理
  • 实时数据处理和分析

Python 生态集成

PyIceberg

  • 支持 Python 环境下的 Iceberg 表访问
  • 与 Gravitino Iceberg REST 服务集成
  • 支持数据科学和机器学习工作流
  • 提供 Pandas 兼容的数据接口

Daft

  • 现代化的分布式数据处理框架
  • 专为 AI/ML 工作负载优化
  • 支持多模态数据处理
  • 与 Gravitino 元数据管理集成

云原生集成

Kubernetes

  • 支持 Kubernetes 原生部署
  • 提供 Helm Charts 和 Operator
  • 支持自动扩缩容和故障恢复
  • 集成云原生监控和日志系统

API 和 SDK

REST API

  • 完整的 RESTful API 接口
  • 支持所有元数据管理操作
  • 标准化的 HTTP 接口
  • 支持多种认证方式

Java SDK

  • 原生 Java 客户端库
  • 类型安全的 API 接口
  • 支持连接池和重试机制
  • 完整的异常处理

Python SDK

  • Python 客户端库
  • 支持异步操作
  • 与 Jupyter Notebook 集成
  • 支持数据科学工作流

这些集成能力使得 Gravitino 能够无缝融入现有的数据基础设施,为用户提供统一、高效的数据管理体验。后续文章将详细介绍 Gravitino 的各项能力、各个集成组件的配置和使用方法,敬请关注。

下一步


Apache Gravitino正在快速发展中,本文基于最新版本编写。如遇到问题,建议查阅官方文档或在GitHub上提交issue。

十几年前,记得我刚做企业数字化咨询时,我总被客户问到同一个问题:“能不能在三个月内帮我们把报销、工单、库存管理全打通?”但每次我都只能苦笑。

那时候还没有很好的工具,而如果用传统编码开发就像盖砖房,从打地基到砌墙抹灰,一步都不能省,三个月也只够搭个框架。当时我就想,要是有套积木式的开发工具就好了,业务人员说要什么,我们随手搭一搭,几天就能交出能用的系统。

后来几年,市面上也陆续冒出来一些“快速开发工具”,我带着客户也试过好几款。但用下来总觉得差口气:

要么只能做些简单表单,稍微复杂点的流程就卡壳;

要么和现有ERP、CRM系统完全割裂,数据得手动导来导去;

最头疼的是,改个字段还要找技术人员,调整个审批节点要等排期,本质上还是没跳出“依赖专业开发”的怪圈。

直到这两年,尤其是2026年低代码平台集体升级后,我才真切感受到:那个“用积木搭系统”的时代,真的来了。它们不再过去是边缘型工具(只能做一些简单功能的系统),而是成为了能扛事的核心基建,真正把想法变应用的周期,从月级压缩到了周级甚至天级。

一、低代码平台定义:

(一)权威定义界定

Gartner在2025年底的报告里给过明确界定:低代码开发平台(LCAP)是通过可视化建模+少量脚本,快速搭建业务应用的工具,核心是把数据建模、流程编排、权限管控等模块做成可复用组件,让开发从“手写代码”变成“模块化装配”。

这话翻译成人话,就是把传统开发里重复的、标准化的工作都做成“现成零件”,技术人员只需补少量代码解决复杂逻辑,业务人员甚至能自己拖拽配置简单应用。

这里面,让我触动最大的是:我上周帮一家制造企业搭生产工单系统,用低代码平台把需求落地,全程只写了30行自定义脚本,这在以前是不敢想的。

(二)核心特征解析

真正靠谱的低代码平台,都逃不开三个核心特征,少一个都容易踩坑。

一是“可视化全链路”,从表单设计、流程编排到页面展示、报表生成,全程拖拽操作,业务人员盯着就能看懂,不用再靠技术人员翻译需求。

二是“高低代码融合”,这是2026年的主流趋势,既能让业务人员无代码上手,又能给技术人员留足扩展空间,比如用自定义脚本处理复杂计算,用API对接特殊系统。

三是“一键部署与版本管控”,比如支持Dev/Test/Prod多环境隔离,应用改坏了能一键回滚,避免上线后出问题没法补救,这对中大型企业来说真的特别重要。

(三)企业价值落地

低代码的价值从来不是省代码,而是“提效率、降门槛、保灵活”。

我服务过的一家装备制造客户,用低代码打通了订单需求、研发项目与生产交付全链路,以前要跨3个部门、花两周才能理顺的需求追溯,现在在系统里一点就能查全,出错率下降了70%。

对中小企业,它能快速补齐数字化短板,不用花大价钱请外包团队;

对大型企业,它能支撑高频的业务迭代,比如市场部门要做活动报名系统,当天提需求当天就能搭好上线;

对技术团队或软件外包公司,它把程序员从重复劳动里解放出来,聚焦核心业务逻辑,人效至少提升2倍。

二、企业低代码平台选型核心框架:

这十几年帮客户选型踩过无数雷,我总结出一个道理:低代码选型不是看单一功能多炫,而是看能不能适配企业的真实场景。

以下五个维度,少一个都可能导致项目失败。

(一)技术架构适配性

架构是底子,底子不稳后期必崩。我见过一家连锁企业,前期选了国内某轻量型零代码平台,门店扩张到50家后,系统直接卡顿崩溃。

因为平台不支持分布式部署,数据处理能力跟不上。

要想避免此类问题发生,我建议大家选型时重点看这三点:

一是是否支持微服务与云原生,适配企业后期扩张;

二是多环境隔离与版本管理,避免开发、测试、生产环境互相干扰;

三是移动端适配与离线能力,尤其是门店、巡检等场景,离线表单与数据同步功能必不可少。

(二)功能完整性与场景适配

不同平台有不同的特性,适配场景天差地别。比如有的平台擅长审批流程,有的擅长数据看板,有的则适配复杂业务建模。

我通常会让客户先拿一个核心场景试手,比如采购审批、工单管理,看平台能否覆盖全流程。

以采购场景为例,要能实现需求提报、供应商选择、合同审批、入库对账全链路配置,还要支持自定义校验规则,比如超过10万金额自动触发多级审批,这样才算是真正适配业务。

(三)AI融合深度

2026年的低代码平台,AI能力的评估也很重要。但也要分清“伪AI”和“真AI”。

有些平台只做了代码片段生成,顶多省点打字时间;而真正的AI融合,是贯穿开发全链路的。

我上个月试用一家企业级AI低代码平台,用自然语言说“搭建一个销售台账,自动统计每月业绩并生成报表”,AI直接生成了数据模型、表单页面和统计逻辑,我只需要微调字段名称就行。

这里面更实用的是智能调试功能,系统能自动排查流程卡点,比人工找bug快多了。对业务人员来说,这种“自然语言转应用”的能力,才是真正降低了使用门槛。

(四)生态集成与数据能力

企业数字化不是从零开始,低代码平台必须能和现有系统打通贯通,否则就是新的信息孤岛。

我帮客户选型时,一定会做集成测试:能不能对接SAP、Oracle等传统ERP,能不能和企业微信、钉钉、飞书打通推送,能不能从数据仓库拉取历史数据。

优秀的低代码平台通常有丰富的现成连接器,支持REST API、Webhooks等多种集成方式,还能实现可视化数据映射。比如把ERP里的库存数据同步到低代码工单系统,字段对应错误能自动提醒,不用技术人员逐行核对。

(五)安全合规与服务保障

对金融、政务、制造等行业,安全合规是红线。选型时要重点看:

是否支持私有化部署,满足数据本地化要求;

是否有行级、字段级权限管控,避免敏感数据泄露;

是否通过ISO27001、等保安全等资质认证,操作日志是否完整可审计。

此外,后续的服务保障也不能忽视掉。我有个客户之前就被某平台售后搞的哑口无言。平台出现了一个问题,售后三天才响应,导致客户业务停滞。

所以,这一块要擦亮眼睛,深入评估。

三、2026年主流低代码平台推荐

这大半年我实测了市面上十多款低代码平台,结合不同行业场景,筛选出三款综合能力突出、适配性强的平台,各有侧重,可按需对号入座。

(一)织信低代码平台

织信的核心优势是“中大型企业复杂场景适配”,团队核心成员来自华为、平安,对企业业务管控和系统集成的理解很到位。我们团队目前是织信低代码平台的代理商。我们也是仔细筛选评估了4个月,最终才选择的织信。

他们最吸引我们的点是:一,功能很强大,算是国内顶尖的,拓展性强,这个我跟他们团队开过一次线上会议,就已经感受到了。二,合作模式性价比很高,买断式+SaaS多租户模式,可以让我们也有自己的利润空间。

我记得去年在帮一家工程设计院选型时,也是用织信低代码打通了投标立项、客户需求、设计任务与成果交付全链路,最惊艳的是它的业务对象建模能力,能把需求、任务、成本、预算等模块深度关联,实现全流程追溯。

它支持私有化部署,满足集团型客户的数据主权需求,OpenAPI能力也很强,能轻松对接SAP、Oracle等传统系统。适配场景集中在军工、制造业、工程建筑、战略咨询、金融服务等行业,适合有复杂业务逻辑、强集成需求的中大型企业。不足是标准化模版偏少,中小企业如果没有IT人员,上手需要一定的学习成本。

(二)网易CodeWave

网易CodeWave的亮点是“AI原生与全栈智能化”,以网易自研大模型为底座,把AI能力贯穿开发、测试、运维全链路。我用它搭建运营活动管理系统时,只说“做一个带报名、核销、数据统计的活动页面”,AI就自动生成了页面布局、交互逻辑和统计报表,还能通过AI测试机器人自动排查bug,效率比传统开发提升一倍多。

它采用自研NASL语言,支持多人协作开发,在游戏、电商、金融等行业有丰富内部实践,某大型国有银行用它开发台账管理、结算管理系统,提效降本达60%。适合对AI能力要求高、追求快速迭代的互联网企业和中小企业,不足是生态连接器数量比泛微少,对接部分传统系统需要额外开发。

(三)泛微e-builder

泛微e-builder胜在“协同能力与生态成熟度”,作为老牌协同办公厂商,它天然适配企业内部协同场景,支持无代码、低代码、全代码三种构建模式,业务人员能拖拽搭建轻量应用,技术人员可通过全代码模式定制复杂系统。

它的AI融合能力很实用,上传Excel或用自然语言描述需求,就能自动生成应用,还能对接企业微信、微信,实现内部员工与外部客户、合作伙伴的实时协同。云商店有上千款成熟应用模板,覆盖87个细分行业,开箱即用,适合重协同、需要快速落地标准化场景的中大型企业,尤其是集团型组织。缺点是在极端复杂的业务建模场景,灵活性不如织信。

总结:低代码的核心是“让业务驱动技术”

十多年从业下来,我见证了低代码从小众工具到企业数字化核心基建的转变。2026年的低代码平台,早已不是“少写代码”那么简单,而是通过AI赋能、生态集成,实现了“业务人员能上手、技术人员能提效、企业能快速落地需求”的闭环。

选型时不用盲目追功能最全,而是要找准企业的核心需求:中大型企业复杂场景选织信,重协同、要标准化模板选泛微e-builder,追AI效率、快速迭代选网易CodeWave。记住,低代码的终极价值,是让技术不再成为业务的瓶颈,让每个企业都能拥有“按需搭建系统”的能力。

未来两年,随着AI与低代码的深度融合,“人人都是开发者”或许真的会成为现实。而对企业来说,提前布局适合自己的低代码平台,就是抓住数字化转型的快车道。

从零构建 GitHub Issues 集成:HagiCode 的前端直连实践

本文记录了在 HagiCode 平台中集成 GitHub Issues 的全过程。我们将探讨如何通过"前端直连 + 后端最小化"的架构,在保持后端轻量的同时,实现安全的 OAuth 认证与高效的 Issues 同步。

背景:为什么要集成 GitHub?

HagiCode 作为一个 AI 辅助开发平台,核心价值在于连接想法与实现。但在实际使用中,我们发现用户在 HagiCode 中完成了 Proposal(提案)后,往往需要手动将内容复制到 GitHub Issues 中进行项目跟踪。

这带来了几个明显的痛点:

  1. 工作流割裂:用户需要在两个系统之间来回切换,体验不仅不流畅,还容易导致关键信息在复制粘贴的过程中丢失。
  2. 协作不便:团队其他成员习惯在 GitHub 上查看任务,无法直接看到 HagiCode 中的提案进展。
  3. 重复劳动:每当提案更新,就要人工去 GitHub 更新对应的 Issue,增加不必要的维护成本。

为了解决这个问题,我们决定引入 GitHub Issues Integration 功能,打通 HagiCode 会话与 GitHub 仓库的连接,实现"一键同步"。

关于 HagiCode

嘿,介绍一下我们正在做的东西

我们正在开发 HagiCode —— 一款 AI 驱动的代码智能助手,让开发体验变得更智能、更便捷、更有趣。

智能 —— AI 全程辅助,从想法到代码,让编码效率提升数倍。便捷 —— 多线程并发操作,充分利用资源,开发流程顺畅无阻。有趣 —— 游戏化机制和成就系统,让编码不再枯燥,充满成就感。

项目正在快速迭代中,如果你对技术写作、知识管理或者 AI 辅助开发感兴趣,欢迎来 GitHub 看看~


技术选型:前端直连 vs 后端代理

在设计集成方案时,摆在我们面前的有两条路:传统的"后端代理模式"和更激进的"前端直连模式"。

方案对比

在传统的后端代理模式中,前端所有的请求都要先经过我们的后端,再由后端去调用 GitHub API。这虽然逻辑集中,但给后端带来了不小的负担:

  1. 后端臃肿:需要编写专门的 GitHub API 客户端封装,还要处理 OAuth 的复杂状态机。
  2. Token 风险:用户的 GitHub Token 必须存储在后端数据库中,虽然可以加密,但毕竟增加了安全风险面。
  3. 开发成本:需要数据库迁移来存储 Token,还需要维护一套额外的同步服务。

前端直连模式则要轻量得多。在这个方案中,我们只利用后端来处理最敏感的"密钥交换"环节(OAuth callback),获取到 Token 后,直接存在浏览器的 localStorage 里。后续创建 Issue、更新评论等操作,直接由前端发 HTTP 请求到 GitHub。

对比维度后端代理模式前端直连模式
后端复杂度需要完整的 OAuth 服务和 GitHub API 客户端仅需一个 OAuth 回调端点
Token 管理需加密存储在数据库,有泄露风险存储在浏览器,仅用户自己可见
实施成本需数据库迁移、多服务开发主要是前端工作量
用户体验逻辑统一,但服务器延迟可能稍高响应极快,直接与 GitHub 交互

考虑到我们要的是快速集成和最小化后端改动,最终我们采用了"前端直连模式"。这就像给浏览器发了一张"临时通行证",拿到证之后,浏览器就可以自己去 GitHub 办事了,不需要每次都找后端管理员批准。


核心设计:数据流与安全

在确定架构后,我们需要设计具体的数据流。整个同步流程的核心在于如何安全地获取 Token 并高效地利用它。

整体架构图

整个系统可以抽象为三个角色:浏览器(前端)、HagiCode 后端、GitHub。

+--------------+        +--------------+        +--------------+
|  前端 React  |        |    后端      |        |    GitHub    |
|              |        |   ASP.NET    |        |    REST API  |
|  +--------+  |        |              |        |              |
|  |  OAuth |--+--------> /callback    |        |              |
|  |  流程  |  |        |              |        |              |
|  +--------+  |        |              |        |              |
|              |        |              |        |              |
|  +--------+  |        |  +--------+  |        |  +--------+  |
|  |GitHub  |  +------------>Session |  +----------> Issues |  |
|  |API     |  |        |  |Metadata|  |        |  |        |  |
|  |直连    |  |        |  +--------+  |        |  +--------+  |
|  +--------+  |        |              |        |              |
+--------------+        +--------------+        +--------------+

关键点在于:只有 OAuth 的一小步(获取 code 换 token)需要经过后端,之后的粗活累活(创建 Issue)都是前端直接跟 GitHub 打交道。

同步数据流详解

当用户点击 HagiCode 界面上的"Sync to GitHub"按钮时,会发生一系列复杂的动作:

用户点击 "Sync to GitHub"
         │
         ▼
1. 前端检查 localStorage 获取 GitHub Token
         │
         ▼
2. 格式化 Issue 内容(将 Proposal 转换为 Markdown)
         │
         ▼
3. 前端直接调用 GitHub API 创建/更新 Issue
         │
         ▼
4. 调用 HagiCode 后端 API 更新 Session.metadata (存储 Issue URL 等信息)
         │
         ▼
5. 后端通过 SignalR 广播 SessionUpdated 事件
         │
         ▼
6. 前端接收事件,更新 UI 显示"已同步"状态

安全设计

安全问题始终是集成第三方服务的重中之重。我们做了以下考量:

  1. 防 CSRF 攻击:在 OAuth 跳转时,生成随机的 state 参数并存入 sessionStorage。回调时严格验证 state,防止请求被伪造。
  2. Token 存储隔离:Token 仅存储在浏览器的 localStorage 中,利用同源策略(Same-Origin Policy),只有 HagiCode 的脚本才能读取,避免了服务器端数据库泄露波及用户。
  3. 错误边界:针对 GitHub API 常见的错误(如 401 Token 过期、422 验证失败、429 速率限制),设计了专门的错误处理逻辑,给用户以友好的提示。

实践:代码实现细节

纸上得来终觉浅,咱们来看看具体的代码是怎么实现的。

1. 后端最小化改动

后端只需要做两件事:存储同步信息、处理 OAuth 回调。

数据库变更
我们只需要在 Sessions 表增加一个 Metadata 列,用来存储 JSON 格式的扩展信息。

-- 添加 metadata 列到 Sessions 表
ALTER TABLE "Sessions" ADD COLUMN "Metadata" text NULL;

实体与 DTO 定义

// src/HagiCode.DomainServices.Contracts/Entities/Session.cs
public class Session : AuditedAggregateRoot<SessionId>
{
    // ... 其他属性 ...

    /// <summary>
    /// JSON metadata for storing extension data like GitHub integration
    /// </summary>
    public string? Metadata { get; set; }
}

// DTO 定义,方便前端序列化
public class GitHubIssueMetadata
{
    public required string Owner { get; set; }
    public required string Repo { get; set; }
    public int IssueNumber { get; set; }
    public required string IssueUrl { get; set; }
    public DateTime SyncedAt { get; set; }
    public string LastSyncStatus { get; set; } = "success";
}

public class SessionMetadata
{
    public GitHubIssueMetadata? GitHubIssue { get; set; }
}

2. 前端 OAuth 流程

这是连接的入口。我们使用标准的 Authorization Code Flow。

// src/HagiCode.Client/src/services/githubOAuth.ts

// 生成授权 URL 并跳转
export async function generateAuthUrl(): Promise<string> {
  const state = generateRandomString(); // 生成防 CSRF 的随机串
  sessionStorage.setItem('hagicode_github_state', state);
  
  const params = new URLSearchParams({
    client_id: clientId,
    redirect_uri: window.location.origin + '/settings?tab=github&oauth=callback',
    scope: ['repo', 'public_repo'].join(' '),
    state: state,
  });
  
  return `https://github.com/login/oauth/authorize?${params.toString()}`;
}

// 在回调页面处理 Code 换取 Token
export async function exchangeCodeForToken(code: string, state: string): Promise<GitHubToken> {
  // 1. 验证 State 防止 CSRF
  const savedState = sessionStorage.getItem('hagicode_github_state');
  if (state !== savedState) throw new Error('Invalid state parameter');

  // 2. 调用后端 API 进行 Token 交换
  // 注意:这里必须经过后端,因为需要 ClientSecret,不能暴露在前端
  const response = await fetch('/api/GitHubOAuth/callback', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code, state, redirectUri: window.location.origin + '/settings?tab=github&oauth=callback' }),
  });

  if (!response.ok) throw new Error('Failed to exchange token');
  
  const token = await response.json();
  
  // 3. 存入 LocalStorage
  saveToken(token);
  return token;
}

3. GitHub API 客户端封装

有了 Token 之后,我们就需要一个强有力的工具来调 GitHub API。

// src/HagiCode.Client/src/services/githubApiClient.ts

const GITHUB_API_BASE = 'https://api.github.com';

// 核心请求封装
async function githubApi<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
  const token = localStorage.getItem('gh_token');
  if (!token) throw new Error('Not connected to GitHub');
  
  const response = await fetch(`${GITHUB_API_BASE}${endpoint}`, {
    ...options,
    headers: {
      ...options.headers,
      Authorization: `Bearer ${token}`,
      Accept: 'application/vnd.github.v3+json', // 指定 API 版本
    },
  });
  
  // 错误处理逻辑
  if (!response.ok) {
    if (response.status === 401) throw new Error('GitHub Token 失效,请重新连接');
    if (response.status === 403) throw new Error('无权访问该仓库或超出速率限制');
    if (response.status === 422) throw new Error('Issue 验证失败,可能标题重复');
    throw new Error(`GitHub API Error: ${response.statusText}`);
  }
  
  return response.json();
}

// 创建 Issue
export async function createIssue(owner: string, repo: string, data: { title: string, body: string, labels: string[] }) {
  return githubApi(`/repos/${owner}/${repo}/issues`, {
    method: 'POST',
    body: JSON.stringify(data),
  });
}

4. 内容格式化与同步

最后一步,就是把 HagiCode 的 Session 数据转换成 GitHub Issue 的格式。这有点像"翻译"工作。

// 将 Session 对象转换为 Markdown 字符串
function formatIssueForSession(session: Session): string {
  let content = `# ${session.title}\n\n`;
  content += `**> HagiCode Session:** #${session.code}\n`;
  content += `**> Status:** ${session.status}\n\n`;
  content += `## Description\n\n${session.description || 'No description provided.'}\n\n`;
  
  // 如果是 Proposal 类型,添加额外字段
  if (session.type === 'proposal') {
    content += `## Chief Complaint\n\n${session.chiefComplaint || ''}\n\n`;
    // 添加一个深链接,方便从 GitHub 跳回 HagiCode
    content += `---\n\n**[View in HagiCode](hagicode://sessions/${session.id})**\n`;
  }
  
  return content;
}

// 点击同步按钮的主逻辑
const handleSync = async (session: Session) => {
  try {
    const repoInfo = parseRepositoryFromUrl(session.repoUrl); // 解析仓库 URL
    if (!repoInfo) throw new Error('Invalid repository URL');

    toast.loading('正在同步到 GitHub...');
    
    // 1. 格式化内容
    const issueBody = formatIssueForSession(session);
    
    // 2. 调用 API
    const issue = await githubApiClient.createIssue(repoInfo.owner, repoInfo.repo, {
      title: `[HagiCode] ${session.title}`,
      body: issueBody,
      labels: ['hagicode', 'proposal', `status:${session.status}`],
    });
    
    // 3. 更新 Session Metadata (保存 Issue 链接)
    await SessionsService.patchApiSessionsSessionId(session.id, {
      metadata: {
        githubIssue: {
          owner: repoInfo.owner,
          repo: repoInfo.repo,
          issueNumber: issue.number,
          issueUrl: issue.html_url,
          syncedAt: new Date().toISOString(),
        }
      }
    });

    toast.success('同步成功!');
  } catch (err) {
    console.error(err);
    toast.error('同步失败,请检查 Token 或网络');
  }
};

总结与展望

通过这套"前端直连"方案,我们用最少的后端代码实现了 GitHub Issues 的无缝集成。

收获

  1. 开发效率高:后端改动极小,主要是数据库加一个字段和一个简单的 OAuth 回调接口,大部分逻辑都在前端完成。
  2. 安全性好:Token 不经过服务器数据库,降低了泄露风险。
  3. 用户体验佳:直接从前端发起请求,响应速度快,不需要经过后端中转。

注意事项

在实际部署时,有几个坑大家要注意:

  • OAuth App 设置:记得在 GitHub OAuth App 设置里填正确的 Authorization callback URL(通常是 http://localhost:3000/settings?tab=github&oauth=callback)。
  • 速率限制:GitHub API 对未认证请求限制较严,但用 Token 后通常足够(5000次/小时)。
  • URL 解析:用户输入的 Repo URL 千奇百怪,记得正则要匹配 .git 后缀、SSH 格式等情况。

后续增强

目前的功能还是单向同步(HagiCode -> GitHub)。未来我们计划通过 GitHub Webhooks 实现双向同步,比如在 GitHub 里关闭 Issue,HagiCode 这边的会话状态也能自动更新。这需要我们在后端暴露一个 Webhook 接收端点,这也是下一步要做的有趣工作。

希望这篇文章能给你的第三方集成开发带来一点灵感!如果有问题,欢迎在 HagiCode GitHub 上提 Issue 讨论。

写这篇文章的原因主要还是因为V2EX上的这个贴子,这个贴子中说——

“对接同事的接口,他定义的所有接口都是 post 请求,理由是 https 用 post 更安全,之前习惯使用 restful api ,如果说 https 只有 post 请求是安全的话?那为啥还需要 get 、put 、delete ?我该如何反驳他。”

然后该贴中大量的回复大概有这么几种论调,1)POST挺好的,就应该这么干,沟通少,2)一把梭,早点干完早点回家,3)吵赢了又怎么样?工作而已,优雅不能当饭吃。虽然评论没有一边倒,但是也有大量的人支持。然后,我在Twitter上嘲讽了一下,用POST干一切就像看到了来你家装修工人说,“老子干活就是用钉子钉一切,什么螺丝、螺栓、卡扣、插销……通通不用,钉枪一把梭,方便,快捷,安全,干完早回家……不过,还是有一些网友觉得用POST挺好的,而且可以节约时间。所以,正好,我在《我做系统架构的原则》中的“原则五”中反对API返回码无论对错全是200的返回那,我专门写下这一篇文章,以正视听。

这篇文章主要分成下面这几个部分:

  1. 为什么要用不同的HTTP动词?
  2. Restful 进行复杂查询
  3. 几个主要问题的回应
    • POST 更安全吗?
    • 全用 POST 可以节省时间沟通少吗?
    • 早点回家的正确姿势
    • 工作而已,优雅不能当饭吃

目录

为什么要用不同的HTTP动词

编程世界通常来说有两种逻辑:“业务逻辑” 和 “控制逻辑”。

  • 业务逻辑。就是你实现业务需求的功能的代码,就是跟用户需求强相关的代码。比如,把用户提交的数据保存起来,查询用户的数据,完成一个订单交易,为用户退款……等等,这些是业务逻辑
  • 控制逻辑。就是我们用于控制程序运行的非功能性的代码。比如,用于控制程序循环的变量和条件,使用多线程或分布式的技术,使用HTTP/TCP协议,使用什么样数据库,什么样的中间件……等等,这些跟用户需求完全没关系的东西。

网络协议也是一样的,一般来说,几乎所有的主流网络协议都有两个部分,一个是协议头,一个是协议体。协议头中是协议自己要用的数据,协议体才是用户的数据。所以,协议头主要是用于协议的控制逻辑,而协议体则是业务逻辑。

HTTP的动词(或是Method)是在协议头中,所以,其主要用于控制逻辑。

下面是HTTP的动词规范,一般来说,REST API 需要开发人员严格遵循下面的标准规范(参看RFC7231 章节4.2.2 – Idempotent Methods

方法 描述 幂等
GET 用于查询操作,对应于数据库的 select 操作 ✔︎
PUT 用于所有的信息更新,对应于数据库的 update 操作 ✔︎︎
DELETE 用于更新操作,对应于数据库的 delete 操作 ✔︎︎
POST 用于新增操作,对应于数据库的 insert 操作
HEAD 用于返回一个资源对象的“元数据”,或是用于探测API是否健康 ✔︎
PATCH 用于局部信息的更新,对应于数据库的 update 操作
OPTIONS 获取API的相关的信息。 ✔︎

其中,PUT 和 PACTH 都是更新业务资源信息,如果资源对象不存在则可以新建一个,但他们两者的区别是,PUT 用于更新一个业务对象的所有完整信息,就像是我们通过表单提交所有的数据,而 PACTH 则对更为API化的数据更新操作,只需要更需要更新的字段(参看 RFC 5789 )。

当然,现实世界中,可能并不一定严格地按照数据库操作的CRUD来理解API,比如,你有一个登录的API /login 你觉得这个API应该是 GETPOSTPUT 还是 PATCH ?登录的时候用户需要输入用户名和密码,然后跟数据库里的对比(select操作)后反回一个登录的session token,然后这个token作为用户登录的状态令牌。如果按上面表格来说,应该是 select 操作进行 GET ,但是从语义上来说,登录并不是查询信息,应该是用户状态的更新或是新增操作(新增session),所以还是应该使用 POST,而 /logout 你可以使用 DELETE这里相说明一下,不要机械地通过数据库的CRUD来对应这些动词,很多时候,还是要分析一下业务语义。

另外,我们注意到,在这个表格的最后一列中加入了“是否幂等”的,API的幂等对于控制逻辑来说是一件很重要的事。所谓幂等,就是该API执行多次和执行一次的结果是完全一样的,没有副作用。

  • POST 用于新增加数据,比如,新增一个交易订单,这肯定不能是幂等的
  • DELETE 用于删除数据,一个数据删除多次和删除一次的结果是一样的,所以,是幂等的
  • PUT 用于全部数更新,所以,是幂等的。
  • PATCH用于局部更新,比如,更新某个字段 cnt = cnt+1,明显不可能是幂等操作。

幂等这个特性对于远程调用是一件非常关键的事,就是说,远程调用有很多时候会因为网络原因导致调用timeout,对于timeout的请求,我们是无法知道服务端是否已经是收到请求并执行了,此时,我们不能贸然重试请求,对于不是幂等的调用来说,这会是灾难性的。比如像转帐这样的业务逻辑,转一次和转多次结果是不一样的,如果重新的话有可能就会多转了一次。所以,这个时候,如果你的API遵从了HTTP动词的规范,那么你写起程序来就可以明白在哪些动词下可以重试,而在哪些动词下不能重试。如果你把所有的API都用POST来表达的话,就完全失控了。

除了幂等这样的控制逻辑之外,你可能还会有如下的这些控制逻辑的需求:

  • 缓存。通过CDN或是网关对API进行缓存,很显然,我们要在查询GET 操作上建议缓存。
  • 流控。你可以通过HTTP的动词进行更粒度的流控,比如:限制API的请用频率,在读操作上和写操作上应该是不一样的。
  • 路由。比如:写请求路由到写服务上,读请求路由到读服务上。
  • 权限。可以获得更细粒度的权限控制和审计。
  • 监控。因为不同的方法的API的性能都不一样,所以,可以区分做性能分析。
  • 压测。当你需要压力测试API时,如果没有动词的区分的话,我相信你的压力测试很难搞吧。
  • ……等等

也许,你会说,我的业务太简单了,没有必要搞这么复杂。OK,没有问题,但是我觉得你最差的情况下,也是需要做到“读写分离”的,就是说,至少要有两个动词,GET 表示是读操作,POST表示是写操作。

Restful 复杂查询

一般来说,对于查询类的API,主要就是要完成四种操作:排序,过滤,搜索,分页。下面是一些相关的规范。参考于两个我觉得写的最好的Restful API的规范文档,Microsoft REST API GuidelinesPaypal API Design Guidelines

  • 排序。对于结果集的排序,使用 sort 关键字,以及 {field_name}|{asc|desc},{field_name}|{asc|desc} 的相关语法。比如,某API需要返回公司的列表,并按照某些字段排序,如:GET /admin/companies?sort=rank|asc 或是 GET /admin/companies?sort=rank|asc,zip_code|desc

  • 过滤。对于结果集的过滤,使用 filter 关键字,以及 {field_name} op{value} 的语法。比如: GET /companies?category=banking&location=china 。但是,有些时候,我们需要更为灵活的表达式,我们就需要在URL上构造我们的表达式。这里需要定义六个比较操作:=<><=>=,以及三个逻辑操作:andornot。(表达式中的一些特殊字符需要做一定的转义,比如:>= 转成 ge)于是,我们就会有如下的查询表达式:GET /products?$filter=name eq 'Milk' and price lt 2.55 查找所有的价柗小于2.55的牛奶。

  • 搜索。对于相关的搜索,使用 search 关键字,以及关键词。如:GET /books/search?description=algorithm 或是直接就是全文搜索 GET /books/search?key=algorithm

  • 分页。对于结果集进行分页处理,分页必需是一个默认行为,这样不会产生大量的返回数据。


    • 使用pageper_page代表页码和每页数据量,比如:GET /books?page=3&per_page=20
    • 可选。上面提到的page方式为使用相对位置来获取数据,可能会存在两个问题:性能(大数据量)与数据偏差(高频更新)。此时可以使用绝对位置来获取数据:事先记录下当前已获取数据里最后一条数据的ID时间等信息,以此获取 “该ID之前的数据” 或 “该时刻之前的数据”。示例:GET /news?max_id=23454345&per_page=20 或 GET /news?published_before=2011-01-01T00:00:00Z&per_page=20

注意:这里需要注意一下,在理论上来说GET是可以带 body 的,但是很多程序的类库或是中间件并不支持 GET 带 body,导致你只能用 POST 来传递参数。这里的原则是:

  1. 对于简单的查询,很多参数都设计在 restful API 的路径上了,而 filter/sort/pagination 也不会带来很多的复杂,所以应该使用 GET 

  2. 对于复杂的查询来说,可能会有很复杂的查询参数,比如:ElasticSearch 上的 index/_search里的 DSL,你也应该尽可能的使用 GET,而不是POST 除非客观条件上不支持GET。ElasticSearch 的官方文档里也是这么说的。

The authors of Elasticsearch prefer using GET for a search request because they feel that it describes the action—​retrieving information—​better than the POST verb. (我们推荐使用 GET而不是 POST,因为语义更清楚)However, because GET with a request body is not universally supported, the search API also accepts POST requests (除非你的类库或是服务器不支持 GET带参数 ,你再用POST,我们两个都支持)

陈皓注:但是在 ElasticSearch 7.11 后,GET 也不支持 body 了。这是 ElasticSearch 的设计和实现不对应了。

另外,对于一些更为复杂的操作,建议通过分别调用多个API的方式来完成,虽然这样会增加网络请求的次数,但是这样的可以让后端程序和数据耦合度更小,更容易成为微服务的架构。

最后,如果你想在Rest中使用像GraphQL那样的查询语言,你可以考虑一下类似 OData 的解决方案。OData 是 Open Data Protocol 的缩写,最初由 Microsoft 于 2007 年开发。它是一种开放协议,使您能够以简单和标准的方式创建和使用可查询和可互操作的 RESTful API。

几个主要问题的回应

下面是对几个问题的直接回应,如果大家需要我回应更多的问题,可以在后面留言,我会把问题和我的回应添加到下面。

1)为什么API 要Restful,并符合规范?

Restful API算是一个HTTP的规范和标准了,你要说是最佳实践也好,总之,它是一个全世界对HTTP API的一个共识。在这个共识上,你可以无成本地享受很多的技术红利,比如:CDN,API网关,服务治理,监控……等等。这些都是可以让你大幅度降低研发成本,避免踩坑的原因。

2)为什么“过早优化”不适用于API设计?

因为API是一种契约,一旦被使用上,就很难再变更了,就算你发行新的版本的API,你还要驱动各种调用方升级他们的调用方式。所以,接口设计就像数据库模式设计一下,一旦设计好了,未来再变更就比较难了。所以,还是要好好设计。正如前面我给的几个文档——Microsoft REST API GuidelinesPaypal API Design Guidelines 或是 Google API Design Guide 都是让你好好设计API的不错的 Guidelines.

3)POST 更安全吗?

不会。

很多同学以为 GET 的请求数据在URL中,而 POST 的则不是,所以以为 POST 更安全。不是这样的,整个请求的HTTP URL PATH会全部封装在HTTP的协议头中。只要是HTTPS,就是安全的。当然,有些网关如nginx会把URL打到日志中,或是会放在浏览器的历史记录中,所以有人会说 GET 请求不安全,但是,POST 也没有好到哪里去,在 CSRF 这个最常见的安全问题上,则完全就是针对 POST 的。  安全是一件很复杂的事,无论你用哪方法或动词都会不能代表你会更安全。

另外,

  • 如果你要 防止你的 GET 上有敏感信息,应该加个密,这个跟 POST是一样的。
  • 如果你要防止 GET 会被中间人修改,你应该做一个URL签名。(通常来说, 我们都在 GET 上做签名,POST 就忘做了)
  • 如果你要防止有人发一些恶意链接来 hack 你的用户(传说中的 GET 不如 POST 安全的一个问题),你应该用 HMAC 之类的认证技术做好认证(参看 HTTP API 认证授权术)。

总之,你要明白,GETPOST 的安全问题都一样的,不要有谁比谁更安全,然后你就可以掉以轻心的这样的想法,安全都是要很严肃对待的。

4)全用 POST 可以节省时间减少沟通吗?

不但不会,反而更糟糕。

说这种话的人,我感觉是不会思考问题。

  • 其一,为API赋于不同的动词,这个几乎不需要时间。把CRUD写在不同的函数下也是一种很好的编程风格。另外现在几乎所有的开发框架都支持很快速的CRUD的开发,比如Spring Boot,写数据库的CRUD基本上就不需要写SQL语言相关的查询代码,非常之方便。
  • 其二,使用规范的方式,可以节约新加入团队人员的学习成本,而且可以大大减少跨团队的沟能成本。规范和标准其实就是在节约团队时间提升整体效率的,这个我们整个人类进行协作的基础。所以,这个世界上有很多的标准,你只要照着这个标准来,你的所生产的零件就可以适配到其它厂商的产品上。而不需要相互沟通。
  • 其三,全用POST接口一把梭,不规范不标准,使用你的这个山寨API的人就得来不断的问你,反而增加了沟通。另外,也许你开发业务功能很快了,但是你在做控制逻辑的时候,你就要返工了,从长期上来讲,你的欠下了技术债,这个债反而导致了更大的成本。
5)早点回家的正确姿势

不要以为你回家早就没事了,如果你的代码有这样那样的问题,别人看懂,或是出误用了你的代码出了问题,那么,你早回家有什么意义呢?你一样要被打扰,甚至被叫到公司来处理问题。所以,你应该做的是为了“长期的早回家”,而不是“短期的早回家”,要像长期的早回家,通常来说是这样的:

  • 把代码组织设计好,有更好的扩展性。这样在面对新需求的时候,你就可以做到少改代码,甚至不改代码。这样你才可能早回家。不然,每次需求一来,你得重新写,你怎么可能早回家?
  • 你的代码质量是不错的,有不错的文档和注释。所以,别人不会老有问题来找你,或是你下班后,叫你来处理问题。甚至任何人都可以很容易地接手你的代码,这样你才可能真正不被打扰
6)工作而已,优雅不能当饭吃

回应两点:

其一,遵循个规范而已,把“正常”叫“优雅”,可见标准有多低。这么低的标准也只能“为了吃饭而生存了”。

其二,作为一个“职业程序员”,要学会热爱和尊重自己的职业,热爱自己职业最重要的就是不要让外行人看扁这个职业,自己都不尊重这个职业,你让别人怎么尊重?尊重自己的职业,不仅仅只是能够获得让人羡慕的报酬,而更是要让自己的这个职业的更有含金量

希望大家都能尊重自己从事的这个职业,成为真正的职业化的程序员,而不是一个码农!

你的工作给你权力,而只有你的行为才会给你尊重

每次在 Apache SeaTunnel 里配置非关系型数据库,看着那几百行还要手动定义的字段映射,是不是挺崩溃的?配置错一个字段,任务就报错,这种“体力活”真的该结束了。

最近 Apache SeaTunnel 社区的 Issue #10339 提案捅破了这层窗户纸:既然有 Apache Gravitino 这么强大的元数据服务,为什么不直接让它自动同步 Schema?这个提议一出,社区反响热烈,核心维护者们已经把它列入了年度 RoadMap。目前的讨论很务实,大家正盯着怎么让 Apache SeaTunnel 在提交作业时自动‘抓取’最新的元数据,好让大家彻底告别那种‘对着数据库手敲配置’的原始生活。

🫱 Issue 链接: https://github.com/apache/seatunnel/issues/10339

Issue 概述

先来看看提交这个 Issue 的作者是为什么想到这个点子的,以及他初步的核心设计概念。🔽

本 PR 实现了 Apache Gravitino 与 SeaTunnel 的集成,将其作为非关系型连接器的外部元数据服务。通过 Gravitino 的 REST API 自动获取表结构和元数据,SeaTunnel 用户无需再在连接器配置中手动定义冗长且复杂的 Schema 映射。

背景

目前,Apache SeaTunnel 中的许多非关系型连接器(如 Elasticsearch、向量数据库和数据湖引擎)要求用户在作业配置中显式定义完整的列 Schema。这导致了以下问题:

  • 配置繁琐且易错:字段映射内容冗长,极易发生人为错误。
  • 架构冗余:不同作业之间存在大量重复的 Schema 定义。
  • 数据不一致风险:实际存储层与 SeaTunnel 配置文件之间容易出现架构脱节。

变更内容

本 PR 增加了基于 Gravitino 的 Catalog 和 Schema 解析器,使 SeaTunnel 能够:

  • 通过 REST API 从 Gravitino 查询表定义。
  • 自动获取列名、数据类型及相关属性。
  • 直接根据 Gravitino 元数据构建 SeaTunnel 内部 Schema。
  • 针对受支持的连接器,取消强制手动定义 schema { fields { ... } } 的要求。

实现后,用户只需在作业配置中指定 Gravitino Catalog 和相关的表引用即可。

核心优势

  • 零手动映射:非关系型数据源实现 Schema 自动对齐。
  • 单一事实来源:确保表结构与中心化元数据仓库保持高度一致。
  • 提升可靠性:显著提高配置的准确性,降低长期维护成本。
  • 支持复杂类型:通过统一元数据,简化了对嵌套结构、JSON、向量等高级类型的处理。

执行范围

所有基于 Gravitino 的 Schema 解析和校验均在 SeaTunnel Engine 客户端完成(即在作业提交前)。这种设计确保了:

  • 在作业预检阶段即可发现无效或不兼容的 Schema。
  • 运行时的任务仅接收经过验证和标准化的 Schema,降低了执行失败的概率。

影响

这一更新极大地简化了非关系型连接器的作业设置。除了提升易用性,它还为整个 SeaTunnel 生态系统在统一架构管理、架构演进以及高级数据类型支持方面奠定了技术框架。

核心思路

针对 FTP、S3、ES、MongoDB 等半结构化与非结构化数据源,SeaTunnel 现支持通过 Gravitino REST API 自动解析表结构(Schema)。

需要注意的是,这并非要取代现有的显式配置,而是一项完全向前兼容的可选新机制

解析优先级如下:

1. 显式配置(Inline Schema)永远优先

只要连接器配置中包含了 schema 代码块,SeaTunnel 就必须忽略 Gravitino,直接以显式定义的 Schema 为准。

FtpFile {
  path = "/tmp/seatunnel/sink/text"
  # ... 其他基础配置 ...
  
  # 只要这里定义了,就不会去查 Gravitino
  schema = {
    name = string
    age  = int
  }
}

2. 通过 env 全局配置 Gravitino(推荐模式)

SeaTunnel 已在引擎层面集成了 Gravitino Metalake。
env 中全局开启后,所有非关系型数据源都能直接通过名称引用 Schema。

env {
  metalake_enabled = true
  metalake_type    = "gravitino"
  metalake_url     = "http://localhost:8090/api/metalakes/metalake_name/catalogs/"
}

2.1 使用 schema_path 引用

FtpFile {
  # ... 基础配置 ...
  schema_path = "catalog_name.ykw.test_table"
}

2.2 使用 schema_url 引用

FtpFile {
  # ... 基础配置 ...
  schema_url = "http://localhost:8090/api/metalakes/laowang_test/.../tables/all_type"
}

3. 兜底逻辑:读取操作系统环境变量

如果在作业的 env 块中没有定义 Gravitino,SeaTunnel 会尝试从操作系统环境变量中读取以下配置:
metalake_enabled | metalake_type | metalake_url
其行为逻辑与第 2 节中的 env 配置完全一致。

4. 在连接器层级单独配置 Gravitino

如果全局没有配置元数据中心,也可以在具体的连接器(Connector)内部直接定义 Gravitino。

4.1 直接使用 schema_url

FtpFile {
  # ... 基础配置 ...
  metalake_type = "gravitino"
  schema_url = "http://localhost:8090/api/.../tables/all_type"
}

4.2 组合使用 metalake_url 与 schema_path

FtpFile {
  # ... 基础配置 ...
  metalake_type = "gravitino"
  metalake_url  = "http://localhost:8090/api/metalakes/metalake_name/catalogs/"
  schema_path   = "catalog_name.ykw.test_table"
}

5. 探测器定位 (Find detector)

系统会根据 metalake_type 自动匹配并加载对应的 REST API HTTP 探测器。

6. 映射与构建 CatalogTable

探测器调用拼接好的 URL 获取响应体(ResponseBody),随后将其交给映射器(Mapper)进行类型匹配,最终完成 CatalogTable 的构建。

7. 流程图如下

Issue 进展

目前,Apache SeaTunnel 项目核心贡献者对此提议给出了正面评价,并将其添加到 Apache SeaTunnel Roadmap 中。

Apache SeaTunnel PMC Member 对这个提议提出一些疑问,比如这种集成属于哪一层级,对多引擎兼容性的考量,类型转换的准确性等,并根据社区设计规范,要求发起者提交一份正式的设计文档(Design Document)。提交者的回复非常具有建设性,他通过 “客户端预处理”和“抽象 Catalog 接口” 这两个核心设计点,有效地回应了社区对于系统耦合度和运行稳定性的担忧。

目前,这个讨论的回到了该 Issue 的提交者手中,社区正在等待他提交那份正式的 Design Document。

可以看到,这个方案要是落地,咱以后写任务可能就一两行配置的事儿。目前设计稿正在打磨中,非常需要大家去评论区吐吐槽、提提建议,毕竟这个功能好不好用,咱们一线开发者最清楚。走,去 GitHub 围观一下,说不定你的一个提议就能决定下一个版本的样子!🔽
https://github.com/apache/seatunnel/issues/10339

写这篇文章的原因主要还是因为V2EX上的这个贴子,这个贴子中说——

“对接同事的接口,他定义的所有接口都是 post 请求,理由是 https 用 post 更安全,之前习惯使用 restful api ,如果说 https 只有 post 请求是安全的话?那为啥还需要 get 、put 、delete ?我该如何反驳他。”

然后该贴中大量的回复大概有这么几种论调,1)POST挺好的,就应该这么干,沟通少,2)一把梭,早点干完早点回家,3)吵赢了又怎么样?工作而已,优雅不能当饭吃。虽然评论没有一边倒,但是也有大量的人支持。然后,我在Twitter上嘲讽了一下,用POST干一切就像看到了来你家装修工人说,“老子干活就是用钉子钉一切,什么螺丝、螺栓、卡扣、插销……通通不用,钉枪一把梭,方便,快捷,安全,干完早回家……不过,还是有一些网友觉得用POST挺好的,而且可以节约时间。所以,正好,我在《我做系统架构的原则》中的“原则五”中反对API返回码无论对错全是200的返回那,我专门写下这一篇文章,以正视听。

这篇文章主要分成下面这几个部分:

  1. 为什么要用不同的HTTP动词?
  2. Restful 进行复杂查询
  3. 几个主要问题的回应
    • POST 更安全吗?
    • 全用 POST 可以节省时间沟通少吗?
    • 早点回家的正确姿势
    • 工作而已,优雅不能当饭吃

目录

为什么要用不同的HTTP动词

编程世界通常来说有两种逻辑:“业务逻辑” 和 “控制逻辑”。

  • 业务逻辑。就是你实现业务需求的功能的代码,就是跟用户需求强相关的代码。比如,把用户提交的数据保存起来,查询用户的数据,完成一个订单交易,为用户退款……等等,这些是业务逻辑
  • 控制逻辑。就是我们用于控制程序运行的非功能性的代码。比如,用于控制程序循环的变量和条件,使用多线程或分布式的技术,使用HTTP/TCP协议,使用什么样数据库,什么样的中间件……等等,这些跟用户需求完全没关系的东西。

网络协议也是一样的,一般来说,几乎所有的主流网络协议都有两个部分,一个是协议头,一个是协议体。协议头中是协议自己要用的数据,协议体才是用户的数据。所以,协议头主要是用于协议的控制逻辑,而协议体则是业务逻辑。

HTTP的动词(或是Method)是在协议头中,所以,其主要用于控制逻辑。

下面是HTTP的动词规范,一般来说,REST API 需要开发人员严格遵循下面的标准规范(参看RFC7231 章节4.2.2 – Idempotent Methods

方法 描述 幂等
GET 用于查询操作,对应于数据库的 select 操作 ✔︎
PUT 用于所有的信息更新,对应于数据库的 update 操作 ✔︎︎
DELETE 用于更新操作,对应于数据库的 delete 操作 ✔︎︎
POST 用于新增操作,对应于数据库的 insert 操作
HEAD 用于返回一个资源对象的“元数据”,或是用于探测API是否健康 ✔︎
PATCH 用于局部信息的更新,对应于数据库的 update 操作
OPTIONS 获取API的相关的信息。 ✔︎

其中,PUT 和 PACTH 都是更新业务资源信息,如果资源对象不存在则可以新建一个,但他们两者的区别是,PUT 用于更新一个业务对象的所有完整信息,就像是我们通过表单提交所有的数据,而 PACTH 则对更为API化的数据更新操作,只需要更需要更新的字段(参看 RFC 5789 )。

当然,现实世界中,可能并不一定严格地按照数据库操作的CRUD来理解API,比如,你有一个登录的API /login 你觉得这个API应该是 GETPOSTPUT 还是 PATCH ?登录的时候用户需要输入用户名和密码,然后跟数据库里的对比(select操作)后反回一个登录的session token,然后这个token作为用户登录的状态令牌。如果按上面表格来说,应该是 select 操作进行 GET ,但是从语义上来说,登录并不是查询信息,应该是用户状态的更新或是新增操作(新增session),所以还是应该使用 POST,而 /logout 你可以使用 DELETE这里相说明一下,不要机械地通过数据库的CRUD来对应这些动词,很多时候,还是要分析一下业务语义。

另外,我们注意到,在这个表格的最后一列中加入了“是否幂等”的,API的幂等对于控制逻辑来说是一件很重要的事。所谓幂等,就是该API执行多次和执行一次的结果是完全一样的,没有副作用。

  • POST 用于新增加数据,比如,新增一个交易订单,这肯定不能是幂等的
  • DELETE 用于删除数据,一个数据删除多次和删除一次的结果是一样的,所以,是幂等的
  • PUT 用于全部数更新,所以,是幂等的。
  • PATCH用于局部更新,比如,更新某个字段 cnt = cnt+1,明显不可能是幂等操作。

幂等这个特性对于远程调用是一件非常关键的事,就是说,远程调用有很多时候会因为网络原因导致调用timeout,对于timeout的请求,我们是无法知道服务端是否已经是收到请求并执行了,此时,我们不能贸然重试请求,对于不是幂等的调用来说,这会是灾难性的。比如像转帐这样的业务逻辑,转一次和转多次结果是不一样的,如果重新的话有可能就会多转了一次。所以,这个时候,如果你的API遵从了HTTP动词的规范,那么你写起程序来就可以明白在哪些动词下可以重试,而在哪些动词下不能重试。如果你把所有的API都用POST来表达的话,就完全失控了。

除了幂等这样的控制逻辑之外,你可能还会有如下的这些控制逻辑的需求:

  • 缓存。通过CDN或是网关对API进行缓存,很显然,我们要在查询GET 操作上建议缓存。
  • 流控。你可以通过HTTP的动词进行更粒度的流控,比如:限制API的请用频率,在读操作上和写操作上应该是不一样的。
  • 路由。比如:写请求路由到写服务上,读请求路由到读服务上。
  • 权限。可以获得更细粒度的权限控制和审计。
  • 监控。因为不同的方法的API的性能都不一样,所以,可以区分做性能分析。
  • 压测。当你需要压力测试API时,如果没有动词的区分的话,我相信你的压力测试很难搞吧。
  • ……等等

也许,你会说,我的业务太简单了,没有必要搞这么复杂。OK,没有问题,但是我觉得你最差的情况下,也是需要做到“读写分离”的,就是说,至少要有两个动词,GET 表示是读操作,POST表示是写操作。

Restful 复杂查询

一般来说,对于查询类的API,主要就是要完成四种操作:排序,过滤,搜索,分页。下面是一些相关的规范。参考于两个我觉得写的最好的Restful API的规范文档,Microsoft REST API GuidelinesPaypal API Design Guidelines

  • 排序。对于结果集的排序,使用 sort 关键字,以及 {field_name}|{asc|desc},{field_name}|{asc|desc} 的相关语法。比如,某API需要返回公司的列表,并按照某些字段排序,如:GET /admin/companies?sort=rank|asc 或是 GET /admin/companies?sort=rank|asc,zip_code|desc

  • 过滤。对于结果集的过滤,使用 filter 关键字,以及 {field_name} op{value} 的语法。比如: GET /companies?category=banking&location=china 。但是,有些时候,我们需要更为灵活的表达式,我们就需要在URL上构造我们的表达式。这里需要定义六个比较操作:=<><=>=,以及三个逻辑操作:andornot。(表达式中的一些特殊字符需要做一定的转义,比如:>= 转成 ge)于是,我们就会有如下的查询表达式:GET /products?$filter=name eq 'Milk' and price lt 2.55 查找所有的价柗小于2.55的牛奶。

  • 搜索。对于相关的搜索,使用 search 关键字,以及关键词。如:GET /books/search?description=algorithm 或是直接就是全文搜索 GET /books/search?key=algorithm

  • 分页。对于结果集进行分页处理,分页必需是一个默认行为,这样不会产生大量的返回数据。


    • 使用pageper_page代表页码和每页数据量,比如:GET /books?page=3&per_page=20
    • 可选。上面提到的page方式为使用相对位置来获取数据,可能会存在两个问题:性能(大数据量)与数据偏差(高频更新)。此时可以使用绝对位置来获取数据:事先记录下当前已获取数据里最后一条数据的ID时间等信息,以此获取 “该ID之前的数据” 或 “该时刻之前的数据”。示例:GET /news?max_id=23454345&per_page=20 或 GET /news?published_before=2011-01-01T00:00:00Z&per_page=20

注意:这里需要注意一下,在理论上来说GET是可以带 body 的,但是很多程序的类库或是中间件并不支持 GET 带 body,导致你只能用 POST 来传递参数。这里的原则是:

  1. 对于简单的查询,很多参数都设计在 restful API 的路径上了,而 filter/sort/pagination 也不会带来很多的复杂,所以应该使用 GET 

  2. 对于复杂的查询来说,可能会有很复杂的查询参数,比如:ElasticSearch 上的 index/_search里的 DSL,你也应该尽可能的使用 GET,而不是POST 除非客观条件上不支持GET。ElasticSearch 的官方文档里也是这么说的。

The authors of Elasticsearch prefer using GET for a search request because they feel that it describes the action—​retrieving information—​better than the POST verb. (我们推荐使用 GET而不是 POST,因为语义更清楚)However, because GET with a request body is not universally supported, the search API also accepts POST requests (除非你的类库或是服务器不支持 GET带参数 ,你再用POST,我们两个都支持)

陈皓注:但是在 ElasticSearch 7.11 后,GET 也不支持 body 了。这是 ElasticSearch 的设计和实现不对应了。

另外,对于一些更为复杂的操作,建议通过分别调用多个API的方式来完成,虽然这样会增加网络请求的次数,但是这样的可以让后端程序和数据耦合度更小,更容易成为微服务的架构。

最后,如果你想在Rest中使用像GraphQL那样的查询语言,你可以考虑一下类似 OData 的解决方案。OData 是 Open Data Protocol 的缩写,最初由 Microsoft 于 2007 年开发。它是一种开放协议,使您能够以简单和标准的方式创建和使用可查询和可互操作的 RESTful API。

几个主要问题的回应

下面是对几个问题的直接回应,如果大家需要我回应更多的问题,可以在后面留言,我会把问题和我的回应添加到下面。

1)为什么API 要Restful,并符合规范?

Restful API算是一个HTTP的规范和标准了,你要说是最佳实践也好,总之,它是一个全世界对HTTP API的一个共识。在这个共识上,你可以无成本地享受很多的技术红利,比如:CDN,API网关,服务治理,监控……等等。这些都是可以让你大幅度降低研发成本,避免踩坑的原因。

2)为什么“过早优化”不适用于API设计?

因为API是一种契约,一旦被使用上,就很难再变更了,就算你发行新的版本的API,你还要驱动各种调用方升级他们的调用方式。所以,接口设计就像数据库模式设计一下,一旦设计好了,未来再变更就比较难了。所以,还是要好好设计。正如前面我给的几个文档——Microsoft REST API GuidelinesPaypal API Design Guidelines 或是 Google API Design Guide 都是让你好好设计API的不错的 Guidelines.

3)POST 更安全吗?

不会。

很多同学以为 GET 的请求数据在URL中,而 POST 的则不是,所以以为 POST 更安全。不是这样的,整个请求的HTTP URL PATH会全部封装在HTTP的协议头中。只要是HTTPS,就是安全的。当然,有些网关如nginx会把URL打到日志中,或是会放在浏览器的历史记录中,所以有人会说 GET 请求不安全,但是,POST 也没有好到哪里去,在 CSRF 这个最常见的安全问题上,则完全就是针对 POST 的。  安全是一件很复杂的事,无论你用哪方法或动词都会不能代表你会更安全。

另外,

  • 如果你要 防止你的 GET 上有敏感信息,应该加个密,这个跟 POST是一样的。
  • 如果你要防止 GET 会被中间人修改,你应该做一个URL签名。(通常来说, 我们都在 GET 上做签名,POST 就忘做了)
  • 如果你要防止有人发一些恶意链接来 hack 你的用户(传说中的 GET 不如 POST 安全的一个问题),你应该用 HMAC 之类的认证技术做好认证(参看 HTTP API 认证授权术)。

总之,你要明白,GETPOST 的安全问题都一样的,不要有谁比谁更安全,然后你就可以掉以轻心的这样的想法,安全都是要很严肃对待的。

4)全用 POST 可以节省时间减少沟通吗?

不但不会,反而更糟糕。

说这种话的人,我感觉是不会思考问题。

  • 其一,为API赋于不同的动词,这个几乎不需要时间。把CRUD写在不同的函数下也是一种很好的编程风格。另外现在几乎所有的开发框架都支持很快速的CRUD的开发,比如Spring Boot,写数据库的CRUD基本上就不需要写SQL语言相关的查询代码,非常之方便。
  • 其二,使用规范的方式,可以节约新加入团队人员的学习成本,而且可以大大减少跨团队的沟能成本。规范和标准其实就是在节约团队时间提升整体效率的,这个我们整个人类进行协作的基础。所以,这个世界上有很多的标准,你只要照着这个标准来,你的所生产的零件就可以适配到其它厂商的产品上。而不需要相互沟通。
  • 其三,全用POST接口一把梭,不规范不标准,使用你的这个山寨API的人就得来不断的问你,反而增加了沟通。另外,也许你开发业务功能很快了,但是你在做控制逻辑的时候,你就要返工了,从长期上来讲,你的欠下了技术债,这个债反而导致了更大的成本。
5)早点回家的正确姿势

不要以为你回家早就没事了,如果你的代码有这样那样的问题,别人看懂,或是出误用了你的代码出了问题,那么,你早回家有什么意义呢?你一样要被打扰,甚至被叫到公司来处理问题。所以,你应该做的是为了“长期的早回家”,而不是“短期的早回家”,要像长期的早回家,通常来说是这样的:

  • 把代码组织设计好,有更好的扩展性。这样在面对新需求的时候,你就可以做到少改代码,甚至不改代码。这样你才可能早回家。不然,每次需求一来,你得重新写,你怎么可能早回家?
  • 你的代码质量是不错的,有不错的文档和注释。所以,别人不会老有问题来找你,或是你下班后,叫你来处理问题。甚至任何人都可以很容易地接手你的代码,这样你才可能真正不被打扰
6)工作而已,优雅不能当饭吃

回应两点:

其一,遵循个规范而已,把“正常”叫“优雅”,可见标准有多低。这么低的标准也只能“为了吃饭而生存了”。

其二,作为一个“职业程序员”,要学会热爱和尊重自己的职业,热爱自己职业最重要的就是不要让外行人看扁这个职业,自己都不尊重这个职业,你让别人怎么尊重?尊重自己的职业,不仅仅只是能够获得让人羡慕的报酬,而更是要让自己的这个职业的更有含金量

希望大家都能尊重自己从事的这个职业,成为真正的职业化的程序员,而不是一个码农!

你的工作给你权力,而只有你的行为才会给你尊重

写这篇文章的原因主要还是因为V2EX上的这个贴子,这个贴子中说——

“对接同事的接口,他定义的所有接口都是 post 请求,理由是 https 用 post 更安全,之前习惯使用 restful api ,如果说 https 只有 post 请求是安全的话?那为啥还需要 get 、put 、delete ?我该如何反驳他。”

然后该贴中大量的回复大概有这么几种论调,1)POST挺好的,就应该这么干,沟通少,2)一把梭,早点干完早点回家,3)吵赢了又怎么样?工作而已,优雅不能当饭吃。虽然评论没有一边倒,但是也有大量的人支持。然后,我在Twitter上嘲讽了一下,用POST干一切就像看到了来你家装修工人说,“老子干活就是用钉子钉一切,什么螺丝、螺栓、卡扣、插销……通通不用,钉枪一把梭,方便,快捷,安全,干完早回家……不过,还是有一些网友觉得用POST挺好的,而且可以节约时间。所以,正好,我在《我做系统架构的原则》中的“原则五”中反对API返回码无论对错全是200的返回那,我专门写下这一篇文章,以正视听。

这篇文章主要分成下面这几个部分:

  1. 为什么要用不同的HTTP动词?
  2. Restful 进行复杂查询
  3. 几个主要问题的回应
    • POST 更安全吗?
    • 全用 POST 可以节省时间沟通少吗?
    • 早点回家的正确姿势
    • 工作而已,优雅不能当饭吃

目录

为什么要用不同的HTTP动词

编程世界通常来说有两种逻辑:“业务逻辑” 和 “控制逻辑”。

  • 业务逻辑。就是你实现业务需求的功能的代码,就是跟用户需求强相关的代码。比如,把用户提交的数据保存起来,查询用户的数据,完成一个订单交易,为用户退款……等等,这些是业务逻辑
  • 控制逻辑。就是我们用于控制程序运行的非功能性的代码。比如,用于控制程序循环的变量和条件,使用多线程或分布式的技术,使用HTTP/TCP协议,使用什么样数据库,什么样的中间件……等等,这些跟用户需求完全没关系的东西。

网络协议也是一样的,一般来说,几乎所有的主流网络协议都有两个部分,一个是协议头,一个是协议体。协议头中是协议自己要用的数据,协议体才是用户的数据。所以,协议头主要是用于协议的控制逻辑,而协议体则是业务逻辑。

HTTP的动词(或是Method)是在协议头中,所以,其主要用于控制逻辑。

下面是HTTP的动词规范,一般来说,REST API 需要开发人员严格遵循下面的标准规范(参看RFC7231 章节4.2.2 – Idempotent Methods

方法 描述 幂等
GET 用于查询操作,对应于数据库的 select 操作 ✔︎
PUT 用于所有的信息更新,对应于数据库的 update 操作 ✔︎︎
DELETE 用于更新操作,对应于数据库的 delete 操作 ✔︎︎
POST 用于新增操作,对应于数据库的 insert 操作
HEAD 用于返回一个资源对象的“元数据”,或是用于探测API是否健康 ✔︎
PATCH 用于局部信息的更新,对应于数据库的 update 操作
OPTIONS 获取API的相关的信息。 ✔︎

其中,PUT 和 PACTH 都是更新业务资源信息,如果资源对象不存在则可以新建一个,但他们两者的区别是,PUT 用于更新一个业务对象的所有完整信息,就像是我们通过表单提交所有的数据,而 PACTH 则对更为API化的数据更新操作,只需要更需要更新的字段(参看 RFC 5789 )。

当然,现实世界中,可能并不一定严格地按照数据库操作的CRUD来理解API,比如,你有一个登录的API /login 你觉得这个API应该是 GETPOSTPUT 还是 PATCH ?登录的时候用户需要输入用户名和密码,然后跟数据库里的对比(select操作)后反回一个登录的session token,然后这个token作为用户登录的状态令牌。如果按上面表格来说,应该是 select 操作进行 GET ,但是从语义上来说,登录并不是查询信息,应该是用户状态的更新或是新增操作(新增session),所以还是应该使用 POST,而 /logout 你可以使用 DELETE这里相说明一下,不要机械地通过数据库的CRUD来对应这些动词,很多时候,还是要分析一下业务语义。

另外,我们注意到,在这个表格的最后一列中加入了“是否幂等”的,API的幂等对于控制逻辑来说是一件很重要的事。所谓幂等,就是该API执行多次和执行一次的结果是完全一样的,没有副作用。

  • POST 用于新增加数据,比如,新增一个交易订单,这肯定不能是幂等的
  • DELETE 用于删除数据,一个数据删除多次和删除一次的结果是一样的,所以,是幂等的
  • PUT 用于全部数更新,所以,是幂等的。
  • PATCH用于局部更新,比如,更新某个字段 cnt = cnt+1,明显不可能是幂等操作。

幂等这个特性对于远程调用是一件非常关键的事,就是说,远程调用有很多时候会因为网络原因导致调用timeout,对于timeout的请求,我们是无法知道服务端是否已经是收到请求并执行了,此时,我们不能贸然重试请求,对于不是幂等的调用来说,这会是灾难性的。比如像转帐这样的业务逻辑,转一次和转多次结果是不一样的,如果重新的话有可能就会多转了一次。所以,这个时候,如果你的API遵从了HTTP动词的规范,那么你写起程序来就可以明白在哪些动词下可以重试,而在哪些动词下不能重试。如果你把所有的API都用POST来表达的话,就完全失控了。

除了幂等这样的控制逻辑之外,你可能还会有如下的这些控制逻辑的需求:

  • 缓存。通过CDN或是网关对API进行缓存,很显然,我们要在查询GET 操作上建议缓存。
  • 流控。你可以通过HTTP的动词进行更粒度的流控,比如:限制API的请用频率,在读操作上和写操作上应该是不一样的。
  • 路由。比如:写请求路由到写服务上,读请求路由到读服务上。
  • 权限。可以获得更细粒度的权限控制和审计。
  • 监控。因为不同的方法的API的性能都不一样,所以,可以区分做性能分析。
  • 压测。当你需要压力测试API时,如果没有动词的区分的话,我相信你的压力测试很难搞吧。
  • ……等等

也许,你会说,我的业务太简单了,没有必要搞这么复杂。OK,没有问题,但是我觉得你最差的情况下,也是需要做到“读写分离”的,就是说,至少要有两个动词,GET 表示是读操作,POST表示是写操作。

Restful 复杂查询

一般来说,对于查询类的API,主要就是要完成四种操作:排序,过滤,搜索,分页。下面是一些相关的规范。参考于两个我觉得写的最好的Restful API的规范文档,Microsoft REST API GuidelinesPaypal API Design Guidelines

  • 排序。对于结果集的排序,使用 sort 关键字,以及 {field_name}|{asc|desc},{field_name}|{asc|desc} 的相关语法。比如,某API需要返回公司的列表,并按照某些字段排序,如:GET /admin/companies?sort=rank|asc 或是 GET /admin/companies?sort=rank|asc,zip_code|desc

  • 过滤。对于结果集的过滤,使用 filter 关键字,以及 {field_name} op{value} 的语法。比如: GET /companies?category=banking&location=china 。但是,有些时候,我们需要更为灵活的表达式,我们就需要在URL上构造我们的表达式。这里需要定义六个比较操作:=<><=>=,以及三个逻辑操作:andornot。(表达式中的一些特殊字符需要做一定的转义,比如:>= 转成 ge)于是,我们就会有如下的查询表达式:GET /products?$filter=name eq 'Milk' and price lt 2.55 查找所有的价柗小于2.55的牛奶。

  • 搜索。对于相关的搜索,使用 search 关键字,以及关键词。如:GET /books/search?description=algorithm 或是直接就是全文搜索 GET /books/search?key=algorithm

  • 分页。对于结果集进行分页处理,分页必需是一个默认行为,这样不会产生大量的返回数据。


    • 使用pageper_page代表页码和每页数据量,比如:GET /books?page=3&per_page=20
    • 可选。上面提到的page方式为使用相对位置来获取数据,可能会存在两个问题:性能(大数据量)与数据偏差(高频更新)。此时可以使用绝对位置来获取数据:事先记录下当前已获取数据里最后一条数据的ID时间等信息,以此获取 “该ID之前的数据” 或 “该时刻之前的数据”。示例:GET /news?max_id=23454345&per_page=20 或 GET /news?published_before=2011-01-01T00:00:00Z&per_page=20

注意:这里需要注意一下,在理论上来说GET是可以带 body 的,但是很多程序的类库或是中间件并不支持 GET 带 body,导致你只能用 POST 来传递参数。这里的原则是:

  1. 对于简单的查询,很多参数都设计在 restful API 的路径上了,而 filter/sort/pagination 也不会带来很多的复杂,所以应该使用 GET 

  2. 对于复杂的查询来说,可能会有很复杂的查询参数,比如:ElasticSearch 上的 index/_search里的 DSL,你也应该尽可能的使用 GET,而不是POST 除非客观条件上不支持GET。ElasticSearch 的官方文档里也是这么说的。

The authors of Elasticsearch prefer using GET for a search request because they feel that it describes the action—​retrieving information—​better than the POST verb. (我们推荐使用 GET而不是 POST,因为语义更清楚)However, because GET with a request body is not universally supported, the search API also accepts POST requests (除非你的类库或是服务器不支持 GET带参数 ,你再用POST,我们两个都支持)

陈皓注:但是在 ElasticSearch 7.11 后,GET 也不支持 body 了。这是 ElasticSearch 的设计和实现不对应了。

另外,对于一些更为复杂的操作,建议通过分别调用多个API的方式来完成,虽然这样会增加网络请求的次数,但是这样的可以让后端程序和数据耦合度更小,更容易成为微服务的架构。

最后,如果你想在Rest中使用像GraphQL那样的查询语言,你可以考虑一下类似 OData 的解决方案。OData 是 Open Data Protocol 的缩写,最初由 Microsoft 于 2007 年开发。它是一种开放协议,使您能够以简单和标准的方式创建和使用可查询和可互操作的 RESTful API。

几个主要问题的回应

下面是对几个问题的直接回应,如果大家需要我回应更多的问题,可以在后面留言,我会把问题和我的回应添加到下面。

1)为什么API 要Restful,并符合规范?

Restful API算是一个HTTP的规范和标准了,你要说是最佳实践也好,总之,它是一个全世界对HTTP API的一个共识。在这个共识上,你可以无成本地享受很多的技术红利,比如:CDN,API网关,服务治理,监控……等等。这些都是可以让你大幅度降低研发成本,避免踩坑的原因。

2)为什么“过早优化”不适用于API设计?

因为API是一种契约,一旦被使用上,就很难再变更了,就算你发行新的版本的API,你还要驱动各种调用方升级他们的调用方式。所以,接口设计就像数据库模式设计一下,一旦设计好了,未来再变更就比较难了。所以,还是要好好设计。正如前面我给的几个文档——Microsoft REST API GuidelinesPaypal API Design Guidelines 或是 Google API Design Guide 都是让你好好设计API的不错的 Guidelines.

3)POST 更安全吗?

不会。

很多同学以为 GET 的请求数据在URL中,而 POST 的则不是,所以以为 POST 更安全。不是这样的,整个请求的HTTP URL PATH会全部封装在HTTP的协议头中。只要是HTTPS,就是安全的。当然,有些网关如nginx会把URL打到日志中,或是会放在浏览器的历史记录中,所以有人会说 GET 请求不安全,但是,POST 也没有好到哪里去,在 CSRF 这个最常见的安全问题上,则完全就是针对 POST 的。  安全是一件很复杂的事,无论你用哪方法或动词都会不能代表你会更安全。

另外,

  • 如果你要 防止你的 GET 上有敏感信息,应该加个密,这个跟 POST是一样的。
  • 如果你要防止 GET 会被中间人修改,你应该做一个URL签名。(通常来说, 我们都在 GET 上做签名,POST 就忘做了)
  • 如果你要防止有人发一些恶意链接来 hack 你的用户(传说中的 GET 不如 POST 安全的一个问题),你应该用 HMAC 之类的认证技术做好认证(参看 HTTP API 认证授权术)。

总之,你要明白,GETPOST 的安全问题都一样的,不要有谁比谁更安全,然后你就可以掉以轻心的这样的想法,安全都是要很严肃对待的。

4)全用 POST 可以节省时间减少沟通吗?

不但不会,反而更糟糕。

说这种话的人,我感觉是不会思考问题。

  • 其一,为API赋于不同的动词,这个几乎不需要时间。把CRUD写在不同的函数下也是一种很好的编程风格。另外现在几乎所有的开发框架都支持很快速的CRUD的开发,比如Spring Boot,写数据库的CRUD基本上就不需要写SQL语言相关的查询代码,非常之方便。
  • 其二,使用规范的方式,可以节约新加入团队人员的学习成本,而且可以大大减少跨团队的沟能成本。规范和标准其实就是在节约团队时间提升整体效率的,这个我们整个人类进行协作的基础。所以,这个世界上有很多的标准,你只要照着这个标准来,你的所生产的零件就可以适配到其它厂商的产品上。而不需要相互沟通。
  • 其三,全用POST接口一把梭,不规范不标准,使用你的这个山寨API的人就得来不断的问你,反而增加了沟通。另外,也许你开发业务功能很快了,但是你在做控制逻辑的时候,你就要返工了,从长期上来讲,你的欠下了技术债,这个债反而导致了更大的成本。
5)早点回家的正确姿势

不要以为你回家早就没事了,如果你的代码有这样那样的问题,别人看懂,或是出误用了你的代码出了问题,那么,你早回家有什么意义呢?你一样要被打扰,甚至被叫到公司来处理问题。所以,你应该做的是为了“长期的早回家”,而不是“短期的早回家”,要像长期的早回家,通常来说是这样的:

  • 把代码组织设计好,有更好的扩展性。这样在面对新需求的时候,你就可以做到少改代码,甚至不改代码。这样你才可能早回家。不然,每次需求一来,你得重新写,你怎么可能早回家?
  • 你的代码质量是不错的,有不错的文档和注释。所以,别人不会老有问题来找你,或是你下班后,叫你来处理问题。甚至任何人都可以很容易地接手你的代码,这样你才可能真正不被打扰
6)工作而已,优雅不能当饭吃

回应两点:

其一,遵循个规范而已,把“正常”叫“优雅”,可见标准有多低。这么低的标准也只能“为了吃饭而生存了”。

其二,作为一个“职业程序员”,要学会热爱和尊重自己的职业,热爱自己职业最重要的就是不要让外行人看扁这个职业,自己都不尊重这个职业,你让别人怎么尊重?尊重自己的职业,不仅仅只是能够获得让人羡慕的报酬,而更是要让自己的这个职业的更有含金量

希望大家都能尊重自己从事的这个职业,成为真正的职业化的程序员,而不是一个码农!

你的工作给你权力,而只有你的行为才会给你尊重


该漏洞由 Patchstack 于 2025 年 11 月 21 日保留,影响插件 4.0.49 及之前的所有版本,核心成因是访问控制安全级别配置不当,导致攻击者可绕过权限验证,未授权调用插件新闻订阅相关 REST API 接口,实现用户订阅状态篡改、非法订阅/退订等操作。



一、漏洞概述

漏洞编号:CVE-2025-66128

漏洞名称:WooCommerce Brevo Sendinblue 插件授权缺失漏洞

漏洞类型:访问控制绕过(授权缺失)

受影响组件:WooCommerce Brevo Sendinblue 插件(≤ 4.0.49 版本)

影响环境:Kali Linux + Apache 2.4.65 + MariaDB 11.8.5 + PHP-FPM + WordPress

漏洞核心路径:/wp-json/sendinblue/v1/subscription/update-status

漏洞危害:未授权攻击者可篡改用户订阅状态、干扰营销活动,违反 GDPR 合规要求,损害用户隐私与品牌声誉





二、漏洞复现

利用CVE-2025-66128授权缺失漏洞,通过未授权POST请求直接调用目标API接口,验证漏洞可利用性:

直接触发目标漏洞路由

1. 无参数请求(验证参数校验缺失)

image.png







2. 非法参数格式请求(验证参数格式校验缺失)

image.png





3. 伪造Referer请求(验证Referer防护缺失)

image.png





4. 批量篡改订阅状态请求(验证批量操作无限制)

image.png





5. 路由无有效防护



image.png





6. 使用非授权IP,验证无IP限制

image.png







三、漏洞分析

CVE-2025-66128漏洞的核心根源在于ApiManager类的 add_rest_endpoints()方法中,所有REST API路由注册均未配置permission_callback权限验证回调函数,且回调逻辑内部无任何补充权限校验,导致未授权用户可直接调用各类敏感接口。



漏洞触发的关键逻辑链

1 路由注册触发:ApiManager类的add_hooks()方法(源码第45-84行)虽未直接注册路由,但结合主文件逻辑可知,插件初始化时会调用add_rest_endpoints()方法,完成所有API路由的注册;

2 权限校验缺失:WordPress REST API的register_rest_route()函数要求通过permission_callback参数定义权限校验逻辑,若未配置该参数,默认对所有用户(包括未授权游客)开放;

3 回调逻辑无补充校验:所有路由的回调函数(如save_settings()disconnect_connection()get_file_contents()等)内部均未添加用户登录状态、角色权限等校验逻辑,直接执行核心操作。



敏感接口无参数校验与权限隔离

get_file_contents()和 delete_attachment() 方法用于文件操作,虽做了基础文件名过滤,但未校验调用者权限,导致未授权用户可操作插件上传目录下的任意文件。





save_settings()email_settings()方法用于修改插件核心配置与邮件设置,仅执行“读取请求体-更新选项”的简单逻辑,无任何权限校验与参数合法性校验。





api-manager.php中虽存在validate_api_key()方法(源码第497-517行)用于API密钥校验,但该方法未被任何路由的permission_callback复用,且仅针对密钥验证,未覆盖所有敏感接口,导致权限校验逻辑形同虚设。



四、漏洞防御

1为所有路由数组添加 permission 字段,注册路由时补充 permission_callback 参数:



2. 权限回调内直接调用现有 validate_api_key 方法,叠加管理员权限校验:



3. 在 ApiManager 类顶部新增常量定义,统一键名:





参考链接

ERROR: The request could not be satisfied



WooCommerce Brevo Sendinblue 插件授权缺失漏洞 CVE-2025-66128 详解 - qife - 博客园



https://avd.aliyun.com/detail?id=AVD-2025-66128