mirror of
https://github.com/TheWhiteDog9487/WebAPI.git
synced 2026-08-11 23:01:30 +08:00
@@ -8,6 +8,14 @@
|
|||||||
能用,但缺的东西非常多。
|
能用,但缺的东西非常多。
|
||||||
就像这个文档一样。
|
就像这个文档一样。
|
||||||
|
|
||||||
|
# 命令行参数
|
||||||
|
| 参数 | 说明 |
|
||||||
|
|---|------------------------------------------------------------|
|
||||||
|
| `install` / `--install` | 安装为 systemd 服务 |
|
||||||
|
| `uninstall` / `--uninstall` | 卸载 systemd 服务 |
|
||||||
|
| `--Discord_Bot_Token <令牌>` | 设置 Discord 机器人令牌,优先级高于同名环境变量 |
|
||||||
|
| `--Disable-Discord` | 安装systemd服务时禁用 Discord 相关功能,故不再要求提供令牌 |
|
||||||
|
|
||||||
# 注意事项
|
# 注意事项
|
||||||
**本项目不保证配置文件和环境变量的向后兼容!!!**
|
**本项目不保证配置文件和环境变量的向后兼容!!!**
|
||||||
**也就是说,如果你也要用的话,你可能必须在更新之后重新配置环境变量和配置文件**
|
**也就是说,如果你也要用的话,你可能必须在更新之后重新配置环境变量和配置文件**
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "xyz.thewhitedog9487"
|
group = "xyz.thewhitedog9487"
|
||||||
version = "0.8.2"
|
version = "0.8.3"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
toolchain {
|
toolchain {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class OpenApiConfiguration {
|
|||||||
.contact(Contact()
|
.contact(Contact()
|
||||||
.name("TheWhiteDog9487")
|
.name("TheWhiteDog9487")
|
||||||
.url("https://www.github.com/TheWhiteDog9487/WebAPI"))
|
.url("https://www.github.com/TheWhiteDog9487/WebAPI"))
|
||||||
.version("0.8.2")
|
.version("0.8.3")
|
||||||
.license(License()
|
.license(License()
|
||||||
.name("WTFPL")
|
.name("WTFPL")
|
||||||
.url("https://spdx.org/licenses/WTFPL")) )
|
.url("https://spdx.org/licenses/WTFPL")) )
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ import xyz.thewhitedog9487.WebAPI.Data.Entity.AccessLog
|
|||||||
import xyz.thewhitedog9487.WebAPI.Miscellaneous.SpringContextHolder.SpringContext
|
import xyz.thewhitedog9487.WebAPI.Miscellaneous.SpringContextHolder.SpringContext
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
class ApiKeyAuthenticationFilter(): OncePerRequestFilter() {
|
class ApiKeyAuthenticationFilter: OncePerRequestFilter() {
|
||||||
val ApiKeyManagerInstance = SpringContext.getBean<ApiKeyManager>()
|
val ApiKeyManagerInstance = SpringContext.getBean<ApiKeyManager>()
|
||||||
|
|
||||||
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
|
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
|
||||||
|
|||||||
@@ -41,22 +41,18 @@ class AccessLog(val AccessLogRepositoryInstance: AccessLogRepository) {
|
|||||||
examples = [
|
examples = [
|
||||||
ExampleObject(
|
ExampleObject(
|
||||||
name = "Limit小于0",
|
name = "Limit小于0",
|
||||||
value = """
|
value = """{
|
||||||
{
|
|
||||||
"code": 400,
|
"code": 400,
|
||||||
"message": "Limit参数不能小于0",
|
"message": "Limit参数不能小于0",
|
||||||
"data": null
|
"data": null
|
||||||
}
|
}""" ),
|
||||||
""" ),
|
|
||||||
ExampleObject(
|
ExampleObject(
|
||||||
name = "字段不存在",
|
name = "字段不存在",
|
||||||
value = """
|
value = """{
|
||||||
{
|
|
||||||
"code": 400,
|
"code": 400,
|
||||||
"message": "试图访问一个不存在的数据库字段",
|
"message": "试图访问一个不存在的数据库字段",
|
||||||
"data": "AccessLog类中不存在名为 NotExistField 的字段"
|
"data": "AccessLog类中不存在名为 NotExistField 的字段"
|
||||||
}
|
}""" ) ] ) ] ),
|
||||||
""" ) ] ) ] ),
|
|
||||||
ApiResponse(
|
ApiResponse(
|
||||||
responseCode = "200",
|
responseCode = "200",
|
||||||
description = "成功获取到数据",
|
description = "成功获取到数据",
|
||||||
@@ -68,8 +64,7 @@ class AccessLog(val AccessLogRepositoryInstance: AccessLogRepository) {
|
|||||||
schema = Schema(implementation = AccessLog::class) ),
|
schema = Schema(implementation = AccessLog::class) ),
|
||||||
examples = [
|
examples = [
|
||||||
ExampleObject(
|
ExampleObject(
|
||||||
value = """
|
value = """[
|
||||||
[
|
|
||||||
{
|
{
|
||||||
"ID": 18,
|
"ID": 18,
|
||||||
"RequestID": "3",
|
"RequestID": "3",
|
||||||
@@ -105,27 +100,8 @@ class AccessLog(val AccessLogRepositoryInstance: AccessLogRepository) {
|
|||||||
"Header": "host: dev.thewhitedog9487.xyz\nx-real-ip: 172.71.150.157\nx-forwarded-for: 2409:8962:77a:49c:a579:f5af:58a3:e2c, 172.71.150.157\nx-forwarded-proto: https\nconnection: close\nx-api-key: asdasdaFSEA452453sdcv\nuser-agent: IntelliJ HTTP Client/IntelliJ IDEA 2025.2.3\naccept: */*\ncf-ray: 98cc7d6aaea1c37a-SEA\naccept-encoding: gzip, br\ncdn-loop: cloudflare; loops=1\ncf-connecting-ip: 2409:8962:77a:49c:a579:f5af:58a3:e2c\ncf-ipcountry: CN\ncf-visitor: {\"scheme\":\"https\"}\ncookie: JSESSIONID=A49527B913170D86A5544A252BD31040",
|
"Header": "host: dev.thewhitedog9487.xyz\nx-real-ip: 172.71.150.157\nx-forwarded-for: 2409:8962:77a:49c:a579:f5af:58a3:e2c, 172.71.150.157\nx-forwarded-proto: https\nconnection: close\nx-api-key: asdasdaFSEA452453sdcv\nuser-agent: IntelliJ HTTP Client/IntelliJ IDEA 2025.2.3\naccept: */*\ncf-ray: 98cc7d6aaea1c37a-SEA\naccept-encoding: gzip, br\ncdn-loop: cloudflare; loops=1\ncf-connecting-ip: 2409:8962:77a:49c:a579:f5af:58a3:e2c\ncf-ipcountry: CN\ncf-visitor: {\"scheme\":\"https\"}\ncookie: JSESSIONID=A49527B913170D86A5544A252BD31040",
|
||||||
"Timestamp": "2025-10-11T07:02:05.718Z",
|
"Timestamp": "2025-10-11T07:02:05.718Z",
|
||||||
"ResponseStatusCode": 200
|
"ResponseStatusCode": 200
|
||||||
},
|
|
||||||
{
|
|
||||||
"ID": 263,
|
|
||||||
"RequestID": "35",
|
|
||||||
"CF_Connecting_IP": "2409:8962:77a:49c:a579:f5af:58a3:e2c",
|
|
||||||
"X_Forwarded_For": "2409:8962:77a:49c:a579:f5af:58a3:e2c, 108.162.246.209",
|
|
||||||
"RemoteAddress": "192.168.128.11",
|
|
||||||
"CF_IPCountry": "CN",
|
|
||||||
"ISO3166": "CHN",
|
|
||||||
"UserAgent": "IntelliJ HTTP Client/IntelliJ IDEA 2025.2.3",
|
|
||||||
"HttpMethod": "GET",
|
|
||||||
"Protocol": "http",
|
|
||||||
"ProtocolVersion": "HTTP/1.0",
|
|
||||||
"URL": "http://dev.thewhitedog9487.xyz/ip/ip",
|
|
||||||
"QueryString": null,
|
|
||||||
"Header": "host: dev.thewhitedog9487.xyz\nx-real-ip: 108.162.246.209\nx-forwarded-for: 2409:8962:77a:49c:a579:f5af:58a3:e2c, 108.162.246.209\nx-forwarded-proto: https\nconnection: close\naccept-encoding: gzip, br\nuser-agent: IntelliJ HTTP Client/IntelliJ IDEA 2025.2.3\naccept: */*\ncf-ray: 98cc9ec12c48a1a5-SEA\ncdn-loop: cloudflare; loops=1\ncf-connecting-ip: 2409:8962:77a:49c:a579:f5af:58a3:e2c\ncf-ipcountry: CN\ncf-visitor: {\"scheme\":\"https\"}\ncookie: JSESSIONID=A49527B913170D86A5544A252BD31040",
|
|
||||||
"Timestamp": "2025-10-11T07:24:51.504Z",
|
|
||||||
"ResponseStatusCode": 200
|
|
||||||
}
|
}
|
||||||
]
|
]""" ) ] ) ] ),
|
||||||
""" ) ] ) ] ),
|
|
||||||
ApiResponse(
|
ApiResponse(
|
||||||
responseCode = "401",
|
responseCode = "401",
|
||||||
description = "未提供API密钥",
|
description = "未提供API密钥",
|
||||||
@@ -135,13 +111,11 @@ class AccessLog(val AccessLogRepositoryInstance: AccessLogRepository) {
|
|||||||
schema = Schema(implementation = ResponseData::class),
|
schema = Schema(implementation = ResponseData::class),
|
||||||
examples = [
|
examples = [
|
||||||
ExampleObject(
|
ExampleObject(
|
||||||
value = """
|
value = """{
|
||||||
{
|
|
||||||
"code": 401,
|
"code": 401,
|
||||||
"message": "API密钥验证失败,未传递X-API-Key请求头",
|
"message": "API密钥验证失败,未传递X-API-Key请求头",
|
||||||
"data": null
|
"data": null
|
||||||
}
|
}""" ) ] ) ] ),
|
||||||
""" ) ] ) ] ),
|
|
||||||
ApiResponse(
|
ApiResponse(
|
||||||
responseCode = "403",
|
responseCode = "403",
|
||||||
description = "API密钥无效",
|
description = "API密钥无效",
|
||||||
@@ -151,13 +125,11 @@ class AccessLog(val AccessLogRepositoryInstance: AccessLogRepository) {
|
|||||||
schema = Schema(implementation = ResponseData::class),
|
schema = Schema(implementation = ResponseData::class),
|
||||||
examples = [
|
examples = [
|
||||||
ExampleObject(
|
ExampleObject(
|
||||||
value = """
|
value = """{
|
||||||
{
|
|
||||||
"code": 403,
|
"code": 403,
|
||||||
"message": "API密钥验证失败,API密钥无效",
|
"message": "API密钥验证失败,API密钥无效",
|
||||||
"data": null
|
"data": null
|
||||||
}
|
}""" ) ] ) ] ) ] )
|
||||||
""" ) ] ) ] ) ] )
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun GetLog(@Parameter(description = "用于身份验证的API密钥", `in` = ParameterIn.HEADER, required = true, example = "ds1858dscc8745sfwe" )
|
fun GetLog(@Parameter(description = "用于身份验证的API密钥", `in` = ParameterIn.HEADER, required = true, example = "ds1858dscc8745sfwe" )
|
||||||
@RequestHeader("X-API-Key") ApiKey: String,
|
@RequestHeader("X-API-Key") ApiKey: String,
|
||||||
|
|||||||
@@ -26,12 +26,13 @@ class IP {
|
|||||||
1. CF-Connecting-IP
|
1. CF-Connecting-IP
|
||||||
2. X-Forwarded-For
|
2. X-Forwarded-For
|
||||||
3. 直接使用请求的远程地址""")
|
3. 直接使用请求的远程地址""")
|
||||||
@ApiResponse(responseCode = "200",
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "成功获取到IP地址,内容为纯文本格式的IP地址",
|
||||||
content = [Content(
|
content = [Content(
|
||||||
mediaType = MediaType.TEXT_PLAIN_VALUE,
|
mediaType = MediaType.TEXT_PLAIN_VALUE,
|
||||||
schema = Schema(implementation = String::class),
|
schema = Schema(implementation = String::class),
|
||||||
examples = [ExampleObject(value = "78.141.226.247")])],
|
examples = [ExampleObject(value = "78.141.226.247") ] ) ] )
|
||||||
description = "成功获取到IP地址,内容为纯文本格式的IP地址")
|
|
||||||
@GetMapping("")
|
@GetMapping("")
|
||||||
fun GetIP(@RequestHeader("CF-Connecting-IP", required = false) Header_CFConnectingIP: String?,
|
fun GetIP(@RequestHeader("CF-Connecting-IP", required = false) Header_CFConnectingIP: String?,
|
||||||
@RequestHeader("X-Forwarded-For", required = false) Header_XForwardedFor: String?,
|
@RequestHeader("X-Forwarded-For", required = false) Header_XForwardedFor: String?,
|
||||||
@@ -54,18 +55,20 @@ class IP {
|
|||||||
如果请求没有经过Cloudflare,则会返回"未找到CF-IPCountry头部"
|
如果请求没有经过Cloudflare,则会返回"未找到CF-IPCountry头部"
|
||||||
""")
|
""")
|
||||||
@ApiResponses(value = [
|
@ApiResponses(value = [
|
||||||
ApiResponse(responseCode = "200",
|
ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "成功获取到ISO 3166-1 alpha-2国家代码,内容为纯文本格式的国家代码",
|
||||||
content = [Content(
|
content = [Content(
|
||||||
mediaType = MediaType.TEXT_PLAIN_VALUE,
|
mediaType = MediaType.TEXT_PLAIN_VALUE,
|
||||||
schema = Schema(implementation = String::class),
|
schema = Schema(implementation = String::class),
|
||||||
examples = [ExampleObject(value = "HK")])],
|
examples = [ExampleObject(value = "HK") ] ) ] ),
|
||||||
description = "成功获取到ISO 3166-1 alpha-2国家代码,内容为纯文本格式的国家代码"),
|
ApiResponse(
|
||||||
ApiResponse(responseCode = "404",
|
responseCode = "404",
|
||||||
|
description = "未找到CF-IPCountry头部,内容为纯文本格式的错误信息",
|
||||||
content = [Content(
|
content = [Content(
|
||||||
mediaType = MediaType.TEXT_PLAIN_VALUE,
|
mediaType = MediaType.TEXT_PLAIN_VALUE,
|
||||||
schema = Schema(implementation = String::class),
|
schema = Schema(implementation = String::class),
|
||||||
examples = [ExampleObject(value = "未找到CF-IPCountry头部")] ) ],
|
examples = [ExampleObject(value = "未找到CF-IPCountry头部") ] ) ] ) ] )
|
||||||
description = "未找到CF-IPCountry头部,内容为纯文本格式的错误信息") ] )
|
|
||||||
@GetMapping("iso3166")
|
@GetMapping("iso3166")
|
||||||
fun GetIso3166(@RequestHeader("CF-IPCountry", required = false) Header_CFIPCountry: String?): ResponseEntity<String> {
|
fun GetIso3166(@RequestHeader("CF-IPCountry", required = false) Header_CFIPCountry: String?): ResponseEntity<String> {
|
||||||
Header_CFIPCountry?.let {
|
Header_CFIPCountry?.let {
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import org.springframework.http.HttpStatus
|
|||||||
import org.springframework.http.MediaType
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.http.converter.HttpMessageNotReadableException
|
import org.springframework.http.converter.HttpMessageNotReadableException
|
||||||
import org.springframework.web.bind.MissingRequestHeaderException
|
|
||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
|
import xyz.thewhitedog9487.WebAPI.Exception.DiscordBotTokenDoesNotConfiguredException
|
||||||
import xyz.thewhitedog9487.WebAPI.Exception.NoSuchDiscordChannelException
|
import xyz.thewhitedog9487.WebAPI.Exception.NoSuchDiscordChannelException
|
||||||
import xyz.thewhitedog9487.WebAPI.Miscellaneous.VirtualThreadCoroutineDispatcher
|
import xyz.thewhitedog9487.WebAPI.Miscellaneous.VirtualThreadCoroutineDispatcher
|
||||||
import xyz.thewhitedog9487.WebAPI.Miscellaneous.link
|
import xyz.thewhitedog9487.WebAPI.Miscellaneous.link
|
||||||
@@ -30,7 +30,7 @@ import java.net.URI
|
|||||||
@Tag(name="远程消息处理相关")
|
@Tag(name="远程消息处理相关")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/message")
|
@RequestMapping("/message")
|
||||||
class Message(val DiscordBotClient: Kord) {
|
class Message(val DiscordBotClient: Kord?) {
|
||||||
@JsonAutoDetect(
|
@JsonAutoDetect(
|
||||||
fieldVisibility = JsonAutoDetect.Visibility.ANY,
|
fieldVisibility = JsonAutoDetect.Visibility.ANY,
|
||||||
getterVisibility = JsonAutoDetect.Visibility.NONE,
|
getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||||
@@ -51,92 +51,97 @@ class Message(val DiscordBotClient: Kord) {
|
|||||||
需要在请求头中提供X-API-Key以进行身份验证
|
需要在请求头中提供X-API-Key以进行身份验证
|
||||||
需要在请求体中提供频道ID和消息内容""")
|
需要在请求体中提供频道ID和消息内容""")
|
||||||
@ApiResponses(value = [
|
@ApiResponses(value = [
|
||||||
ApiResponse(responseCode = "201",
|
ApiResponse(
|
||||||
content = [Content(
|
responseCode = "201",
|
||||||
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
description = "消息发送成功,响应体中包含新消息的ID、频道ID和内容",
|
||||||
schema = Schema(implementation = ResponseData::class),
|
content = [Content(
|
||||||
examples = [ExampleObject(value =
|
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
||||||
"""
|
schema = Schema(implementation = ResponseData::class),
|
||||||
{
|
examples = [ExampleObject(value = """{
|
||||||
"code": 201,
|
"code": 201,
|
||||||
"message": "消息发送成功",
|
"message": "消息发送成功",
|
||||||
"data": {
|
"data": {
|
||||||
"新ID": "1407848258813825135",
|
"新ID": "1407848258813825135",
|
||||||
"频道ID": "1398192763845214239",
|
"频道ID": "1398192763845214239",
|
||||||
"内容": "写点什么好呢"
|
"内容": "写点什么好呢"
|
||||||
}
|
}
|
||||||
}
|
}""") ] ) ] ),
|
||||||
""", name = "成功") ] ) ],
|
ApiResponse(
|
||||||
description = "消息发送成功,响应体中包含新消息的ID、频道ID和内容"),
|
responseCode = "400",
|
||||||
ApiResponse(responseCode = "400",
|
description = "客户端发送的请求存在问题,请检查响应的data字段以获取更多信息",
|
||||||
content = [Content(
|
content = [Content(
|
||||||
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
||||||
schema = Schema(implementation = ResponseData::class),
|
schema = Schema(implementation = ResponseData::class),
|
||||||
examples = [ExampleObject(value =
|
examples = [ExampleObject(
|
||||||
"""
|
name = "请求体无法解析",
|
||||||
{
|
value = """{
|
||||||
"code": 400,
|
"code": 400,
|
||||||
"message": "请求体无法解析",
|
"message": "请求体无法解析",
|
||||||
"data": {
|
"data": {
|
||||||
"错误信息": "JSON parse error: Unexpected character ('}' (code 125)): was expecting double-quote to start field name"
|
"错误信息": "JSON parse error: Unexpected character ('}' (code 125)): was expecting double-quote to start field name"
|
||||||
}
|
}
|
||||||
}
|
}"""),
|
||||||
""", name = "请求体无法解析"),
|
ExampleObject(
|
||||||
ExampleObject(value =
|
name = "频道不存在",
|
||||||
"""
|
value =
|
||||||
{
|
"""{
|
||||||
"code": 400,
|
"code": 400,
|
||||||
"message": "消息发送失败",
|
"message": "消息发送失败",
|
||||||
"data": {
|
"data": {
|
||||||
"错误信息": "ID为1398192763845214239的频道不存在",
|
"错误信息": "ID为1398192763845214239的频道不存在",
|
||||||
"频道ID": "1398192763845214239"
|
"频道ID": "1398192763845214239"
|
||||||
}
|
}
|
||||||
}
|
}""") ] ) ] ),
|
||||||
""", name = "频道不存在")] ) ],
|
ApiResponse(
|
||||||
description = "客户端发送的请求存在问题,请检查响应的data字段以获取更多信息"),
|
responseCode = "401",
|
||||||
ApiResponse(responseCode = "401",
|
description = "未提供API密钥",
|
||||||
content = [Content(
|
content = [Content(
|
||||||
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
||||||
schema = Schema(implementation = ResponseData::class),
|
schema = Schema(implementation = ResponseData::class),
|
||||||
examples = [ExampleObject(value =
|
examples = [ExampleObject(value = """{
|
||||||
"""
|
"code": 401,
|
||||||
{
|
"message": "API密钥验证失败,未传递X-API-Key请求头",
|
||||||
"code": 401,
|
"data": null
|
||||||
"message": "API密钥验证失败,未传递X-API-Key请求头",
|
}""") ] ) ] ),
|
||||||
"data": null
|
ApiResponse(
|
||||||
}
|
responseCode = "403",
|
||||||
""") ] ) ],
|
description = "API密钥无效",
|
||||||
description = "未提供API密钥"),
|
content = [Content(
|
||||||
ApiResponse(responseCode = "403",
|
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
||||||
content = [Content(
|
schema = Schema(implementation = ResponseData::class),
|
||||||
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
examples = [ExampleObject(value = """{
|
||||||
schema = Schema(implementation = ResponseData::class),
|
"code": 403,
|
||||||
examples = [ExampleObject(value =
|
"message": "API密钥验证失败,API密钥无效",
|
||||||
"""
|
"data": null
|
||||||
{
|
}""") ] ) ] ),
|
||||||
"code": 403,
|
ApiResponse(
|
||||||
"message": "API密钥验证失败,API密钥无效",
|
responseCode = "503",
|
||||||
"data": null
|
description = "服务端未配置Discord令牌,无法使用Discord相关功能",
|
||||||
}
|
content = [Content(
|
||||||
""") ] ) ],
|
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
||||||
description = "API密钥无效"),
|
schema = Schema(implementation = ResponseData::class),
|
||||||
ApiResponse(responseCode = "500",
|
examples = [ExampleObject(value = """{
|
||||||
content = [Content(
|
"code": 503,
|
||||||
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
"message": "服务端尚未配置Discord令牌,无法使用Discord相关功能!",
|
||||||
schema = Schema(implementation = ResponseData::class),
|
"data": null
|
||||||
examples = [ExampleObject(value =
|
}""") ] ) ] ),
|
||||||
"""{
|
ApiResponse(
|
||||||
"code": 500,
|
responseCode = "500",
|
||||||
"message": "处理请求时发生未知错误",
|
|
||||||
"data": {
|
|
||||||
"错误信息": "POST /channels/1398192763845214/messages returned 404 Not Found with response {code=10003, message=Unknown Channel}"
|
|
||||||
}
|
|
||||||
}""", name = "未知错误") ] ) ],
|
|
||||||
description =
|
description =
|
||||||
"""
|
"""
|
||||||
处理请求时发生未知错误,可能是由于Discord服务器问题或其他内部错误,请查看响应Body以确定原因
|
处理请求时发生未知错误,可能是由于Discord服务器问题或其他内部错误,请查看响应Body以确定原因
|
||||||
另外,所有未被针对性处理的异常都会触发此响应
|
另外,所有未被针对性处理的异常都会触发此响应
|
||||||
""") ] )
|
""",
|
||||||
|
content = [Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
||||||
|
schema = Schema(implementation = ResponseData::class),
|
||||||
|
examples = [ExampleObject(value = """{
|
||||||
|
"code": 500,
|
||||||
|
"message": "处理请求时发生未知错误",
|
||||||
|
"data": {
|
||||||
|
"错误信息": "POST /channels/1398192763845214/messages returned 404 Not Found with response {code=10003, message=Unknown Channel}"
|
||||||
|
}
|
||||||
|
}""") ] ) ] ) ] )
|
||||||
@PostMapping("/discord")
|
@PostMapping("/discord")
|
||||||
suspend fun DiscordPush(
|
suspend fun DiscordPush(
|
||||||
@Parameter(description = "用于身份验证的API密钥", `in` = ParameterIn.HEADER, required = true, example = "ds1858dscc8745sfwe")
|
@Parameter(description = "用于身份验证的API密钥", `in` = ParameterIn.HEADER, required = true, example = "ds1858dscc8745sfwe")
|
||||||
@@ -148,9 +153,10 @@ class Message(val DiscordBotClient: Kord) {
|
|||||||
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
||||||
schema = Schema(implementation = PostMessageData::class) ) ] )
|
schema = Schema(implementation = PostMessageData::class) ) ] )
|
||||||
@RequestBody RequestBody: PostMessageData): ResponseEntity<ResponseData> {
|
@RequestBody RequestBody: PostMessageData): ResponseEntity<ResponseData> {
|
||||||
|
DiscordBotClient ?: throw DiscordBotTokenDoesNotConfiguredException()
|
||||||
return withContext(VirtualThreadCoroutineDispatcher) {
|
return withContext(VirtualThreadCoroutineDispatcher) {
|
||||||
val ChannelID = Snowflake(RequestBody.ChannelId)
|
val ChannelID = Snowflake(RequestBody.ChannelId)
|
||||||
val MessageData = DiscordBotClient
|
val MessageData = DiscordBotClient!!
|
||||||
.getChannelOf<TextChannel>(ChannelID)
|
.getChannelOf<TextChannel>(ChannelID)
|
||||||
?.createMessage(RequestBody.Content)
|
?.createMessage(RequestBody.Content)
|
||||||
?: throw NoSuchDiscordChannelException(ChannelID)
|
?: throw NoSuchDiscordChannelException(ChannelID)
|
||||||
@@ -162,6 +168,13 @@ class Message(val DiscordBotClient: Kord) {
|
|||||||
"新ID" to MessageData.id.toString(),
|
"新ID" to MessageData.id.toString(),
|
||||||
"频道ID" to ChannelID.value,
|
"频道ID" to ChannelID.value,
|
||||||
"内容" to RequestBody.Content ) ) ) } }
|
"内容" to RequestBody.Content ) ) ) } }
|
||||||
|
|
||||||
|
@ExceptionHandler(DiscordBotTokenDoesNotConfiguredException::class)
|
||||||
|
fun TriggerWhenDiscordBotTokenDoesNotConfigured(Exception: DiscordBotTokenDoesNotConfiguredException, Request: HttpServletRequest): ResponseEntity<ResponseData> {
|
||||||
|
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
|
||||||
|
.body(ResponseData(
|
||||||
|
HttpStatus.SERVICE_UNAVAILABLE.value(),
|
||||||
|
Exception.message ) ) }
|
||||||
@ExceptionHandler(NoSuchDiscordChannelException::class)
|
@ExceptionHandler(NoSuchDiscordChannelException::class)
|
||||||
fun TriggerWhenSpecificChannelDoesNotExist(Exception: NoSuchDiscordChannelException, Request: HttpServletRequest): ResponseEntity<ResponseData> {
|
fun TriggerWhenSpecificChannelDoesNotExist(Exception: NoSuchDiscordChannelException, Request: HttpServletRequest): ResponseEntity<ResponseData> {
|
||||||
return ResponseEntity.badRequest()
|
return ResponseEntity.badRequest()
|
||||||
@@ -171,13 +184,6 @@ class Message(val DiscordBotClient: Kord) {
|
|||||||
mapOf(
|
mapOf(
|
||||||
"错误信息" to Exception.message,
|
"错误信息" to Exception.message,
|
||||||
"频道ID" to Exception.TargetChannelId.value ) ) ) }
|
"频道ID" to Exception.TargetChannelId.value ) ) ) }
|
||||||
@ExceptionHandler(MissingRequestHeaderException::class)
|
|
||||||
fun HandleMissingHeader(Exception: MissingRequestHeaderException, Request: HttpServletRequest): ResponseEntity<ResponseData> {
|
|
||||||
return ResponseEntity.badRequest()
|
|
||||||
.body(ResponseData(
|
|
||||||
HttpStatus.BAD_REQUEST.value(),
|
|
||||||
"请求缺少必要的头部信息",
|
|
||||||
mapOf("缺失的头部" to Exception.headerName ) ) ) }
|
|
||||||
@ExceptionHandler(HttpMessageNotReadableException::class)
|
@ExceptionHandler(HttpMessageNotReadableException::class)
|
||||||
fun HandleMessageNotReadable(Exception: HttpMessageNotReadableException, Request: HttpServletRequest): ResponseEntity<ResponseData> {
|
fun HandleMessageNotReadable(Exception: HttpMessageNotReadableException, Request: HttpServletRequest): ResponseEntity<ResponseData> {
|
||||||
return ResponseEntity.badRequest()
|
return ResponseEntity.badRequest()
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ cookie: JSESSIONID=A49527B913170D86A5544A252BD31040
|
|||||||
var Header: String = "",
|
var Header: String = "",
|
||||||
|
|
||||||
@JsonProperty("Timestamp")
|
@JsonProperty("Timestamp")
|
||||||
@Schema(description = "请求时间(ISO-8601格式)", example = "2025-09-29T03:49:42.766Z")
|
@Schema(description = "请求时间 ISO-8601格式", example = "2025-09-29T03:49:42.766Z")
|
||||||
var Timestamp: Instant = Instant.now(),
|
var Timestamp: Instant = Instant.now(),
|
||||||
|
|
||||||
@JsonProperty("ResponseStatusCode")
|
@JsonProperty("ResponseStatusCode")
|
||||||
@@ -126,11 +126,11 @@ cookie: JSESSIONID=A49527B913170D86A5544A252BD31040
|
|||||||
httpRequest.requestURL.toString(),
|
httpRequest.requestURL.toString(),
|
||||||
httpRequest.queryString,
|
httpRequest.queryString,
|
||||||
httpRequest.headerNames.toList().joinToString("\n") { name ->
|
httpRequest.headerNames.toList().joinToString("\n") { name ->
|
||||||
return@joinToString "$name: ${httpRequest.getHeaders(name).toList().joinToString(", ")}" },
|
return@joinToString "$name: ${httpRequest.getHeaders(name).toList().joinToString(", ") }" },
|
||||||
Instant.now(),
|
Instant.now(),
|
||||||
0 )
|
0 )
|
||||||
fun SaveIntoDatabase(CachedResponse: ContentCachingResponseWrapper? = null){
|
fun SaveIntoDatabase(CachedResponse: ContentCachingResponseWrapper? = null){
|
||||||
val SQLiteWriteLock = SpringContext.getBean("getSQLiteWriteLock", ReentrantLock::class.java)
|
val SQLiteWriteLock = SpringContext.getBean<ReentrantLock>("SQLiteWriteLock")
|
||||||
val AccessLogRepository = SpringContext.getBean<AccessLogRepository>()
|
val AccessLogRepository = SpringContext.getBean<AccessLogRepository>()
|
||||||
try {
|
try {
|
||||||
SQLiteWriteLock.lock()
|
SQLiteWriteLock.lock()
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
package xyz.thewhitedog9487.WebAPI.Exception
|
||||||
|
|
||||||
|
class DiscordBotTokenDoesNotConfiguredException: Exception("服务端尚未配置Discord令牌,无法使用Discord相关功能!")
|
||||||
@@ -20,27 +20,27 @@ class GlobalSharedBean {
|
|||||||
val Logger = KotlinLogging.logger{}
|
val Logger = KotlinLogging.logger{}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun DiscordClient(@Value("\${Discord_Bot_Token:}") DiscordBotToken: String): Kord {
|
fun DiscordClient(@Value("\${Discord_Bot_Token:}") DiscordBotToken: String): Kord? {
|
||||||
if (DiscordBotToken.isEmpty()) {
|
if (DiscordBotToken.isEmpty()) {
|
||||||
Logger.error { "未通过参数--Discord_Bot_Token或环境变量Discord_Bot_Token提供Discord登录令牌,请检查配置。" }
|
Logger.warn { "未通过参数--Discord_Bot_Token或环境变量Discord_Bot_Token提供Discord登录令牌,将无法使用Discord相关功能。" }
|
||||||
throw IllegalArgumentException("未提供Discord登录令牌") }
|
return null }
|
||||||
return runBlocking(VirtualThreadCoroutineDispatcher) { Kord(DiscordBotToken) } }
|
return runBlocking(VirtualThreadCoroutineDispatcher) { Kord(DiscordBotToken) } }
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun KordLifecycleManager(KordInstance: Kord) = object: SmartLifecycle {
|
fun KordLifecycleManager(KordInstance: Kord?) = object: SmartLifecycle {
|
||||||
var Job: Job? = null
|
var Job: Job? = null
|
||||||
override fun start() {
|
override fun start() {
|
||||||
Job = CoroutineScope(VirtualThreadCoroutineDispatcher).launch {
|
Job = CoroutineScope(VirtualThreadCoroutineDispatcher).launch {
|
||||||
KordInstance.login { } } }
|
KordInstance?.login { } } }
|
||||||
|
|
||||||
override fun stop() {
|
override fun stop() {
|
||||||
runBlocking(VirtualThreadCoroutineDispatcher) {
|
runBlocking(VirtualThreadCoroutineDispatcher) {
|
||||||
try {
|
try {
|
||||||
KordInstance.logout()
|
KordInstance?.logout()
|
||||||
} catch (_: IllegalStateException) { }
|
} catch (_: IllegalStateException) { }
|
||||||
Job?.cancelAndJoin() } }
|
Job?.cancelAndJoin() } }
|
||||||
|
|
||||||
override fun isRunning() = Job?.isActive ?: false }
|
override fun isRunning() = Job?.isActive ?: false }
|
||||||
|
|
||||||
@get:Bean
|
@get:Bean(name = ["SQLiteWriteLock"])
|
||||||
val SQLiteWriteLock: Lock = ReentrantLock() }
|
val SQLiteWriteLock: Lock = ReentrantLock() }
|
||||||
|
|||||||
@@ -60,13 +60,16 @@ fun ProcessArguments(CommandLineArguments: Array<String>) {
|
|||||||
在Native Image / Jar文件旁边生成指向自身的,名为Current的软连接
|
在Native Image / Jar文件旁边生成指向自身的,名为Current的软连接
|
||||||
然后在软连接旁生成systemd服务文件,执行目标指向软连接
|
然后在软连接旁生成systemd服务文件,执行目标指向软连接
|
||||||
*/
|
*/
|
||||||
|
val DisableDiscord = "--Disable-Discord" in CommandLineArguments
|
||||||
val DiscordBotToken = if ("--Discord_Bot_Token" in CommandLineArguments &&
|
val DiscordBotToken = if ("--Discord_Bot_Token" in CommandLineArguments &&
|
||||||
CommandLineArguments.getOrNull(CommandLineArguments.indexOf("--Discord_Bot_Token") + 1) != null ){
|
CommandLineArguments.getOrNull(CommandLineArguments.indexOf("--Discord_Bot_Token") + 1) != null ){
|
||||||
CommandLineArguments[CommandLineArguments.indexOf("--Discord_Bot_Token") + 1] }
|
CommandLineArguments[CommandLineArguments.indexOf("--Discord_Bot_Token") + 1] }
|
||||||
else System.getenv().getOrDefault("Discord_Bot_Token", "")
|
else System.getenv().getOrDefault("Discord_Bot_Token", "")
|
||||||
if (DiscordBotToken.isEmpty()) {
|
if (DiscordBotToken.isEmpty() && DisableDiscord == false) {
|
||||||
Logger.error { "必须通过--Discord_Bot_Token参数或Discord_Bot_Token环境变量提供Discord机器人的令牌以使本程序正常工作。" }
|
Logger.error { "必须通过--Discord_Bot_Token参数或Discord_Bot_Token环境变量提供Discord机器人的令牌以使本程序正常工作。" }
|
||||||
throw IllegalArgumentException("缺少必须的--Discord_Bot_Token参数") }
|
throw IllegalArgumentException("缺少必须的--Discord_Bot_Token参数") }
|
||||||
|
if (DisableDiscord) {
|
||||||
|
Logger.warn { "已通过--Disable-Discord参数禁用Discord功能,程序无法使用Discord相关功能。" } }
|
||||||
Logger.info { "开始安装systemd服务" }
|
Logger.info { "开始安装systemd服务" }
|
||||||
Logger.debug { "SystemProperties.os.name: $CurrentOS" }
|
Logger.debug { "SystemProperties.os.name: $CurrentOS" }
|
||||||
if (CurrentOS.startsWith("Linux")) {
|
if (CurrentOS.startsWith("Linux")) {
|
||||||
@@ -100,7 +103,7 @@ fun ProcessArguments(CommandLineArguments: Array<String>) {
|
|||||||
ExecStart=$ExecCommand
|
ExecStart=$ExecCommand
|
||||||
Restart=always
|
Restart=always
|
||||||
Type=simple
|
Type=simple
|
||||||
Environment="Discord_Bot_Token=$DiscordBotToken"
|
${if (DisableDiscord == false) "Environment=\"Discord_Bot_Token=$DiscordBotToken\"" else ""}
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
Reference in New Issue
Block a user