From c85bb5fa3293733ca281862234892c5b4271fe8f Mon Sep 17 00:00:00 2001 From: BBIT-Kai <2911862937@qq.com> Date: Fri, 5 Sep 2025 09:29:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84Ktor=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ink/snowflake/server/Application.kt | 4 +- .../ink/snowflake/server/controller/AI.kt | 44 ------------------ .../ink/snowflake/server/controller/LLM.kt | 45 +++++++++++++++++++ .../ink/snowflake/server/controller/User.kt | 10 ----- .../server/model/database/AIProfilesTable.kt | 33 ++++++++------ .../server/model/database/UsersTable.kt | 5 ++- .../model/response/AiListForUserResponse.kt | 6 +-- .../ink/snowflake/server/utils/MyUtils.kt | 1 + .../ink/snowflake/server/utils/TokenUtils.kt | 10 ++++- .../ink/snowflake/server/utils/dao/AIDao.kt | 25 +++++++++++ .../ink/snowflake/server/utils/dao/UserDAO.kt | 3 +- .../server/utils/plugins/Security.kt | 1 - .../server/utils/plugins/StatusPages.kt | 7 +++ 13 files changed, 117 insertions(+), 77 deletions(-) delete mode 100644 ktor/src/main/kotlin/ink/snowflake/server/controller/AI.kt create mode 100644 ktor/src/main/kotlin/ink/snowflake/server/controller/LLM.kt diff --git a/ktor/src/main/kotlin/ink/snowflake/server/Application.kt b/ktor/src/main/kotlin/ink/snowflake/server/Application.kt index 54fe914..abe6d36 100644 --- a/ktor/src/main/kotlin/ink/snowflake/server/Application.kt +++ b/ktor/src/main/kotlin/ink/snowflake/server/Application.kt @@ -4,7 +4,7 @@ import com.google.gson.Gson import ink.snowflake.server.controller.User import ink.snowflake.server.controller.chat import ink.snowflake.server.utils.plugins.configureSockets -import ink.snowflake.server.controller.AI +import ink.snowflake.server.controller.LLM import ink.snowflake.server.controller.ImageAnalytics import ink.snowflake.server.controller.RemoteDebug import ink.snowflake.server.controller.VideoAnalytics @@ -73,5 +73,5 @@ fun Application.module() { // 业务-图片分析 ImageAnalytics() // 业务-AI - AI() + LLM() } diff --git a/ktor/src/main/kotlin/ink/snowflake/server/controller/AI.kt b/ktor/src/main/kotlin/ink/snowflake/server/controller/AI.kt deleted file mode 100644 index 94b8637..0000000 --- a/ktor/src/main/kotlin/ink/snowflake/server/controller/AI.kt +++ /dev/null @@ -1,44 +0,0 @@ -package ink.snowflake.server.controller - -import ai.koog.agents.core.agent.AIAgent -import ai.koog.prompt.executor.llms.all.simpleOllamaAIExecutor -import ai.koog.prompt.llm.LLMCapability -import ai.koog.prompt.llm.LLMProvider -import ai.koog.prompt.llm.LLModel -import ink.snowflake.server.SERVER_PATH_OSS -import ink.snowflake.server.model.response.BaseResponse -import io.ktor.server.application.Application -import io.ktor.server.response.respond -import io.ktor.server.routing.get -import io.ktor.server.routing.route -import io.ktor.server.routing.routing - -fun Application.AI() { - - val agent = AIAgent( - executor = simpleOllamaAIExecutor("http://${SERVER_PATH_OSS}:13011/"), - systemPrompt = "You are a helpful assistant. Answer user questions concisely.", //系统提示词 - llmModel = LLModel( - provider = LLMProvider.Ollama, - id = "llama3.2:latest", - capabilities = listOf( - LLMCapability.Temperature, -// LLMCapability.Tools - ) - ), // 模型 - temperature = 0.7, // 温度 -// toolRegistry = ToolRegistry { -// tool(SayToUser) // 注册工具 -// }, - maxIterations = 30 // - ) - - routing { - route("/api") { - get("/getAiList") { - val result = agent.run("Hello! How can you help me?") - call.respond(BaseResponse(data = result)) - } - } - } -} \ No newline at end of file diff --git a/ktor/src/main/kotlin/ink/snowflake/server/controller/LLM.kt b/ktor/src/main/kotlin/ink/snowflake/server/controller/LLM.kt new file mode 100644 index 0000000..09f5bfc --- /dev/null +++ b/ktor/src/main/kotlin/ink/snowflake/server/controller/LLM.kt @@ -0,0 +1,45 @@ +package ink.snowflake.server.controller + +import ai.koog.agents.core.agent.AIAgent +import ai.koog.prompt.executor.llms.all.simpleOllamaAIExecutor +import ai.koog.prompt.llm.LLMCapability +import ai.koog.prompt.llm.LLMProvider +import ai.koog.prompt.llm.LLModel +import ink.snowflake.server.SERVER_PATH_OSS +import ink.snowflake.server.model.response.AiListForUserResponse +import ink.snowflake.server.model.response.BaseResponse +import ink.snowflake.server.model.response.SessionListResponse +import ink.snowflake.server.utils.TokenUtils.getUserIdByToken +import ink.snowflake.server.utils.dao.AIDao +import io.ktor.server.application.Application +import io.ktor.server.auth.authenticate +import io.ktor.server.response.respond +import io.ktor.server.routing.get +import io.ktor.server.routing.route +import io.ktor.server.routing.routing +import kotlinx.html.Entities + +fun Application.LLM() { + + routing { + route("/api/llm") { +// authenticate { +// get("/getAiList") { +// val allAIProfile = AIDao.getAllAIProfiles().map { +// AiListForUserResponse( +// id = it.id, +// name = it.name ?: "", +// welcomeWords = it.welcome_words ?: "" +// ) +// } +// call.respond(BaseResponse(data = allAIProfile)) +// } +// get("/getSessions") { +// val userId = getUserIdByToken(call) +// val sessionList = AIDao.getSessionList(userId) +// call.respond(BaseResponse(data = sessionList)) +// } +// } + } + } +} \ No newline at end of file diff --git a/ktor/src/main/kotlin/ink/snowflake/server/controller/User.kt b/ktor/src/main/kotlin/ink/snowflake/server/controller/User.kt index f57a3ae..6a75926 100644 --- a/ktor/src/main/kotlin/ink/snowflake/server/controller/User.kt +++ b/ktor/src/main/kotlin/ink/snowflake/server/controller/User.kt @@ -158,16 +158,6 @@ fun Application.User(config: AppConfig) { ) } authenticate { - get("/getAiList") { - val allAIProfile = AIDao.getAllAIProfiles().map { - AiListForUserResponse( - aiId = it.id, - aiName = it.name, - aiAvatar = it.avatarUrl ?: "" - ) - } - call.respond(BaseResponse(data = allAIProfile)) - } get("/getUserInfo") { val userId = getUserIdByToken(call) if (userId != null) { diff --git a/ktor/src/main/kotlin/ink/snowflake/server/model/database/AIProfilesTable.kt b/ktor/src/main/kotlin/ink/snowflake/server/model/database/AIProfilesTable.kt index 837ffc7..eaf7a9d 100644 --- a/ktor/src/main/kotlin/ink/snowflake/server/model/database/AIProfilesTable.kt +++ b/ktor/src/main/kotlin/ink/snowflake/server/model/database/AIProfilesTable.kt @@ -5,21 +5,28 @@ import com.google.gson.reflect.TypeToken import ink.snowflake.server.gson import kotlinx.serialization.Contextual import org.jetbrains.exposed.v1.core.ResultRow -import org.jetbrains.exposed.v1.core.dao.id.IntIdTable import org.jetbrains.exposed.v1.core.dao.id.UUIDTable -import org.jetbrains.exposed.v1.datetime.timestamp +import org.jetbrains.exposed.v1.datetime.timestampWithTimeZone import org.jetbrains.exposed.v1.json.json import java.util.UUID // 定义 AI 表 -object AIProfilesTable : UUIDTable("ai_profiles") { +object AIProfilesTable : UUIDTable("ai_chat_profiles") { val name = varchar("name", 100) // AI 名称 val avatarUrl = varchar("avatar_url", 255).nullable() // AI 头像 URL - val aiPersonality = json("ai_personality", serialize = { gson.toJson(it) }, deserialize = { - gson.fromJson(it, object : TypeToken>() {}.type) - }) // 存储 AI 人设,使用 JSON 类型 - val memoryEnabled = bool("memory_enabled").default(true) // 是否启用记忆功能 - val creationDate = timestamp("creation_date") + val aiPersonality = json( + "ai_personality", + serialize = { gson.toJson(it) }, + deserialize = { + if (it.isNullOrBlank()) { + emptyMap() + } else { + gson.fromJson>(it, object : TypeToken>() {}.type) + } + } + ) // 存储 AI 人设,使用 JSON 类型 + val welcomeWords = varchar("welcome_words", 100) // 是否启用记忆功能 + val createdAt = timestampWithTimeZone("created_at") val isActive = bool("is_active").default(true) // 是否激活 } @@ -27,11 +34,11 @@ object AIProfilesTable : UUIDTable("ai_profiles") { data class AIProfile( @Contextual val id: UUID, - val name: String, + val name: String?, val avatarUrl: String?, val aiPersonality: Map, - val memoryEnabled: Boolean, - val creationDate: String, + val welcome_words: String?, + val created_at: String, val isActive: Boolean ) @@ -41,8 +48,8 @@ fun ResultRow.toAIProfile(): AIProfile { name = this[AIProfilesTable.name], avatarUrl = this[AIProfilesTable.avatarUrl], aiPersonality = this[AIProfilesTable.aiPersonality] as Map, - memoryEnabled = this[AIProfilesTable.memoryEnabled], - creationDate = this[AIProfilesTable.creationDate].toString(), + welcome_words = this[AIProfilesTable.welcomeWords], + created_at = this[AIProfilesTable.createdAt].toString(), isActive = this[AIProfilesTable.isActive] ) } diff --git a/ktor/src/main/kotlin/ink/snowflake/server/model/database/UsersTable.kt b/ktor/src/main/kotlin/ink/snowflake/server/model/database/UsersTable.kt index 853c108..dd52873 100644 --- a/ktor/src/main/kotlin/ink/snowflake/server/model/database/UsersTable.kt +++ b/ktor/src/main/kotlin/ink/snowflake/server/model/database/UsersTable.kt @@ -1,7 +1,9 @@ package ink.snowflake.server.model.database +import kotlinx.serialization.json.Json import org.jetbrains.exposed.v1.core.dao.id.UUIDTable import org.jetbrains.exposed.v1.datetime.timestamp +import org.jetbrains.exposed.v1.json.json object UsersTable : UUIDTable("users") { // 用户名 @@ -26,5 +28,6 @@ object UsersTable : UUIDTable("users") { val updatedAt = timestamp("updated_at").nullable() // 角色 - val roles = text("roles").nullable() + val roles = json("roles", + serialize = { roles: List -> Json.encodeToString(roles) },deserialize = { str: String -> Json.decodeFromString>(str) }).nullable() } diff --git a/ktor/src/main/kotlin/ink/snowflake/server/model/response/AiListForUserResponse.kt b/ktor/src/main/kotlin/ink/snowflake/server/model/response/AiListForUserResponse.kt index c92325d..2fb5818 100644 --- a/ktor/src/main/kotlin/ink/snowflake/server/model/response/AiListForUserResponse.kt +++ b/ktor/src/main/kotlin/ink/snowflake/server/model/response/AiListForUserResponse.kt @@ -7,7 +7,7 @@ import java.util.UUID @Serializable data class AiListForUserResponse( @Contextual - val aiId: UUID, - val aiName :String, - val aiAvatar : String + val id: UUID, + val name :String, + val welcomeWords : String ) \ No newline at end of file diff --git a/ktor/src/main/kotlin/ink/snowflake/server/utils/MyUtils.kt b/ktor/src/main/kotlin/ink/snowflake/server/utils/MyUtils.kt index 4c93e27..911c5b5 100644 --- a/ktor/src/main/kotlin/ink/snowflake/server/utils/MyUtils.kt +++ b/ktor/src/main/kotlin/ink/snowflake/server/utils/MyUtils.kt @@ -15,6 +15,7 @@ import java.time.format.DateTimeFormatter import java.util.* object MyUtils { + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") fun formatDateToTargetString(date: Date, targetFormat: String): String { val formatter = SimpleDateFormat(targetFormat) // 创建格式化器 diff --git a/ktor/src/main/kotlin/ink/snowflake/server/utils/TokenUtils.kt b/ktor/src/main/kotlin/ink/snowflake/server/utils/TokenUtils.kt index 6c52ea6..554e65d 100644 --- a/ktor/src/main/kotlin/ink/snowflake/server/utils/TokenUtils.kt +++ b/ktor/src/main/kotlin/ink/snowflake/server/utils/TokenUtils.kt @@ -3,6 +3,7 @@ package ink.snowflake.server.utils import com.auth0.jwt.JWT import com.auth0.jwt.algorithms.Algorithm import ink.snowflake.server.model.response.Token +import ink.snowflake.server.utils.Log.UnauthorizedException import io.ktor.server.application.ApplicationCall import io.ktor.server.auth.jwt.JWTPrincipal import io.ktor.server.auth.principal @@ -23,9 +24,14 @@ object TokenUtils { return generateToken(config, userId, 10 * 24 * 60 * 60, "refresh_token") } - fun getUserIdByToken(call: ApplicationCall) : UUID?{ + fun getUserIdByToken(call: ApplicationCall) : UUID{ // 通过token获取user_id - return UUID.fromString(call.principal()?.payload?.getClaim("user_id")?.asString()) + val userId = call.principal()?.payload?.getClaim("user_id")?.asString() + if(userId != null){ + return UUID.fromString(userId) + }else{ + throw UnauthorizedException("Token is missing or invalid") // 自定义异常 + } } fun generateToken(config: AppConfig, userId: UUID, second: Int, tokenType: String): Token { diff --git a/ktor/src/main/kotlin/ink/snowflake/server/utils/dao/AIDao.kt b/ktor/src/main/kotlin/ink/snowflake/server/utils/dao/AIDao.kt index 7cddb55..35979af 100644 --- a/ktor/src/main/kotlin/ink/snowflake/server/utils/dao/AIDao.kt +++ b/ktor/src/main/kotlin/ink/snowflake/server/utils/dao/AIDao.kt @@ -2,9 +2,15 @@ package ink.snowflake.server.utils.dao import ink.snowflake.server.model.database.AIProfile import ink.snowflake.server.model.database.AIProfilesTable +import ink.snowflake.server.model.database.AISessionsTable import ink.snowflake.server.model.database.toAIProfile +import ink.snowflake.server.model.response.SessionListResponse +import ink.snowflake.server.utils.MyUtils.formatter +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.jdbc.select import org.jetbrains.exposed.v1.jdbc.selectAll import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import java.util.UUID object AIDao { /** @@ -16,4 +22,23 @@ object AIDao { } } + /** + * 获取用户对话列表 + */ + fun getSessionList(userId: UUID): List { + return transaction { + AISessionsTable + .select(AISessionsTable.id, AISessionsTable.title, AISessionsTable.updatedAt) + .where { AISessionsTable.userId eq userId } + .orderBy(AISessionsTable.updatedAt, SortOrder.DESC) + .map { + SessionListResponse( + id = it[AISessionsTable.id].value, + title = it[AISessionsTable.title], + updatedAt = it[AISessionsTable.updatedAt].format(formatter) + ) + } + } + } + } diff --git a/ktor/src/main/kotlin/ink/snowflake/server/utils/dao/UserDAO.kt b/ktor/src/main/kotlin/ink/snowflake/server/utils/dao/UserDAO.kt index 5376d18..89e81d8 100644 --- a/ktor/src/main/kotlin/ink/snowflake/server/utils/dao/UserDAO.kt +++ b/ktor/src/main/kotlin/ink/snowflake/server/utils/dao/UserDAO.kt @@ -7,6 +7,7 @@ import org.jetbrains.exposed.v1.datetime.timestampLiteral import org.jetbrains.exposed.v1.jdbc.insertAndGetId import org.jetbrains.exposed.v1.jdbc.selectAll import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.json.json import java.util.UUID @@ -75,7 +76,7 @@ object UserDAO { username = it[UsersTable.username], email = it[UsersTable.email], phone = it[UsersTable.phone], - roles = it[UsersTable.roles]?.removePrefix("{")!!.removeSuffix("}").split(","), + roles = it[UsersTable.roles]?:listOf(), ) } .singleOrNull() diff --git a/ktor/src/main/kotlin/ink/snowflake/server/utils/plugins/Security.kt b/ktor/src/main/kotlin/ink/snowflake/server/utils/plugins/Security.kt index 284d6db..4d33bca 100644 --- a/ktor/src/main/kotlin/ink/snowflake/server/utils/plugins/Security.kt +++ b/ktor/src/main/kotlin/ink/snowflake/server/utils/plugins/Security.kt @@ -36,5 +36,4 @@ fun Application.configureSecurity(config: AppConfig) { } } } - } diff --git a/ktor/src/main/kotlin/ink/snowflake/server/utils/plugins/StatusPages.kt b/ktor/src/main/kotlin/ink/snowflake/server/utils/plugins/StatusPages.kt index 7cd82e7..30de4dd 100644 --- a/ktor/src/main/kotlin/ink/snowflake/server/utils/plugins/StatusPages.kt +++ b/ktor/src/main/kotlin/ink/snowflake/server/utils/plugins/StatusPages.kt @@ -1,6 +1,7 @@ package ink.snowflake.server.utils.plugins import ink.snowflake.server.model.response.BaseResponse +import ink.snowflake.server.utils.Log.UnauthorizedException import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.plugins.statuspages.* @@ -9,6 +10,12 @@ import io.ktor.server.response.* fun Application.configureStatusPages() { install(StatusPages) { + exception { call, cause -> + call.respondText( + text = "Unauthorized: ${cause.message}", + status = HttpStatusCode.Unauthorized + ) + } // 处理特定的异常 exception { call, cause -> call.respondText("App in illegal state as ${cause.message}", status = HttpStatusCode.InternalServerError)