完善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.User
|
||||||
import ink.snowflake.server.controller.chat
|
import ink.snowflake.server.controller.chat
|
||||||
import ink.snowflake.server.utils.plugins.configureSockets
|
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.ImageAnalytics
|
||||||
import ink.snowflake.server.controller.RemoteDebug
|
import ink.snowflake.server.controller.RemoteDebug
|
||||||
import ink.snowflake.server.controller.VideoAnalytics
|
import ink.snowflake.server.controller.VideoAnalytics
|
||||||
@@ -73,5 +73,5 @@ fun Application.module() {
|
|||||||
// 业务-图片分析
|
// 业务-图片分析
|
||||||
ImageAnalytics()
|
ImageAnalytics()
|
||||||
// 业务-AI
|
// 业务-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 {
|
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") {
|
get("/getUserInfo") {
|
||||||
val userId = getUserIdByToken(call)
|
val userId = getUserIdByToken(call)
|
||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
|
|||||||
@@ -5,21 +5,28 @@ import com.google.gson.reflect.TypeToken
|
|||||||
import ink.snowflake.server.gson
|
import ink.snowflake.server.gson
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
import org.jetbrains.exposed.v1.core.ResultRow
|
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.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 org.jetbrains.exposed.v1.json.json
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
// 定义 AI 表
|
// 定义 AI 表
|
||||||
object AIProfilesTable : UUIDTable("ai_profiles") {
|
object AIProfilesTable : UUIDTable("ai_chat_profiles") {
|
||||||
val name = varchar("name", 100) // AI 名称
|
val name = varchar("name", 100) // AI 名称
|
||||||
val avatarUrl = varchar("avatar_url", 255).nullable() // AI 头像 URL
|
val avatarUrl = varchar("avatar_url", 255).nullable() // AI 头像 URL
|
||||||
val aiPersonality = json("ai_personality", serialize = { gson.toJson(it) }, deserialize = {
|
val aiPersonality = json(
|
||||||
gson.fromJson(it, object : TypeToken<Map<String, String>>() {}.type)
|
"ai_personality",
|
||||||
}) // 存储 AI 人设,使用 JSON 类型
|
serialize = { gson.toJson(it) },
|
||||||
val memoryEnabled = bool("memory_enabled").default(true) // 是否启用记忆功能
|
deserialize = {
|
||||||
val creationDate = timestamp("creation_date")
|
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) // 是否激活
|
val isActive = bool("is_active").default(true) // 是否激活
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,11 +34,11 @@ object AIProfilesTable : UUIDTable("ai_profiles") {
|
|||||||
data class AIProfile(
|
data class AIProfile(
|
||||||
@Contextual
|
@Contextual
|
||||||
val id: UUID,
|
val id: UUID,
|
||||||
val name: String,
|
val name: String?,
|
||||||
val avatarUrl: String?,
|
val avatarUrl: String?,
|
||||||
val aiPersonality: Map<String, String>,
|
val aiPersonality: Map<String, String>,
|
||||||
val memoryEnabled: Boolean,
|
val welcome_words: String?,
|
||||||
val creationDate: String,
|
val created_at: String,
|
||||||
val isActive: Boolean
|
val isActive: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,8 +48,8 @@ fun ResultRow.toAIProfile(): AIProfile {
|
|||||||
name = this[AIProfilesTable.name],
|
name = this[AIProfilesTable.name],
|
||||||
avatarUrl = this[AIProfilesTable.avatarUrl],
|
avatarUrl = this[AIProfilesTable.avatarUrl],
|
||||||
aiPersonality = this[AIProfilesTable.aiPersonality] as Map<String, String>,
|
aiPersonality = this[AIProfilesTable.aiPersonality] as Map<String, String>,
|
||||||
memoryEnabled = this[AIProfilesTable.memoryEnabled],
|
welcome_words = this[AIProfilesTable.welcomeWords],
|
||||||
creationDate = this[AIProfilesTable.creationDate].toString(),
|
created_at = this[AIProfilesTable.createdAt].toString(),
|
||||||
isActive = this[AIProfilesTable.isActive]
|
isActive = this[AIProfilesTable.isActive]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package ink.snowflake.server.model.database
|
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.core.dao.id.UUIDTable
|
||||||
import org.jetbrains.exposed.v1.datetime.timestamp
|
import org.jetbrains.exposed.v1.datetime.timestamp
|
||||||
|
import org.jetbrains.exposed.v1.json.json
|
||||||
|
|
||||||
object UsersTable : UUIDTable("users") {
|
object UsersTable : UUIDTable("users") {
|
||||||
// 用户名
|
// 用户名
|
||||||
@@ -26,5 +28,6 @@ object UsersTable : UUIDTable("users") {
|
|||||||
val updatedAt = timestamp("updated_at").nullable()
|
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
|
@Serializable
|
||||||
data class AiListForUserResponse(
|
data class AiListForUserResponse(
|
||||||
@Contextual
|
@Contextual
|
||||||
val aiId: UUID,
|
val id: UUID,
|
||||||
val aiName :String,
|
val name :String,
|
||||||
val aiAvatar : String
|
val welcomeWords : String
|
||||||
)
|
)
|
||||||
@@ -15,6 +15,7 @@ import java.time.format.DateTimeFormatter
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
object MyUtils {
|
object MyUtils {
|
||||||
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||||
|
|
||||||
fun formatDateToTargetString(date: Date, targetFormat: String): String {
|
fun formatDateToTargetString(date: Date, targetFormat: String): String {
|
||||||
val formatter = SimpleDateFormat(targetFormat) // 创建格式化器
|
val formatter = SimpleDateFormat(targetFormat) // 创建格式化器
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package ink.snowflake.server.utils
|
|||||||
import com.auth0.jwt.JWT
|
import com.auth0.jwt.JWT
|
||||||
import com.auth0.jwt.algorithms.Algorithm
|
import com.auth0.jwt.algorithms.Algorithm
|
||||||
import ink.snowflake.server.model.response.Token
|
import ink.snowflake.server.model.response.Token
|
||||||
|
import ink.snowflake.server.utils.Log.UnauthorizedException
|
||||||
import io.ktor.server.application.ApplicationCall
|
import io.ktor.server.application.ApplicationCall
|
||||||
import io.ktor.server.auth.jwt.JWTPrincipal
|
import io.ktor.server.auth.jwt.JWTPrincipal
|
||||||
import io.ktor.server.auth.principal
|
import io.ktor.server.auth.principal
|
||||||
@@ -23,9 +24,14 @@ object TokenUtils {
|
|||||||
return generateToken(config, userId, 10 * 24 * 60 * 60, "refresh_token")
|
return generateToken(config, userId, 10 * 24 * 60 * 60, "refresh_token")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUserIdByToken(call: ApplicationCall) : UUID?{
|
fun getUserIdByToken(call: ApplicationCall) : UUID{
|
||||||
// 通过token获取user_id
|
// 通过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 {
|
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.AIProfile
|
||||||
import ink.snowflake.server.model.database.AIProfilesTable
|
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.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.selectAll
|
||||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
object AIDao {
|
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.insertAndGetId
|
||||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||||
|
import org.jetbrains.exposed.v1.json.json
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
|
||||||
@@ -75,7 +76,7 @@ object UserDAO {
|
|||||||
username = it[UsersTable.username],
|
username = it[UsersTable.username],
|
||||||
email = it[UsersTable.email],
|
email = it[UsersTable.email],
|
||||||
phone = it[UsersTable.phone],
|
phone = it[UsersTable.phone],
|
||||||
roles = it[UsersTable.roles]?.removePrefix("{")!!.removeSuffix("}").split(","),
|
roles = it[UsersTable.roles]?:listOf(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|||||||
@@ -36,5 +36,4 @@ fun Application.configureSecurity(config: AppConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ink.snowflake.server.utils.plugins
|
package ink.snowflake.server.utils.plugins
|
||||||
|
|
||||||
import ink.snowflake.server.model.response.BaseResponse
|
import ink.snowflake.server.model.response.BaseResponse
|
||||||
|
import ink.snowflake.server.utils.Log.UnauthorizedException
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.plugins.statuspages.*
|
import io.ktor.server.plugins.statuspages.*
|
||||||
@@ -9,6 +10,12 @@ import io.ktor.server.response.*
|
|||||||
fun Application.configureStatusPages() {
|
fun Application.configureStatusPages() {
|
||||||
|
|
||||||
install(StatusPages) {
|
install(StatusPages) {
|
||||||
|
exception<UnauthorizedException> { call, cause ->
|
||||||
|
call.respondText(
|
||||||
|
text = "Unauthorized: ${cause.message}",
|
||||||
|
status = HttpStatusCode.Unauthorized
|
||||||
|
)
|
||||||
|
}
|
||||||
// 处理特定的异常
|
// 处理特定的异常
|
||||||
exception<IllegalStateException> { call, cause ->
|
exception<IllegalStateException> { call, cause ->
|
||||||
call.respondText("App in illegal state as ${cause.message}", status = HttpStatusCode.InternalServerError)
|
call.respondText("App in illegal state as ${cause.message}", status = HttpStatusCode.InternalServerError)
|
||||||
|
|||||||
Reference in New Issue
Block a user