完善Ktor后端项目

This commit is contained in:
BBIT-Kai
2025-09-05 09:29:38 +08:00
parent 587bae2505
commit c85bb5fa32
13 changed files with 117 additions and 77 deletions
@@ -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()
}
@@ -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))
}
}
}
}
@@ -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))
// }
// }
}
}
}
@@ -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) {
@@ -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<Map<String, String>>() {}.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<Map<String, String>>(it, object : TypeToken<Map<String, String>>() {}.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<String, String>,
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<String, String>,
memoryEnabled = this[AIProfilesTable.memoryEnabled],
creationDate = this[AIProfilesTable.creationDate].toString(),
welcome_words = this[AIProfilesTable.welcomeWords],
created_at = this[AIProfilesTable.createdAt].toString(),
isActive = this[AIProfilesTable.isActive]
)
}
@@ -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<String> -> Json.encodeToString(roles) },deserialize = { str: String -> Json.decodeFromString<List<String>>(str) }).nullable()
}
@@ -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
)
@@ -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) // 创建格式化器
@@ -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<JWTPrincipal>()?.payload?.getClaim("user_id")?.asString())
val userId = call.principal<JWTPrincipal>()?.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 {
@@ -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<SessionListResponse> {
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)
)
}
}
}
}
@@ -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()
@@ -36,5 +36,4 @@ fun Application.configureSecurity(config: AppConfig) {
}
}
}
}
@@ -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<UnauthorizedException> { call, cause ->
call.respondText(
text = "Unauthorized: ${cause.message}",
status = HttpStatusCode.Unauthorized
)
}
// 处理特定的异常
exception<IllegalStateException> { call, cause ->
call.respondText("App in illegal state as ${cause.message}", status = HttpStatusCode.InternalServerError)