调整数据库结构,ID不再自增,使用UUID,修改后端逻辑

This commit is contained in:
BBIT-Kai
2025-08-22 10:05:24 +08:00
parent 10ca219970
commit f79a82bb87
46 changed files with 462 additions and 429 deletions
@@ -1,16 +1,22 @@
package ink.snowflake.server package ink.snowflake.server
import com.google.gson.Gson import com.google.gson.Gson
import ink.snowflake.server.plugins.* import ink.snowflake.server.controller.User
import ink.snowflake.server.route.User import ink.snowflake.server.controller.chat
import ink.snowflake.server.route.chat import ink.snowflake.server.utils.plugins.configureSockets
import ink.snowflake.server.plugins.configureSockets import ink.snowflake.server.controller.AI
import ink.snowflake.server.route.AI import ink.snowflake.server.controller.ImageAnalytics
import ink.snowflake.server.route.ImageAnalytics import ink.snowflake.server.controller.RemoteDebug
import ink.snowflake.server.route.RemoteDebug import ink.snowflake.server.controller.VideoAnalytics
import ink.snowflake.server.route.VideoAnalytics import ink.snowflake.server.controller.VideoAnalyticsJetson
import ink.snowflake.server.route.VideoAnalyticsJetson
import ink.snowflake.server.utils.AppConfig import ink.snowflake.server.utils.AppConfig
import ink.snowflake.server.utils.plugins.configureCORS
import ink.snowflake.server.utils.plugins.configureDatabases
import ink.snowflake.server.utils.plugins.configureSecurity
import ink.snowflake.server.utils.plugins.configureSerialization
import ink.snowflake.server.utils.plugins.configureStaticPath
import ink.snowflake.server.utils.plugins.configureStatusPages
import ink.snowflake.server.utils.plugins.configureTemplating
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.tomcat.jakarta.* import io.ktor.server.tomcat.jakarta.*
@@ -1,13 +1,7 @@
package ink.snowflake.server.route package ink.snowflake.server.controller
import ai.koog.agents.core.agent.AIAgent import ai.koog.agents.core.agent.AIAgent
import ai.koog.agents.core.tools.ToolRegistry
import ai.koog.agents.ext.tool.SayToUser
import ai.koog.prompt.executor.clients.openai.OpenAIModels
import ai.koog.prompt.executor.llms.all.simpleOllamaAIExecutor import ai.koog.prompt.executor.llms.all.simpleOllamaAIExecutor
import ai.koog.prompt.executor.llms.all.simpleOpenAIExecutor
import ai.koog.prompt.executor.ollama.client.OllamaClient
import ai.koog.prompt.executor.ollama.client.OllamaModelCard
import ai.koog.prompt.llm.LLMCapability import ai.koog.prompt.llm.LLMCapability
import ai.koog.prompt.llm.LLMProvider import ai.koog.prompt.llm.LLMProvider
import ai.koog.prompt.llm.LLModel import ai.koog.prompt.llm.LLModel
@@ -17,7 +11,6 @@ import io.ktor.server.response.respond
import io.ktor.server.routing.get import io.ktor.server.routing.get
import io.ktor.server.routing.route import io.ktor.server.routing.route
import io.ktor.server.routing.routing import io.ktor.server.routing.routing
import kotlinx.coroutines.launch
fun Application.AI() { fun Application.AI() {
@@ -1,4 +1,4 @@
package ink.snowflake.server.route package ink.snowflake.server.controller
import com.cyberecho.mdoel.database.WSChatRecords import com.cyberecho.mdoel.database.WSChatRecords
import ink.snowflake.server.gson import ink.snowflake.server.gson
@@ -6,7 +6,7 @@ import ink.snowflake.server.model.request.ChatRequest
import ink.snowflake.server.model.response.ChatResponse import ink.snowflake.server.model.response.ChatResponse
import ink.snowflake.server.model.response.BaseResponse import ink.snowflake.server.model.response.BaseResponse
import ink.snowflake.server.utils.WebSocketManager import ink.snowflake.server.utils.WebSocketManager
import ink.snowflake.server.database.ChatRecordsDao import ink.snowflake.server.utils.dao.ChatRecordsDao
import ink.snowflake.server.utils.getUserIdByToken import ink.snowflake.server.utils.getUserIdByToken
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.engine.cio.* import io.ktor.client.engine.cio.*
@@ -1,14 +1,13 @@
package ink.snowflake.server.route package ink.snowflake.server.controller
import ink.snowflake.server.model.request.ImageAnalyticsRequest import ink.snowflake.server.model.request.ImageAnalyticsRequest
import ink.snowflake.server.model.response.* import ink.snowflake.server.model.response.*
import ink.snowflake.server.database.ImageDao import ink.snowflake.server.utils.dao.ImageDao
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.auth.* import io.ktor.server.auth.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import io.ktor.server.request.* import io.ktor.server.request.*
import io.ktor.server.response.* import io.ktor.server.response.*
import kotlin.text.isNullOrEmpty
fun Application.ImageAnalytics() { fun Application.ImageAnalytics() {
routing { routing {
@@ -1,8 +1,8 @@
package ink.snowflake.server.route package ink.snowflake.server.controller
import com.google.gson.Gson import com.google.gson.Gson
import ink.snowflake.server.SERVER_PATH_FRP import ink.snowflake.server.SERVER_PATH_FRP
import ink.snowflake.server.model.request.DevicesInfo import ink.snowflake.server.model.request.DevicesInfoRequest
import ink.snowflake.server.model.response.* import ink.snowflake.server.model.response.*
import ink.snowflake.server.utils.runCommand import ink.snowflake.server.utils.runCommand
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
@@ -79,13 +79,13 @@ fun Application.RemoteDebug() {
val name = call.parameters["name"] val name = call.parameters["name"]
val response: HttpResponse = client.get("http://171.212.101.201:65534/api/proxy/tcp") val response: HttpResponse = client.get("http://171.212.101.201:65534/api/proxy/tcp")
val responseBody: String = response.bodyAsText() val responseBody: String = response.bodyAsText()
val devicesInfo: DevicesInfo = Gson().fromJson(responseBody, DevicesInfo::class.java) val devicesInfoRequest: DevicesInfoRequest = Gson().fromJson(responseBody, DevicesInfoRequest::class.java)
val onlineDevices = devicesInfo.proxies.stream() val onlineDevices = devicesInfoRequest.proxies.stream()
.filter { it.status == "online" && it.conf != null && it.conf.remotePort >= 10000 && it.conf.remotePort <= 20000 } .filter { it.status == "online" && it.conf != null && it.conf.remotePort >= 10000 && it.conf.remotePort <= 20000 }
val devices: MutableList<DeviceItem> = mutableListOf() val devices: MutableList<DeviceItemResponse> = mutableListOf()
for (data in onlineDevices) { for (data in onlineDevices) {
if (name == "null" || name == null || data.name.contains(name) && data.conf != null) { if (name == "null" || name == null || data.name.contains(name) && data.conf != null) {
devices.add(DeviceItem(data.name, data.conf!!.remotePort)) devices.add(DeviceItemResponse(data.name, data.conf!!.remotePort))
} }
} }
call.respond(BaseResponse(data = devices)) call.respond(BaseResponse(data = devices))
@@ -1,4 +1,4 @@
package ink.snowflake.server.route package ink.snowflake.server.controller
import com.auth0.jwt.JWT import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm import com.auth0.jwt.algorithms.Algorithm
@@ -8,7 +8,8 @@ import ink.snowflake.server.model.request.RefreshTokenRequest
import ink.snowflake.server.model.request.RegisterRequest import ink.snowflake.server.model.request.RegisterRequest
import ink.snowflake.server.model.response.* import ink.snowflake.server.model.response.*
import ink.snowflake.server.utils.AppConfig import ink.snowflake.server.utils.AppConfig
import ink.snowflake.server.database.UserDAO import ink.snowflake.server.utils.dao.AIDao
import ink.snowflake.server.utils.dao.UserDAO
import ink.snowflake.server.utils.getUserIdByToken import ink.snowflake.server.utils.getUserIdByToken
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.auth.* import io.ktor.server.auth.*
@@ -49,32 +50,27 @@ fun Application.User(config: AppConfig) {
val password = loginRequest.password val password = loginRequest.password
// 查找用户 // 查找用户
val userId = UserDAO.getUserIdByEmail(email) val userId = UserDAO.getUserIdByEmail(email)
val res = if(userId == null){ call.respond(
if (userId == null) {
// 账号不存在 // 账号不存在
-1 BaseResponse(status = false, message = "尚未注册", data = null)
} else { } else {
val userPassword = UserDAO.getPasswordById(userId) val userPassword = UserDAO.getPasswordById(userId)
// 验证密码 // 验证密码
if (password == userPassword) { if (password == userPassword) {
// 登录成功 // 登录成功
userId BaseResponse (
status = true, data = LoginResponse(
userId,
generateAccessToken(config, userId),
generateRefreshToken(config, userId)
)
)
} else { } else {
// 账号密码不匹配 // 账号密码不匹配
-2 BaseResponse(status = false, message = "账号密码不匹配,请重新登录", data = null)
} }
} }
call.respond(
when (res) {
-1 -> BaseResponse(status = false, message = "尚未注册", data = null)
-2 -> BaseResponse(status = false, message = "账号密码不匹配,请重新登录", data = null)
else -> BaseResponse(
status = true, data = LoginResponse(
userId.toString(),
generateAccessToken(config, res),
generateRefreshToken(config, res)
)
)
}
) )
} }
post("/sendCode") { post("/sendCode") {
@@ -98,40 +94,36 @@ fun Application.User(config: AppConfig) {
// 从 Redis 获取保存的验证码 // 从 Redis 获取保存的验证码
val bucket: RBucket<String> = redisClient.getBucket("verificationCode:$account") val bucket: RBucket<String> = redisClient.getBucket("verificationCode:$account")
val storedCode = bucket.get() val storedCode = bucket.get()
call.respond(
if (storedCode == null) { if (storedCode == null) {
call.respond(BaseResponse(status = false, message = "验证码过期", data = null)) BaseResponse(status = false, message = "验证码过期,请重新获取", data = null)
} else if (storedCode != register.code) { } else if (storedCode != register.code) {
call.respond(BaseResponse(status = false, message = "验证码错误", data = null)) BaseResponse(status = false, message = "验证码错误,请检查或重新获取", data = null)
} else { } else {
// 查找用户 // 查找用户
val userId = UserDAO.getUserIdByEmail(register.account) val userId = UserDAO.getUserIdByEmail(register.account)
val res = if(userId != null){ if (userId == null) {
// 如果用户已存在,返回 0
0
}else{
// 用户不存在,插入新用户 // 用户不存在,插入新用户
UserDAO.registerByEmailAndGetId(register.account,register.password) val id = UserDAO.registerByEmailAndGetId(register.account, register.password)
} if (id == null) {
if (res > 0) { BaseResponse(status = true, message = "注册失败,请联系管理员", data = null)
call.respond(
BaseResponse(
status = true, message = "注册成功", data =
BaseResponse(
status = true, data = LoginResponse(
account,
generateAccessToken(config, res),
generateRefreshToken(config, res)
)
)
)
)
} else { } else {
call.respond(BaseResponse(status = false, message = "账户已存在", data = null)) BaseResponse(
status = true, message = "注册成功", data = LoginResponse(
id,
generateAccessToken(config, id),
generateRefreshToken(config, id)
)
)
}
} else {
// 如果用户已存在,返回 0
BaseResponse(status = false, message = "账户已存在", data = null)
} }
} }
)
} }
post("/refreshToken") { post("/refreshToken") {
try {
val token = call.receive<RefreshTokenRequest>() val token = call.receive<RefreshTokenRequest>()
val verifier = JWT.require(Algorithm.HMAC256(config.jwtSecret)) val verifier = JWT.require(Algorithm.HMAC256(config.jwtSecret))
.withAudience(config.jwtAudience) .withAudience(config.jwtAudience)
@@ -139,36 +131,39 @@ fun Application.User(config: AppConfig) {
.build() .build()
val decodedJWT = verifier.verify(token.refreshToken) val decodedJWT = verifier.verify(token.refreshToken)
val tokenType = decodedJWT.getClaim("token_type").asString() val tokenType = decodedJWT.getClaim("token_type").asString()
if (tokenType != "refresh_token") {
call.respond( call.respond(
try {
if (tokenType != "refresh_token") {
BaseResponse( BaseResponse(
status = false, status = false,
message = "拿什么乱七八糟的东西跟我换Access Token呢,???", message = "拿什么乱七八糟的东西跟我换Access Token呢,???",
data = null data = null
) )
)
} }
val userId = decodedJWT.getClaim("user_id").asInt() val userId = UUID.fromString(decodedJWT.getClaim("user_id").asString())
// 生成新的access token和refresh token // 生成新的access token和refresh token
call.respond(
BaseResponse( BaseResponse(
status = true, data = RefreshTokenResponse( status = true, data = RefreshTokenResponse(
generateAccessToken(config, userId), generateAccessToken(config, userId),
generateRefreshToken(config, userId) generateRefreshToken(config, userId)
) )
) )
) BaseResponse(data = generateAccessToken(config, userId))
call.respond(BaseResponse(data = generateAccessToken(config, userId)))
} catch (ex: Exception) { } catch (ex: Exception) {
call.respond(BaseResponse(status = false, message = "token解析错误", data = null)) BaseResponse(status = false, message = "token解析错误", data = null)
} }
)
} }
authenticate { authenticate {
get("/getAiList") { get("/getAiList") {
// val allAIProfile = AIDao.getAllAIProfiles().stream().map { val allAIProfile = AIDao.getAllAIProfiles().map {
// AiListForUser(aiId = it.id, aiName = it.name, aiAvatar = it.avatarUrl ?: "") AiListForUserResponse(
// }.toList() aiId = it.id,
// call.respond(BaseResponse(data = allAIProfile)) aiName = it.name,
aiAvatar = it.avatarUrl ?: ""
)
}
call.respond(BaseResponse(data = allAIProfile))
} }
get("/getUserInfo") { get("/getUserInfo") {
val userId = getUserIdByToken(call) val userId = getUserIdByToken(call)
@@ -190,22 +185,22 @@ fun Application.User(config: AppConfig) {
} }
} }
fun generateAccessToken(config: AppConfig, userId: Int): Token { fun generateAccessToken(config: AppConfig, userId: UUID): Token {
// return generateToken(config, userId, 120, "access_token") // return generateToken(config, userId, 120, "access_token")
return generateToken(config, userId, 2 * 60 * 60, "access_token") return generateToken(config, userId, 2 * 60 * 60, "access_token")
} }
fun generateRefreshToken(config: AppConfig, userId: Int): Token { fun generateRefreshToken(config: AppConfig, userId: UUID): Token {
return generateToken(config, userId, 10 * 24 * 60 * 60, "refresh_token") return generateToken(config, userId, 10 * 24 * 60 * 60, "refresh_token")
} }
fun generateToken(config: AppConfig, userId: Int, second: Int, tokenType: String): Token { fun generateToken(config: AppConfig, userId: UUID, second: Int, tokenType: String): Token {
val expiresAt = Date(System.currentTimeMillis() + second * 1000) // val expiresAt = Date(System.currentTimeMillis() + second * 1000) //
val token = JWT.create() val token = JWT.create()
.withAudience(config.jwtAudience) .withAudience(config.jwtAudience)
.withIssuer(config.jwtDomain) .withIssuer(config.jwtDomain)
.withClaim("user_id", userId) .withClaim("user_id", userId.toString())
.withClaim("token_type", tokenType) .withClaim("token_type", tokenType)
.withExpiresAt(expiresAt) .withExpiresAt(expiresAt)
.sign(Algorithm.HMAC256(config.jwtSecret)) .sign(Algorithm.HMAC256(config.jwtSecret))
@@ -238,13 +233,13 @@ fun sendVerificationEmail(config: AppConfig, recipientEmail: String, verificatio
val message = MimeMessage(session).apply { val message = MimeMessage(session).apply {
setFrom(InternetAddress(config.smtpUser)) setFrom(InternetAddress(config.smtpUser))
setRecipient(Message.RecipientType.TO, InternetAddress(recipientEmail)) setRecipient(Message.RecipientType.TO, InternetAddress(recipientEmail))
subject = "Welcome to CyberEcho! Verification Code:$verificationCode" subject = "Welcome to BBIT-Lab! Verification Code:$verificationCode"
val htmlContent = """ val htmlContent = """
<html> <html>
<body style="font-family: Arial, sans-serif; color: #333;"> <body style="font-family: Arial, sans-serif; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #ddd; border-radius: 10px; background-color: #f9f9f9;"> <div style="max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #ddd; border-radius: 10px; background-color: #f9f9f9;">
<h2 style="color: #4CAF50;">Welcome to CyberEcho!</h2> <h2 style="color: #4CAF50;">Welcome to BBIT-Lab!</h2>
<p style="font-size: 16px;">Thank you for signing up with <strong>CyberEcho</strong>! We are excited to have you on board.</p> <p style="font-size: 16px;">Thank you for signing up with <strong>BBIT-Lab</strong>! We are excited to have you on board.</p>
<p style="font-size: 16px;">To complete your registration, please use the verification code below:</p> <p style="font-size: 16px;">To complete your registration, please use the verification code below:</p>
@@ -258,7 +253,7 @@ fun sendVerificationEmail(config: AppConfig, recipientEmail: String, verificatio
<hr style="border-top: 1px solid #ddd; margin-top: 30px;"> <hr style="border-top: 1px solid #ddd; margin-top: 30px;">
<p style="font-size: 14px; color: #888; text-align: center;"> <p style="font-size: 14px; color: #888; text-align: center;">
Best regards<br> Best regards<br>
<strong>The CyberEcho Account Team</strong> <strong>The BBIT-Lab Account Team</strong>
</p> </p>
</div> </div>
</body> </body>
@@ -1,15 +1,15 @@
package ink.snowflake.server.route package ink.snowflake.server.controller
import ink.snowflake.server.SERVER_PATH_OSS import ink.snowflake.server.SERVER_PATH_OSS
import ink.snowflake.server.VIDEO_INPUT_PATH import ink.snowflake.server.VIDEO_INPUT_PATH
import ink.snowflake.server.database.VideoDao import ink.snowflake.server.utils.dao.VideoDao
import ink.snowflake.server.database.table.VideoTable import ink.snowflake.server.model.database.VideosTable
import ink.snowflake.server.database.table.VideoTable.vAAverageMaskedRatio import ink.snowflake.server.model.database.VideosTable.vAAverageMaskedRatio
import ink.snowflake.server.database.table.VideoTable.vACountPeople import ink.snowflake.server.model.database.VideosTable.vACountPeople
import ink.snowflake.server.database.table.VideoTable.vAMaxAction import ink.snowflake.server.model.database.VideosTable.vAMaxAction
import ink.snowflake.server.database.table.VideoTable.vAMaxStayTime import ink.snowflake.server.model.database.VideosTable.vAMaxStayTime
import ink.snowflake.server.database.table.VideoTable.vATotalPeople import ink.snowflake.server.model.database.VideosTable.vATotalPeople
import ink.snowflake.server.database.table.VideoTable.vStartDateTime import ink.snowflake.server.model.database.VideosTable.vStartDateTime
import ink.snowflake.server.model.request.VideoAnalyticsRequest import ink.snowflake.server.model.request.VideoAnalyticsRequest
import ink.snowflake.server.model.response.* import ink.snowflake.server.model.response.*
import ink.snowflake.server.utils.WebSocketManager.broadcastMessage import ink.snowflake.server.utils.WebSocketManager.broadcastMessage
@@ -23,8 +23,6 @@ import io.ktor.server.routing.*
import io.ktor.server.websocket.* import io.ktor.server.websocket.*
import io.ktor.websocket.* import io.ktor.websocket.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.toJavaLocalDateTime import kotlinx.datetime.toJavaLocalDateTime
@@ -114,12 +112,12 @@ fun Application.VideoAnalytics() {
get("/getAnalyticsDetailByVideoId") { get("/getAnalyticsDetailByVideoId") {
// 获取 vId 参数 // 获取 vId 参数
val vIdParam = call.parameters["vId"] val vIdParam = call.parameters["vId"]
val vId = vIdParam?.toIntOrNull() // 将 vId 转换为 Int,确保安全 if (vIdParam == null) {
if (vId == null) { call.respond(BaseResponse(status = true, message = "缺少参数vId", data = null))
call.respond(BaseResponse(status = true, message = "Invalid vId", data = null))
return@get return@get
} }
// 查询视频信息 // 查询视频信息
val vId = UUID.fromString(vIdParam)
val video = VideoDao.getAnalyticsDetailByVideoId(vId) val video = VideoDao.getAnalyticsDetailByVideoId(vId)
if (video == null) { if (video == null) {
call.respond(BaseResponse(status = false, message = "不存在该视频", data = null)) call.respond(BaseResponse(status = false, message = "不存在该视频", data = null))
@@ -216,17 +214,17 @@ fun Application.VideoAnalytics() {
BaseResponse( BaseResponse(
data = VideoAnalyticsDetail( data = VideoAnalyticsDetail(
v_id = vId, v_id = vId,
v_name = video[VideoTable.vName], v_name = video[VideosTable.vName],
v_video_play_path = "http://${SERVER_PATH_OSS}:9000/video/" + video[VideoTable.vObjectName], v_video_play_path = "http://${SERVER_PATH_OSS}:9000/video/" + video[VideosTable.vObjectName],
v_file_name = video[VideoTable.vFileName], v_file_name = video[VideosTable.vFileName],
v_duration = video[VideoTable.vDuration], v_duration = video[VideosTable.vDuration],
v_size = video[VideoTable.vSize], v_size = video[VideosTable.vSize],
v_start_datetime = video[vStartDateTime]?.toString() ?: "", v_start_datetime = video[vStartDateTime]?.toString() ?: "",
v_video_codec = video[VideoTable.vVideoCodec], v_video_codec = video[VideosTable.vVideoCodec],
v_audio_codec = video[VideoTable.vAudioCodec], v_audio_codec = video[VideosTable.vAudioCodec],
v_overall_bit_rate = video[VideoTable.vOverallBitRate], v_overall_bit_rate = video[VideosTable.vOverallBitRate],
v_resolution = video[VideoTable.vResolution], v_resolution = video[VideosTable.vResolution],
v_a_time = video[VideoTable.vATime].toString(), v_a_time = video[VideosTable.vATime].toString(),
v_a_total_people = video[vATotalPeople], // 总人数 v_a_total_people = video[vATotalPeople], // 总人数
v_a_count_people = video[vACountPeople], // 佩戴口罩人数 v_a_count_people = video[vACountPeople], // 佩戴口罩人数
v_a_max_stay_time = video[vAMaxStayTime], // 最大停留时间 v_a_max_stay_time = video[vAMaxStayTime], // 最大停留时间
@@ -1,4 +1,4 @@
package ink.snowflake.server.route package ink.snowflake.server.controller
import ink.snowflake.server.model.response.* import ink.snowflake.server.model.response.*
import io.ktor.server.application.* import io.ktor.server.application.*
@@ -1,51 +0,0 @@
package ink.snowflake.server.database
import ink.snowflake.server.database.table.ChatRecord
import ink.snowflake.server.database.table.ChatRecordsTable
import ink.snowflake.server.database.table.toChatRecord
import kotlinx.datetime.Clock
import org.jetbrains.exposed.v1.core.SortOrder
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
object ChatRecordsDao {
/**
* 插入聊天记录
*/
fun insertChatRecord(
userId: Int,
message: String,
messageType: String,
sendAt: Long,
from: String,
emotion: String?
): Boolean {
return transaction {
ChatRecordsTable
.insertAndGetId {
it[ChatRecordsTable.userId] = userId
it[ChatRecordsTable.message] = message
it[ChatRecordsTable.messageType] = messageType
it[ChatRecordsTable.sentAt] = timestampLiteral(Clock.System.now())
it[ChatRecordsTable.from] = from
it[ChatRecordsTable.emotion] = emotion
}.value > 0
}
}
/**
* 取最近五条聊天记录
*/
fun getRecentChatRecords(userId: Int): List<ChatRecord> {
return transaction {
ChatRecordsTable
.selectAll()
.where { ChatRecordsTable.userId eq userId }
.orderBy(ChatRecordsTable.sentAt to SortOrder.DESC)
.limit(5)
.toList().map { it.toChatRecord() }
}
}
}
@@ -1,89 +0,0 @@
package ink.snowflake.server.database
import ink.snowflake.server.database.table.UserTable
import ink.snowflake.server.model.response.UserInfo
import kotlinx.datetime.Clock
import org.jetbrains.exposed.v1.core.SqlExpressionBuilder.eq
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
object UserDAO {
/**
* 根据 email 获取密码
* @return 密码的 SHA-256 哈希值
*/
fun getPasswordById(id: Int): String? {
return transaction {
// 查询指定 email 的用户并返回 passwordHash
UserTable
.selectAll().where { UserTable.id eq id }
.map { it[UserTable.passwordHash] }
.singleOrNull() // 如果没有找到用户,返回 null
}
}
/**
* 根据 email 获取用户 ID
* @return 用户的 ID
*/
fun getUserIdByEmail(email: String): Int? {
return transaction {
// 查询指定 email 的用户并返回 id
UserTable
.selectAll().where { UserTable.email eq email }
.map { it[UserTable.id].value }
.singleOrNull() // 如果没有找到用户,返回 null
}
}
/**
* 添加用户
* @return 如果添加成功,返回 1;如果用户已存在,返回 0;如果添加失败,返回 -1
*/
fun registerByEmailAndGetId(account: String, hashPassword: String): Int {
return transaction {
try {
val newUserId = UserTable
.insertAndGetId {
it[email] = account
it[passwordHash] = hashPassword
it[createdAt] = timestampLiteral(Clock.System.now())
it[updatedAt] = timestampLiteral(Clock.System.now())
}
// 如果插入成功,返回 userId
if (newUserId.value > 0) {
newUserId.value
} else {
// 插入失败,返回 -1
-1
}
} catch (e: Exception) {
// 如果发生异常(例如数据库连接问题),返回 -1
e.printStackTrace() // 打印异常堆栈
-1
}
}
}
fun getUserInfoByUserId(userId: Int?): UserInfo? {
if (userId == null) return null
return transaction {
UserTable
.selectAll().where { UserTable.id eq userId }
.map {
UserInfo(
username = it[UserTable.username],
email = it[UserTable.email],
phone = it[UserTable.phone],
roles = it[UserTable.roles]?.removePrefix("{")!!.removeSuffix("}").split(","),
)
}
.singleOrNull()
}
}
}
@@ -1,12 +0,0 @@
package ink.snowflake.server.database.table
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
object VideoAnalyticsDetailTable : IntIdTable("video_analytics_detail") {
val vId = integer("v_id").references(VideoTable.id)
val aTimeStamp = integer("a_time_stamp")
val aTotalPeople = integer("a_total_people")
val aTotalPeopleMasked = integer("a_total_people_masked")
val aAction = varchar("a_action", 50)
}
@@ -1,24 +0,0 @@
package ink.snowflake.server.database.table
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.datetime.datetime
object VideoTable : IntIdTable("video") {
val vName = varchar("v_name", 255)
val vObjectName = varchar("v_object_name", 255)
val vFileName = varchar("v_file_name", 255)
val vStartDateTime = datetime("v_start_datetime").nullable()
val vDuration = float("v_duration")
val vSize = float("v_size")
val vVideoCodec = varchar("v_video_codec", 50)
val vAudioCodec = varchar("v_audio_codec", 50)
val vOverallBitRate = integer("v_overall_bit_rate")
val vResolution = varchar("v_resolution", 20)
val vATime = datetime("v_a_time")
val vATotalPeople = integer("v_a_total_people")
val vACountPeople = integer("v_a_count_people")
val vAMaxStayTime = float("v_a_max_stay_time")
val vAMaxAction = varchar("v_a_max_action", 50)
val vAAverageMaskedRatio = float("v_a_average_masked_ratio")
}
@@ -1,15 +1,18 @@
package ink.snowflake.server.database.table package ink.snowflake.server.model.database
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import ink.snowflake.server.gson import ink.snowflake.server.gson
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.IntIdTable
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 import org.jetbrains.exposed.v1.json.json
import java.util.UUID
// 定义 AI 表 // 定义 AI 表
object AIProfilesTable : IntIdTable("ai_profiles") { object AIProfilesTable : UUIDTable("ai_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("ai_personality", serialize = { gson.toJson(it) }, deserialize = {
@@ -22,7 +25,8 @@ object AIProfilesTable : IntIdTable("ai_profiles") {
@Serializable @Serializable
data class AIProfile( data class AIProfile(
val id: Int, @Contextual
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>,
@@ -1,13 +1,15 @@
package ink.snowflake.server.database.table package ink.snowflake.server.model.database
import org.jetbrains.exposed.v1.core.Column import org.jetbrains.exposed.v1.core.Column
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.datetime.timestamp import org.jetbrains.exposed.v1.datetime.timestamp
import java.util.UUID
import kotlin.uuid.Uuid
object ChatRecordsTable : IntIdTable("chat_records") { object ChatRecordsTable : UUIDTable("chat_records") {
// 用户 ID(外键) // 用户 ID(外键)
val userId: Column<Int> = integer("user_id").references(UserTable.id) val userId: Column<UUID> = uuid("user_id").references(UsersTable.id)
// 消息内容 // 消息内容
val message: Column<String> = text("message") val message: Column<String> = text("message")
@@ -26,8 +28,8 @@ object ChatRecordsTable : IntIdTable("chat_records") {
} }
data class ChatRecord( data class ChatRecord(
val id: Int, val id: UUID,
val userId: Int, val userId: UUID,
val message: String, val message: String,
val messageType: String, val messageType: String,
val sentAt: String,// eg. "2024-12-10T06:19:04.433006Z" val sentAt: String,// eg. "2024-12-10T06:19:04.433006Z"
@@ -1,12 +1,12 @@
package ink.snowflake.server.database.table package ink.snowflake.server.model.database
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import ink.snowflake.server.gson import ink.snowflake.server.gson
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable import org.jetbrains.exposed.v1.core.dao.id.UUIDTable
import org.jetbrains.exposed.v1.datetime.datetime import org.jetbrains.exposed.v1.datetime.datetime
import org.jetbrains.exposed.v1.json.json import org.jetbrains.exposed.v1.json.json
object ImageTable : IntIdTable("image_sca") { object ScaImagesTable : UUIDTable("sca_images") {
val name = varchar("name", 255) val name = varchar("name", 255)
val upload_datetime = datetime("upload_datetime") val upload_datetime = datetime("upload_datetime")
val file_name = varchar("file_name", 255) val file_name = varchar("file_name", 255)
@@ -1,9 +1,9 @@
package ink.snowflake.server.database.table package ink.snowflake.server.model.database
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.timestamp
object UserTable : IntIdTable("users") { object UsersTable : UUIDTable("users") {
// 用户名 // 用户名
val username = varchar("username", length = 50).nullable() val username = varchar("username", length = 50).nullable()
@@ -0,0 +1,13 @@
package ink.snowflake.server.model.database
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.core.dao.id.UUIDTable
object VideoAnalyticsDetailsTable : UUIDTable("video_analytics_details") {
val vId = uuid("v_id").references(VideosTable.id)
val aTimeStamp = integer("time_stamp")
val aTotalPeople = integer("total_people")
val aTotalPeopleMasked = integer("total_people_masked")
val aAction = varchar("action", 50)
}
@@ -0,0 +1,25 @@
package ink.snowflake.server.model.database
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.core.dao.id.UUIDTable
import org.jetbrains.exposed.v1.datetime.datetime
object VideosTable : UUIDTable("videos") {
val vName = varchar("name", 255)
val vObjectName = varchar("object_name", 255)
val vFileName = varchar("file_name", 255)
val vStartDateTime = datetime("start_datetime").nullable()
val vDuration = float("duration")
val vSize = float("size")
val vVideoCodec = varchar("video_codec", 50)
val vAudioCodec = varchar("audio_codec", 50)
val vOverallBitRate = integer("overall_bit_rate")
val vResolution = varchar("resolution", 20)
val vATime = datetime("a_time")
val vATotalPeople = integer("a_total_people")
val vACountPeople = integer("a_count_people")
val vAMaxStayTime = float("a_max_stay_time")
val vAMaxAction = varchar("a_max_action", 50)
val vAAverageMaskedRatio = float("a_average_masked_ratio")
}
@@ -2,7 +2,7 @@ package ink.snowflake.server.model.request
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class DevicesInfo( data class DevicesInfoRequest(
@SerializedName("proxies") @SerializedName("proxies")
val proxies: List<Proxy> = listOf() val proxies: List<Proxy> = listOf()
) { ) {
@@ -1,10 +1,13 @@
package ink.snowflake.server.model.request package ink.snowflake.server.model.request
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.util.UUID
@Serializable @Serializable
data class ImageAnalyticsRequest( data class ImageAnalyticsRequest(
val id : Int, @Contextual
val id : UUID,
val name : String, val name : String,
val upload_datetime: String, // 上传时间 val upload_datetime: String, // 上传时间
val file_name: String, // 文件名 val file_name: String, // 文件名
@@ -1,6 +1,5 @@
package ink.snowflake.server.model.request package ink.snowflake.server.model.request
import ink.snowflake.server.model.response.Token
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
@@ -1,10 +1,13 @@
package ink.snowflake.server.model.response package ink.snowflake.server.model.response
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.util.UUID
@Serializable @Serializable
data class AiListForUser( data class AiListForUserResponse(
val aiId :Int, @Contextual
val aiId: UUID,
val aiName :String, val aiName :String,
val aiAvatar : String val aiAvatar : String
) )
@@ -3,7 +3,7 @@ package ink.snowflake.server.model.response
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
data class DeviceItem ( data class DeviceItemResponse (
val deviceName:String, val deviceName:String,
val devicePort:Int, val devicePort:Int,
) )
@@ -1,8 +1,12 @@
package ink.snowflake.server.model.response package ink.snowflake.server.model.response
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.util.UUID
@Serializable @Serializable
data class LoginResponse(val userId: String, val accessToken: Token, val refreshToken: Token) data class LoginResponse(
@Contextual
val userId: UUID, val accessToken: Token, val refreshToken: Token)
@@ -1,10 +1,9 @@
package ink.snowflake.server.model.response package ink.snowflake.server.model.response
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
data class UserInfo( data class UserInfoResponse(
val username: String?, val username: String?,
val email: String?, val email: String?,
val phone: String?, val phone: String?,
@@ -1,10 +1,13 @@
package ink.snowflake.server.model.response package ink.snowflake.server.model.response
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.util.UUID
@Serializable @Serializable
data class VideoAnalyticsDetail( data class VideoAnalyticsDetail(
val v_id: Int, @Contextual
val v_id: UUID,
val v_name: String, val v_name: String,
val v_video_play_path: String, val v_video_play_path: String,
val v_file_name: String, val v_file_name: String,
@@ -1,10 +1,13 @@
package ink.snowflake.server.model.response package ink.snowflake.server.model.response
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.util.UUID
@Serializable @Serializable
data class VideoListResponse( data class VideoListResponse(
val v_id: Int, @Contextual
val v_id: UUID,
val v_name: String, val v_name: String,
val v_a_time: String? val v_a_time: String?
) )
@@ -1,9 +1,11 @@
package com.cyberecho.mdoel.database package com.cyberecho.mdoel.database
import java.util.UUID
data class WSChatRecords( data class WSChatRecords(
val id: Long = 0, // 消息的唯一标识符,自增 val id: Long = 0, // 消息的唯一标识符,自增
val aiId: Int, // 交流的对象AI_ID val aiId: UUID, // 交流的对象AI_ID
val messageType: String,// 消息类型(文本、图片、语音等) val messageType: String,// 消息类型(文本、图片、语音等)
val content: String, // 消息内容 val content: String, // 消息内容
val timestamp: Long, // 发送时间(时间戳,单位为毫秒) val timestamp: Long, // 发送时间(时间戳,单位为毫秒)
@@ -1,20 +0,0 @@
package ink.snowflake.server.plugins
import io.ktor.http.*
import io.ktor.serialization.*
import io.ktor.serialization.gson.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.configureSerialization() {
install(ContentNegotiation) {
json()
gson{
setPrettyPrinting()
}
}
}
@@ -1,6 +1,5 @@
package ink.snowflake.server.utils package ink.snowflake.server.utils
import ink.snowflake.server.model.response.UserInfo
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.auth.* import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.* import io.ktor.server.auth.jwt.*
@@ -8,9 +7,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.format
import kotlinx.datetime.toJavaLocalDateTime import kotlinx.datetime.toJavaLocalDateTime
import org.jetbrains.exposed.v1.jdbc.transactions.experimental.newSuspendedTransaction
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@@ -18,10 +15,9 @@ import java.time.format.DateTimeFormatter
import java.util.* import java.util.*
fun getUserIdByToken(call: ApplicationCall) : Int?{ fun getUserIdByToken(call: ApplicationCall) : UUID?{
// 通过token获取user_id // 通过token获取user_id
return call.principal<JWTPrincipal>()?.payload?.getClaim("user_id")?.asInt() return UUID.fromString(call.principal<JWTPrincipal>()?.payload?.getClaim("user_id")?.asString())
} }
fun formatDateToTargetString(date: Date, targetFormat: String): String { fun formatDateToTargetString(date: Date, targetFormat: String): String {
@@ -7,6 +7,7 @@ import io.ktor.websocket.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
object WebSocketManager { object WebSocketManager {
@@ -14,17 +15,17 @@ object WebSocketManager {
val scope = CoroutineScope(Dispatchers.IO) val scope = CoroutineScope(Dispatchers.IO)
// 存储所有 WebSocket 连接的 Mapkey 为 user_idvalue 为 WebSocketSession // 存储所有 WebSocket 连接的 Mapkey 为 user_idvalue 为 WebSocketSession
private val connections = ConcurrentHashMap<Int, WebSocketSession>() private val connections = ConcurrentHashMap<UUID, WebSocketSession>()
// 添加 WebSocket 连接 // 添加 WebSocket 连接
fun addConnection(userId: Int, session: WebSocketSession) { fun addConnection(userId: UUID, session: WebSocketSession) {
connections[userId] = session connections[userId] = session
println("新连接:$userId") println("新连接:$userId")
} }
// 移除 WebSocket 连接 // 移除 WebSocket 连接
fun removeConnection(userId: Int) { fun removeConnection(userId: UUID) {
connections.remove(userId) connections.remove(userId)
println("断开连接:$userId") println("断开连接:$userId")
} }
@@ -39,7 +40,7 @@ object WebSocketManager {
} }
// 向特定用户发送消息 // 向特定用户发送消息
suspend fun sendMessageToUser(userId: Int, message: WSChatRecords) { suspend fun sendMessageToUser(userId: UUID, message: WSChatRecords) {
connections[userId]?.send(gson.toJson(message)) connections[userId]?.send(gson.toJson(message))
} }
@@ -1,8 +1,8 @@
package ink.snowflake.server.database package ink.snowflake.server.utils.dao
import ink.snowflake.server.database.table.AIProfile import ink.snowflake.server.model.database.AIProfile
import ink.snowflake.server.database.table.AIProfilesTable import ink.snowflake.server.model.database.AIProfilesTable
import ink.snowflake.server.database.table.toAIProfile import ink.snowflake.server.model.database.toAIProfile
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
@@ -0,0 +1,61 @@
package ink.snowflake.server.utils.dao
import ink.snowflake.server.model.database.ChatRecord
import ink.snowflake.server.model.database.ChatRecordsTable
import ink.snowflake.server.model.database.toChatRecord
import kotlinx.datetime.Clock
import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.datetime.timestampLiteral
import org.jetbrains.exposed.v1.exceptions.ExposedSQLException
import org.jetbrains.exposed.v1.jdbc.insert
import org.jetbrains.exposed.v1.jdbc.insertAndGetId
import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import java.util.UUID
import kotlin.uuid.Uuid
object ChatRecordsDao {
/**
* 插入聊天记录
*/
fun insertChatRecord(
userId: UUID,
message: String,
messageType: String,
sendAt: Long,
from: String,
emotion: String?
): Boolean {
return transaction {
try {
ChatRecordsTable
.insert {
it[ChatRecordsTable.userId] = userId
it[ChatRecordsTable.message] = message
it[ChatRecordsTable.messageType] = messageType
it[ChatRecordsTable.sentAt] = timestampLiteral(Clock.System.now())
it[ChatRecordsTable.from] = from
it[ChatRecordsTable.emotion] = emotion
}
true
} catch (e: ExposedSQLException) {
e.printStackTrace()
false
}
}
}
/**
* 取最近五条聊天记录
*/
fun getRecentChatRecords(userId: UUID): List<ChatRecord> {
return transaction {
ChatRecordsTable
.selectAll()
.where { ChatRecordsTable.userId eq userId }
.orderBy(ChatRecordsTable.sentAt to SortOrder.DESC)
.limit(5)
.toList().map { it.toChatRecord() }
}
}
}
@@ -1,8 +1,8 @@
package ink.snowflake.server.database package ink.snowflake.server.utils.dao
import ink.snowflake.server.SERVER_PATH_OSS import ink.snowflake.server.SERVER_PATH_OSS
import ink.snowflake.server.model.request.ImageAnalyticsRequest import ink.snowflake.server.model.request.ImageAnalyticsRequest
import ink.snowflake.server.database.table.ImageTable import ink.snowflake.server.model.database.ScaImagesTable
import ink.snowflake.server.utils.formatLocalDateTimeToString import ink.snowflake.server.utils.formatLocalDateTimeToString
import kotlinx.datetime.toKotlinLocalDateTime import kotlinx.datetime.toKotlinLocalDateTime
import org.jetbrains.exposed.v1.core.SortOrder import org.jetbrains.exposed.v1.core.SortOrder
@@ -15,7 +15,7 @@ object ImageDao {
fun insertImageAnalyticsData(request: ImageAnalyticsRequest) { fun insertImageAnalyticsData(request: ImageAnalyticsRequest) {
return transaction { return transaction {
ImageTable.insert { ScaImagesTable.insert {
it[upload_datetime] = Timestamp.valueOf(request.upload_datetime) it[upload_datetime] = Timestamp.valueOf(request.upload_datetime)
.toLocalDateTime().toKotlinLocalDateTime() .toLocalDateTime().toKotlinLocalDateTime()
it[file_name] = request.file_name it[file_name] = request.file_name
@@ -34,25 +34,25 @@ object ImageDao {
fun getImageList(name: String): List<ImageAnalyticsRequest> { fun getImageList(name: String): List<ImageAnalyticsRequest> {
return transaction { return transaction {
ImageTable.selectAll() ScaImagesTable.selectAll()
.where { ImageTable.name like "%$name%" } .where { ScaImagesTable.name like "%$name%" }
.orderBy(ImageTable.upload_datetime, SortOrder.DESC) .orderBy(ScaImagesTable.upload_datetime, SortOrder.DESC)
.map { .map {
ImageAnalyticsRequest( ImageAnalyticsRequest(
id = it[ImageTable.id].value, id = it[ScaImagesTable.id].value,
name = it[ImageTable.name], name = it[ScaImagesTable.name],
upload_datetime = formatLocalDateTimeToString(it[ImageTable.upload_datetime]), upload_datetime = formatLocalDateTimeToString(it[ScaImagesTable.upload_datetime]),
file_name = it[ImageTable.file_name], file_name = it[ScaImagesTable.file_name],
image_pre = "http://${SERVER_PATH_OSS}:9000/image-sca/" + it[ImageTable.image_pre], image_pre = "http://${SERVER_PATH_OSS}:9000/image-sca/" + it[ScaImagesTable.image_pre],
image_after = "http://${SERVER_PATH_OSS}:9000/image-sca/" + it[ImageTable.image_after], image_after = "http://${SERVER_PATH_OSS}:9000/image-sca/" + it[ScaImagesTable.image_after],
resolution = it[ImageTable.resolution], resolution = it[ScaImagesTable.resolution],
size = it[ImageTable.size], size = it[ScaImagesTable.size],
cocoon_count = it[ImageTable.cocoon_count], cocoon_count = it[ScaImagesTable.cocoon_count],
max_confidence = it[ImageTable.max_confidence], max_confidence = it[ScaImagesTable.max_confidence],
min_confidence = it[ImageTable.min_confidence], min_confidence = it[ScaImagesTable.min_confidence],
average_confidence = it[ImageTable.average_confidence], average_confidence = it[ScaImagesTable.average_confidence],
other_info = it[ImageTable.other_info] as Map<String, String>, other_info = it[ScaImagesTable.other_info] as Map<String, String>,
processing_time = formatLocalDateTimeToString(it[ImageTable.processing_time]) processing_time = formatLocalDateTimeToString(it[ScaImagesTable.processing_time])
) )
} }
} }
@@ -0,0 +1,90 @@
package ink.snowflake.server.utils.dao
import ink.snowflake.server.model.database.UsersTable
import ink.snowflake.server.model.response.UserInfoResponse
import kotlinx.datetime.Clock
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 java.util.UUID
object UserDAO {
/**
* 根据 email 获取密码
* @return 密码的 SHA-256 哈希值
*/
fun getPasswordById(id: UUID): String? {
return transaction {
// 查询指定 email 的用户并返回 passwordHash
UsersTable
.selectAll().where { UsersTable.id eq id }
.map { it[UsersTable.passwordHash] }
.singleOrNull() // 如果没有找到用户,返回 null
}
}
/**
* 根据 email 获取用户 ID
* @return 用户的 ID
*/
fun getUserIdByEmail(email: String): UUID? {
return transaction {
// 查询指定 email 的用户并返回 id
UsersTable
.selectAll().where { UsersTable.email eq email }
.map { it[UsersTable.id].value }
.singleOrNull() // 如果没有找到用户,返回 null
}
}
/**
* 添加用户
* @return 如果添加成功,返回 1;如果用户已存在,返回 0;如果添加失败,返回 -1
*/
fun registerByEmailAndGetId(account: String, hashPassword: String): UUID? {
return transaction {
try {
val newUserId = UsersTable
.insertAndGetId {
it[username] = account
it[email] = account
it[passwordHash] = hashPassword
it[createdAt] = timestampLiteral(Clock.System.now())
it[updatedAt] = timestampLiteral(Clock.System.now())
}
// 如果插入成功,返回 userId
newUserId.value
} catch (e: Exception) {
// 如果发生异常(例如数据库连接问题),返回 -1
e.printStackTrace()
null
}
}
}
fun getUserInfoByUserId(userId: UUID?): UserInfoResponse? {
if (userId == null) return null
return transaction {
try {
UsersTable
.selectAll().where { UsersTable.id eq userId }
.map {
UserInfoResponse(
username = it[UsersTable.username],
email = it[UsersTable.email],
phone = it[UsersTable.phone],
roles = it[UsersTable.roles]?.removePrefix("{")!!.removeSuffix("}").split(","),
)
}
.singleOrNull()
} catch (e: Exception) {
// 如果发生异常(例如数据库连接问题),返回 -1
e.printStackTrace()
null
}
}
}
}
@@ -1,9 +1,9 @@
package ink.snowflake.server.database package ink.snowflake.server.utils.dao
import ink.snowflake.server.database.table.VideoAnalyticsDetailTable import ink.snowflake.server.model.database.VideoAnalyticsDetailsTable
import ink.snowflake.server.database.table.VideoTable import ink.snowflake.server.model.database.VideosTable
import ink.snowflake.server.database.table.VideoTable.vATime import ink.snowflake.server.model.database.VideosTable.vATime
import ink.snowflake.server.database.table.VideoTable.vName import ink.snowflake.server.model.database.VideosTable.vName
import ink.snowflake.server.model.request.VideoAnalyticsRequest import ink.snowflake.server.model.request.VideoAnalyticsRequest
import ink.snowflake.server.model.request.VideoDetail import ink.snowflake.server.model.request.VideoDetail
import ink.snowflake.server.model.response.VideoListResponse import ink.snowflake.server.model.response.VideoListResponse
@@ -16,13 +16,14 @@ 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 java.sql.Timestamp import java.sql.Timestamp
import java.util.UUID
object VideoDao { object VideoDao {
fun insertVideoAnalyticsData(request: VideoAnalyticsRequest) { fun insertVideoAnalyticsData(request: VideoAnalyticsRequest) {
return transaction { return transaction {
// 插入视频信息 // 插入视频信息
val videoId = VideoTable.insertAndGetId { val videoId = VideosTable.insertAndGetId {
it[vName] = request.v_name it[vName] = request.v_name
it[vObjectName] = request.v_object_name it[vObjectName] = request.v_object_name
it[vFileName] = request.v_file_name it[vFileName] = request.v_file_name
@@ -43,7 +44,7 @@ object VideoDao {
} }
// 插入视频分析详情 // 插入视频分析详情
request.v_a_details.forEach { detail -> request.v_a_details.forEach { detail ->
VideoAnalyticsDetailTable.insert { VideoAnalyticsDetailsTable.insert {
it[vId] = videoId.value it[vId] = videoId.value
it[aTimeStamp] = detail.a_time_stamp it[aTimeStamp] = detail.a_time_stamp
it[aTotalPeople] = detail.a_total_people it[aTotalPeople] = detail.a_total_people
@@ -56,39 +57,39 @@ object VideoDao {
fun getVideoList(name: String?): List<VideoListResponse> { fun getVideoList(name: String?): List<VideoListResponse> {
return transaction { return transaction {
val query = VideoTable.selectAll() val query = VideosTable.selectAll()
// 动态加 where 条件 // 动态加 where 条件
if (!name.isNullOrBlank()) { if (!name.isNullOrBlank()) {
query.where { VideoTable.vName like "%${name}%" } query.where { vName like "%${name}%" }
} }
query.orderBy(VideoTable.vATime, SortOrder.DESC) query.orderBy(vATime, SortOrder.DESC)
.map { .map {
VideoListResponse( VideoListResponse(
v_id = it[VideoTable.id].value, v_id = it[VideosTable.id].value,
v_name = it[VideoTable.vName], v_name = it[vName],
v_a_time = formatLocalDateTimeToString(it[VideoTable.vATime]) v_a_time = formatLocalDateTimeToString(it[vATime])
) )
} }
} }
} }
fun getAnalyticsDetailByVideoId(vId: Int): ResultRow? { fun getAnalyticsDetailByVideoId(vId: UUID): ResultRow? {
return transaction { return transaction {
VideoTable.selectAll().where { VideoTable.id eq vId }.singleOrNull() VideosTable.selectAll().where { VideosTable.id eq vId }.singleOrNull()
} }
} }
fun selectVideoDetailByVid(vId: Int): List<VideoDetail> { fun selectVideoDetailByVid(vId: UUID): List<VideoDetail> {
return transaction { return transaction {
VideoAnalyticsDetailTable VideoAnalyticsDetailsTable
.selectAll() .selectAll()
.where { VideoAnalyticsDetailTable.vId eq vId } .where { VideoAnalyticsDetailsTable.vId eq vId }
.map { .map {
VideoDetail( VideoDetail(
a_time_stamp = it[VideoAnalyticsDetailTable.aTimeStamp], a_time_stamp = it[VideoAnalyticsDetailsTable.aTimeStamp],
a_total_people = it[VideoAnalyticsDetailTable.aTotalPeople], a_total_people = it[VideoAnalyticsDetailsTable.aTotalPeople],
a_total_people_masked = it[VideoAnalyticsDetailTable.aTotalPeopleMasked], a_total_people_masked = it[VideoAnalyticsDetailsTable.aTotalPeopleMasked],
a_action = it[VideoAnalyticsDetailTable.aAction] a_action = it[VideoAnalyticsDetailsTable.aAction]
) )
} }
} }
@@ -1,4 +1,4 @@
package ink.snowflake.server.plugins package ink.snowflake.server.utils.plugins
import ink.snowflake.server.utils.AppConfig import ink.snowflake.server.utils.AppConfig
import io.ktor.http.* import io.ktor.http.*
@@ -14,7 +14,7 @@ fun Application.configureCORS() {
allowHost("localhost:8089") allowHost("localhost:8089")
allowHost("127.0.0.1:8089") allowHost("127.0.0.1:8089")
allowHost("171.212.101.199:8089") allowHost("171.212.101.199:8089")
allowHost("debug.scaitcn.com:8089") allowHost("s1.ronsunny.cn:8089")
// 进一步配置 CORS // 进一步配置 CORS
allowMethod(HttpMethod.Get) allowMethod(HttpMethod.Get)
@@ -1,4 +1,4 @@
package ink.snowflake.server.plugins package ink.snowflake.server.utils.plugins
import ink.snowflake.server.utils.AppConfig import ink.snowflake.server.utils.AppConfig
import org.jetbrains.exposed.v1.jdbc.Database import org.jetbrains.exposed.v1.jdbc.Database
@@ -1,4 +1,4 @@
package ink.snowflake.server.plugins package ink.snowflake.server.utils.plugins
import com.auth0.jwt.JWT import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm import com.auth0.jwt.algorithms.Algorithm
@@ -0,0 +1,28 @@
package ink.snowflake.server.utils.plugins
import ink.snowflake.server.model.UUIDSerializer
import io.ktor.serialization.gson.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import java.util.UUID
fun Application.configureSerialization() {
install(ContentNegotiation) {
json(
Json {
prettyPrint = true
serializersModule = module
encodeDefaults = true
}
)
gson{
setPrettyPrinting()
}
}
}
val module = SerializersModule {
contextual(UUID::class, UUIDSerializer)
}
@@ -1,4 +1,4 @@
package ink.snowflake.server.plugins package ink.snowflake.server.utils.plugins
import ink.snowflake.server.utils.AppConfig import ink.snowflake.server.utils.AppConfig
import io.ktor.server.application.* import io.ktor.server.application.*
@@ -1,4 +1,4 @@
package ink.snowflake.server.plugins package ink.snowflake.server.utils.plugins
import ink.snowflake.server.model.response.BaseResponse import ink.snowflake.server.model.response.BaseResponse
import io.ktor.http.* import io.ktor.http.*
@@ -22,7 +22,7 @@ fun Application.configureStatusPages() {
call.respond( call.respond(
HttpStatusCode.InternalServerError, BaseResponse<Nothing>( HttpStatusCode.InternalServerError, BaseResponse<Nothing>(
status = false, status = false,
message = cause.localizedMessage message = cause.localizedMessage?:"Unknown error"
) )
) )
} }
@@ -1,4 +1,4 @@
package ink.snowflake.server.plugins package ink.snowflake.server.utils.plugins
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
@@ -1,4 +1,4 @@
package ink.snowflake.server.plugins package ink.snowflake.server.utils.plugins
import io.ktor.serialization.gson.* import io.ktor.serialization.gson.*
import io.ktor.server.application.* import io.ktor.server.application.*
+2 -1
View File
@@ -16,7 +16,8 @@ ktor:
realm: "Snowflake Server" realm: "Snowflake Server"
secret: "secret_jwt" secret: "secret_jwt"
database: database:
url: "jdbc:postgresql://localhost:5432/ktor" url: "jdbc:postgresql://10.10.10.9:5432/ktor2"
# url: "jdbc:postgresql://localhost:5432/ktor2"
driver: "org.postgresql.Driver" driver: "org.postgresql.Driver"
user: "postgres" user: "postgres"
password: "123456" password: "123456"