Web安全
基础漏洞
01前端基础【HTML】
02前端基础【CSS】
03后端基础【PHP速通】
04后端基础【PHP面向对象】
05MySQL基础操作
06前后端联动【代码练习】
07SQL注入【1】
07SQL 注入【2】
08SQL注入 Labs
08SQL注入速查表
09XSS
09跨站脚本攻击【XSS】
09XSS Labs
10跨站请求伪造【CSRF】
11服务器端请求伪造【SSRF】
12XML 外部实体注入【XXE】
13代码执行漏洞
14命令执行漏洞
15文件包含漏洞
16文件上传漏洞
17反序列化漏洞
18业务逻辑漏洞
19未授权访问漏洞集合
20跨源资源共享【CORS】
21SSTI模板注入
22并发漏洞
23点击劫持【Clickjacking 】
24请求走私
25路径遍历
26访问控制
27身份验证漏洞
28WebSocket
29Web缓存中毒
30HTTP 主机头攻击
31信息泄露漏洞
32原型污染
33NoSQL注入
API 安全
01web应用程序
02HTTP协议
03API概述
04分类类型
05交换格式
06身份验证
07常见API漏洞
08crAPI靶场
09JWT
10OAuth 2.0身份验证
11GraphQL【1】
11GraphQL【2】
12DVGA靶场
13服务器端参数污染
14API文档
15API Labs
16OAuth Labs
17GraphQL API Labs
18JWT Labs
小程序
小程序抓包
数据库
MySQL
Oracle
MongoDB
Redis
PostgreSQL
SQL server
中间件
Nginx
Apache HTTP Server
IIS
Tomcat
框架
ThinkPHP
Spring
Spring Boot
Django
访问控制
-
+
首页
11GraphQL【1】
官网地址:https://graphql.org/ ## 一、定义 ### 1.1 概述 GraphQL(Graph Query Language)是 **一种用于 API 的数据查询语言**(DQL)和数据处理语言(DML).。 GraphQL 不与任何特定数据库或存储引擎绑定,而是由现有的代码和数据支持。 它的核心特性是:**客户端精确声明所需数据的结构,服务端返回同类型的 JSON 数据**。 和传统 REST API 最大的不同:GraphQL **不是按 URL 切资源**,而是按 **Schema 切领域**,一次请求可跨资源组合数据。 > “切资源” 和 “切领域” 在 REST 里,API 设计通常是围绕**资源(Resource)**来组织的: - 每个 URL 代表一个资源或资源集合 - 操作方式由 HTTP 动词(GET / POST / PUT / DELETE)决定 - 一个资源的返回结果通常是**固定结构**,和这个 URL 强绑定 **例子:** ``` GET /users/123 → 获取用户 123 GET /users/123/posts → 获取用户 123 的所有帖子 GET /posts/456 → 获取帖子 456 ``` 特点: - URL 是主切分点(每个资源都有独立的端点) - 如果要获取**多个资源的组合数据**,通常需要多次请求 比如要显示“用户信息 + 最近 3 篇帖子”,可能要: 1. GET /users/123 2. GET /users/123/posts?limit=3 GraphQL 则是围绕**领域模型(Domain Model)**来组织的: - 所有请求走一个统一端点(如 /graphql) - 客户端直接声明想要的领域数据形状,不需要关心具体 URL - 领域模型由 **Schema** 定义(类型、字段、关系) - 一个请求里可以横跨多个类型(比如用户 + 帖子 + 评论) **例子(一次请求拿齐用户和帖子):** ``` { user(id: 123) { name posts(limit: 3) { title comments { content } } } } ``` 特点: - Schema(类型系统)是主切分点 - 请求不是针对某个固定 URL,而是针对**数据图谱**(Graph)上的节点 - 一个请求可以组合多个关联实体,返回形状与请求一致 > 类比理解 可以把它类比成**点菜**: - **REST 切资源**: - 菜单上每道菜(URL)都固定,点一道菜只能吃到那道菜的固定配料 - 想要多种组合就得多点几道菜(多次请求) - **GraphQL 切领域**: - 厨房有一个完整食材(领域模型)清单 - 你一次点菜时就能告诉厨师:“我要牛排 + 配菜只要土豆 + 饮料只要柠檬水” - 厨师按你的要求一次上齐(单请求组合数据)  ### 1.2 GraphQL 查询示例 *基本 GraphQL 查询* ``` query{ user{ id email firstName lastName } } ``` 响应是 JSON: *基本 GraphQL 响应* ```json { "data": { "user": { "id": "1", "email": "paolo@maolin101.com", "firstName": "Paolo", "lastName": "Stagno" } } } ``` ## 二、设计动机 GraphQL 的诞生(Facebook 2012 内部使用,2015 开源)是为了解决 REST 常见问题: | REST 常见痛点 | GraphQL 对应解决 | | -------------------------------------- | -------------------------------------- | | Over-fetching(返回多余数据) | 客户端只取需要字段 | | Under-fetching(多次请求才能拿齐数据) | 一次请求组合多个实体 | | 多版本 API(v1, v2...) | 无需版本化,通过 Schema 演化 | | API 文档与实际数据不一致 | 强类型 Schema,自省查询实时生成文档 | | 后端接口变动影响大 | 通过添加字段、弃用字段实现非破坏性升级 | ## 三、核心概念与构件 ### 3.1 Schema(模式) Schema 是 GraphQL 服务端与客户端的“契约”,描述了: - **有哪些类型**(Type) - **有哪些操作**(Operation) - **每个字段的数据类型与参数** Schema 用 **SDL(Schema Definition Language)** 编写: ``` schema { query: Query mutation: Mutation subscription: Subscription } ``` ### 3.2 Type(类型系统) GraphQL 是强类型查询语言,类型包括: - **标量类型(Scalar)**:Int, Float, String, Boolean, ID - **对象类型(Object)**:自定义结构体,包含字段 - **枚举类型(Enum)**:固定集合 - **接口(Interface)**:声明字段签名,由其他类型实现 - **联合类型(Union)**:多个类型之一,没有共有字段要求 - **输入类型(Input Object)**:用于参数传递 - **修饰符**: - Type! → 非空 - [Type] → 列表 - [Type!]! → 非空列表且元素非空 ### 3.3 Operation(操作类型) - **Query**:只读请求(类似 GET) - **Mutation**:修改数据(类似 POST/PUT/DELETE) - **Subscription**:实时推送(WebSocket/SSE) ### 3.4 Field & Arguments(字段与参数) - 每个字段可以带参数 - 参数可选/必填 ```sql posts(first: 10, after: "cursor123") ``` ### 3.5 Resolver(解析器) Resolver 是服务端负责**取数**的函数,示例: ```js const resolvers = { Query: { posts: (parent, args, context) => { return db.getPosts(args.first, args.after); } }, Post: { author: (post) => db.getUser(post.authorId) } }; ``` > 典型的 SDL是这样的: ```sql type Query { posts(first: Int = 20, after: String): [Post!]! } type Post { id: ID! title: String! author: User! } type User { id: ID! name: String! } ``` - Query.posts 对应上面 posts(...) 这个字段。 - Post.author 对应 author: User! 这个字段。 **Resolver 的键名**(Query.posts、Post.author)必须与 Schema 中的**类型名.字段名**一一对应。 > Resolver 的 4 个入参 标准签名是 (parent, args, context, info),示例中用到了前三个: parent(也叫 root/obj):**上一级字段的返回值**。 - 顶层 Query.posts 的 parent 通常是 {}(rootValue)。 - Post.author 的 parent 就是**某一条 Post 对象**,所以我把它命名成 post 好理解。 args:调用该字段时传入的**参数对象**,如 first、after。 context:**本次请求的上下文**(鉴权用户、数据库句柄、数据加载器、请求 ID 等)。 示例中的代码直接用外层 db 变量,通常更推荐从 context.db 取(见下文更规范写法)。 info:执行信息(字段名、路径、选择集 AST、schema 等)。 > 解析 由于客户端传来的是 ```sql posts(first: 10, after: "cursor123") ``` 所以: 1. GraphQL 调用 Query.posts:args = { first: 10, after: "cursor123" },解析器返回db.getPosts(args.first, args.after)的结果。假设返回的结果如下: ```js [ { id: 'p1', authorId: 'u1' }, { id: 'p2', authorId: 'u2' }, ... ] ``` 2. GraphQL 会对列表里的**每一条 Post 分别调用一次**Post.author(post): ```js Post.author({ id:'p1', ..., authorId:'u1' }) → db.getUser('u1') Post.author({ id:'p2', ..., authorId:'u2' }) → db.getUser('u2') ... ``` ### 3.6 Execution(执行过程) 大致流程为: 1. 接收客户端的 GraphQL 查询字符串 2. 解析(Parse)成 AST 3. 校验(Validate)是否符合 Schema 4. 执行(Execute)Resolver 5. 组装成与查询形状相同的 JSON 把 GraphQL 的 **Execution(执行过程)**拆细,从请求到返回的每一步,明确它是怎么工作的。 **1、接受请求** 客户端发起 GraphQL 请求: ```sql query { posts(first: 10, after: "cursor123") { id title author { id name } } } ``` - 请求通过 HTTP POST 或 GET 发送到 GraphQL 服务器 - GraphQL 接收请求后,进入 执行前处理阶段 **2、构建 Context** GraphQL 在执行前会生成一个 **context** 对象: - 传给每个 resolver 的第三个参数 - 常包含: - 当前登录用户信息(user) - 数据库或 ORM 句柄(db) - DataLoader 或缓存对象 - 请求 ID 或 trace 信息 示例: ```sql const context = { user: currentUser, db, loaders: buildLoaders(db) }; ``` **3、解析(Parse)** - GraphQL 把请求字符串解析成 AST(抽象语法树) - AST 是一个树状结构,表示请求里每个字段和参数 示例 AST(简化版): ``` Query └─ posts(first:10, after:"cursor123") ├─ id ├─ title └─ author ├─ id └─ name ``` **4、校验(Validate)** - GraphQL 会根据 **Schema** 校验: - 字段是否存在 (posts, id, title, author, name) - 参数类型是否匹配 (first: Int, after: String) - 是否遵守非空约束 (id!, author!) - 如果校验失败,返回错误,不会进入执行阶段 **5、执行(Execution)** 执行阶段是 GraphQL 的核心,按 **字段树(Field Tree)** 递归执行 resolver: > 执行顶层 Query 字段 ``` posts: (parent, args, context) => db.getPosts(args.first, args.after) ``` - **parent**: 对顶层 Query 来说通常是 {} - **args**: { first: 10, after: "cursor123" } - **context**: 上一步构建的对象 - Resolver 调用 db.getPosts(10, "cursor123") 返回一个 Post 数组: ``` [ { id:'p1', title:'A', authorId:'u1' }, { id:'p2', title:'B', authorId:'u2' }, ] ``` 注意:如果 db.getPosts 返回 Promise,GraphQL 会自动等待 Promise 完成。 > 执行子字段 GraphQL 会递归解析数组中的每个 Post 对象: - **标量字段**(id、title) - 默认解析器:直接取对象属性值 - 示例: ``` post.id // 'p1' post.title // 'A' ``` - **对象字段**(author) - 调用自定义 resolver: ``` author: (post) => db.getUser(post.authorId) ``` - 对每条 Post 并行执行(返回 Promise),最终得到 User 对象: ``` { id:'u1', name:'Alice' }, { id:'u2', name:'Bob' } ``` > 递归解析对象字段的子字段 - User 对象的字段(id, name)也执行默认解析器 - GraphQL 构建最终数据结构,与查询选择集完全一致: ```json { "data": { "posts": [ { "id": "p1", "title": "A", "author": { "id": "u1", "name": "Alice" } }, { "id": "p2", "title": "B", "author": { "id": "u2", "name": "Bob" } } ] } } ``` **6、并发与批量处理** - GraphQL 会 **尽量并行**解析同一层级的 resolver - N+1 问题:author resolver 会为每个 Post 调用一次 db.getUser - 优化方法:使用 **DataLoader** 批量加载用户数据 **7、错误处理与非空字段冒泡** - 如果 resolver 抛错: - GraphQL 会把错误放到 errors 数组 - 如果字段是非空 (!),错误会“冒泡”到父级 - 例如: - author: User! 出错 → 对应 Post 置为 null - 如果 Post 列表是 [Post!]! → 整个 posts 置为 null **8、返回结果** - 当所有字段解析完成,GraphQL 拼接成 JSON,严格符合查询的选择集 - 返回给客户端: ```json { "data": { ... }, "errors": [ ... ] // 如果有错误 } ``` ## 四、查询与返回 **示例 Query:** ```sql query GetFeed($first: Int!) { me { id name } posts(first: $first) { edges { node { id title author { name } } } } } ``` **变量:** ```json { "first": 5 } ``` **返回:** ```json { "data": { "me": { "id": "u1", "name": "Ada" }, "posts": { "edges": [ { "node": { "id": "p1", "title": "Hello", "author": { "name": "Ada" } } } ] } } } ``` ## 五、语言特性 **变量(Variables):**让查询可复用,避免硬编码。 **片段(Fragments):**复用字段集合,尤其是接口/联合类型多态查询时: ```json fragment PostFields on Post { id title } ``` **指令(Directives):** - 内置:@include(if:), @skip(if:) - 扩展:@defer, @stream(渐进传输) **自省(Introspection):**可查询 Schema 本身,生成文档或代码。 ## 六、错误处理 - GraphQL 响应始终是 { data, errors } - 字段解析失败 → data 中该字段为 null,errors 给出错误信息 - 业务错误建议通过 **union 类型** 或 **显式字段** 表达,而不是全部放到 errors ## 七、HTTP 传输 - 单一端点 /graphql - 常用 POST,也可 GET(配合持久化查询) - 生产环境常用 **Persisted Query**(查询哈希化)提升安全性与缓存命中 ## 八、性能与可伸缩 **N+1 查询问题:**使用批量加载(DataLoader 模式) **分页:**推荐 Cursor-based(Relay 风格),避免 Offset 大跳页问题 **缓存策略:** - 文档级缓存(相同 Query+变量) - 字段级缓存 - 客户端规范化缓存(Apollo/Relay) **查询复杂度限制:** - 限制最大深度 - 给字段设置权重,限制总分 ## 九、安全性 - 鉴权:在 Resolver 或自定义指令处理 - 禁用/限制自省(生产环境) - 速率与复杂度限流 - 输入验证与转义 ## 十、实时订阅(Subscriptions) - 常用 WebSocket 传输 - 用于聊天、实时监控、协作等场景 ## 十一、工具与生态 - 服务端:Apollo Server、Yoga、graphql-java、Hot Chocolate、gqlgen、Strawberry 等 - 客户端:Apollo Client、Relay、urql - 工具链:GraphiQL、Playground、Codegen、Inspector - 网关与联邦:Apollo Federation、Schema Stitching、GraphQL Mesh ## 十二、常见架构模式 **单体 GraphQL API**(适合中小团队) **BFF(Backend For Frontend)**(按前端类型裁剪) **联邦(Federation)**(多团队协作,各自管理子图) ## 十三、何时不该用 GraphQL - 静态、可完全缓存的公共数据(CDN 更好) - 文件流/大文件上传 - 团队缺乏 Schema 治理能力 ## 十四、GraphQL 全流程架构图  > 图中逻辑说明: - **客户端发送请求**:GraphQL 查询或变更请求(Query/Mutation)。 - **Server 解析**:Server 将请求字符串解析为 AST(抽象语法树)。 - **验证请求**:检查查询是否符合 schema,包括类型、字段、权限等。 - **执行树构建**:把请求映射为执行树,表示字段解析顺序。 - **执行解析器**:逐字段调用 resolver,从数据源获取数据。 - **数据源**:可以是数据库、REST API、微服务等,支持聚合数据。 - **返回结果**:最终将解析结果组装成 JSON 响应返回客户端。 这个图覆盖了 GraphQL **全流程**,从请求到响应,体现了执行树、resolver 和多数据源聚合的核心逻辑。
毛林
2025年9月7日 11:24
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码