ai extraction
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
package com.android.trisolarisserver.component
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.UUID
|
||||
|
||||
@Component
|
||||
class DocumentStorage(
|
||||
@Value("\${storage.documents.root:/home/androidlover5842/docs}")
|
||||
private val rootPath: String
|
||||
) {
|
||||
fun store(
|
||||
propertyId: UUID,
|
||||
guestId: UUID,
|
||||
bookingId: UUID,
|
||||
file: MultipartFile
|
||||
): StoredDocument {
|
||||
val safeExt = file.originalFilename?.substringAfterLast('.', "")?.takeIf { it.isNotBlank() }
|
||||
val fileName = buildString {
|
||||
append(UUID.randomUUID().toString())
|
||||
if (safeExt != null) {
|
||||
append('.')
|
||||
append(safeExt.replace(Regex("[^A-Za-z0-9]"), ""))
|
||||
}
|
||||
}
|
||||
|
||||
val dir = Paths.get(rootPath, propertyId.toString(), guestId.toString(), bookingId.toString())
|
||||
Files.createDirectories(dir)
|
||||
val path = dir.resolve(fileName).normalize()
|
||||
file.inputStream.use { input ->
|
||||
Files.copy(input, path)
|
||||
}
|
||||
return StoredDocument(
|
||||
storagePath = path.toString(),
|
||||
originalFilename = file.originalFilename ?: fileName,
|
||||
contentType = file.contentType,
|
||||
sizeBytes = file.size
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class StoredDocument(
|
||||
val storagePath: String,
|
||||
val originalFilename: String,
|
||||
val contentType: String?,
|
||||
val sizeBytes: Long
|
||||
)
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.android.trisolarisserver.component
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Component
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.time.Instant
|
||||
import java.util.Base64
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
@Component
|
||||
class DocumentTokenService(
|
||||
@Value("\${storage.documents.tokenSecret}")
|
||||
private val tokenSecret: String,
|
||||
@Value("\${storage.documents.tokenTtlSeconds:300}")
|
||||
private val ttlSeconds: Long
|
||||
) {
|
||||
fun createToken(documentId: String): String {
|
||||
val exp = Instant.now().epochSecond + ttlSeconds
|
||||
val payload = "$documentId:$exp"
|
||||
val sig = hmac(payload)
|
||||
val token = "$payload:$sig"
|
||||
return Base64.getUrlEncoder().withoutPadding()
|
||||
.encodeToString(token.toByteArray(StandardCharsets.UTF_8))
|
||||
}
|
||||
|
||||
fun validateToken(token: String, documentId: String): Boolean {
|
||||
val raw = try {
|
||||
String(Base64.getUrlDecoder().decode(token), StandardCharsets.UTF_8)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
return false
|
||||
}
|
||||
val parts = raw.split(":")
|
||||
if (parts.size != 3) return false
|
||||
val tokenDocId = parts[0]
|
||||
val exp = parts[1].toLongOrNull() ?: return false
|
||||
val sig = parts[2]
|
||||
if (tokenDocId != documentId) return false
|
||||
if (Instant.now().epochSecond > exp) return false
|
||||
val expected = hmac("$tokenDocId:$exp")
|
||||
return expected == sig
|
||||
}
|
||||
|
||||
private fun hmac(payload: String): String {
|
||||
val mac = Mac.getInstance("HmacSHA256")
|
||||
val key = SecretKeySpec(tokenSecret.toByteArray(StandardCharsets.UTF_8), "HmacSHA256")
|
||||
mac.init(key)
|
||||
val bytes = mac.doFinal(payload.toByteArray(StandardCharsets.UTF_8))
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.android.trisolarisserver.component
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.http.HttpEntity
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.client.RestTemplate
|
||||
|
||||
@Component
|
||||
class LlamaClient(
|
||||
private val restTemplate: RestTemplate,
|
||||
private val objectMapper: ObjectMapper,
|
||||
@Value("\${ai.llama.baseUrl}")
|
||||
private val baseUrl: String
|
||||
) {
|
||||
fun ask(imageUrl: String, question: String): String {
|
||||
val payload = mapOf(
|
||||
"model" to "qwen",
|
||||
"messages" to listOf(
|
||||
mapOf(
|
||||
"role" to "user",
|
||||
"content" to listOf(
|
||||
mapOf("type" to "text", "text" to question),
|
||||
mapOf("type" to "image_url", "image_url" to mapOf("url" to imageUrl))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val headers = HttpHeaders()
|
||||
headers.contentType = MediaType.APPLICATION_JSON
|
||||
val entity = HttpEntity(payload, headers)
|
||||
val response = restTemplate.postForEntity(baseUrl, entity, String::class.java)
|
||||
val body = response.body ?: return ""
|
||||
val node = objectMapper.readTree(body)
|
||||
return node.path("choices").path(0).path("message").path("content").asText()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user