整理代码
This commit is contained in:
@@ -5,9 +5,9 @@ import ink.snowflake.server.gson
|
|||||||
import ink.snowflake.server.model.request.ChatRequest
|
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.TokenUtils.getUserIdByToken
|
||||||
import ink.snowflake.server.utils.WebSocketManager
|
import ink.snowflake.server.utils.WebSocketManager
|
||||||
import ink.snowflake.server.utils.dao.ChatRecordsDao
|
import ink.snowflake.server.utils.dao.ChatRecordsDao
|
||||||
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.*
|
||||||
import io.ktor.client.request.*
|
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.SERVER_PATH_FRP
|
||||||
import ink.snowflake.server.model.request.DevicesInfoRequest
|
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.CommandUtils.runCommand
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.engine.cio.CIO
|
import io.ktor.client.engine.cio.CIO
|
||||||
import io.ktor.client.plugins.auth.Auth
|
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.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.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.AIDao
|
||||||
import ink.snowflake.server.utils.dao.UserDAO
|
import ink.snowflake.server.utils.dao.UserDAO
|
||||||
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.*
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
||||||
@@ -42,236 +46,144 @@ fun Application.User(config: AppConfig) {
|
|||||||
// 初始化 Redis 连接
|
// 初始化 Redis 连接
|
||||||
val redisClient: RedissonClient = setupRedis()
|
val redisClient: RedissonClient = setupRedis()
|
||||||
routing {
|
routing {
|
||||||
route("/api") {
|
route("/api/user") {
|
||||||
route("/user") {
|
post("/login") {
|
||||||
post("/login") {
|
val loginRequest = call.receive<LoginRequest>()
|
||||||
val loginRequest = call.receive<LoginRequest>()
|
val email = loginRequest.account
|
||||||
val email = loginRequest.account
|
val password = loginRequest.password
|
||||||
val password = loginRequest.password
|
// 查找用户
|
||||||
// 查找用户
|
val userId = UserDAO.getUserIdByEmail(email)
|
||||||
val userId = UserDAO.getUserIdByEmail(email)
|
call.respond(
|
||||||
call.respond(
|
if (userId == null) {
|
||||||
if (userId == null) {
|
// 账号不存在
|
||||||
// 账号不存在
|
BaseResponse(status = false, message = "尚未注册", data = null)
|
||||||
BaseResponse(status = false, message = "尚未注册", data = null)
|
} else {
|
||||||
} else {
|
val userPassword = UserDAO.getPasswordById(userId)
|
||||||
val userPassword = UserDAO.getPasswordById(userId)
|
// 验证密码
|
||||||
// 验证密码
|
if (password == userPassword) {
|
||||||
if (password == userPassword) {
|
// 登录成功
|
||||||
// 登录成功
|
|
||||||
BaseResponse (
|
|
||||||
status = true, data = LoginResponse(
|
|
||||||
userId,
|
|
||||||
generateAccessToken(config, userId),
|
|
||||||
generateRefreshToken(config, userId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// 账号密码不匹配
|
|
||||||
BaseResponse(status = false, message = "账号密码不匹配,请重新登录", data = null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
post("/sendCode") {
|
|
||||||
val account = call.receive<CommonRequest>()
|
|
||||||
// 将验证码存入 Redis,设置过期时间为 10 分钟
|
|
||||||
val bucket: RBucket<String> = redisClient.getBucket("verificationCode:${account.str}")
|
|
||||||
// 生成随机验证码(6位数字)
|
|
||||||
val verificationCode = generateVerificationCode()
|
|
||||||
println("验证码:$verificationCode")
|
|
||||||
// 10分钟的过期时间
|
|
||||||
bucket.set(verificationCode, Duration.ofMinutes(10))
|
|
||||||
launch {
|
|
||||||
sendVerificationEmail(config, account.str, verificationCode)
|
|
||||||
}
|
|
||||||
call.respond(BaseResponse(status = true, message = "已发送验证码", data = null))
|
|
||||||
}
|
|
||||||
post("/register") {
|
|
||||||
// 可能返回的情况:1. 注册成功 -1. 账户已存在 -2. 验证码错误 -3. 验证码过期
|
|
||||||
val register = call.receive<RegisterRequest>()
|
|
||||||
val account = register.account
|
|
||||||
// 从 Redis 获取保存的验证码
|
|
||||||
val bucket: RBucket<String> = redisClient.getBucket("verificationCode:$account")
|
|
||||||
val storedCode = bucket.get()
|
|
||||||
call.respond(
|
|
||||||
if (storedCode == null) {
|
|
||||||
BaseResponse(status = false, message = "验证码过期,请重新获取", data = null)
|
|
||||||
} else if (storedCode != register.code) {
|
|
||||||
BaseResponse(status = false, message = "验证码错误,请检查或重新获取", data = null)
|
|
||||||
} else {
|
|
||||||
// 查找用户
|
|
||||||
val userId = UserDAO.getUserIdByEmail(register.account)
|
|
||||||
if (userId == null) {
|
|
||||||
// 用户不存在,插入新用户
|
|
||||||
val id = UserDAO.registerByEmailAndGetId(register.account, register.password)
|
|
||||||
if (id == null) {
|
|
||||||
BaseResponse(status = true, message = "注册失败,请联系管理员", data = null)
|
|
||||||
} else {
|
|
||||||
BaseResponse(
|
|
||||||
status = true, message = "注册成功", data = LoginResponse(
|
|
||||||
id,
|
|
||||||
generateAccessToken(config, id),
|
|
||||||
generateRefreshToken(config, id)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 如果用户已存在,返回 0
|
|
||||||
BaseResponse(status = false, message = "账户已存在", data = null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
post("/refreshToken") {
|
|
||||||
val token = call.receive<RefreshTokenRequest>()
|
|
||||||
val verifier = JWT.require(Algorithm.HMAC256(config.jwtSecret))
|
|
||||||
.withAudience(config.jwtAudience)
|
|
||||||
.withIssuer(config.jwtDomain)
|
|
||||||
.build()
|
|
||||||
val decodedJWT = verifier.verify(token.refreshToken)
|
|
||||||
val tokenType = decodedJWT.getClaim("token_type").asString()
|
|
||||||
call.respond(
|
|
||||||
try {
|
|
||||||
if (tokenType != "refresh_token") {
|
|
||||||
BaseResponse(
|
|
||||||
status = false,
|
|
||||||
message = "拿什么乱七八糟的东西跟我换Access Token呢,???",
|
|
||||||
data = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val userId = UUID.fromString(decodedJWT.getClaim("user_id").asString())
|
|
||||||
// 生成新的access token和refresh token
|
|
||||||
BaseResponse(
|
BaseResponse(
|
||||||
status = true, data = RefreshTokenResponse(
|
status = true, data = LoginResponse(
|
||||||
|
userId,
|
||||||
generateAccessToken(config, userId),
|
generateAccessToken(config, userId),
|
||||||
generateRefreshToken(config, userId)
|
generateRefreshToken(config, userId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
BaseResponse(data = generateAccessToken(config, userId))
|
} else {
|
||||||
} catch (ex: Exception) {
|
// 账号密码不匹配
|
||||||
BaseResponse(status = false, message = "token解析错误", data = null)
|
BaseResponse(status = false, message = "账号密码不匹配,请重新登录", data = null)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
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) {
|
post("/sendCode") {
|
||||||
val userInfo = UserDAO.getUserInfoByUserId(userId)
|
val account = call.receive<CommonRequest>()
|
||||||
if (userInfo != null) {
|
// 将验证码存入 Redis,设置过期时间为 10 分钟
|
||||||
val response = BaseResponse(data = userInfo)
|
val bucket: RBucket<String> = redisClient.getBucket("verificationCode:${account.str}")
|
||||||
call.respond(response)
|
// 生成随机验证码(6位数字)
|
||||||
|
val verificationCode = generateVerificationCode()
|
||||||
|
println("验证码:$verificationCode")
|
||||||
|
// 10分钟的过期时间
|
||||||
|
bucket.set(verificationCode, Duration.ofMinutes(10))
|
||||||
|
launch {
|
||||||
|
sendVerificationEmail(config, account.str, verificationCode)
|
||||||
|
}
|
||||||
|
call.respond(BaseResponse(status = true, message = "已发送验证码", data = null))
|
||||||
|
}
|
||||||
|
post("/register") {
|
||||||
|
// 可能返回的情况:1. 注册成功 -1. 账户已存在 -2. 验证码错误 -3. 验证码过期
|
||||||
|
val register = call.receive<RegisterRequest>()
|
||||||
|
val account = register.account
|
||||||
|
// 从 Redis 获取保存的验证码
|
||||||
|
val bucket: RBucket<String> = redisClient.getBucket("verificationCode:$account")
|
||||||
|
val storedCode = bucket.get()
|
||||||
|
call.respond(
|
||||||
|
if (storedCode == null) {
|
||||||
|
BaseResponse(status = false, message = "验证码过期,请重新获取", data = null)
|
||||||
|
} else if (storedCode != register.code) {
|
||||||
|
BaseResponse(status = false, message = "验证码错误,请检查或重新获取", data = null)
|
||||||
|
} else {
|
||||||
|
// 查找用户
|
||||||
|
val userId = UserDAO.getUserIdByEmail(register.account)
|
||||||
|
if (userId == null) {
|
||||||
|
// 用户不存在,插入新用户
|
||||||
|
val id = UserDAO.registerByEmailAndGetId(register.account, register.password)
|
||||||
|
if (id == null) {
|
||||||
|
BaseResponse(status = true, message = "注册失败,请联系管理员", data = null)
|
||||||
} else {
|
} else {
|
||||||
call.respond(BaseResponse(data = "查无此人"))
|
BaseResponse(
|
||||||
|
status = true, message = "注册成功", data = LoginResponse(
|
||||||
|
id,
|
||||||
|
generateAccessToken(config, id),
|
||||||
|
generateRefreshToken(config, id)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
call.respond(BaseResponse(data = "Token出错"))
|
// 如果用户已存在,返回 0
|
||||||
|
BaseResponse(status = false, message = "账户已存在", data = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
post("/refreshToken") {
|
||||||
|
val token = call.receive<RefreshTokenRequest>()
|
||||||
|
val verifier = JWT.require(Algorithm.HMAC256(config.jwtSecret))
|
||||||
|
.withAudience(config.jwtAudience)
|
||||||
|
.withIssuer(config.jwtDomain)
|
||||||
|
.build()
|
||||||
|
val decodedJWT = verifier.verify(token.refreshToken)
|
||||||
|
val tokenType = decodedJWT.getClaim("token_type").asString()
|
||||||
|
call.respond(
|
||||||
|
try {
|
||||||
|
if (tokenType != "refresh_token") {
|
||||||
|
BaseResponse(
|
||||||
|
status = false,
|
||||||
|
message = "拿什么乱七八糟的东西跟我换Access Token呢,???",
|
||||||
|
data = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val userId = UUID.fromString(decodedJWT.getClaim("user_id").asString())
|
||||||
|
// 生成新的access token和refresh token
|
||||||
|
BaseResponse(
|
||||||
|
status = true, data = RefreshTokenResponse(
|
||||||
|
generateAccessToken(config, userId),
|
||||||
|
generateRefreshToken(config, userId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
BaseResponse(data = generateAccessToken(config, userId))
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
BaseResponse(status = false, message = "token解析错误", data = null)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
val userInfo = UserDAO.getUserInfoByUserId(userId)
|
||||||
|
if (userInfo != null) {
|
||||||
|
val response = BaseResponse(data = userInfo)
|
||||||
|
call.respond(response)
|
||||||
|
} else {
|
||||||
|
call.respond(BaseResponse(data = "查无此人"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
call.respond(BaseResponse(data = "Token出错"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.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.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.WebSocketManager.broadcastMessage
|
||||||
import ink.snowflake.server.utils.runCommand
|
|
||||||
import io.ktor.http.content.*
|
import io.ktor.http.content.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.auth.*
|
import io.ktor.server.auth.*
|
||||||
@@ -26,6 +28,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.datetime.LocalDateTime
|
import kotlinx.datetime.LocalDateTime
|
||||||
import kotlinx.datetime.toJavaLocalDateTime
|
import kotlinx.datetime.toJavaLocalDateTime
|
||||||
|
import kotlinx.html.Entities
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
@@ -99,7 +102,7 @@ fun Application.VideoAnalytics() {
|
|||||||
)
|
)
|
||||||
println("-----------------" + command.joinToString(" "))
|
println("-----------------" + command.joinToString(" "))
|
||||||
runCommand(command) {
|
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.time.format.DateTimeFormatter
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
object MyUtils {
|
||||||
|
|
||||||
fun getUserIdByToken(call: ApplicationCall) : UUID?{
|
fun formatDateToTargetString(date: Date, targetFormat: String): String {
|
||||||
// 通过token获取user_id
|
val formatter = SimpleDateFormat(targetFormat) // 创建格式化器
|
||||||
return UUID.fromString(call.principal<JWTPrincipal>()?.payload?.getClaim("user_id")?.asString())
|
return formatter.format(date) // 格式化日期并返回字符串
|
||||||
}
|
|
||||||
|
|
||||||
fun formatDateToTargetString(date: Date, targetFormat: String): String {
|
|
||||||
val formatter = SimpleDateFormat(targetFormat) // 创建格式化器
|
|
||||||
return formatter.format(date) // 格式化日期并返回字符串
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
fun formatLocalDateTimeToString(localDateTime: LocalDateTime): String {
|
||||||
// 使用 ProcessBuilder 构建命令并启动
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") // 创建格式化器
|
||||||
val process = ProcessBuilder(command)
|
return localDateTime.toJavaLocalDateTime().format(formatter) // 转换并格式化
|
||||||
.redirectErrorStream(true) // 合并标准输出和错误输出
|
}
|
||||||
.start()
|
|
||||||
|
|
||||||
// 创建一个 CoroutineScope 来管理协程
|
fun getFriendlyActionName(name: String): String {
|
||||||
val scope = CoroutineScope(Dispatchers.IO)
|
return if (name == "feed") {
|
||||||
|
"喂桑"
|
||||||
// 使用协程读取标准输出和错误输出
|
} else if (name == "disinfection") {
|
||||||
scope.launch {
|
"消毒"
|
||||||
try {
|
} else {
|
||||||
val reader = BufferedReader(InputStreamReader(process.inputStream))
|
name
|
||||||
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,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.SERVER_PATH_OSS
|
||||||
import ink.snowflake.server.model.request.ImageAnalyticsRequest
|
import ink.snowflake.server.model.request.ImageAnalyticsRequest
|
||||||
import ink.snowflake.server.model.database.ScaImagesTable
|
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 kotlinx.datetime.toKotlinLocalDateTime
|
||||||
import org.jetbrains.exposed.v1.core.SortOrder
|
import org.jetbrains.exposed.v1.core.SortOrder
|
||||||
import org.jetbrains.exposed.v1.jdbc.insert
|
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.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
|
||||||
import ink.snowflake.server.utils.formatLocalDateTimeToString
|
import ink.snowflake.server.utils.MyUtils.formatLocalDateTimeToString
|
||||||
import kotlinx.datetime.toKotlinLocalDateTime
|
import kotlinx.datetime.toKotlinLocalDateTime
|
||||||
import org.jetbrains.exposed.v1.core.ResultRow
|
import org.jetbrains.exposed.v1.core.ResultRow
|
||||||
import org.jetbrains.exposed.v1.core.SortOrder
|
import org.jetbrains.exposed.v1.core.SortOrder
|
||||||
|
|||||||
Reference in New Issue
Block a user