整理代码

This commit is contained in:
BBIT-Kai
2025-08-25 09:17:59 +08:00
parent bbc56b313d
commit d2823b51af
11 changed files with 346 additions and 342 deletions
@@ -5,9 +5,9 @@ import ink.snowflake.server.gson
import ink.snowflake.server.model.request.ChatRequest
import ink.snowflake.server.model.response.ChatResponse
import ink.snowflake.server.model.response.BaseResponse
import ink.snowflake.server.utils.TokenUtils.getUserIdByToken
import ink.snowflake.server.utils.WebSocketManager
import ink.snowflake.server.utils.dao.ChatRecordsDao
import ink.snowflake.server.utils.getUserIdByToken
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
@@ -4,7 +4,7 @@ import com.google.gson.Gson
import ink.snowflake.server.SERVER_PATH_FRP
import ink.snowflake.server.model.request.DevicesInfoRequest
import ink.snowflake.server.model.response.*
import ink.snowflake.server.utils.runCommand
import ink.snowflake.server.utils.CommandUtils.runCommand
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.auth.Auth
@@ -8,9 +8,13 @@ import ink.snowflake.server.model.request.RefreshTokenRequest
import ink.snowflake.server.model.request.RegisterRequest
import ink.snowflake.server.model.response.*
import ink.snowflake.server.utils.AppConfig
import ink.snowflake.server.utils.EmailUtils.generateVerificationCode
import ink.snowflake.server.utils.EmailUtils.sendVerificationEmail
import ink.snowflake.server.utils.TokenUtils.generateAccessToken
import ink.snowflake.server.utils.TokenUtils.generateRefreshToken
import ink.snowflake.server.utils.TokenUtils.getUserIdByToken
import ink.snowflake.server.utils.dao.AIDao
import ink.snowflake.server.utils.dao.UserDAO
import ink.snowflake.server.utils.getUserIdByToken
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
@@ -42,8 +46,7 @@ fun Application.User(config: AppConfig) {
// 初始化 Redis 连接
val redisClient: RedissonClient = setupRedis()
routing {
route("/api") {
route("/user") {
route("/api/user") {
post("/login") {
val loginRequest = call.receive<LoginRequest>()
val email = loginRequest.account
@@ -59,7 +62,7 @@ fun Application.User(config: AppConfig) {
// 验证密码
if (password == userPassword) {
// 登录成功
BaseResponse (
BaseResponse(
status = true, data = LoginResponse(
userId,
generateAccessToken(config, userId),
@@ -182,96 +185,5 @@ fun Application.User(config: AppConfig) {
}
}
}
}
}
fun generateAccessToken(config: AppConfig, userId: UUID): Token {
// return generateToken(config, userId, 120, "access_token")
return generateToken(config, userId, 2 * 60 * 60, "access_token")
}
fun generateRefreshToken(config: AppConfig, userId: UUID): Token {
return generateToken(config, userId, 10 * 24 * 60 * 60, "refresh_token")
}
fun generateToken(config: AppConfig, userId: UUID, second: Int, tokenType: String): Token {
val expiresAt = Date(System.currentTimeMillis() + second * 1000) //
val token = JWT.create()
.withAudience(config.jwtAudience)
.withIssuer(config.jwtDomain)
.withClaim("user_id", userId.toString())
.withClaim("token_type", tokenType)
.withExpiresAt(expiresAt)
.sign(Algorithm.HMAC256(config.jwtSecret))
return Token(token, expiresAt.time, DateFormat.getDateTimeInstance().format(expiresAt))
}
// 生成4位随机验证码
fun generateVerificationCode(): String {
return String.format("%04d", Random().nextInt(10000))
}
fun sendVerificationEmail(config: AppConfig, recipientEmail: String, verificationCode: String) {
// 设置邮件会话的属性
val properties = Properties().apply {
put("mail.smtp.host", config.smtpHost)
put("mail.smtp.port", config.smtpPort)
put("mail.smtp.auth", "true") // 启用身份验证
put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory") // 启用 SSL
put("mail.smtp.socketFactory.fallback", "false") // 禁用备用连接
}
// 创建会话
val session = Session.getInstance(properties, object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(config.smtpUser, config.smtpPassword)
}
})
try {
// Create email content with a more polished template
val message = MimeMessage(session).apply {
setFrom(InternetAddress(config.smtpUser))
setRecipient(Message.RecipientType.TO, InternetAddress(recipientEmail))
subject = "Welcome to BBIT-Lab! Verification Code:$verificationCode"
val htmlContent = """
<html>
<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;">
<h2 style="color: #4CAF50;">Welcome to BBIT-Lab!</h2>
<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>
<div style="background-color: #f1f1f1; padding: 15px; border-radius: 5px; font-size: 20px; text-align: center; color: #333;">
<strong style="color: #4CAF50;">$verificationCode</strong>
</div>
<p style="font-size: 16px; margin-top: 20px;">This code is valid for the next <strong>10 minutes</strong>. If you did not request this, please ignore this email.</p>
<p style="font-size: 16px; margin-top: 20px;">Please do not reply to this email. This inbox is not monitored.</p>
<hr style="border-top: 1px solid #ddd; margin-top: 30px;">
<p style="font-size: 14px; color: #888; text-align: center;">
Best regards<br>
<strong>The BBIT-Lab Account Team</strong>
</p>
</div>
</body>
</html>
"""
setContent(htmlContent, "text/html")
}
// 发送邮件
Transport.send(message)
println("Verification email sent successfully to $recipientEmail")
} catch (e: MessagingException) {
e.printStackTrace()
println("Failed to send verification email.")
}
}
fun hashPassword(password: String): String {
val bytes = MessageDigest.getInstance("SHA-256").digest(password.toByteArray(UTF_8))
return bytes.joinToString("") { "%02x".format(it) }
}
@@ -12,8 +12,10 @@ import ink.snowflake.server.model.database.VideosTable.vATotalPeople
import ink.snowflake.server.model.database.VideosTable.vStartDateTime
import ink.snowflake.server.model.request.VideoAnalyticsRequest
import ink.snowflake.server.model.response.*
import ink.snowflake.server.utils.CommandUtils.runCommand
import ink.snowflake.server.utils.MyUtils
import ink.snowflake.server.utils.MyUtils.getFriendlyActionName
import ink.snowflake.server.utils.WebSocketManager.broadcastMessage
import ink.snowflake.server.utils.runCommand
import io.ktor.http.content.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
@@ -26,6 +28,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.toJavaLocalDateTime
import kotlinx.html.Entities
import java.io.File
import java.io.IOException
import java.time.format.DateTimeFormatter
@@ -99,7 +102,7 @@ fun Application.VideoAnalytics() {
)
println("-----------------" + command.joinToString(" "))
runCommand(command) {
println(it)
println(Entities.it)
}
}
// 获取已分析视频列表
@@ -254,13 +257,3 @@ suspend fun broadcastMessage(message: String) { // 封装的广播消息方法
}
}
}
private fun getFriendlyActionName(name: String): String {
return if (name == "feed") {
"喂桑"
} else if (name == "disinfection") {
"消毒"
} else {
name
}
}
@@ -0,0 +1,67 @@
package ink.snowflake.server.utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.BufferedReader
import java.io.InputStreamReader
object CommandUtils {
fun runCommand(command: String): String {
println("command--$command")
return try {
val process = Runtime.getRuntime().exec(command)
// 等待命令执行完毕
process.waitFor()
// 读取输出流
val reader = BufferedReader(InputStreamReader(process.inputStream))
val errorReader = BufferedReader(InputStreamReader(process.errorStream))
val output = StringBuilder()
// 读取标准输出
reader.use { r ->
var line: String? = r.readLine()
while (line != null) {
output.append(line).append("\n")
line = r.readLine()
}
}
// 读取错误输出
errorReader.use { er ->
var errorLine: String? = er.readLine()
while (errorLine != null) {
output.append("ERROR: ").append(errorLine).append("\n")
errorLine = er.readLine()
}
}
output.toString()
} catch (e: Exception) {
"Error running command: ${e.message}"
}
}
fun runCommand(command: List<String>, logCallback: (String) -> Unit): Process {
// 使用 ProcessBuilder 构建命令并启动
val process = ProcessBuilder(command)
.redirectErrorStream(true) // 合并标准输出和错误输出
.start()
// 创建一个 CoroutineScope 来管理协程
val scope = CoroutineScope(Dispatchers.IO)
// 使用协程读取标准输出和错误输出
scope.launch {
try {
val reader = BufferedReader(InputStreamReader(process.inputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
logCallback(line ?: "") // 将日志推送到回调
}
} catch (e: Exception) {
logCallback("Error reading output: ${e.message}")
}
}
return process
}
}
@@ -0,0 +1,78 @@
package ink.snowflake.server.utils
import java.util.Properties
import java.util.Random
import javax.mail.Authenticator
import javax.mail.Message
import javax.mail.MessagingException
import javax.mail.PasswordAuthentication
import javax.mail.Session
import javax.mail.Transport
import javax.mail.internet.InternetAddress
import javax.mail.internet.MimeMessage
object EmailUtils {
// 生成4位随机验证码
fun generateVerificationCode(): String {
return String.format("%04d", Random().nextInt(10000))
}
fun sendVerificationEmail(config: AppConfig, recipientEmail: String, verificationCode: String) {
// 设置邮件会话的属性
val properties = Properties().apply {
put("mail.smtp.host", config.smtpHost)
put("mail.smtp.port", config.smtpPort)
put("mail.smtp.auth", "true") // 启用身份验证
put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory") // 启用 SSL
put("mail.smtp.socketFactory.fallback", "false") // 禁用备用连接
}
// 创建会话
val session = Session.getInstance(properties, object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(config.smtpUser, config.smtpPassword)
}
})
try {
// Create email content with a more polished template
val message = MimeMessage(session).apply {
setFrom(InternetAddress(config.smtpUser))
setRecipient(Message.RecipientType.TO, InternetAddress(recipientEmail))
subject = "Welcome to BBIT-Lab! Verification Code:$verificationCode"
val htmlContent = """
<html>
<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;">
<h2 style="color: #4CAF50;">Welcome to BBIT-Lab!</h2>
<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>
<div style="background-color: #f1f1f1; padding: 15px; border-radius: 5px; font-size: 20px; text-align: center; color: #333;">
<strong style="color: #4CAF50;">$verificationCode</strong>
</div>
<p style="font-size: 16px; margin-top: 20px;">This code is valid for the next <strong>10 minutes</strong>. If you did not request this, please ignore this email.</p>
<p style="font-size: 16px; margin-top: 20px;">Please do not reply to this email. This inbox is not monitored.</p>
<hr style="border-top: 1px solid #ddd; margin-top: 30px;">
<p style="font-size: 14px; color: #888; text-align: center;">
Best regards<br>
<strong>The BBIT-Lab Account Team</strong>
</p>
</div>
</body>
</html>
"""
setContent(htmlContent, "text/html")
}
// 发送邮件
Transport.send(message)
println("Verification email sent successfully to $recipientEmail")
} catch (e: MessagingException) {
e.printStackTrace()
println("Failed to send verification email.")
}
}
}
@@ -14,75 +14,26 @@ import java.text.SimpleDateFormat
import java.time.format.DateTimeFormatter
import java.util.*
object MyUtils {
fun getUserIdByToken(call: ApplicationCall) : UUID?{
// 通过token获取user_id
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 {
val formatter = SimpleDateFormat(targetFormat) // 创建格式化器
return formatter.format(date) // 格式化日期并返回字符串
}
}
fun formatLocalDateTimeToString(localDateTime: LocalDateTime): String {
fun formatLocalDateTimeToString(localDateTime: LocalDateTime): String {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") // 创建格式化器
return localDateTime.toJavaLocalDateTime().format(formatter) // 转换并格式化
}
fun runCommand(command: String): String {
println("command--$command")
return try {
val process = Runtime.getRuntime().exec(command)
// 等待命令执行完毕
process.waitFor()
// 读取输出流
val reader = BufferedReader(InputStreamReader(process.inputStream))
val errorReader = BufferedReader(InputStreamReader(process.errorStream))
val output = StringBuilder()
// 读取标准输出
reader.use { r ->
var line: String? = r.readLine()
while (line != null) {
output.append(line).append("\n")
line = r.readLine()
}
}
// 读取错误输出
errorReader.use { er ->
var errorLine: String? = er.readLine()
while (errorLine != null) {
output.append("ERROR: ").append(errorLine).append("\n")
errorLine = er.readLine()
}
}
output.toString()
} catch (e: Exception) {
"Error running command: ${e.message}"
}
}
fun runCommand(command: List<String>, logCallback: (String) -> Unit): Process {
// 使用 ProcessBuilder 构建命令并启动
val process = ProcessBuilder(command)
.redirectErrorStream(true) // 合并标准输出和错误输出
.start()
// 创建一个 CoroutineScope 来管理协程
val scope = CoroutineScope(Dispatchers.IO)
// 使用协程读取标准输出和错误输出
scope.launch {
try {
val reader = BufferedReader(InputStreamReader(process.inputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
logCallback(line ?: "") // 将日志推送到回调
}
} catch (e: Exception) {
logCallback("Error reading output: ${e.message}")
}
}
return process
}
fun getFriendlyActionName(name: String): String {
return if (name == "feed") {
"喂桑"
} else if (name == "disinfection") {
"消毒"
} else {
name
}
}
}
@@ -0,0 +1,49 @@
package ink.snowflake.server.utils
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import ink.snowflake.server.model.response.Token
import io.ktor.server.application.ApplicationCall
import io.ktor.server.auth.jwt.JWTPrincipal
import io.ktor.server.auth.principal
import java.security.MessageDigest
import java.text.DateFormat
import java.util.Date
import java.util.UUID
import kotlin.text.Charsets.UTF_8
object TokenUtils {
fun generateAccessToken(config: AppConfig, userId: UUID): Token {
// return generateToken(config, userId, 120, "access_token")
return generateToken(config, userId, 2 * 60 * 60, "access_token")
}
fun generateRefreshToken(config: AppConfig, userId: UUID): Token {
return generateToken(config, userId, 10 * 24 * 60 * 60, "refresh_token")
}
fun getUserIdByToken(call: ApplicationCall) : UUID?{
// 通过token获取user_id
return UUID.fromString(call.principal<JWTPrincipal>()?.payload?.getClaim("user_id")?.asString())
}
fun generateToken(config: AppConfig, userId: UUID, second: Int, tokenType: String): Token {
val expiresAt = Date(System.currentTimeMillis() + second * 1000) //
val token = JWT.create()
.withAudience(config.jwtAudience)
.withIssuer(config.jwtDomain)
.withClaim("user_id", userId.toString())
.withClaim("token_type", tokenType)
.withExpiresAt(expiresAt)
.sign(Algorithm.HMAC256(config.jwtSecret))
return Token(token, expiresAt.time, DateFormat.getDateTimeInstance().format(expiresAt))
}
fun hashPassword(password: String): String {
val bytes = MessageDigest.getInstance("SHA-256").digest(password.toByteArray(UTF_8))
return bytes.joinToString("") { "%02x".format(it) }
}
}
@@ -1,46 +0,0 @@
var aiState = "等待分析任务中"
// 实时发送AI状态
webSocket("/handleState") { // WebSocket 路由
clients.add(this) // 添加当前连接的客户端
send(aiState) // 向客户端发送连接成功消息
try {
incoming.consumeEach { frame -> // 持续接收消息
when (frame) {
is Frame.Text -> {
aiState = frame.readText() // 更新状态
broadcastMessage(aiState) // 使用封装的方法广播消息
}
is Frame.Close -> {
println("Closed")
clients.remove(this)
close() // 确保关闭 WebSocket 连接
return@consumeEach
}
// 其他消息类型的处理
is Frame.Binary -> TODO() // 处理二进制消息
is Frame.Ping -> TODO() // 处理 Ping 消息
is Frame.Pong -> TODO() // 处理 Pong 消息
}
}
} catch (e: Exception) {
// 处理接收消息时的异常
close(CloseReason(CloseReason.Codes.NORMAL, "Client disconnected"))
e.printStackTrace()
} finally {
clients.remove(this) // 确保在连接关闭时移除客户端
}
}
// 拍照保存为图片 并且调用Python程序进行分析
//get("/takePhoto") {
// val camera = call.request.queryParameters["cameraId"]
// if (camera.isNullOrEmpty()) {
// call.respond(BaseResponse(status = false, message = "摄像头名称不能为空", data = null))
// return@get
// }
// stopHLSStream(camera)
// call.respond(BaseResponse(message = "摄像头流已停止", data = null))
//}
@@ -3,7 +3,7 @@ package ink.snowflake.server.utils.dao
import ink.snowflake.server.SERVER_PATH_OSS
import ink.snowflake.server.model.request.ImageAnalyticsRequest
import ink.snowflake.server.model.database.ScaImagesTable
import ink.snowflake.server.utils.formatLocalDateTimeToString
import ink.snowflake.server.utils.MyUtils.formatLocalDateTimeToString
import kotlinx.datetime.toKotlinLocalDateTime
import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.jdbc.insert
@@ -7,7 +7,7 @@ import ink.snowflake.server.model.database.VideosTable.vName
import ink.snowflake.server.model.request.VideoAnalyticsRequest
import ink.snowflake.server.model.request.VideoDetail
import ink.snowflake.server.model.response.VideoListResponse
import ink.snowflake.server.utils.formatLocalDateTimeToString
import ink.snowflake.server.utils.MyUtils.formatLocalDateTimeToString
import kotlinx.datetime.toKotlinLocalDateTime
import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.SortOrder