老赵写字的地方

Nop 平台的 GraphQL 和 REST API

Nop 平台的 API 层有独特的设计:同一个引擎同时提供 GraphQL 和 REST 两种协议,不需要额外适配。

为什么需要两套协议

GraphQL 适合复杂查询场景——客户端可以精确指定需要哪些字段,避免过度获取。REST 适合简单调用场景——用 curl 就能测试,调试方便。

传统做法是维护两套端点,各有各的路由、序列化、权限控制。Nop 用一套模型同时暴露两种协议。

基础概念:BizModel

Nop 中每个业务对象对应一个 BizModel(业务模型)。它定义了该对象的 CRUD 操作以及自定义方法。

自动生成的 BizModel(以 Article 为例)包含以下内建操作:

save          — 新增/修改
get           — 按 ID 查询
delete        — 删除
findPage      — 分页查询
findList      — 列表查询

这些操作同时注册为 GraphQL Mutation/Query 和 REST 端点。

REST 端点

每个 BizModel 自动注册以下 REST 端点:

POST   /ArticleBizModel__save         保存
GET    /ArticleBizModel__get?id=xxx    查询单个
POST   /ArticleBizModel__findPage      分页查询
POST   /ArticleBizModel__batchModify   批量修改

URL 格式为 /{BizModel}__{method}。请求体统一为 JSON。

查询示例:

curl "http://localhost:8080/ArticleBizModel__get?id=abc123" \
  -H "nop-tenant: 0"

分页查询:

curl -X POST "http://localhost:8080/ArticleBizModel__findPage" \
  -H "Content-Type: application/json" \
  -H "nop-tenant: 0" \
  -d '{
    "query": {
      "title__contains": "Nop",
      "orderBy": [{"field": "createTime", "desc": true}]
    },
    "limit": 20
  }'

支持丰富的筛选条件:

后缀 含义
__contains 包含
__gt 大于
__ge 大于等于
__lt 小于
__le 小于等于
__in 在列表中
__exists 子查询存在
__isNull 为空

GraphQL 端点

GraphQL 端点在http://localhost:8080/graphql。支持 introspection,可以用 GraphiQL 探索。

查询单个文章:

query {
  ArticleBizModel__get(id: "abc123") {
    title
    content
    author
    createTime
  }
}

创建文章:

mutation {
  ArticleBizModel__save(data: {
    title: "Hello Nop",
    content: "文章内容",
    author: "test"
  }) {
    articleId
    title
  }
}

GraphQL 的优势在于可以跨对象查询:

query {
  ArticleBizModel__findPage(query: {author__contains: "test"}) {
    items {
      title
      createTime
      # 关联对象自动可查
      tags {
        name
      }
    }
    total
  }
}

关联对象不需要手写 join——ORM 层自动处理。

自定义业务方法

通过继承 BizModel 添加自定义操作:

@BizModel("Article")
public class ArticleBizModelEx extends _ArticleBizModel {
 
    @BizQuery
    public List<Article> getTopArticles(
        @Name("limit") int limit
    ) {
        return dao().findTopByViews(limit);
    }
 
    @BizAction
    public void publishArticle(
        @Name("id") String id
    ) {
        Article article = dao().get(id);
        article.setStatus("published");
        dao().update(article);
    }
}

@BizQuery 标记的方法注册为 GraphQL Query + REST GET。 @BizAction 标记的方法注册为 GraphQL Mutation + REST POST。

自定义方法自动获得端点:

REST:  GET  /ArticleBizModel__getTopArticles?limit=10
REST:  POST /ArticleBizModel__publishArticle

GraphQL:  query { ArticleBizModel__getTopArticles(limit: 10) { ... } }
GraphQL:  mutation { ArticleBizModel__publishArticle(id: "xxx") { ... } }

权限控制

每个方法都可以通过 acl.xml 配置权限:

<!-- my-app.api.xml -->
<beans>
  <bean id="ArticleBizModel" class="...">
    <actions>
      <action name="save" roles="admin" />
      <action name="getTopArticles" roles="user" />
      <action name="publishArticle" roles="editor" />
    </actions>
  </bean>
</beans>

未授权的调用返回 403。

协议对比

特性 REST GraphQL
端点 URL /{BizModel}__{method} /graphql
客户端 curl、浏览器 GraphiQL、Apollo
字段选择 固定返回 客户端指定
关联对象 单独请求 同一请求中
批量操作 逐个请求 同一请求中
自省 无 有(Introspection)

两套协议由同一套 BizModel 定义驱动,不需要各自维护一份代码。

进一步阅读

zh/nop/practical_guide/graphql_rest_guide.txt · 最后更改: 由 tom