diff --git a/README.md b/README.md index 2f34dde..8574d1d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,14 @@ 能用,但缺的东西非常多。 就像这个文档一样。 +# 命令行参数 +| 参数 | 说明 | +|---|------------------------------------------------------------| +| `install` / `--install` | 安装为 systemd 服务 | +| `uninstall` / `--uninstall` | 卸载 systemd 服务 | +| `--Discord_Bot_Token <令牌>` | 设置 Discord 机器人令牌,优先级高于同名环境变量 | +| `--Disable-Discord` | 安装systemd服务时禁用 Discord 相关功能,故不再要求提供令牌 | + # 注意事项 **本项目不保证配置文件和环境变量的向后兼容!!!** **也就是说,如果你也要用的话,你可能必须在更新之后重新配置环境变量和配置文件** diff --git a/build.gradle.kts b/build.gradle.kts index b99a5fb..670f772 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ plugins { } group = "xyz.thewhitedog9487" -version = "0.8.2" +version = "0.8.3" java { toolchain { diff --git a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Configuration/OpenApiConfiguration.kt b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Configuration/OpenApiConfiguration.kt index 72b3e72..4017fcf 100644 --- a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Configuration/OpenApiConfiguration.kt +++ b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Configuration/OpenApiConfiguration.kt @@ -21,7 +21,7 @@ class OpenApiConfiguration { .contact(Contact() .name("TheWhiteDog9487") .url("https://www.github.com/TheWhiteDog9487/WebAPI")) - .version("0.8.2") + .version("0.8.3") .license(License() .name("WTFPL") .url("https://spdx.org/licenses/WTFPL")) ) diff --git a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Configuration/Security/ApiKeyAuthenticationFilter.kt b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Configuration/Security/ApiKeyAuthenticationFilter.kt index aa605a3..a8ebe55 100644 --- a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Configuration/Security/ApiKeyAuthenticationFilter.kt +++ b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Configuration/Security/ApiKeyAuthenticationFilter.kt @@ -16,7 +16,7 @@ import xyz.thewhitedog9487.WebAPI.Data.Entity.AccessLog import xyz.thewhitedog9487.WebAPI.Miscellaneous.SpringContextHolder.SpringContext import java.nio.charset.StandardCharsets -class ApiKeyAuthenticationFilter(): OncePerRequestFilter() { +class ApiKeyAuthenticationFilter: OncePerRequestFilter() { val ApiKeyManagerInstance = SpringContext.getBean() override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) { diff --git a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Controller/AccessLog.kt b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Controller/AccessLog.kt index 8b05890..218d24c 100644 --- a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Controller/AccessLog.kt +++ b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Controller/AccessLog.kt @@ -41,22 +41,18 @@ class AccessLog(val AccessLogRepositoryInstance: AccessLogRepository) { examples = [ ExampleObject( name = "Limit小于0", - value = """ - { + value = """{ "code": 400, "message": "Limit参数不能小于0", "data": null - } - """ ), + }""" ), ExampleObject( name = "字段不存在", - value = """ - { + value = """{ "code": 400, "message": "试图访问一个不存在的数据库字段", "data": "AccessLog类中不存在名为 NotExistField 的字段" - } - """ ) ] ) ] ), + }""" ) ] ) ] ), ApiResponse( responseCode = "200", description = "成功获取到数据", @@ -68,8 +64,7 @@ class AccessLog(val AccessLogRepositoryInstance: AccessLogRepository) { schema = Schema(implementation = AccessLog::class) ), examples = [ ExampleObject( - value = """ - [ + value = """[ { "ID": 18, "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", "Timestamp": "2025-10-11T07:02:05.718Z", "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( responseCode = "401", description = "未提供API密钥", @@ -135,13 +111,11 @@ class AccessLog(val AccessLogRepositoryInstance: AccessLogRepository) { schema = Schema(implementation = ResponseData::class), examples = [ ExampleObject( - value = """ - { + value = """{ "code": 401, "message": "API密钥验证失败,未传递X-API-Key请求头", "data": null - } - """ ) ] ) ] ), + }""" ) ] ) ] ), ApiResponse( responseCode = "403", description = "API密钥无效", @@ -151,13 +125,11 @@ class AccessLog(val AccessLogRepositoryInstance: AccessLogRepository) { schema = Schema(implementation = ResponseData::class), examples = [ ExampleObject( - value = """ - { + value = """{ "code": 403, "message": "API密钥验证失败,API密钥无效", "data": null - } - """ ) ] ) ] ) ] ) + }""" ) ] ) ] ) ] ) @GetMapping fun GetLog(@Parameter(description = "用于身份验证的API密钥", `in` = ParameterIn.HEADER, required = true, example = "ds1858dscc8745sfwe" ) @RequestHeader("X-API-Key") ApiKey: String, diff --git a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Controller/IP.kt b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Controller/IP.kt index 85dc470..ae5d4c7 100644 --- a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Controller/IP.kt +++ b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Controller/IP.kt @@ -26,12 +26,13 @@ class IP { 1. CF-Connecting-IP 2. X-Forwarded-For 3. 直接使用请求的远程地址""") - @ApiResponse(responseCode = "200", + @ApiResponse( + responseCode = "200", + description = "成功获取到IP地址,内容为纯文本格式的IP地址", content = [Content( mediaType = MediaType.TEXT_PLAIN_VALUE, schema = Schema(implementation = String::class), - examples = [ExampleObject(value = "78.141.226.247")])], - description = "成功获取到IP地址,内容为纯文本格式的IP地址") + examples = [ExampleObject(value = "78.141.226.247") ] ) ] ) @GetMapping("") fun GetIP(@RequestHeader("CF-Connecting-IP", required = false) Header_CFConnectingIP: String?, @RequestHeader("X-Forwarded-For", required = false) Header_XForwardedFor: String?, @@ -54,18 +55,20 @@ class IP { 如果请求没有经过Cloudflare,则会返回"未找到CF-IPCountry头部" """) @ApiResponses(value = [ - ApiResponse(responseCode = "200", + ApiResponse( + responseCode = "200", + description = "成功获取到ISO 3166-1 alpha-2国家代码,内容为纯文本格式的国家代码", 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", + examples = [ExampleObject(value = "HK") ] ) ] ), + ApiResponse( + responseCode = "404", + description = "未找到CF-IPCountry头部,内容为纯文本格式的错误信息", content = [Content( mediaType = MediaType.TEXT_PLAIN_VALUE, schema = Schema(implementation = String::class), - examples = [ExampleObject(value = "未找到CF-IPCountry头部")] ) ], - description = "未找到CF-IPCountry头部,内容为纯文本格式的错误信息") ] ) + examples = [ExampleObject(value = "未找到CF-IPCountry头部") ] ) ] ) ] ) @GetMapping("iso3166") fun GetIso3166(@RequestHeader("CF-IPCountry", required = false) Header_CFIPCountry: String?): ResponseEntity { Header_CFIPCountry?.let { diff --git a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Controller/Message.kt b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Controller/Message.kt index dd1027e..d23b212 100644 --- a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Controller/Message.kt +++ b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Controller/Message.kt @@ -20,8 +20,8 @@ import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.http.converter.HttpMessageNotReadableException -import org.springframework.web.bind.MissingRequestHeaderException import org.springframework.web.bind.annotation.* +import xyz.thewhitedog9487.WebAPI.Exception.DiscordBotTokenDoesNotConfiguredException import xyz.thewhitedog9487.WebAPI.Exception.NoSuchDiscordChannelException import xyz.thewhitedog9487.WebAPI.Miscellaneous.VirtualThreadCoroutineDispatcher import xyz.thewhitedog9487.WebAPI.Miscellaneous.link @@ -30,7 +30,7 @@ import java.net.URI @Tag(name="远程消息处理相关") @RestController @RequestMapping("/message") -class Message(val DiscordBotClient: Kord) { +class Message(val DiscordBotClient: Kord?) { @JsonAutoDetect( fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, @@ -51,92 +51,97 @@ class Message(val DiscordBotClient: Kord) { 需要在请求头中提供X-API-Key以进行身份验证 需要在请求体中提供频道ID和消息内容""") @ApiResponses(value = [ - ApiResponse(responseCode = "201", - content = [Content( - mediaType = MediaType.APPLICATION_JSON_VALUE, - schema = Schema(implementation = ResponseData::class), - examples = [ExampleObject(value = -""" -{ - "code": 201, - "message": "消息发送成功", - "data": { - "新ID": "1407848258813825135", - "频道ID": "1398192763845214239", - "内容": "写点什么好呢" - } -} -""", name = "成功") ] ) ], - description = "消息发送成功,响应体中包含新消息的ID、频道ID和内容"), - ApiResponse(responseCode = "400", - content = [Content( - mediaType = MediaType.APPLICATION_JSON_VALUE, - schema = Schema(implementation = ResponseData::class), - examples = [ExampleObject(value = -""" -{ - "code": 400, - "message": "请求体无法解析", - "data": { - "错误信息": "JSON parse error: Unexpected character ('}' (code 125)): was expecting double-quote to start field name" - } -} -""", name = "请求体无法解析"), - ExampleObject(value = -""" -{ - "code": 400, - "message": "消息发送失败", - "data": { - "错误信息": "ID为1398192763845214239的频道不存在", - "频道ID": "1398192763845214239" - } -} -""", name = "频道不存在")] ) ], - description = "客户端发送的请求存在问题,请检查响应的data字段以获取更多信息"), - ApiResponse(responseCode = "401", - content = [Content( - mediaType = MediaType.APPLICATION_JSON_VALUE, - schema = Schema(implementation = ResponseData::class), - examples = [ExampleObject(value = -""" -{ - "code": 401, - "message": "API密钥验证失败,未传递X-API-Key请求头", - "data": null -} -""") ] ) ], - description = "未提供API密钥"), - ApiResponse(responseCode = "403", - content = [Content( - mediaType = MediaType.APPLICATION_JSON_VALUE, - schema = Schema(implementation = ResponseData::class), - examples = [ExampleObject(value = -""" -{ - "code": 403, - "message": "API密钥验证失败,API密钥无效", - "data": null -} -""") ] ) ], - description = "API密钥无效"), - ApiResponse(responseCode = "500", - 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}" - } -}""", name = "未知错误") ] ) ], + ApiResponse( + responseCode = "201", + description = "消息发送成功,响应体中包含新消息的ID、频道ID和内容", + content = [Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = Schema(implementation = ResponseData::class), + examples = [ExampleObject(value = """{ + "code": 201, + "message": "消息发送成功", + "data": { + "新ID": "1407848258813825135", + "频道ID": "1398192763845214239", + "内容": "写点什么好呢" + } + }""") ] ) ] ), + ApiResponse( + responseCode = "400", + description = "客户端发送的请求存在问题,请检查响应的data字段以获取更多信息", + content = [Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = Schema(implementation = ResponseData::class), + examples = [ExampleObject( + name = "请求体无法解析", + value = """{ + "code": 400, + "message": "请求体无法解析", + "data": { + "错误信息": "JSON parse error: Unexpected character ('}' (code 125)): was expecting double-quote to start field name" + } + }"""), + ExampleObject( + name = "频道不存在", + value = + """{ + "code": 400, + "message": "消息发送失败", + "data": { + "错误信息": "ID为1398192763845214239的频道不存在", + "频道ID": "1398192763845214239" + } + }""") ] ) ] ), + ApiResponse( + responseCode = "401", + description = "未提供API密钥", + content = [Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = Schema(implementation = ResponseData::class), + examples = [ExampleObject(value = """{ + "code": 401, + "message": "API密钥验证失败,未传递X-API-Key请求头", + "data": null + }""") ] ) ] ), + ApiResponse( + responseCode = "403", + description = "API密钥无效", + content = [Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = Schema(implementation = ResponseData::class), + examples = [ExampleObject(value = """{ + "code": 403, + "message": "API密钥验证失败,API密钥无效", + "data": null + }""") ] ) ] ), + ApiResponse( + responseCode = "503", + description = "服务端未配置Discord令牌,无法使用Discord相关功能", + content = [Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = Schema(implementation = ResponseData::class), + examples = [ExampleObject(value = """{ + "code": 503, + "message": "服务端尚未配置Discord令牌,无法使用Discord相关功能!", + "data": null + }""") ] ) ] ), + ApiResponse( + responseCode = "500", description = -""" + """ 处理请求时发生未知错误,可能是由于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") suspend fun DiscordPush( @Parameter(description = "用于身份验证的API密钥", `in` = ParameterIn.HEADER, required = true, example = "ds1858dscc8745sfwe") @@ -148,9 +153,10 @@ class Message(val DiscordBotClient: Kord) { mediaType = MediaType.APPLICATION_JSON_VALUE, schema = Schema(implementation = PostMessageData::class) ) ] ) @RequestBody RequestBody: PostMessageData): ResponseEntity { + DiscordBotClient ?: throw DiscordBotTokenDoesNotConfiguredException() return withContext(VirtualThreadCoroutineDispatcher) { val ChannelID = Snowflake(RequestBody.ChannelId) - val MessageData = DiscordBotClient + val MessageData = DiscordBotClient!! .getChannelOf(ChannelID) ?.createMessage(RequestBody.Content) ?: throw NoSuchDiscordChannelException(ChannelID) @@ -162,6 +168,13 @@ class Message(val DiscordBotClient: Kord) { "新ID" to MessageData.id.toString(), "频道ID" to ChannelID.value, "内容" to RequestBody.Content ) ) ) } } + + @ExceptionHandler(DiscordBotTokenDoesNotConfiguredException::class) + fun TriggerWhenDiscordBotTokenDoesNotConfigured(Exception: DiscordBotTokenDoesNotConfiguredException, Request: HttpServletRequest): ResponseEntity { + return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) + .body(ResponseData( + HttpStatus.SERVICE_UNAVAILABLE.value(), + Exception.message ) ) } @ExceptionHandler(NoSuchDiscordChannelException::class) fun TriggerWhenSpecificChannelDoesNotExist(Exception: NoSuchDiscordChannelException, Request: HttpServletRequest): ResponseEntity { return ResponseEntity.badRequest() @@ -171,13 +184,6 @@ class Message(val DiscordBotClient: Kord) { mapOf( "错误信息" to Exception.message, "频道ID" to Exception.TargetChannelId.value ) ) ) } - @ExceptionHandler(MissingRequestHeaderException::class) - fun HandleMissingHeader(Exception: MissingRequestHeaderException, Request: HttpServletRequest): ResponseEntity { - return ResponseEntity.badRequest() - .body(ResponseData( - HttpStatus.BAD_REQUEST.value(), - "请求缺少必要的头部信息", - mapOf("缺失的头部" to Exception.headerName ) ) ) } @ExceptionHandler(HttpMessageNotReadableException::class) fun HandleMessageNotReadable(Exception: HttpMessageNotReadableException, Request: HttpServletRequest): ResponseEntity { return ResponseEntity.badRequest() diff --git a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Data/Entity/AccessLog.kt b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Data/Entity/AccessLog.kt index b7804b9..d48249e 100644 --- a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Data/Entity/AccessLog.kt +++ b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Data/Entity/AccessLog.kt @@ -105,7 +105,7 @@ cookie: JSESSIONID=A49527B913170D86A5544A252BD31040 var Header: String = "", @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(), @JsonProperty("ResponseStatusCode") @@ -126,11 +126,11 @@ cookie: JSESSIONID=A49527B913170D86A5544A252BD31040 httpRequest.requestURL.toString(), httpRequest.queryString, httpRequest.headerNames.toList().joinToString("\n") { name -> - return@joinToString "$name: ${httpRequest.getHeaders(name).toList().joinToString(", ")}" }, + return@joinToString "$name: ${httpRequest.getHeaders(name).toList().joinToString(", ") }" }, Instant.now(), 0 ) fun SaveIntoDatabase(CachedResponse: ContentCachingResponseWrapper? = null){ - val SQLiteWriteLock = SpringContext.getBean("getSQLiteWriteLock", ReentrantLock::class.java) + val SQLiteWriteLock = SpringContext.getBean("SQLiteWriteLock") val AccessLogRepository = SpringContext.getBean() try { SQLiteWriteLock.lock() diff --git a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Exception/DiscordBotTokenDoesNotConfiguredException.kt b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Exception/DiscordBotTokenDoesNotConfiguredException.kt new file mode 100644 index 0000000..2c1b056 --- /dev/null +++ b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Exception/DiscordBotTokenDoesNotConfiguredException.kt @@ -0,0 +1,3 @@ +package xyz.thewhitedog9487.WebAPI.Exception + +class DiscordBotTokenDoesNotConfiguredException: Exception("服务端尚未配置Discord令牌,无法使用Discord相关功能!") diff --git a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Miscellaneous/GlobalSharedBeans.kt b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Miscellaneous/GlobalSharedBeans.kt index 1051db6..d869d2a 100644 --- a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Miscellaneous/GlobalSharedBeans.kt +++ b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Miscellaneous/GlobalSharedBeans.kt @@ -20,27 +20,27 @@ class GlobalSharedBean { val Logger = KotlinLogging.logger{} @Bean - fun DiscordClient(@Value("\${Discord_Bot_Token:}") DiscordBotToken: String): Kord { + fun DiscordClient(@Value("\${Discord_Bot_Token:}") DiscordBotToken: String): Kord? { if (DiscordBotToken.isEmpty()) { - Logger.error { "未通过参数--Discord_Bot_Token或环境变量Discord_Bot_Token提供Discord登录令牌,请检查配置。" } - throw IllegalArgumentException("未提供Discord登录令牌") } + Logger.warn { "未通过参数--Discord_Bot_Token或环境变量Discord_Bot_Token提供Discord登录令牌,将无法使用Discord相关功能。" } + return null } return runBlocking(VirtualThreadCoroutineDispatcher) { Kord(DiscordBotToken) } } @Bean - fun KordLifecycleManager(KordInstance: Kord) = object: SmartLifecycle { + fun KordLifecycleManager(KordInstance: Kord?) = object: SmartLifecycle { var Job: Job? = null override fun start() { Job = CoroutineScope(VirtualThreadCoroutineDispatcher).launch { - KordInstance.login { } } } + KordInstance?.login { } } } override fun stop() { runBlocking(VirtualThreadCoroutineDispatcher) { try { - KordInstance.logout() + KordInstance?.logout() } catch (_: IllegalStateException) { } Job?.cancelAndJoin() } } override fun isRunning() = Job?.isActive ?: false } - @get:Bean - val SQLiteWriteLock: Lock = ReentrantLock() } \ No newline at end of file + @get:Bean(name = ["SQLiteWriteLock"]) + val SQLiteWriteLock: Lock = ReentrantLock() } diff --git a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Startup/SystemdInstallerManager.kt b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Startup/SystemdInstallerManager.kt index b6ba541..dafd166 100644 --- a/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Startup/SystemdInstallerManager.kt +++ b/src/main/kotlin/xyz/thewhitedog9487/WebAPI/Startup/SystemdInstallerManager.kt @@ -60,13 +60,16 @@ fun ProcessArguments(CommandLineArguments: Array) { 在Native Image / Jar文件旁边生成指向自身的,名为Current的软连接 然后在软连接旁生成systemd服务文件,执行目标指向软连接 */ + val DisableDiscord = "--Disable-Discord" in CommandLineArguments val DiscordBotToken = if ("--Discord_Bot_Token" in CommandLineArguments && CommandLineArguments.getOrNull(CommandLineArguments.indexOf("--Discord_Bot_Token") + 1) != null ){ CommandLineArguments[CommandLineArguments.indexOf("--Discord_Bot_Token") + 1] } else System.getenv().getOrDefault("Discord_Bot_Token", "") - if (DiscordBotToken.isEmpty()) { + if (DiscordBotToken.isEmpty() && DisableDiscord == false) { Logger.error { "必须通过--Discord_Bot_Token参数或Discord_Bot_Token环境变量提供Discord机器人的令牌以使本程序正常工作。" } throw IllegalArgumentException("缺少必须的--Discord_Bot_Token参数") } + if (DisableDiscord) { + Logger.warn { "已通过--Disable-Discord参数禁用Discord功能,程序无法使用Discord相关功能。" } } Logger.info { "开始安装systemd服务" } Logger.debug { "SystemProperties.os.name: $CurrentOS" } if (CurrentOS.startsWith("Linux")) { @@ -100,7 +103,7 @@ fun ProcessArguments(CommandLineArguments: Array) { ExecStart=$ExecCommand Restart=always Type=simple - Environment="Discord_Bot_Token=$DiscordBotToken" + ${if (DisableDiscord == false) "Environment=\"Discord_Bot_Token=$DiscordBotToken\"" else ""} [Install] WantedBy=multi-user.target