Files
aiot-platform-cloud/.qoder/repowiki/zh/content/API接口文档/API接口文档.md

358 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# API接口文档
<cite>
**本文引用的文件**
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md)
- [viewsh-gateway/src/main/resources/application.yaml](file://viewsh-gateway/src/main/resources/application.yaml)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/user/AdminUserApi.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/user/AdminUserApi.java)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/permission/PermissionApi.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/permission/PermissionApi.java)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/sms/SmsCodeApi.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/sms/SmsCodeApi.java)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/social/SocialUserApi.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/social/SocialUserApi.java)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/enums/ErrorCodeConstants.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/enums/ErrorCodeConstants.java)
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceControlApi.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceControlApi.java)
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDevicePropertyQueryApi.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDevicePropertyQueryApi.java)
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceStatusQueryApi.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceStatusQueryApi.java)
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/enums/ErrorCodeConstants.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/enums/ErrorCodeConstants.java)
- [viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/SecurityProperties.java](file://viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/SecurityProperties.java)
- [viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/ViewshWebSecurityConfigurerAdapter.java](file://viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/ViewshWebSecurityConfigurerAdapter.java)
- [script/idea/http-client.env.json](file://script/idea/http-client.env.json)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件为 AIOT 平台云项目的 API 接口文档,覆盖 RESTful API 的规范、认证与授权机制、版本管理与兼容策略、错误码体系、接口调用示例、性能与最佳实践,以及测试与调试方法。文档内容以仓库现有实现与技术文档为依据,确保准确性与可追溯性。
## 项目结构
- API 以微服务形式提供,统一通过网关进行路由与文档聚合。
- 网关负责:
- 将请求前缀映射到对应后端服务;
- 聚合 Knife4j 文档;
- 重写 Swagger 路径以便文档展示。
- 后端服务通过 OpenFeign 定义 RPC 接口契约,便于跨服务调用。
```mermaid
graph TB
GW["网关<br/>viewsh-gateway"] --> SYS["系统服务<br/>system-server"]
GW --> INFRA["基础设施服务<br/>infra-server"]
GW --> IOT["物联网服务<br/>iot-server"]
GW --> OPS["运营服务<br/>ops-server"]
GW --> OTHERS["其他服务<br/>..."]
DOC["Knife4j 聚合文档<br/>/doc.html"] --> GW
```
图表来源
- [viewsh-gateway/src/main/resources/application.yaml](file://viewsh-gateway/src/main/resources/application.yaml#L34-L210)
章节来源
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md#L61-L73)
- [viewsh-gateway/src/main/resources/application.yaml](file://viewsh-gateway/src/main/resources/application.yaml#L34-L271)
## 核心组件
- 网关路由与文档聚合
- 网关根据请求前缀将流量转发至对应微服务,并对 Swagger 文档进行聚合展示。
- 统一响应结构
- 所有接口返回统一结构HTTP 状态码与业务 code 分离,便于前端与客户端处理。
- 错误码体系
- 系统模块与 IoT 模块分别维护错误码常量,便于定位业务异常。
章节来源
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md#L5-L42)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/enums/ErrorCodeConstants.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/enums/ErrorCodeConstants.java#L12-L171)
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/enums/ErrorCodeConstants.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/enums/ErrorCodeConstants.java#L12-L82)
## 架构总览
- 网关路由规则
- 管理后台与移动端接口前缀清晰划分,便于权限与路由控制。
- 文档聚合
- 通过 Knife4j 聚合各微服务的 OpenAPI 文档,统一入口访问。
```mermaid
sequenceDiagram
participant C as "客户端"
participant G as "网关"
participant S as "后端服务"
C->>G : "HTTP 请求含前缀"
G->>G : "匹配路由规则"
G->>S : "转发请求重写路径"
S-->>G : "统一响应结构"
G-->>C : "返回响应"
```
图表来源
- [viewsh-gateway/src/main/resources/application.yaml](file://viewsh-gateway/src/main/resources/application.yaml#L34-L271)
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md#L45-L59)
章节来源
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md#L61-L73)
- [viewsh-gateway/src/main/resources/application.yaml](file://viewsh-gateway/src/main/resources/application.yaml#L34-L271)
## 详细组件分析
### 认证与授权机制
- 访问令牌传递方式
- 支持在请求头中携带令牌(默认 Header 名称),以及通过查询参数传递(用于 WebSocket 等场景)。
- 安全配置
- 基于 Spring Security 的 Web 安全适配器支持免登录白名单、CSRF、表单登录与 HTTP Basic 等。
- 令牌使用
- 管理后台接口通常需要携带 Bearer Token具体接口请参考各服务的 OpenAPI 文档。
```mermaid
flowchart TD
Start(["开始"]) --> CheckHeader["检查请求头是否存在令牌"]
CheckHeader --> HasHeader{"存在?"}
HasHeader --> |是| UseHeader["使用请求头令牌"]
HasHeader --> |否| CheckParam["检查查询参数是否存在令牌"]
CheckParam --> HasParam{"存在?"}
HasParam --> |是| UseParam["使用查询参数令牌"]
HasParam --> |否| Deny["拒绝访问未认证"]
UseHeader --> End(["结束"])
UseParam --> End
Deny --> End
```
图表来源
- [viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/SecurityProperties.java](file://viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/SecurityProperties.java#L17-L28)
- [viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/ViewshWebSecurityConfigurerAdapter.java](file://viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/ViewshWebSecurityConfigurerAdapter.java#L1-L23)
章节来源
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md#L75-L106)
- [viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/SecurityProperties.java](file://viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/SecurityProperties.java#L17-L28)
- [viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/ViewshWebSecurityConfigurerAdapter.java](file://viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/ViewshWebSecurityConfigurerAdapter.java#L1-L23)
### 统一响应结构与错误码
- 统一响应结构
- code业务状态码0 表示成功)
- msg错误提示信息
- data业务数据
- 常用 HTTP 状态码与业务错误码
- 成功、参数校验失败、未登录/Token 过期、无权限、限流、系统内部错误、系统模块与 IoT 模块业务错误等
```mermaid
erDiagram
RESPONSE {
int code
string msg
json data
}
```
图表来源
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md#L17-L42)
章节来源
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md#L17-L42)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/enums/ErrorCodeConstants.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/enums/ErrorCodeConstants.java#L12-L171)
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/enums/ErrorCodeConstants.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/enums/ErrorCodeConstants.java#L12-L82)
### 网关路由与文档聚合
- 路由规则
- 管理后台前缀:/admin-api/{module}/**
- 移动端前缀:/app-api/**(部分复用系统服务)
- 物联网设备上报等特殊路径由网关单独处理
- 文档聚合
- 通过 Knife4j 聚合各服务的 OpenAPI 文档,统一入口访问
```mermaid
flowchart TD
A["请求 /admin-api/system/**"] --> B["网关匹配 system 路由"]
B --> C["转发到 system-server"]
D["请求 /admin-api/iot/**"] --> E["网关匹配 iot 路由"]
E --> F["转发到 iot-server"]
G["请求 /doc.html"] --> H["Knife4j 聚合各服务文档"]
```
图表来源
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md#L61-L73)
- [viewsh-gateway/src/main/resources/application.yaml](file://viewsh-gateway/src/main/resources/application.yaml#L34-L271)
章节来源
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md#L61-L73)
- [viewsh-gateway/src/main/resources/application.yaml](file://viewsh-gateway/src/main/resources/application.yaml#L34-L271)
### 系统模块接口示例RPC
- 管理员用户查询
- 通过用户 ID 查询用户
- 获取用户列表(按 ID、部门、岗位
- 校验用户有效性
- 权限相关
- 获取拥有多个角色的用户编号集合
- 短信验证码
- 发送验证码
- 使用验证码
- 校验验证码有效性
- 社交用户
- 绑定社交用户
- 解绑社交用户
- 基于用户 ID 或授权码获取社交用户
```mermaid
sequenceDiagram
participant Client as "调用方"
participant Gateway as "网关"
participant System as "system-server"
Client->>Gateway : "GET /admin-api/system/user/v3/api-docs"
Gateway->>System : "转发请求"
System-->>Gateway : "OpenAPI 文档"
Gateway-->>Client : "聚合后的文档"
```
图表来源
- [viewsh-gateway/src/main/resources/application.yaml](file://viewsh-gateway/src/main/resources/application.yaml#L221-L271)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/user/AdminUserApi.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/user/AdminUserApi.java#L32-L82)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/permission/PermissionApi.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/permission/PermissionApi.java#L22-L26)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/sms/SmsCodeApi.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/sms/SmsCodeApi.java#L24-L34)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/social/SocialUserApi.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/social/SocialUserApi.java#L24-L54)
章节来源
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/user/AdminUserApi.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/user/AdminUserApi.java#L32-L82)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/permission/PermissionApi.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/permission/PermissionApi.java#L22-L26)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/sms/SmsCodeApi.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/sms/SmsCodeApi.java#L24-L34)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/social/SocialUserApi.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/social/SocialUserApi.java#L24-L54)
### 物联网模块接口示例RPC
- 设备属性查询
- 批量查询设备属性历史
- 查询设备属性当前值
- 设备状态查询
- 查询设备状态
- 设备控制
- 控制设备执行服务(如下发指令)
```mermaid
sequenceDiagram
participant Client as "调用方"
participant Gateway as "网关"
participant IOT as "iot-server"
Client->>Gateway : "POST /admin-api/iot/device/control"
Gateway->>IOT : "转发控制请求"
IOT-->>Gateway : "控制结果"
Gateway-->>Client : "统一响应"
```
图表来源
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceControlApi.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceControlApi.java)
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDevicePropertyQueryApi.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDevicePropertyQueryApi.java)
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceStatusQueryApi.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceStatusQueryApi.java)
章节来源
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceControlApi.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceControlApi.java)
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDevicePropertyQueryApi.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDevicePropertyQueryApi.java)
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceStatusQueryApi.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceStatusQueryApi.java)
### 错误码与异常处理
- 系统模块错误码
- 包括认证、菜单、角色、用户、部门、岗位、字典、通知公告、短信渠道、短信模板、短信发送、短信验证码、租户、租户套餐、社交用户、OAuth2 客户端与授权、邮箱账号、邮件模板、邮件发送、站内信模板与发送等。
- 物联网模块错误码
- 包括产品、物模型、设备、产品分类、设备分组、OTA 固件与任务、数据流转规则与目的、场景联动、告警配置与记录等。
章节来源
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/enums/ErrorCodeConstants.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/enums/ErrorCodeConstants.java#L12-L171)
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/enums/ErrorCodeConstants.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/enums/ErrorCodeConstants.java#L12-L82)
## 依赖关系分析
- 网关依赖
- 通过路由规则将不同前缀的请求转发到对应服务;
- 通过 Knife4j 配置聚合各服务的 OpenAPI 文档。
- 服务间依赖
- 后端服务通过 OpenFeign 定义 RPC 接口契约,便于跨服务调用与文档生成。
```mermaid
graph LR
G["网关配置"] --> R["路由规则"]
G --> K["Knife4j 聚合"]
SYS["system-server"] --> FEIGN_SYS["Feign 接口定义"]
IOT["iot-server"] --> FEIGN_IOT["Feign 接口定义"]
```
图表来源
- [viewsh-gateway/src/main/resources/application.yaml](file://viewsh-gateway/src/main/resources/application.yaml#L34-L271)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/user/AdminUserApi.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/user/AdminUserApi.java#L25-L28)
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceControlApi.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceControlApi.java)
章节来源
- [viewsh-gateway/src/main/resources/application.yaml](file://viewsh-gateway/src/main/resources/application.yaml#L34-L271)
- [viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/user/AdminUserApi.java](file://viewsh-module-system/viewsh-module-system-api/src/main/java/com/viewsh/module/system/api/user/AdminUserApi.java#L25-L28)
- [viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceControlApi.java](file://viewsh-module-iot/viewsh-module-iot-api/src/main/java/com/viewsh/module/iot/api/device/IotDeviceControlApi.java)
## 性能考量
- 网关缓冲区与序列化配置
- 调整内存缓冲区大小,优化大对象传输;
- Jackson 序列化配置,减少时间戳单位开销。
- 文档聚合与缓存
- 通过 Knife4j 聚合文档,减少客户端多次请求;
- 合理设置服务实例灰度负载均衡,提升可用性。
- 限流与熔断
- 结合网关与服务侧限流策略,保障系统稳定性。
章节来源
- [viewsh-gateway/src/main/resources/application.yaml](file://viewsh-gateway/src/main/resources/application.yaml#L8-L18)
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md#L38-L38)
## 故障排查指南
- 未登录或 Token 过期
- 检查请求头 Authorization 是否正确携带 Bearer Token
- 若使用 WebSocket确认 token 参数是否正确传递。
- 无权限访问
- 确认用户角色与权限范围;
- 检查免登录白名单配置。
- 请求过于频繁
- 检查限流策略与客户端重试逻辑;
- 适当降低请求频率或增加退避策略。
- 系统内部错误
- 查看服务日志与异常堆栈;
- 确认数据库连接与第三方依赖可用性。
章节来源
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md#L35-L41)
- [viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/SecurityProperties.java](file://viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/SecurityProperties.java#L17-L28)
- [viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/ViewshWebSecurityConfigurerAdapter.java](file://viewsh-framework/viewsh-spring-boot-starter-security/src/main/java/com/viewsh/framework/security/config/ViewshWebSecurityConfigurerAdapter.java#L1-L23)
## 结论
本接口文档基于项目现有实现与技术文档整理而成,明确了 RESTful API 规范、认证授权机制、版本与路由策略、统一响应结构与错误码体系,并提供了测试与调试方法。建议在后续迭代中持续完善各服务的 OpenAPI 文档,确保接口定义与实现保持一致。
## 附录
### 版本管理与兼容策略
- 版本控制
- URL 中包含版本号(如 v1、v3便于平滑演进与向后兼容。
- 兼容策略
- 新增字段采用可选策略,避免破坏既有客户端;
- 旧字段保留并标注废弃,逐步迁移。
章节来源
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md#L9-L16)
### 接口调用示例
- 设备遥测数据上报HTTP
- URL/iot/device/report
- 方法POST
- 请求体包含产品标识、设备名称、时间戳与参数对象
- 创建维修工单(管理后台)
- URL/admin-api/ops/work-order/create
- 方法POST
- 请求头Authorization: Bearer {token}
- 请求体包含工单类型、设备 ID、描述与优先级
章节来源
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md#L77-L106)
### 测试工具与调试方法
- IDEA HTTP Client 环境变量
- 可通过环境文件配置基础 URL、Token 等参数,便于批量测试。
- 文档聚合入口
- 通过统一入口访问 Knife4j 聚合文档,快速定位接口与参数。
章节来源
- [script/idea/http-client.env.json](file://script/idea/http-client.env.json)
- [docs/technical-overview/07-接口文档.md](file://docs/technical-overview/07-接口文档.md#L49-L51)