HotTRDealsBackend/services/redis/linkPreviewImageCache.service.js
2026-02-07 22:42:02 +00:00

87 lines
2.5 KiB
JavaScript

const crypto = require("crypto")
const axios = require("axios")
const { getRedisClient } = require("./client")
const IMAGE_KEY_PREFIX = "cache:deal_create:image:"
const DEFAULT_TTL_SECONDS = 5 * 60
const MAX_IMAGE_BYTES = 2 * 1024 * 1024
function createRedisClient() {
return getRedisClient()
}
function normalizeUrl(rawUrl) {
if (!rawUrl || typeof rawUrl !== "string") return null
try {
const url = new URL(rawUrl)
if (!["http:", "https:"].includes(url.protocol)) return null
const hostname = url.hostname.toLowerCase()
const pathname = url.pathname || "/"
const search = url.search || ""
return `${url.protocol}//${hostname}${pathname}${search}`
} catch {
return null
}
}
function buildKey(normalizedUrl) {
const hash = crypto.createHash("sha1").update(normalizedUrl).digest("hex")
return `${IMAGE_KEY_PREFIX}${hash}`
}
async function cacheImageFromUrl(rawUrl, { ttlSeconds = DEFAULT_TTL_SECONDS } = {}) {
const normalized = normalizeUrl(rawUrl)
if (!normalized) return null
const key = buildKey(normalized)
const redis = createRedisClient()
try {
const cached = await redis.get(key)
if (cached) return { key: key.replace(IMAGE_KEY_PREFIX, ""), cached: true }
const response = await axios.get(normalized, {
responseType: "arraybuffer",
timeout: 15000,
maxContentLength: MAX_IMAGE_BYTES,
maxBodyLength: MAX_IMAGE_BYTES,
validateStatus: (status) => status >= 200 && status < 300,
})
const contentType = String(response.headers?.["content-type"] || "")
if (!contentType.startsWith("image/")) return null
const buffer = Buffer.from(response.data || [])
if (!buffer.length || buffer.length > MAX_IMAGE_BYTES) return null
const payload = JSON.stringify({
ct: contentType,
b64: buffer.toString("base64"),
})
await redis.set(key, payload, "EX", Number(ttlSeconds) || DEFAULT_TTL_SECONDS)
return { key: key.replace(IMAGE_KEY_PREFIX, ""), cached: false }
} catch {
return null
} finally {}
}
async function getCachedImageByKey(hashKey) {
if (!hashKey || typeof hashKey !== "string") return null
const redis = createRedisClient()
const key = `${IMAGE_KEY_PREFIX}${hashKey}`
try {
const raw = await redis.get(key)
if (!raw) return null
const parsed = JSON.parse(raw)
if (!parsed?.ct || !parsed?.b64) return null
return { contentType: parsed.ct, buffer: Buffer.from(parsed.b64, "base64") }
} catch {
return null
} finally {}
}
module.exports = {
cacheImageFromUrl,
getCachedImageByKey,
}