为接口增加OpenAPI注解

注解满天飞,原始代码可读性下降
This commit is contained in:
2025-08-21 06:27:27 +08:00
parent 7836ae8035
commit 5e253740f4
5 changed files with 136 additions and 6 deletions
+3 -1
View File
@@ -1,6 +1,9 @@
# 简介
是[https://api.thewhitedog9487.xyz](https://api.thewhitedog9487.xyz)的源代码
# 接口文档
[这里](https://api.thewhitedog9487.xyz/swagger-ui/index.html)
# 目前状态
能用,但缺的东西非常多。
就像这个文档一样。
@@ -11,7 +14,6 @@
# 下一步
- systemd服务自动安装和卸载
- Swagger自动生成API接口文档
# 许可授权
WTFPL
+1 -1
View File
@@ -7,7 +7,7 @@ plugins {
}
group = "xyz.thewhitedog9487"
version = "0.3.0"
version = "0.4.0"
java {
toolchain {
@@ -1,8 +1,16 @@
package xyz.thewhitedog9487.WebAPI.Controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
@@ -12,10 +20,23 @@ import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
@Tag(name="请求客户端IP地址相关")
@Slf4j
@RestController
@RequestMapping("/ip")
class IP {
@Operation(summary = "获取请求客户端的IP地址", description = """
优先级:
1. CF-Connecting-IP
2. X-Forwarded-For
3. 直接使用请求的远程地址
""")
@ApiResponse(responseCode = "200",
content = @Content(
mediaType = MediaType.TEXT_PLAIN_VALUE,
schema = @Schema(implementation = String.class),
examples = @ExampleObject(value = "78.141.226.247")),
description = "成功获取到IP地址,内容为纯文本格式的IP地址")
@GetMapping("/ip")
ResponseEntity<String> GetIP(@RequestHeader Map<String, String> HttpHeader, HttpServletRequest Request) {
if ( HttpHeader.get("CF-Connecting-IP".toLowerCase() ) instanceof String IP) {
@@ -38,6 +59,24 @@ class IP {
MultiValueMap.fromSingleValue(Map.of("Content-Type", "text/plain;charset=UTF-8")),
HttpStatus.OK); } }
@Operation(summary = "获取请求客户端的ISO 3166-1 alpha-2国家代码", description = """
依赖Cloudflare的CF-IPCountry头部
<br>
如果请求没有经过Cloudflare,则会返回"未找到CF-IPCountry头部"
""")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
content = @Content(
mediaType = MediaType.TEXT_PLAIN_VALUE,
schema = @Schema(implementation = String.class),
examples = @ExampleObject(value = "HK") ),
description = "成功获取到ISO 3166-1 alpha-2国家代码,内容为纯文本格式的国家代码"),
@ApiResponse(responseCode = "404",
content = @Content(
mediaType = MediaType.TEXT_PLAIN_VALUE,
schema = @Schema(implementation = String.class),
examples = { @ExampleObject(value = "未找到CF-IPCountry头部") } ),
description = "未找到CF-IPCountry头部,内容为纯文本格式的错误信息") } )
@GetMapping("iso3166")
ResponseEntity<String> GetISO3166(@RequestHeader Map<String, String> HttpHeader) {
if (HttpHeader.get("CF-IPCountry".toLowerCase()) instanceof String CountryCode) {
@@ -3,10 +3,19 @@ package xyz.thewhitedog9487.WebAPI.Controller;
import discord4j.common.util.Snowflake;
import discord4j.core.GatewayDiscordClient;
import discord4j.rest.http.client.ClientException;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MissingRequestHeaderException;
import org.springframework.web.bind.annotation.*;
@@ -14,16 +23,88 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Tag(name="远程消息处理相关")
@Slf4j
@RestController
@RequestMapping("/message")
class Message {
@Autowired GatewayDiscordClient DiscordBotClient;
@Autowired List<String> ApiKeyList;
record PostMessageData(Long ChannelID, String Content) {}
@Schema(description = "包含了发送消息的数据包")
record PostMessageData(
@Schema(description = "Discord频道ID", example = "1398192763845214239") Long ChannelID,
@Schema(description = "要发送的消息内容", example = "成功完成备份,最新文件时间为2025 08 21") String Content) {}
@Operation(summary = "通过Discord Bot向指定频道发送消息", description = """
这是一个私有API,需要在请求头中提供X-API-Key以进行身份验证
<br>
需要在请求体中提供频道ID和消息内容
""")
@ApiResponses(value = {
@ApiResponse(responseCode = "201",
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ResponseData.class),
examples = @ExampleObject(value = "{\n" +
" \"code\": 201,\n" +
" \"message\": \"消息发送成功\",\n" +
" \"data\": {\n" +
" \"新ID\": \"1407848258813825135\",\n" +
" \"内容\": \"写点什么好呢\",\n" +
" \"频道ID\": \"1398192763845214239\"\n" +
" }\n" +
"}") ),
description = "消息发送成功,响应体中包含新消息的ID、频道ID和内容"),
@ApiResponse(responseCode = "400",
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ResponseData.class),
examples = @ExampleObject(value = "{\n" +
" \"code\": 400,\n" +
" \"message\": \"请求缺少必要的头部信息\",\n" +
" \"data\": {\n" +
" \"缺失的头部\": \"X-API-Key\"\n" +
" }\n" +
"}") ),
description = "缺少必须的请求头,请查看响应中的“缺失的头部”以诊断问题"),
@ApiResponse(responseCode = "401",
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ResponseData.class),
examples = @ExampleObject(value = "{\n" +
" \"code\": 401,\n" +
" \"message\": \"API密钥验证失败\",\n" +
" \"data\": {\n" +
" \"提供的密钥\": \"123456\"\n" +
" }\n" +
"}") ),
description = "API密钥验证失败"),
@ApiResponse(responseCode = "500",
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ResponseData.class),
examples = @ExampleObject(value = "{\n" +
" \"code\": 500,\n" +
" \"message\": \"消息发送失败\",\n" +
" \"data\": {\n" +
" \"内容\": \"芝士异常\",\n" +
" \"错误信息\": \"POST /channels/1398192763845214/messages returned 404 Not Found with response {code=10003, message=Unknown Channel}\",\n" +
" \"频道ID\": \"1398192763845214\"\n" +
" }\n" +
"}")),
description = "消息发送失败,可能是由于Discord服务器问题或其他内部错误,请查看响应Body的“内容”子项以确定原因") } )
@PostMapping("/discord")
ResponseEntity<ResponseData> DiscordPush(@RequestHeader(value = "X-API-Key", required = true) String ApiKey, @RequestBody PostMessageData RequestBody){
ResponseEntity<ResponseData> DiscordPush(
@Parameter(description = "用于身份验证的API密钥", required = true, example = "ds1858dscc8745sfwe")
@RequestHeader(value = "X-API-Key", required = true) String ApiKey,
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "包含频道ID和消息内容的JSON对象",
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = PostMessageData.class) ) )
@RequestBody PostMessageData RequestBody){
if ( ApiKeyList.contains(ApiKey) == false ) {
log.warn("API密钥验证失败,密钥:{}", ApiKey);
return new ResponseEntity<>(new ResponseData(
@@ -2,11 +2,19 @@ package xyz.thewhitedog9487.WebAPI.Controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
public record ResponseData(int code, Object message, Object data) {
@Schema(description = "所有API通用的标准响应格式")
public record ResponseData(
@Schema(description = "内部用响应代码" ,example = "201") int code,
@Schema(description = "人类可读说明信息", example = "消息发送成功") Object message,
@Schema(description = "具体的响应数据", example = "{\n" +
" \"新ID\": \"1407848258813825135\",\n" +
" \"内容\": \"写点什么好呢\",\n" +
" \"频道ID\": \"1398192763845214239\"\n" +
" }") Object data) {
static ObjectMapper JsonMapper = new ObjectMapper();
public ResponseData(int code) {
this(code, null, null); }