From 5e253740f4dcad3a24d2bcd09e18001f648b785f Mon Sep 17 00:00:00 2001 From: TheWhiteDog9487 Date: Thu, 21 Aug 2025 06:27:27 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BA=E6=8E=A5=E5=8F=A3=E5=A2=9E=E5=8A=A0Op?= =?UTF-8?q?enAPI=E6=B3=A8=E8=A7=A3=20=E6=B3=A8=E8=A7=A3=E6=BB=A1=E5=A4=A9?= =?UTF-8?q?=E9=A3=9E=EF=BC=8C=E5=8E=9F=E5=A7=8B=E4=BB=A3=E7=A0=81=E5=8F=AF?= =?UTF-8?q?=E8=AF=BB=E6=80=A7=E4=B8=8B=E9=99=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- build.gradle.kts | 2 +- .../thewhitedog9487/WebAPI/Controller/IP.java | 39 +++++++++ .../WebAPI/Controller/Message.java | 85 ++++++++++++++++++- .../WebAPI/Controller/ResponseData.java | 12 ++- 5 files changed, 136 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3402468..89ce2ed 100644 --- a/README.md +++ b/README.md @@ -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 \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index f680b72..e11750d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } group = "xyz.thewhitedog9487" -version = "0.3.0" +version = "0.4.0" java { toolchain { diff --git a/src/main/java/xyz/thewhitedog9487/WebAPI/Controller/IP.java b/src/main/java/xyz/thewhitedog9487/WebAPI/Controller/IP.java index abd512c..383292c 100644 --- a/src/main/java/xyz/thewhitedog9487/WebAPI/Controller/IP.java +++ b/src/main/java/xyz/thewhitedog9487/WebAPI/Controller/IP.java @@ -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 GetIP(@RequestHeader Map 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头部 +
+ 如果请求没有经过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 GetISO3166(@RequestHeader Map HttpHeader) { if (HttpHeader.get("CF-IPCountry".toLowerCase()) instanceof String CountryCode) { diff --git a/src/main/java/xyz/thewhitedog9487/WebAPI/Controller/Message.java b/src/main/java/xyz/thewhitedog9487/WebAPI/Controller/Message.java index 8b33e26..a791552 100644 --- a/src/main/java/xyz/thewhitedog9487/WebAPI/Controller/Message.java +++ b/src/main/java/xyz/thewhitedog9487/WebAPI/Controller/Message.java @@ -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 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以进行身份验证 +
+ 需要在请求体中提供频道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 DiscordPush(@RequestHeader(value = "X-API-Key", required = true) String ApiKey, @RequestBody PostMessageData RequestBody){ + ResponseEntity 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( diff --git a/src/main/java/xyz/thewhitedog9487/WebAPI/Controller/ResponseData.java b/src/main/java/xyz/thewhitedog9487/WebAPI/Controller/ResponseData.java index a985a1f..006ff9e 100644 --- a/src/main/java/xyz/thewhitedog9487/WebAPI/Controller/ResponseData.java +++ b/src/main/java/xyz/thewhitedog9487/WebAPI/Controller/ResponseData.java @@ -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); }