编程导航API接口话题讨论

API接口

49 参与
分享

快来分享你的内容吧~

点击登录,快来和大家讨论吧~
表情
图片
话题
打卡
综合
交流
文章
问答

什么是 RESTful API?凭什么能流行 20 多年?

你是小阿巴,刚入职的后端程序员,负责给前端的阿花提供 API 接口。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/TkuTU5AD18ieWzQD.webp) 结果一周后,你被阿花揍得鼻青脸肿。 阿花:你是我这辈子见过接口写的最烂的程序员! ![](https://pic.code-nav.cn/post_picture/1601072287388278786/g6plcJC6IW9pljzt.webp) 你一脸委屈找到号称 “开发之狗” 的鱼皮诉苦:接口不是能跑就行吗? ![](https://pic.code-nav.cn/post_picture/1601072287388278786/KOKQxUaERG2cYhY8.webp) 鱼皮嘲笑道:小阿巴,你必须得学学 **RESTful API** 了。 你挠挠头:阿巴阿巴,什么玩意,没听说过! ⭐️ 推荐观看视频版,动画更生动:https://bilibili.com/video/BV1WFBXBmExs ## 什么是 RESTful API? 鱼皮:首先,REST 的全称是 **REpresentational State Transfer**,翻译过来叫 “表现层状态转移”。 你一脸懵:鱼皮 gie gie,能说人话吗?我是傻子,听不太懂。 鱼皮:别急,我给你拆开来讲,保证你理解。 **RE(Representational)** 表现层,是指 **资源(Resource)** 的表现形式。 你好奇了:什么是资源? 鱼皮:资源就是 **你想要操作的数据对象**。 比如用户、商品、文章,这些都是资源。用户列表是一个资源,某个具体的用户也是一个资源。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/vHvLDW8P8lcHY1eo.webp) 表现层是指资源呈现出来的具体格式,比如同一个用户资源,可以用 JSON 格式返回给客户端,也可以用 XML 格式返回,这就是不同的 “表现形式”。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/12qVgr4SSV3b6blE.webp) **S(State)** 是指 “状态”。 你:啥是状态? 鱼皮:比如你登录网站后,服务器会在内存中记住 “你是谁”,之后在网站上操作就不用再次登录了,这就是 **有状态**。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/Q5ib13VLwiL4mqZ5.webp) 而 **无状态(Stateless)** 呢,就是服务器不记录客户端的任何信息,每次请求都是独立的。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/46nMMLOSBGV4LrTT.webp) 你:哦哦哦,就像一个人去餐厅吃饭,服务员不记得他上次点了什么,每次都要重新点单,这就是无状态。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/n3G3UQu4UWyImcqa.webp) 反过来,服务员记得他爱吃鱼皮,这就是有状态。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/MYWy82TcYQUtYB9N.webp) 鱼皮:没错,接下来是 **T(Transfer)** 转移。 要注意,转移是 **双向** 的: 1)当你用 GET 请求时,服务器把资源的状态(比如用户信息的 JSON 数据)转移给客户端。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/Aby8xPT6dBxu1Fm4.webp) 2)当你用 POST/PUT 请求时,客户端把资源的新状态(比如新用户的信息)转移给服务器,从而改变服务器上资源的状态。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/ByKqYEhMJGQgVLMA.webp) 组合起来,**REST(Representational State Transfer)** 是一种 **软件架构风格**,让客户端和服务器通过统一的接口,以无状态的方式,互相传递资源的表现层数据(比如 JSON),来查询或者变更资源状态。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/36lZK68tvqYHY8z8.webp) 而 **ful** 是个后缀,就像 powerful(充满力量的)一样,表示 “充满...特性的”。 因此,**RESTful API 是指符合 REST 架构风格的 API**,也就是遵循 REST 原则设计出来的接口。 注意,它 **不是协议、不是标准、不是强制规范**,只是一种建议的设计风格。你可以遵循,也可以不遵循。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/JEVV4wDWqqiVjhjh.webp) 你挠了挠头:说了一大堆,RESTful API 到底长啥样啊? 鱼皮:举个例子,比如你要做个用户管理系统,对用户信息进行增删改查,用 RESTful 风格的 API 就长这样: ```plain GET /users/123 获取 ID 为 123 的用户 POST /users 创建新用户 PUT /users/123 更新用户 123 DELETE /users/123 删除用户 123 ``` 你眼前一亮:哇,比我写的整齐多了! ![](https://pic.code-nav.cn/post_picture/1601072287388278786/UlgqBwbLue7Z9TT4.webp) 快带我学一下 RESTful 的写法吧,我要让前端阿花刮目相看! ![](https://pic.code-nav.cn/post_picture/1601072287388278786/hZQJLJAZMaCjN02a.jpg) ## RESTful API 写法 鱼皮:好,很有志气!接下来我会带你一步步构造一个完整的 RESTful API。分为两部分,**客户端发送请求** 和 **服务端给出响应**。 ### 客户端请求 #### 第一步:确定资源 资源用 URI(统一资源标识符)来表示。核心原则是:**用名词来表示资源,不用动词**。 具体来说,**推荐用名词复数表示资源集合**,比如 `/users` 表示用户列表、`/products` 表示商品列表。 如果要操作 **具体某个资源,就加上 ID**,比如 `/users/123` 表示 ID 为 123 的用户。 资源还 **支持嵌套**,比如 `/users/123/orders` 表示用户 123 的所有订单。 你想了想:那还可以更深层级么?比如 `/users/123/orders/456` 表示用户 123 的订单 456。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/0bDvQHbjkIDw67SS.webp) 鱼皮点点头:你的理解完全正确,但不建议嵌套层级太深。 #### 第二步:选择动作 确定了资源后,接下来要选择 **动作**,也就是你想怎么处理这个资源。 RESTful API 主要通过不同的 HTTP 方法来表示增删改查操作: 1)GET:查询资源 - `GET /users` 查询所有用户 - `GET /users/123` 查询 ID 为 123 的用户 2)POST:创建资源 - `POST /users` 创建新用户 3)PUT:完整更新资源,需要提供资源的所有字段,多次执行结果相同(幂等性) - `PUT /users/123` 完整更新用户 123 4)PATCH:部分更新资源,通常用于更精细的操作 - `PATCH /users/123` 只更新用户 123 的某些字段 5)DELETE:删除资源 - `DELETE /users/123` 删除用户 123 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/FVqWpAeODtaIbNJG.webp) 鱼皮:到这里,一个基本的 RESTful API 请求就构造完成了。 你:就这么简单?我不满足,还有更高级的写法吗? 鱼皮:当然~ #### 第三步:添加查询条件(可选) 有时候我们需要更精确地筛选数据,这时候可以加查询参数,比如: - 分页:`/users?page=2&limit=10` 查询第 2 页,每页 10 条用户数据 - 过滤:`/users?gender=male&age=25` 查询性别为男、年龄 25 的用户 - 排序:`/users?sort=created_at&order=desc` 按创建时间倒序排列用户 你:等等,这查询参数跟 RESTful 有啥关系?正常的请求不都是这么写吗? 鱼皮:确实,查询参数本身不是 RESTful 特有的。但 RESTful 风格强调 **把筛选、排序、分页这些操作,都通过 URL 参数来表达**: ![](https://pic.code-nav.cn/post_picture/1601072287388278786/y5zh1zCnoZTIZZXr.webp) 而不是在请求体里传一堆复杂的 JSON 对象: ![](https://pic.code-nav.cn/post_picture/1601072287388278786/iKfL2kuKAXL3SXUh.webp) 这样一来,URL 更清晰,而且浏览器、CDN、代理服务器都能直接根据 URL 来缓存响应结果。比如 `/users?page=1` 和 `/users?page=2` 是两个不同的 URL,可以分别缓存。但如果把参数放在请求体里,URL 都是 `/users`,缓存就没法区分了。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/flWpltT20T8pc0dR.webp) #### 第四步:版本控制(可选) 随着业务发展,接口可能需要升级。为了不影响老用户,可以在 URI 中标明版本: - `/v1/users` 第一版用户接口 - `/v2/users` 第二版用户接口 这样,老用户继续用 v1,新用户用 v2,互不影响。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/tNWhxUrWKJgESYlx.webp) #### 第五步:保持无状态 此外,还记得我们前面讲 REST 里的 **ST(State Transfer)** 吗? RESTful 的核心原则之一是 **无状态(Stateless)**,客户端每次请求必须包含所有必要信息,服务器不记录客户端状态。 比如用户登录后,不是让服务器记住 “你已经登录了”,而是每次请求都要带上身份凭证(Token),像这样: ```plain GET /orders Header: Authorization: Bearer xxx ``` 这么做的好处是,服务器不用记录谁登录了、谁没登录,每个请求都是独立的。这样一来,你想加多少台服务器都行,任何一台都能处理请求,轻松实现负载均衡和横向扩展。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/bDtdMK7Uyqyhg828.webp) 你点头如捣蒜:怪不得我调用 AI 大模型 API 的时候,就要传这个 Token! ### 服务端响应 鱼皮:讲完客户端请求,再来看服务器收到请求后,该怎么响应? 主要注意 2 点: #### 1、统一响应格式 目前大多数 RESTful API 基本都用 **JSON** 格式,因为轻量、容易解析。 ```json { "id": 123, "name": "小阿巴", "email": "aba@codefather.cn" } ``` 但这并不是强制的,也可以用 XML、HTML 等格式。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/zfh0hkzMUCV9sBvn.webp) #### 2、返回合适的 HTTP 状态码 响应要带上合适的状态码,让客户端一眼看懂发生了什么。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/Si9vogR8DSgHmDBs.webp) HTTP 状态码有很多,大体可以分为 5 类: - **1xx 系列**:信息提示(用得少,了解即可) - **2xx 系列**:成功 - 200 OK:请求成功,正常返回数据(用于 GET、PUT、PATCH) - **3xx 系列**:重定向 - 301 Moved Permanently:资源永久移动到新位置 - 302 Found:资源临时移动 - **4xx 系列**:客户端错误 - 400 Bad Request:请求参数格式错误 - 401 Unauthorized:未验证身份,需要登录 - 403 Forbidden:已认证但没有权限访问 - 404 Not Found:资源不存在 - 405 Method Not Allowed:请求方法不被允许 - **5xx 系列**:服务器错误 - 500 Internal Server Error:服务器内部错误 - 502 Bad Gateway:网关错误 - 503 Service Unavailable:服务暂时不可用 - 504 Gateway Timeout:网关超时 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/UOz2xWrJY91jSjQs.webp) 你恍然大悟:懂了,以后前端看到 500,就知道是我后端的锅;看到 400,就知道是她自己传参传错了。谁也别想甩锅! ![](https://pic.code-nav.cn/post_picture/1601072287388278786/W9ZMEo4VFoTkvZKr.webp) 鱼皮点点头:不错,以上这些,就是 RESTful API 的基本写法。你学会了吗? 你:学废了,学废了! ![](https://pic.code-nav.cn/post_picture/1601072287388278786/HHayn4PaZ2W8MlVq.webp) 鱼皮:那我来考考你,下面哪个是标准的 RESTful API? - A. `GET /getUsers` - B. `GET /user/list` - C. `POST /users/query` - D. `GET /users/delete/123` 你开心地怪叫起来:阿巴,肯定是 C 啊! ![](https://pic.code-nav.cn/post_picture/1601072287388278786/Dl8pVrGE8ULrgJel.webp) 鱼皮:错,**4 个全都不标准**! - A 用了动词 `getUsers` - B 用了单数 `user` 和动词 `list` - C 用 POST 查询,还带了动词 `query` - D 用 GET 删除,还带了动词 `delete` 你掉了根头发:原来这么严格! ![](https://pic.code-nav.cn/post_picture/1601072287388278786/fJILPyU9XJIv1OFR.webp) 等等,你说 RESTful 不能用动词,但有些操作不是标准的增删改查啊,比如用户要支付订单,该怎么设计接口呢?是要用 `POST /orders/123/pay`? 鱼皮摇头:你已经很努力了,但 pay 是动词。更标准的设计是把 “支付” 行为看作 **创建** 一个支付记录,用名词而不是动词。 ```plain POST /orders/123/payments ``` 比如这个请求,表示为订单 123 创建一笔支付记录。 你又掉了根头发:妙啊,怪不得说英语对学编程有帮助呢,我悟了,我悟了! ![](https://pic.code-nav.cn/post_picture/1601072287388278786/4VahWfatV8QtUCjw.webp) ## RESTful 的六大约束 鱼皮:不错,学到这里你已经掌握了 RESTful 的 80%,能够实际应用了。接下来的知识,你只需简单了解一下,就能拿去和面试官吹牛皮了。 比如很多同学都不知道,RESTful 其实有 6 个约束条件: 1. Client-Server(客户端-服务器分离):前后端各干各的活,前端负责展示,后端负责数据处理,互不干扰。 2. Stateless(无状态):每次请求都是独立的,服务器不保存客户端的会话信息,所有必要信息都在请求中携带。 3. Cacheable(可缓存):服务器的响应可以被标记为可缓存或不可缓存,客户端可以重用缓存数据,减少服务器压力,提升性能。 4. Layered System(分层系统):客户端不需要知道直接连的是服务器还是中间层,系统可以灵活地加代理、网关、负载均衡器等。 5. Uniform Interface(统一接口):所有资源都通过统一的接口访问,降低理解成本,提高可维护性。 6. Code-On-Demand(按需代码):可选项,服务器可以返回可执行代码(比如 JavaScript)给客户端执行,但实际工作中很少用。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/yqJF0ZFqpAJeM22V.webp) 你直接听懵了:阿巴阿巴,这么多约束,我必须全遵守吗? 鱼皮:可以不用,RESTful 只是一种 API 的 **建议风格**。在实际工作中,很少有 API 能完美符合所有约束,大家可以灵活调整,甚至什么接口都用 **POST + 动词** 一把梭。只要团队达成一致、用得舒服就行。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/g7oLwtBMD6gvg4aa.webp) 就像刚才那个支付订单的例子,`POST /orders/123/payments` 虽然符合 RESTful 规范,但有同学会觉得 `POST /orders/123/pay` 更直观易懂,也没问题。 不过现阶段,我建议你先养成遵循 RESTful 的好习惯,等积累了经验,再根据实际情况灵活调整。 ### 怎么快速实现 RESTful API? 你:呜呜,但我只是个小阿巴,背不下来这些写法,我怕自己写着写着就不规范了,怎么办啊? ![](https://pic.code-nav.cn/post_picture/1601072287388278786/208lL0V3fRstcUnG.webp) 鱼皮:别担心,有很多方法可以帮你快速实现和检查 RESTful API。 #### 1、使用开发框架 几乎所有主流开发框架都支持 RESTful API 的开发,它们能帮你自动处理很多细节,比如: - Java 的 Spring Boot:通过 `@GetMapping("/users")`、`@PostMapping("/users")` 等注解,你只需要写一行代码就能定义符合 RESTful 风格的路由。框架会自动把对象转成 JSON、设置正确的 HTTP 状态码,你都不用操心。 - Python 的 Django REST Framework:你只需要定义一个数据模型(比如 User 类),框架就能自动生成 `GET /users`、`POST /users`、`PUT /users/123`、`DELETE /users/123` 这一整套 RESTful 接口,大幅减少代码量。 - Go 的 Gin :专门为 RESTful API 设计,语法非常简洁。比如 `router.GET("/users/:id", getUser)` 就能绑定一个 GET 请求,自动从 URL 中提取 ID 参数,还能通过路由分组把 `/api/v1/users` 和 `/api/v2/users` 轻松分开管理。 这些框架虽然不强制你遵循 RESTful,但用它们的特性,开发起来既轻松又规范,帮你省掉大量重复代码。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/aO87TZ9rCvuPII3X.webp) #### 2、使用 IDE 插件 比如 IDEA 的 RESTful Toolkit 插件,可以快速查看和测试接口。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/DLcjCFuozhAFRwWe.webp) 还有 VSCode 的 REST Client 插件,可以直接在编辑器里测试接口。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/9PfcForelLcwbbrj.webp) #### 3、利用 AI 生成 RESTful 有明确的设计规范,而 AI 最擅长处理这种有章可循的东西! 比如直接让 Cursor 帮你用 Spring Boot 写一个用户管理的 RESTful API: ![](https://pic.code-nav.cn/post_picture/1601072287388278786/H00Z7iFxru5pIMF2.webp) 你只需要阿巴阿巴几下,它就能生成规范的代码。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/XJP5OM4XSrs21hci.webp) #### 4、生成接口文档 写完接口后,还可以用 Swagger 这类工具自动生成漂亮的接口文档,直接甩给前端,对方一看就懂,还能在线测试接口,省去大量沟通成本。 ![](https://pic.code-nav.cn/post_picture/1601072287388278786/IrjncT3EafCs17GZ.webp) 你笑得像个孩子:这么一看,RESTful API 不仅让接口规范统一,还能提高开发效率,降低团队沟通成本,前后端都舒服!爽爽爽! ![](https://pic.code-nav.cn/post_picture/1601072287388278786/UHQoZ043K3dKSDaa.webp) 鱼皮点点头:没错,这也是为什么 RESTful 能成为业界主流的原因。 你:学会了学会了,我这就去重构所有接口,让前端阿花刮目相看! ![](https://pic.code-nav.cn/post_picture/1601072287388278786/hnc3pbdboBaZtze3.jpg) ### 结尾 一周后,你把所有接口重构成了 RESTful 风格。 前端阿花打开新的接口文档,眼睛亮了:小阿巴,你居然开窍了?! ![](https://pic.code-nav.cn/post_picture/1601072287388278786/PZgNkQJAGIUAudxp.webp) 你得意地笑了:那是,我可是学过 RESTful 的男人~ 阿花,晚上要不要一起? ![](https://pic.code-nav.cn/post_picture/1601072287388278786/1tmCv9wbzjv4hIxX.webp) 阿花朝你吐了口唾沫:呸,你只不过学了一种 API 风格就得意洋洋。阿坤哥哥不仅精通 RESTful,还能手撕 GraphQL 和 gRPC 呢,你行么? ![](https://pic.code-nav.cn/post_picture/1601072287388278786/CGBELSS9pjoeDUlk.webp) 你难受得不行:啥啥啥,这都是啥啊…… 鱼皮 gie gie 快来救我! ![](https://pic.code-nav.cn/post_picture/1601072287388278786/gGROU1IVerwHYTxM.webp) ## 更多 💻 编程学习交流:[编程导航](https://www.codefather.cn/) 📃 简历快速制作:[老鱼简历](https://www.laoyujianli.com) ✏️ 面试刷题神器:[面试鸭](https://www.mianshiya.com) 📖 AI 学习指南:[AI 知识库](https://ai.codefather.cn/)

什么!!Qi-API接口开放平台仅需两步就可以动态调用新接口!!?😱

大家好我是柒木,想必做过接口开放平台的同学都会遇到 **`该怎么动态发布并调用新接口?`** 这个棘手的问题,这次呢就带大家带来解决这一大难题!😎 ## 相关网址导航 - [**Qi-API 后端 🏘️**](https://github.com/qimu666/qi-api) - [**Qi-API 前端 🏘**️](https://github.com/qimu666/qi-api-frontend) - **[Qi-API-SDK](https://github.com/qimu666/qi-api-sdk)** 🛠 - **[Qi-API-DOC 开发者文档 📖](https://doc.qimuu.icu/)** - **[Qi-API-SDK-demo ✔️](https://github.com/qimu666/qi-api-sdk-demo/blob/master/src/main/java/icu/qimuu/qiapisdkdemo/controller/InvokeController.java)** - **[Qi-API 接口开放平台 🔗](https://api.qimuu.icu/)在线示例网站** - **[QI-API 接口开放平台Docker容器编排方式一键部署](https://www.codefather.cn/post/1873777441621065729)** - **[API 开放平台 笔记](https://www.codefather.cn/note/1805877794735435778)** ## 发布操作步骤(仅需两步!) 1. 在接口服务(interface)项目中开发新接口 (**接口服务可以是独立的项目,但需要在网关中配置路由**) 在接口服务开发一个测试接口: ```java @GetMapping("/test") public String test(String text) { return text; } ``` <img src="https://pic.code-nav.cn/post_picture/1611320795533934593/3VawI9t5ZLOpueip.webp" alt="image-20241231114908610" width="100%" /> 2. 开发完成后重启接口项目后,在管理员后台发布接口,就可以在线调用了!! <img src="https://pic.code-nav.cn/post_picture/1611320795533934593/RGWNRdHnfFHBOA3X.webp" alt="image-20241231115154224" width="100%" /> 3. 在接口大厅找到并请求接口 <img src="https://pic.code-nav.cn/post_picture/1611320795533934593/bqM2SSIBCcRPxUQW.webp" alt="image-20241231115407585" width="100%" /> 4. 恭喜发布成功!! 是不是非常简便!!实现这一功能全得意于 **[Qi-API-SDK](https://github.com/qimu666/qi-api-sdk)** 🛠 ## 实现原理 回到正题,我们看一下 **[Qi-API 接口开放平台 🔗](https://api.qimuu.icu/)** 是怎么动态调用接口的 ```java @PostMapping("/invoke") @Transactional(rollbackFor = Exception.class) public BaseResponse<Object> invokeInterface(@RequestBody InvokeRequest invokeRequest, HttpServletRequest request) { if (ObjectUtils.anyNull(invokeRequest, invokeRequest.getId()) || invokeRequest.getId() <= 0) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } Long id = invokeRequest.getId(); InterfaceInfo interfaceInfo = interfaceInfoService.getById(id); if (interfaceInfo == null) { throw new BusinessException(ErrorCode.NOT_FOUND_ERROR); } if (interfaceInfo.getStatus() != InterfaceStatusEnum.ONLINE.getValue()) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "接口未开启"); } // 构建请求参数 List<InvokeRequest.Field> fieldList = invokeRequest.getRequestParams(); String requestParams = "{}"; if (fieldList != null && fieldList.size() > 0) { JsonObject jsonObject = new JsonObject(); for (InvokeRequest.Field field : fieldList) { jsonObject.addProperty(field.getFieldName(), field.getValue()); } requestParams = gson.toJson(jsonObject); } Map<String, Object> params = new Gson().fromJson(requestParams, new TypeToken<Map<String, Object>>() { }.getType()); UserVO loginUser = userService.getLoginUser(request); String accessKey = loginUser.getAccessKey(); String secretKey = loginUser.getSecretKey(); try { QiApiClient qiApiClient = new QiApiClient(accessKey, secretKey); CurrencyRequest currencyRequest = new CurrencyRequest(); currencyRequest.setMethod(interfaceInfo.getMethod()); currencyRequest.setPath(interfaceInfo.getUrl()); currencyRequest.setRequestParams(params); ResultResponse response = apiService.request(qiApiClient, currencyRequest); return ResultUtils.success(response.getData()); } catch (Exception e) { throw new BusinessException(ErrorCode.SYSTEM_ERROR, e.getMessage()); } } ``` 可以看到在调用前构建了一个通用请求,将请求参数、请求方法、请求地址都传递给sdk提供的request方法,request是一个接口 接收QiApiClient客户端和一个泛型的通用请求。 ```java /** * 通用请求 * * @param qiApiClient qi api客户端 * @param request 要求 * @return {@link T} * @throws ApiException 业务异常 */ <O, T extends ResultResponse> T request(QiApiClient qiApiClient, BaseRequest<O, T> request) throws ApiException; ``` CurrencyRequest通过继承BaseRequest约定参数调用接口 ```java public class CurrencyRequest extends BaseRequest<Object, ResultResponse> { private String method; private String path; /** * get方法 * * @return {@link String} */ @Override public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } /** * 获取路径 * * @return {@link String} */ @Override public String getPath() { return path; } public void setPath(String path) { this.path = path; } /** * 获取响应类 * * @return {@link Class}<{@link ResultResponse}> */ @Override public Class<ResultResponse> getResponseClass() { return ResultResponse.class; } } ``` BaseRequest中通过@JsonAnyGetter将接收的参数进行转换, ```java public abstract class BaseRequest<O, T extends ResultResponse> { private Map<String, Object> requestParams = new HashMap<>(); /** * get方法 * * @return {@link RequestMethodEnum} */ public abstract String getMethod(); /** * 获取路径 * * @return {@link String} */ public abstract String getPath(); /** * 获取响应类 * * @return {@link Class}<{@link T}> */ public abstract Class<T> getResponseClass(); @JsonAnyGetter public Map<String, Object> getRequestParams() { return requestParams; } public void setRequestParams(O params) { this.requestParams = new Gson().fromJson(JSONUtil.toJsonStr(params), new TypeToken<Map<String, Object>>() { }.getType()); } } ``` 每一个继承BaseRequest都会转换,子类来明确sdk用户调用所需要传递的参数,而不是用户谁便传。 例如获取用户输入name接口,规定用户的请求和响应。用户只需要调用我们提供的方法即可完成调用 更多示例:[sdk-request-demo](https://github.com/qimu666/qi-api-sdk-demo/blob/master/src/main/java/icu/qimuu/qiapisdkdemo/controller/InvokeController.java) ```java NameRequest nameRequest = new NameRequest(); NameParams nameRequest = new NameParams(); nameRequest.setName("123"); nameRequest.setRequestParams(nameRequest); NameResponse name = apiService.getName(nameRequest); ``` ```java @Data @Accessors(chain = true) public class NameParams implements Serializable { private static final long serialVersionUID = 3815188540434269370L; private String name; } ``` ```java @Accessors(chain = true) public class NameRequest extends BaseRequest<NameParams, NameResponse> { @Override public String getPath() { return "/name"; } /** * 获取响应类 * * @return {@link Class}<{@link NameResponse}> */ @Override public Class<NameResponse> getResponseClass() { return NameResponse.class; } @Override public String getMethod() { return RequestMethodEnum.GET.getValue(); } } ``` 而CurrencyRequest类的请求参数泛型是Object类,也就是说用户可以自己传递参数,刚好我们可以在接口后台设置哪些参数可以请求,这一点实现了动态传参。 之后会调用抽象类中的request,使用模板方法构建必须的设置和检查 ```java @Slf4j @Data public abstract class BaseService implements ApiService { private QiApiClient qiApiClient; /** * 网关HOST */ private String gatewayHost = "https://gateway.qimuu.icu/api"; /** * 检查配置 * * @param qiApiClient qi api客户端 * @throws ApiException 业务异常 */ public void checkConfig(QiApiClient qiApiClient) throws ApiException { if (qiApiClient == null && this.getQiApiClient() == null) { throw new ApiException(ErrorCode.NO_AUTH_ERROR, "请先配置密钥AccessKey/SecretKey"); } if (qiApiClient != null && !StringUtils.isAnyBlank(qiApiClient.getAccessKey(), qiApiClient.getSecretKey())) { this.setQiApiClient(qiApiClient); } } /** * 执行请求 * * @param request 请求 * @return {@link HttpResponse} * @throws ApiException 业务异常 */ private <O, T extends ResultResponse> HttpResponse doRequest(BaseRequest<O, T> request) throws ApiException { try (HttpResponse httpResponse = getHttpRequestByRequestMethod(request).execute()) { return httpResponse; } catch (Exception e) { throw new ApiException(ErrorCode.OPERATION_ERROR, e.getMessage()); } } /** * 通过请求方法获取http响应 * * @param request 要求 * @return {@link HttpResponse} * @throws ApiException 业务异常 */ private <O, T extends ResultResponse> HttpRequest getHttpRequestByRequestMethod(BaseRequest<O, T> request) throws ApiException { if (ObjectUtils.isEmpty(request)) { throw new ApiException(ErrorCode.OPERATION_ERROR, "请求参数错误"); } String path = request.getPath().trim(); String method = request.getMethod().trim().toUpperCase(); if (ObjectUtils.isEmpty(method)) { throw new ApiException(ErrorCode.OPERATION_ERROR, "请求方法不存在"); } if (StringUtils.isBlank(path)) { throw new ApiException(ErrorCode.OPERATION_ERROR, "请求路径不存在"); } if (path.startsWith(gatewayHost)) { path = path.substring(gatewayHost.length()); } log.info("请求方法:{},请求路径:{},请求参数:{}", method, path, request.getRequestParams()); HttpRequest httpRequest; switch (method) { case "GET": { httpRequest = HttpRequest.get(splicingGetRequest(request, path)); break; } case "POST": { Map<String, Object> requestParams = request.getRequestParams(); String s = JSONUtil.toJsonStr(requestParams); System.err.println(s); httpRequest = HttpRequest.post(gatewayHost + path).body(JSONUtil.toJsonStr(request.getRequestParams())); break; } default: { throw new ApiException(ErrorCode.OPERATION_ERROR, "不支持该请求"); } } return httpRequest.addHeaders(getHeaders(JSONUtil.toJsonStr(request), qiApiClient)); } /** * 获取响应数据 * * @param request 要求 * @return {@link T} * @throws ApiException 业务异常 */ public <O, T extends ResultResponse> T res(BaseRequest<O, T> request) throws ApiException { if (qiApiClient == null || StringUtils.isAnyBlank(qiApiClient.getAccessKey(), qiApiClient.getSecretKey())) { throw new ApiException(ErrorCode.NO_AUTH_ERROR, "请先配置密钥AccessKey/SecretKey"); } T rsp; try { Class<T> clazz = request.getResponseClass(); rsp = clazz.newInstance(); } catch (Exception e) { throw new ApiException(ErrorCode.OPERATION_ERROR, e.getMessage()); } HttpResponse httpResponse = doRequest(request); String body = httpResponse.body(); Map<String, Object> data = new HashMap<>(); if (httpResponse.getStatus() != 200) { ErrorResponse errorResponse = JSONUtil.toBean(body, ErrorResponse.class); data.put("errorMessage", errorResponse.getMessage()); data.put("code", errorResponse.getCode()); } else { try { // 尝试解析为JSON对象 data = new Gson().fromJson(body, new TypeToken<Map<String, Object>>() { }.getType()); } catch (JsonSyntaxException e) { // 解析失败,将body作为普通字符串处理 data.put("value", body); } } rsp.setData(data); return rsp; } /** * 拼接Get请求 * * @param request 要求 * @param path 路径 * @return {@link String} */ private <O, T extends ResultResponse> String splicingGetRequest(BaseRequest<O, T> request, String path) { StringBuilder urlBuilder = new StringBuilder(gatewayHost); // urlBuilder最后是/结尾且path以/开头的情况下,去掉urlBuilder结尾的/ if (urlBuilder.toString().endsWith("/") && path.startsWith("/")) { urlBuilder.setLength(urlBuilder.length() - 1); } urlBuilder.append(path); if (!request.getRequestParams().isEmpty()) { urlBuilder.append("?"); for (Map.Entry<String, Object> entry : request.getRequestParams().entrySet()) { String key = entry.getKey(); String value = entry.getValue().toString(); urlBuilder.append(key).append("=").append(value).append("&"); } urlBuilder.deleteCharAt(urlBuilder.length() - 1); } log.info("GET请求路径:{}", urlBuilder); return urlBuilder.toString(); } /** * 获取请求头 * * @param body 请求体 * @param qiApiClient qi api客户端 * @return {@link Map}<{@link String}, {@link String}> */ private Map<String, String> getHeaders(String body, QiApiClient qiApiClient) { Map<String, String> hashMap = new HashMap<>(4); hashMap.put("accessKey", qiApiClient.getAccessKey()); String encodedBody = SecureUtil.md5(body); hashMap.put("body", encodedBody); hashMap.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); hashMap.put("sign", SignUtils.getSign(encodedBody, qiApiClient.getSecretKey())); return hashMap; } @Override public <O, T extends ResultResponse> T request(BaseRequest<O, T> request) throws ApiException { try { return res(request); } catch (Exception e) { throw new ApiException(ErrorCode.OPERATION_ERROR, e.getMessage()); } } @Override public <O, T extends ResultResponse> T request(QiApiClient qiApiClient, BaseRequest<O, T> request) throws ApiException { checkConfig(qiApiClient); return request(request); } } ``` 之后就会请求对应的接口,返回数据后尝试将数据转换为规定的格式,不成功就转换为普通值 ```java * 获取响应数据 * * @param request 要求 * @return {@link T} * @throws ApiException 业务异常 */ public <O, T extends ResultResponse> T res(BaseRequest<O, T> request) throws ApiException { if (qiApiClient == null || StringUtils.isAnyBlank(qiApiClient.getAccessKey(), qiApiClient.getSecretKey())) { throw new ApiException(ErrorCode.NO_AUTH_ERROR, "请先配置密钥AccessKey/SecretKey"); } T rsp; try { Class<T> clazz = request.getResponseClass(); rsp = clazz.newInstance(); } catch (Exception e) { throw new ApiException(ErrorCode.OPERATION_ERROR, e.getMessage()); } HttpResponse httpResponse = doRequest(request); String body = httpResponse.body(); Map<String, Object> data = new HashMap<>(); if (httpResponse.getStatus() != 200) { ErrorResponse errorResponse = JSONUtil.toBean(body, ErrorResponse.class); data.put("errorMessage", errorResponse.getMessage()); data.put("code", errorResponse.getCode()); } else { try { // 尝试解析为JSON对象 data = new Gson().fromJson(body, new TypeToken<Map<String, Object>>() { }.getType()); } catch (JsonSyntaxException e) { // 解析失败,将body作为普通字符串处理 data.put("value", body); } } rsp.setData(data); return rsp; } ``` 这样就能够实现动态调用接口啦!!! <img src="https://pic.code-nav.cn/post_picture/1611320795533934593/AL6Gx3tc1AGx7a6o.webp" alt="image-20241231125721180" width="100%" />

Swagger3携手Alova.Js:轻松驾驭自动化,后端接口秒级生成!

<h1 id="WhS6j"><font style="color:rgb(28, 30, 33);">概述</font></h1> <font style="color:rgb(28, 30, 33);">在这个前面我有一篇讲过关于前端如何快速生成文档的帖子是基于 </font>[<font style="color:rgb(28, 30, 33);">knife4j-openapi3与Umi/OpenApi</font>](https://www.codefather.cn/post/1807127065467867137)<font style="color:rgb(28, 30, 33);">,在这个帖子中,要将后端接口改成指定的格式才能让前端生成,对于后端的我来说有点小痛苦,我就不想按照这个格式写(其实是因为生成的 CURD 需要修改下对应的 Swagger 格式 ) ,我就在想,有没有其他的框架,能够直接根据我这个文档生成呢?不出意外就是我们下面说到底 Alova.js 了</font> <h1 id="OAmYH"><font style="color:rgb(28, 30, 33);">Alova 介绍</font></h1> <font style="color:rgb(28, 30, 33);">官方文档:</font>[https://alova.js.org/zh-CN/](https://alova.js.org/zh-CN/) <font style="color:rgb(28, 30, 33);">alova(读作`/əˈləʊva/`<font style="color:rgb(28, 30, 33);">) 是一个流程简化的下一代请求工具,它可以将你的 API 集成工作流从 7 个步骤极致地简化为 1 个步骤,你只需要选择 API 即可使用。看官方给出的图片:</font> <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/gu3sOI4Zr1fnnZBH.webp" alt="" width="100%" /> 看图是不是很清晰,通俗点就是一键生成代码,然后选择接口用就好了!! <h2 id="IxCsW">相对于其他的请求库 Alova 有什么优势呢?</h2> 官方文档这里给出来表格我这里就不过多解释了 https://alova.js.org/zh-CN/about/comparison/ <h2 id="tjLJU">运行环境</h2> Alova 使用能够支持 React 吗?支持 Vue3 吗?通通支持,看官网介绍给出的解释是能够支持任何 JS 运行环境 <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/85bIYhTcFRyIqcg0.webp" alt="" width="100%" /> Alova 还支持缓存、并行发送请求、自动管理请求状态等等..... <h1 id="HROvv">快速入门 </h1> <h2 id="SsHP7">创建项目</h2> 这里我们快速创建一个 Vite +Vue3 项目来使用一下这个 Alova.Js 最主要的是要验证是否能够 自动生成文档! > **<font style="color:rgb(69, 76, 225);">兼容性注意</font>** > > <font style="color:rgb(69, 76, 225);">Vite 需要 </font>[<font style="color:rgb(69, 76, 225);">Node.js</font>](https://nodejs.org/en/)<font style="color:rgb(69, 76, 225);"> 版本 18+ 或 20+。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。</font> > > <font style="color:rgb(69, 76, 225);">我这里使用的 Node 版本是 20.17.0</font> > <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/MWlD3qkzv1BAQLJI.webp" alt="" width="100%" /> <h3 id="zAY8C">打开 cmd 窗口创建 Vite 项目</h3> ```shell npm create vite@latest ``` > 名称应该是 alova !!! > <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/Yvc7CKH8QLWU3oVp.webp" alt="" width="100%" /> <h3 id="su9k7">使用 WebStrom 来打开这个项目</h3> 如下图所示: <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/7RNZDarYCnGeKqkH.webp" alt="" width="100%" /> <h3 id="w1yp0">安装依赖</h3> 打开项目控制台输入如下命令安装依赖让项目跑起来 ```shell npm install ``` <h3 id="YyBP7">运行项目</h3> <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/eF2Kob3FqoUHk0Yl.webp" alt="" width="100%" /> <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/2bEkd1ulwWiZtkj9.webp" alt="" width="100%" /> <h2 id="xnY8W">引入 Alova.js</h2> <h3 id="BOpGO">安装 Alova.js 依赖</h3> ```shell npm install alova --save ``` <h3 id="HA5Oo">安装扩展</h3> ```shell npm install @alova/wormhole --save-dev ``` > <font style="color:rgb(28, 30, 33);">同时安装</font>`<font style="color:rgb(28, 30, 33);">@alova/wormhole</font>`<font style="color:rgb(28, 30, 33);">和 alova 的 vscode 扩展可以享受到完整的特性,</font>`<font style="color:rgb(28, 30, 33);">@alova/wormhole</font>`<font style="color:rgb(28, 30, 33);">提供自动生成特性,vscode 扩展可以快速调用</font>`<font style="color:rgb(28, 30, 33);">@alova/wormhole</font>`<font style="color:rgb(28, 30, 33);">的能力,并提供在编辑器中快速查找接口文档的快捷键。</font> > 1. 对于 vscode 打开的朋友,这里能够直接安装 vsode 插件一键生成文档哦 官方文档:[https://alova.js.org/zh-CN/tutorial/getting-started/extension-integration](https://alova.js.org/zh-CN/tutorial/getting-started/extension-integration) 2. 我这里使用的 WebStrom 官方这里也给出了命令调用,我们可以封装到 pageckage.json 中 官方文档:[https://alova.js.org/zh-CN/api/wormhole/#commands](https://alova.js.org/zh-CN/api/wormhole/#commands) 1. 自动生成配置文件 ```shell alova init [-t, --type <type>] [-c --cwd <path>] ``` > <font style="color:rgb(28, 30, 33);">在当前目录下生成 alova.config 配置文件,它将会根据项目类型自动生成不同后缀的配置文件。</font> > > **<font style="color:rgb(28, 30, 33);">参数:</font>** > > + **<font style="color:rgb(28, 30, 33);">-t, --type</font>**<font style="color:rgb(28, 30, 33);">:指定要生成的配置文件类型,可选值有:</font>`<font style="color:rgb(28, 30, 33);">auto/ts/typescript/module/commonjs</font>`<font style="color:rgb(28, 30, 33);">,默认为</font>`<font style="color:rgb(28, 30, 33);">auto</font>`<font style="color:rgb(28, 30, 33);">,它将根据项目类型自动生成不同后缀的配置文件。</font> > + **<font style="color:rgb(28, 30, 33);">-c, --cwd <path></font>**<font style="color:rgb(28, 30, 33);">:指定要生成的配置文件的工作目录,默认为当前目录。</font> > 生成的这个配置类似于 umi 那个能够填写后端 API 接口 json 文件的哪个配置 2. 根据配置文件生成对应的 API 接口文档 ```shell alova gen [-f, --force] [-c --cwd <path>] [-w --workspace] ``` > <font style="color:rgb(28, 30, 33);">gen 将会查找</font>`<font style="color:rgb(28, 30, 33);">alova.config.{cjs,js,mjs,ts}</font>`<font style="color:rgb(28, 30, 33);">配置文件并使用它自动生成 API 相关信息。</font> > > **<font style="color:rgb(28, 30, 33);">参数:</font>** > > + **<font style="color:rgb(28, 30, 33);">-f, --force</font>**<font style="color:rgb(28, 30, 33);">:默认情况下,将会检查最新的 openAPI 文件是否有更新,指定此参数后将会忽略检查,并强制重新生成。</font> > + **<font style="color:rgb(28, 30, 33);">-c, --cwd <path></font>**<font style="color:rgb(28, 30, 33);">:指定要生成的配置文件的工作目录,默认为当前目录。</font> > + **<font style="color:rgb(28, 30, 33);">-w, --workspace</font>**<font style="color:rgb(28, 30, 33);">:指定是否以 workspace 的方式生成,它将会根据</font>`package.json`<font style="color:rgb(28, 30, 33);">中的</font>`workspaces`<font style="color:rgb(28, 30, 33);">,或</font>`pnpm-workspace.yaml`<font style="color:rgb(28, 30, 33);">中定义的子包来查找配置文件,并生成所有子包的 API 相关信息。</font> > 这里直接 `alova init` 试试 <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/bNKdcUMainqal0oR.webp" alt="" width="100%" /> 一般情况下,我们修改这个 input 就好 <h3 id="KcJNT">启动后端项目</h3> 这个项目是我正在学习的一个项目,是 B 站 uniapp 的一个壁纸项目,我想给后台和后台管理页面做出来 然后 uniapp 也做出来,仅供学习哈 <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/rJpawUf19d4CalnA.webp" alt="" width="100%" /> 这个基本上都是使用 springboot-init 生成的,这里我这个 springboot-init 是基于 Springboot2.7 + Swagger3 + satoken 的一个基础模板,模板的源代码在我的 github:[https://github.com/XiaoZhangCode/spring-boot-init](https://github.com/XiaoZhangCode/spring-boot-init) <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/2zbNt1JQNyof3G7Y.webp" alt="" width="100%" /> 这里填写上 后端这个 Swagger 的 json 地址 <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/Vh7XDN1hJaDgyDut.webp" alt="" width="100%" /> 然后输入 `alova gen` 看看效果 <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/gCgk6uH8feagOiAL.webp" alt="" width="100%" /> 生成的格式是这样的, <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/LcJ082Rf3JKsPP3H.webp" alt="" width="100%" /> 我们直接先试试能不能用,打开 HelloWord 组件 代码如下: ```javascript <script setup lang="ts"> import Api from "../api/index.ts" import {onMounted} from "vue"; const userLogin = async () => { let res = await Api.general.userLogin({ data: { userAccount: "admin", userPassword: "12345678" } }); console.log(res) } onMounted(() => { userLogin() }) const getPage = () => { let detailsPage = Api.general.getWallpaperDetailsPage({} as any); detailsPage.then(res => { console.log(res) }) } </script> <template> <div class="card"> <button @click="getPage()">获取请求分页</button> </div> <p> Check out <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank" >create-vue</a >, the official Vue + Vite starter </p> <p> Learn more about IDE Support for Vue in the <a href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support" target="_blank" >Vue Docs Scaling up Guide</a >. </p> <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p> </template> <style scoped> .read-the-docs { color: #888; } </style> ``` 主要是测试下接口调用,这里模拟登录账号后,点击获取一下 获取分页信息 用法和其他请求库是类似的, 鼠标悬浮到方法上,就能看到参数和相应信息,还是挺好的 <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/iiijpqb7wxUW94Al.webp" alt="" width="100%" /> 启动项目看看效果 <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/bURbvf3gsS9SGbIc.webp" alt="" width="100%" /> 登录接口调用成功!测试下分页接口 <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/afRUDD8RNMSnTSKj.webp" alt="" width="100%" /> 这里就有点问题了,刚刚我们明明已经调用过登录了, <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/86GwoutH8qbnjuhe.webp" alt="" width="100%" /> 这是登录接口返回到 cookie 我们看看 请求是否带上了, <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/6DuOVE7KTrtgVShj.webp" alt="" width="100%" /> 没有携带 cookie ,之前 Axios 时候需要在 reuqest.js 中加上: ```plain withCredentials: true, ``` 我们看看这个创建的 CreateAlova 中是否有这个属性 <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/LcbRouYwVpd0sJeu.webp" alt="" width="100%" /> 很遗憾在详解 Alova 这一章没有这个参数,但是在官方文档中,讲解到这个CreateAlova 的实例对象是 Method 的父类 所有 Method 都会继承这个方法中的参数,然后我们在请求适配器中,找到对应的请求适配器`fetchAdapter()`<img src="https://pic.code-nav.cn/post_picture/1707418316274003969/bnxBFVnFu1mvMuX1.webp" alt="" width="100%" /> 找到一个参数配置 <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/clVi1cX0V4Iz8l0a.webp" alt="" width="100%" /> 文档地址:[https://alova.js.org/zh-CN/resource/request-adapter/fetch#%E9%85%8D%E7%BD%AE%E9%A1%B9](https://alova.js.org/zh-CN/resource/request-adapter/fetch#%E9%85%8D%E7%BD%AE%E9%A1%B9) 图中的`credentials`和这个`withCredentials`很像。而在创建 Alova 实例时候 有一个创建 Method 函数之前的钩子函数,这里我们可以统一给 Method 增加配置项,这里我们试试 > `credentials`<font style="color:rgb(6, 6, 7);">选项可以有三个值:</font> > > 1. `omit`<font style="color:rgb(6, 6, 7);">:默认值。当设置为</font>`omit`<font style="color:rgb(6, 6, 7);">时,跨域请求不会发送任何凭证。这意味着请求不会发送cookies、HTTP认证等信息。</font> > 2. `same-origin`<font style="color:rgb(6, 6, 7);">:当设置为</font>`same-origin`<font style="color:rgb(6, 6, 7);">时,只有当URL与调用Fetch的脚本位于同一源(协议、域名和端口都相同)时,才会发送凭证。如果请求的目标URL与当前页面的源不同,那么请求将不会发送任何凭证。</font> > 3. `include`<font style="color:rgb(6, 6, 7);">:当设置为</font>`include`<font style="color:rgb(6, 6, 7);">时,无论是同源请求还是跨域请求,都会发送凭证。这允许跨域请求携带cookies和HTTP认证信息。</font> > <font style="color:rgb(6, 6, 7);">我们将值设置为</font>`include`,这样等到页面登陆后点击获取分页请求就成功啦! <img src="https://pic.code-nav.cn/post_picture/1707418316274003969/l433IBWQ61PWqf3L.webp" alt="" width="100%" /> 目前为止,这个 Alova.js 生成文档以及使用文档生成的 API 接口调用全部过程就到此为止了,整体体验下来还是可以的,因为后端定义的方法名就是前端 API 生成都文档接口名,对于全干工程师来说是非常友好的哈哈哈。 这个 Alova 还有很多优秀的功能,大家感兴趣可以自行了解...

专线搭建、网络变更、数据迁移原理及逻辑

### 问题描述 支付机构(可理解为银行或第三方支付机构)与商户做线上支付业务,调用API支付接口后需要做专线搭建、网络变更(即配置防火墙将商户网络加入银行网络白名单)、数据迁移(迁移前还需要调用迁移接口),想问下这三步的整个流程逻辑以及背后原理,感谢 ### 背景信息 目前主要为商户提供支付接口、迁移接口以及申请防火墙开通申请 ### 具体疑问 专线搭建机制?会用到阿里云之类的 数据迁移机制? 整个流程背后逻辑及原理 ### 已有理解 目前只是比较清楚业务顺序,商户方调用支付接口及迁移接口后,才可做测试,且专线搭建完成后,配置防火墙才可做数据迁移 ### 预期目标 希望可以得到详细解释并理解 ### 相关资料 提供与问题相关的代码片段、文档链接或者其他有助于理解的问题描述。请勿使用模糊不清的截图,可以使用代码块分享代码。 如果有多段代码,推荐使用 [代码小抄工具](https://codecopy.cn) 上传。

Swagger3携手Umi/OpenApi:轻松驾驭自动化,后端接口秒级生成!

# 引言 在鱼皮开发的AI答题应用平台时,我提出过这样一个疑问:如果Swagger的接口文档中如果使用中文注释接口,那么这个umi/OpenApi是否还能正常生成代码? 答案是有点差距 ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/lDmX3iaN-image.png) 像上图,接口文档的参数不明确,没有注释,左侧的接口也是英文,这样子前端对接起来感觉看起来也不是通俗易懂,再看看生成的接口文件: ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/5adPmrKT-image.png) 全是英文,没有中文注释。我想生成的效果是: ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/pm7Q55dQ-image.png) ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/MAEAljj3-image.png) 所以接下来要说的就是如何配置能够生成带有中文参数的接口文件! # 使用技术 ## 后端 这里使用的**新版本Swagger3** 想着用新不用旧,在新版本上踩踩坑 官网:https://doc.xiaominfo.com/docs/quick-start ```xml <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-spring-boot-starter</artifactId> <version>4.4.0</version> </dependency> ``` 配置文件: ```yml --- #################### swagger 相关配置 #################### # 配置springdoc-openapi的相关属性 springdoc: # 配置Swagger-UI的相关属性 swagger-ui: # 设置Swagger-UI的访问路径 path: /swagger-ui.html # 按字母表顺序对标签进行排序 tags-sorter: alpha # 按字母表顺序对操作进行排序 operations-sorter: alpha # 配置API文档的相关属性 api-docs: # 设置API文档的访问路径 path: /v3/api-docs # knife4j的增强配置,不需要增强可以不配 knife4j: enable: true setting: language: zh_cn # 设置语言为中文 enable-footer: false enable-footer-custom: true footer-custom-content: Apache License 2.0 | Copyright 2024-[codeZhang](https://github.com/XiaoZhangCode) enable-filter-multipart-apis: true ``` ## 前端 生成的插件还是鱼皮在项目中常用的`@umi/openapi`插件 官网:https://www.npmjs.com/package/@umijs/openapi AI答题应用平台用的Vue3 安装命令:`npm i --save-dev @umijs/openapi` 在 `package.json` 的 `script` 中添加 `api: "openapi": "ts-node openapi.config.ts"`,生成api ps: 倘若运行错误将`api: "openapi": "ts-node openapi.config.ts`改成`api: "openapi": "node openapi.config.ts` 需要在项目根目录下新建`openapi.config.ts` ```javascript // eslint-disable-next-line @typescript-eslint/no-var-requires const { generateService } = require("@umijs/openapi"); generateService({ requestLibPath: "import request from '@/request'", schemaPath: "http://localhost:8101/api/v3/api-docs" // 写项目实际的接口doc地址, serversPath: "./src", }); ``` 最后`npm run openapi`即可生成 # 实现效果 接口文档是这样的: ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/yEfiZnNw-image.png) 而生成出来的效果是这样的 ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/HZ0jZyqJ-image.png) 发现左侧的文件名称中文变成了拼音 如果说不介意这个拼音的话,那么这样就可以直接用了 # 解决文件名称拼音 官网给出一个自定义`hook`可以自己定义文件名称 ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/c5tOnHEa-image.png) 我们这里把官网源码`clone`下来, 查看下这个方法是在什么时候调用的,能够回调回来哪些信息 在源码中我们可以找到示例代码,以及如图 ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/McJ2vteU-image.png) ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/pQ2cZuy0-image.png) 回到我们AI答题应用平台中,我们可以照猫画虎,完成如下配置 ```javascript // eslint-disable-next-line @typescript-eslint/no-var-requires const { generateService } = require("@umijs/openapi"); generateService({ requestLibPath: "import request from '@/request'", schemaPath: "http://localhost:8101/api/v3/api-docs", serversPath: "./src", hook: { //@ts-ignore customFileNames: (operationObject, apiPath) => { const operationId = operationObject.operationId; if (!operationId) { // @ts-ignore console.warn("[Warning] no operationId", apiPath); return; } const res = operationId.split("_"); if (res.length > 1) { res.shift(); if (res.length > 2) { // @ts-ignore console.warn("[Warning] operationId has more than 2 part", apiPath); } return [res.join("_")]; } else { const controllerName = (res || [])[0]; if (controllerName) { return [controllerName]; } return; } }, }, }); ``` 再运行看下效果! ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/wlZ4noAn-image.png) 这里将每个方法全部都生成了一个文件,这才多少接口,就生成这么多文件,当然如果有鱼友不介意,这样也是可直接使用的 但是我这里还是想改成和之前一样,一个`controller`中的代码是一个文件,那么这里就需要看看他这个钩子回调回来传给我们哪些参数了 我们直接搜索`Service`找到这个js进入 ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/brTqAsiC-image.png) 解释下,在这个黄色框中,将文件根据`tags`分组 最后生成成文件 红色框是由中文变成英文的转换! ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/XyT92O24-image.png) 在下面这个图中,会读取到我们配置文件的`hook`中的自定义钩子函数 ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/FJIdHA4X-image.png) 然后我们在251行打上断点,运行`openApi` ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/5B1xPcf3-image.png) 可以看到传入了三个参数:operationObject, p, method operationObject这个对象就是读取后端接口地址读到的数据,p是这个接口的path路径,最后一个顾名思义就是方法的类型,我们这里往下看可以知道,他是根据这里的`tags`来进行方法分组然后生成的! ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/mWn9hjMm-image.png) 下述红色框将这里的中文变成了拼音,点进去 ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/FcXAuOiu-image.png) 在这个`resolveTypeName`中变成了拼音,那么就是说我们如果给这个tags自行设置,那么生成出来的文件名称就是可以自定义了,于是我们先修改后端接口文档的`@Tag`中的name属性 ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/BPhULtwN-image.png) 变成如下图所示效果 使用`-`来分割想要生成的文件名称 ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/897rL2fF-image.png) 修改前端配置文件,将hook中的自定义文件名称改成如下: 也就是我们将tags取出来,然后根据`-`分割,然后返回英文名称! ```javascript customFileNames: (operationObject, apiPath) => { const tags = operationObject.tags[0]; if (!tags) { // @ts-ignore console.warn("[Warning] no tags", apiPath); return; } const res = tags.split("-"); if (res.length > 1) { res.shift(); if (res.length > 2) { // @ts-ignore console.warn("[Warning] tags has more than 2 part", apiPath); } return [res.join("_")]; } else { const controllerName = (res || [])[0]; if (controllerName) { return [controllerName]; } return; } }, ``` 试试效果 ![image.png](https://pic.code-nav.cn/post_picture/1707418316274003969/rWo07Dxf-image.png) 这样子就生成成功啦!

收获编程导航球友300多条点赞的豪华版API接口开放平台来了!! 附上线部署流程及github action 自动化部署

星球文章地址:https://t.zsxq.com/1934T92nb ## 网站导航 🧭 - [Qi-API 接口开放平台 🔗](https://api.qimuu.icu/nJaixWyb) :https://api.qimuu.icu/nJaixWyb - [Qi-API 后端 🏘️ ](https://github.com/qimu666/qi-api):https://github.com/qimu666/qi-api - [Qi-API 前端 🏘️](https://github.com/qimu666/qi-api-frontend) :https://github.com/qimu666/qi-api-frontend - [Qi-API-SDK🛠](https://github.com/qimu666/qi-api-sdk): https://github.com/qimu666/qi-api-sdk - [Qi-API-DOC 开发者文档 📖](https://doc.qimuu.icu/) : https://doc.qimuu.icu/ - [Qi-API-DOC 开发者文档源码 📖](https://github.com/qimu666/qi-api-doc) : https://github.com/qimu666/qi-api-doc - [Qi-API-SDK-demo ✔️](https://github.com/qimu666/qi-api-sdk-demo) : https://github.com/qimu666/qi-api-sdk-demo - 部署流程见飞书文档:‍‌⁣⁣https://pqx6sigueez.feishu.cn/wiki/ZsDUwytgDibJ41kHBuwc1neVnUe - [Gitee同步地址](https://gitee.com/qimu6):https://gitee.com/qimu6 ## 项目介绍 🙋 😀 作为用户您可以通过注册登录账户,获取接口调用权限,并根据自己的需求浏览和选择适合的接口。您可以在线进行接口调试,快速验证接口的功能和效果。 💻 作为开发者 我们提供了客户端SDK, 通过开发者凭证即可将轻松集成接口到您的项目中,实现更高效的开发和调用。 🤝 您可以将自己的接口接入到Qi-API 接口开放平台平台上,并发布给其他用户使用。 您可以管理和各个接口,以便更好地分析和优化接口性能。 👌 我们还提供了开发者在线文档和技术支持,帮助您快速接入和发布接口。 🏁 无论您是用户还是开发者,Qi-API 接口开放平台都致力于提供稳定、安全、高效的接口调用服务,帮助您实现更快速、便捷的开发和调用体验。 部署流程见文档:‍‌⁣⁣https://pqx6sigueez.feishu.cn/wiki/ZsDUwytgDibJ41kHBuwc1neVnUe ## 项目整体流程图 🗺️: ![api.png](https://pic.code-nav.cn/post_picture/1611320795533934593/ghS0OMlk-api.png) ## 扩展功能点 📋: - ✅ SDK开发 :为了让用户更放便的掉用接口,我把sdk迭代了3个版本,参考各大接口平台SDK、运用多种设计模式,最终,完成了这项简单又复杂的版本。现在引入依赖坐标,几行代码就能轻松调用接口。 - ✅ 开发者API在线文档 :使用vuepress搭建了接口在线文档、提示用户体验,用户可以在这里找到更加详细的接口描述、示例代码、错误码参考等,并且提供调用Qi-API-SDK-Demo示例 - ✅ EasyWeb:提供快速构建Web项目的sdk,只需导入依赖即可使用全局异常处理器、通用返回、接口文档、并且可以自定义自己的错误规范。 - ✅ 提供多种好玩的接口:随机土味情话、每日星座运势、随机毒鸡汤、获取天气信息、随机壁纸等接口。 - ✅ 微信支付宝支付功能 :网站接入支付功能可正常收款,用户可以充值坤(即积分),坤币用来调用接口。 - ✅ 接口管理 :管理员可以发布、审核、下线接口,未发布的接口普通用户接口广场获取不到,管理员可以动态设置请求参数、响应参数、请求参数中参数设置为必选项时,用户请求会校验用户有没有填该选项,并且配置请求参数、响应参数,前端动态生成请求参数、响应参数的描述列表。并生成json代码的返回示例。 - ✅ 在线调试 :无需编写代码,即可在线发起请求调试接口,用户可以动态添加请求参数来在线调用接口,余额是不能调用的哦 - ✅ 邀请好友注册得坤币:好友通过你的链接注册账号双方都可以获得坤币奖励。体验链接: 通过链接注册,即可获得100坤币💰奖励,Qi-API 接口开放平台为您提供稳定、安全、高效的接口调用服务!https://api.qimuu.icu/nJaixWyb - ✅ 每日签到 除了邀请好友注册和充值坤币外,每日签到也可以获得"不菲"的坤币奖励 - ✅ 支付成功邮箱通知 : 用户支付成功后通过邮件来反馈支付状态,但是需要先绑定邮箱账号。 - ✅ 邮箱验证码登录注册:为了提示用户体检,用户可以选择注册平台账号或者使用邮箱账号注册平台。平台账号绑定邮箱后可以使用邮箱验证码登录平台 - ✅ 校验请求参数:必填项请求参数检验,通过校验请求参数可以减少用户积分无意义的消耗。 - ✅ 接口大厅:用户可以在这里获取更多好玩的接口 - ✅ 绑定、换绑邮箱、解绑邮箱,用户绑定邮箱后可以通过邮箱验证码来登录网站,也支持用户更换新的邮箱。 - ✅ 订单管理:用户可以管理订单、支付已经创建完成的订单,取消订单或者删除订单记录 - ✅ 切换主题 :为了提示网站的趣味,增加了深色和暗色主题切换 - ✅ 商品管理 :管理员可以上架或下架商品,或者设置商品的规格,比如设置活动商品,用户只能购买一次 - ✅ 用户管理 :管理员除了管理用户信息外,还可以对违规的用户进行封号,改正后联系管理员解封。 - ...... 更多功能请前往Qi-API 接口开放平台查看 https://api.qimuu.icu/nJaixWyb ## 功能介绍 📋 `坤币`即积分,用于平台接口调用。 | **功能** | 游客 | **普通用户** | **管理员** | | ----------------------------------------------------- |--------------|-----|-----| | **[开发者API在线文档](http://doc.qimuu.icu)** | ✅ | ✅ | ✅ | | 接口大厅搜索接口、浏览接口 | ✅ | ✅ | ✅ | | 邮箱验证码登录注册 | ✅ | ✅ | ✅ | | [**Qi-API-SDK**](https://github.com/qimu666/qi-api-sdk)使用 | ❌ | ✅ | ✅ | | 邀请好友注册得坤币 | ❌ | ✅ | ✅ | | 微信支付宝付款 | ❌ | ✅ | ✅ | | 在线调试接口 | ❌ | ✅ | ✅ | | 每日签到得坤币 | ❌ | ✅ | ✅ | | 钱包充值 | ❌ | ✅ | ✅ | | 支付成功邮箱通知(需要绑定邮箱) | ❌ | ✅ | ✅ | | 更新头像 | ❌ | ✅ | ✅ | | 绑定、换绑、解绑邮箱 | ❌ | ✅ | ✅ | | 取消订单、删除订单 | ❌ | ✅ | ✅ | | 商品管理、上线、下架 | ❌ | ❌ |✅| | 用户管理、封号解封等 | ❌ | ❌ | ✅ | | 接口管理、接口发布审核、下架 | ❌ | ❌ | ✅ | | 退款 | ❌ | ❌| ✅ | ## 项目流程 🗺️ ![QiAPI 接口开放平台](https://img.qimuu.icu/typory/QiAPI%2520%25E6%258E%25A5%25E5%258F%25A3%25E5%25BC%2580%25E6%2594%25BE%25E5%25B9%25B3%25E5%258F%25B0.png) ## 快速启动 🚀 ### 前端 环境要求:Node.js >= 16 安装依赖: ```bash yarn or npm install ``` 启动: ```bash yarn run dev or npm run start:dev ``` 部署: ```bash yarn build or npm run build ``` ### 后端 执行sql目录下ddl.sql ## 项目选型 🎯 ### **后端** - Spring Boot 2.7.0 - Spring MVC - MySQL 数据库 - 腾讯云COS存储 - Dubbo 分布式(RPC、Nacos) - Spring Cloud Gateway 微服务网关 - API 签名认证(Http 调用) - IJPay-AliPay 支付宝支付 - WeiXin-Java-Pay 微信支付 - Swagger + Knife4j 接口文档 - Spring Boot Starter(SDK 开发) - Jakarta.Mail 邮箱通知、验证码 - Spring Session Redis 分布式登录 - Apache Commons Lang3 工具类 - MyBatis-Plus 及 MyBatis X 自动生成 - Hutool、Apache Common Utils、Gson 等工具库 ### 前端 - React 18 - Ant Design Pro 5.x 脚手架 - Ant Design & Procomponents 组件库 - Umi 4 前端框架 - OpenAPI 前端代码生成 ## 功能展示 ✨ ### 首页 ![index.png](https://pic.code-nav.cn/post_picture/1611320795533934593/aarAFFs2-index.png) ### 接口广场 ![interfaceSquare.png](https://pic.code-nav.cn/post_picture/1611320795533934593/hvDq712E-interfaceSquare.png) ### 开发者在线文档 ![api.png](https://pic.code-nav.cn/post_picture/1611320795533934593/z2NSNfqo-api.png) ![api2.png](https://pic.code-nav.cn/post_picture/1611320795533934593/Zb7ReIWn-api2.png) ### 接口描述 #### **在线API** ![interfaceinfo-api.png](https://pic.code-nav.cn/post_picture/1611320795533934593/wkJXsYzU-interfaceinfo-api.png) #### 在线调试工具 ![interfaceinfo-tools.png](https://pic.code-nav.cn/post_picture/1611320795533934593/N0mxKOp7-interfaceinfo-tools.png) #### **错误码参考** ![interfaceinfo-errorcode.png](https://pic.code-nav.cn/post_picture/1611320795533934593/AcIZOINg-interfaceinfo-errorcode.png) #### **接口调用代码示例** ![interfaceinfo-sampleCode.png](https://pic.code-nav.cn/post_picture/1611320795533934593/tIxsOyn3-interfaceinfo-sampleCode.png) ### 管理页 #### 用户管理 ![admin-userManagement.png](https://pic.code-nav.cn/post_picture/1611320795533934593/VbFAvqVR-admin-userManagement.png) #### 商品管理 ![admin-productManagement.png](https://pic.code-nav.cn/post_picture/1611320795533934593/nu2xB2gv-admin-productManagement.png) #### 接口管理 ![admin-interfaceManagement.png](https://pic.code-nav.cn/post_picture/1611320795533934593/mUUvE3ii-admin-interfaceManagement.png) #### 动态更新请求响应参数 ![dynamicRequestParameters.png](https://pic.code-nav.cn/post_picture/1611320795533934593/mmiDKmi1-dynamicRequestParameters.png) ### 积分商城 ![pointPurchase.png](https://pic.code-nav.cn/post_picture/1611320795533934593/v5tuG4au-pointPurchase.png) ### 订单支付 ![pay.png](https://pic.code-nav.cn/post_picture/1611320795533934593/Sg2YXtjB-pay.png) ### 个人信息 #### 信息展示 ![userinfo.png](https://pic.code-nav.cn/post_picture/1611320795533934593/S4xv5YUQ-userinfo.png) #### 每日签到 ##### 签到成功 ![successfullySignedIn.png](https://pic.code-nav.cn/post_picture/1611320795533934593/tpvG7jca-successfullySignedIn.png) ##### 签到失败 ![errorfullySignedIn.png](https://pic.code-nav.cn/post_picture/1611320795533934593/d6He9Q6T-errorfullySignedIn.png) ### 好友邀请 #### **发送邀请** ![Invitefriends.png](https://pic.code-nav.cn/post_picture/1611320795533934593/xWqhjrQB-Invitefriends.png) #### **接收邀请** ![registerThroughInvitationCode.png](https://pic.code-nav.cn/post_picture/1611320795533934593/3YKUh8H7-registerThroughInvitationCode.png) ### 登录/注册 ![login.png](https://pic.code-nav.cn/post_picture/1611320795533934593/ftMqAxjb-login.png) ![register.png](https://pic.code-nav.cn/post_picture/1611320795533934593/AsY5fJCU-register.png) ### 订单管理 - **我的订单** ![orderinfo.png](https://pic.code-nav.cn/post_picture/1611320795533934593/HzfSvomX-orderinfo.png) - **详细订单** ![orderDetails.png](https://pic.code-nav.cn/post_picture/1611320795533934593/OCCUMZjN-orderDetails.png) ### 主题切换 #### 深色主题 ![darkTheme.png](https://pic.code-nav.cn/post_picture/1611320795533934593/1AFJEsTc-darkTheme.png) #### 浅色主题 ![index.png](https://pic.code-nav.cn/post_picture/1611320795533934593/3X3hn91v-index.png)

RESTFUL-风格完成后端接口开发

## REST ### REST风格 API接口开发 #### 1. RESTful (核心思想===>表现层状态转换) 1.1 RESTful是一种软件架构风格,设计风格而不是标准,只是提供了一组设计原则和约束条件。 1.2 RESTful是目前最流行的一种互联网软件架构。 1.3 RESTful架构的核心原则: ```text 1- 以资源为中心,资源是指网络上的一个实体,或者说是网络上的一个具体信息。 - 每个资源对应一个特定的资源路径,即URI。 - 客户端和服务器之间,传递这种资源的某种表现层状态,即表现层状态转换。 以资源为基础 :资源可以是一个图片、音乐、一个XML格式、HTML格式或者JSON格式等网络上的一个实体, 除了一些二进制的资源外普通的文本资源更多以JSON为载体、面向用户的一组数据(通常从数据库中查询而得到)。 2- 统一接口:统一接口是RESTful架构的基础,只要是符合RESTful架构原则的,都可以称为RESTful接口。 - 统一接口包括:资源标识、资源操作、自描述消息、超媒体作为应用状态引擎。 - 对资源的操作包括获取、创建、修改和删除,这些操作正好对应HTTP协议提供的GET、POST、PUT和DELETE方法。 - GET(SELECT):从服务器取出资源(一项或多项)。 - POST(CREATE):在服务器新建一个资源。 - PUT(UPDATE):在服务器更新资源(客户端提供完整资源数据)。 - PATCH(UPDATE):在服务器更新资源(客户端提供需要修改的资源数据)。 - DELETE(DELETE):从服务器删除资源 - 通过统一接口,实现了客户端和服务器的分离,使得客户端不用关心服务器的技术实现,而服务器也不用关心客户端的业务逻辑。 ``` 传统API接口开发和RESTful API接口开发对比: | 传统API接口开发 | RESTful API接口开发 | | :--- | :--- | | 以动作为中心 | 以资源为中心 | | 以动词为中心 | 以名词为中心 | | 以操作为中心 | 以数据为中心 | | 以过程为中心 | 以结果为中心 | | 以业务为中心 | 以实体为中心 | ![image.png](https://pic.code-nav.cn/post_picture/1673111457186713601/edLHAoeZ-image.png) #### 2. RESTful URI设计 1. 不用大写字母,所有单词使用英文且小写。 2. 连字符用中杠"-"而不用下杠"_" 3. 正确使用 "/"表示层级关系,URL的层级不要过深,并且越靠前的层级应该相对越稳定 4. 结尾不要包含正斜杠分隔符"/" 5. URL中不出现动词,用请求方式表示动作 6. 资源表示用复数不要用单数 7. 不要使用文件扩展名 8. 用好HTTP状态码 #### 3. RESTful 响应状态码 使用RESTful风格的API接口开发,一般需要返回响应状态码和响应数据, 响应状态码的使用非常重要,下面列举了常用的状态码: ```text 1. 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。 2. 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。 3. 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务) 4. 204 NO CONTENT - [DELETE]:用户删除数据成功。 5. 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。 6. 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。 7. 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。 8. 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。 9. 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。 10. 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。 11. 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。 12. 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。 ``` #### 4.日常开发使用 Restful 只是一种风格和规范,不是一种标准,不一定要让所有接口全部遵循,一般开发中混用.

nove

地址:https://github.com/novuhq/novu

在线API文档

各种编程语言的API大全,在线使用,不用下载安装 地址:https://tool.oschina.net/apidocs

Eoapi

Eoapi 是一个可扩展的 API 开发工具。Eoapi 集合基础的 API 管理和测试功能,并且可以通过插件简化你的 API 开发工作,让你可以更快更好地创建 API。 地址:https://github.com/eolinker/eoapi

下载 APP