完善Ktor后端项目
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user