Hiển thị thumbnail với biểu tượng 360 trên facebook

This commit is contained in:
2026-06-09 09:32:13 +07:00
parent 826344eea1
commit 913867720f
4 changed files with 48 additions and 18 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+48 -18
View File
@@ -3,6 +3,7 @@ const multer = require('multer');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const sharp = require('sharp');
const User = require('../models/User');
const Asset = require('../models/Asset');
@@ -96,20 +97,13 @@ router.post('/scenes', protect, uploadSinglePanorama, async (req, res) => {
// Lấy tọa độ GPS gốc từ ảnh vừa upload trước khi nén/xử lý
const originalGPS = await getGPSCoordinates(tempFilePath);
// BACKGROUND PROCESSING: Thực hiện song song không chặn response
setImmediate(async () => {
try {
// 1. Resize to 8K
await resizeTo8K(tempFilePath, processedFilePath);
// 2. Inject GPS
await injectGPSCoordinates(processedFilePath, latitude, longitude);
// 3. Cleanup temp file
if (fs.existsSync(tempFilePath)) fs.unlinkSync(tempFilePath);
console.log(`Background processing finished for: ${processedFileName}`);
} catch (err) {
console.error(`Background Image processing failed: ${err.message}`);
}
});
// Thực hiện xử lý ảnh tuần tự để đảm bảo file được tạo thành công trước khi lưu DB
// 1. Resize to 8K (Sẽ convert từ DNG sang JPG nếu sharp hỗ trợ libraw, nếu không sẽ báo lỗi)
await resizeTo8K(tempFilePath, processedFilePath);
// 2. Inject GPS vào file JPG vừa tạo
await injectGPSCoordinates(processedFilePath, latitude, longitude);
// 3. Cleanup temp file (file gốc dng)
if (fs.existsSync(tempFilePath)) fs.unlinkSync(tempFilePath);
// 5. Save Asset to DB
const asset = new Asset({
@@ -151,17 +145,17 @@ router.post('/scenes', protect, uploadSinglePanorama, async (req, res) => {
});
await scene.save();
res.status(202).json({
res.status(201).json({
message: 'Scene created successfully',
scene
});
} catch (error) {
// Cleanup file if error occurs
// Dọn dẹp các file rác nếu quá trình xử lý thất bại
if (req.file && fs.existsSync(req.file.path)) {
try { fs.unlinkSync(req.file.path); } catch (e) {}
}
res.status(500).json({ message: error.message });
res.status(500).json({ message: "Xử lý ảnh 360 thất bại. Vui lòng đảm bảo bạn upload ảnh Panorama đã được stitch (JPEG). Chi tiết: " + error.message });
}
});
@@ -267,8 +261,9 @@ router.get('/share/:sceneId', optionalAuth, async (req, res) => {
<meta property="og:title" content="${scene.name}" />
<meta property="og:description" content="${scene.description || 'Khám phá tour 3D thực tế ảo sinh động'}" />
<meta property="og:image" content="${thumbUrl}" />
<meta property="og:image:secure_url" content="${thumbUrl}" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:height" content="1200" />
<meta property="og:image:type" content="image/jpeg" />
<meta property="og:url" content="${siteUrl}${req.originalUrl}" />
<meta property="og:type" content="website" />
@@ -496,6 +491,41 @@ router.get('/assets/view/:assetId', verifyReferer, optionalAuth, async (req, res
const resolvedPath = path.resolve(asset.filePath);
// Kiểm tra xem có cần chèn watermark 360° hay không (dành cho Social Bot hoặc yêu cầu thủ công)
const userAgent = req.headers['user-agent'] || '';
const isSocialBot = /facebookexternalhit|Facebot|ZaloBot|Twitterbot|Slackbot|LinkedInBot|Embedly/i.test(userAgent);
const wantWatermark = isSocialBot || req.query.watermark === 'true';
if (wantWatermark) {
const iconPath = path.join(__dirname, '../assets/static/360-badge.png');
if (fs.existsSync(iconPath)) {
try {
// Resize ảnh về kích thước vuông (1200x1200) trước khi chèn icon
// Việc này giúp icon 360 hiển thị rõ ràng và tỷ lệ hợp lý hơn
const imageBuffer = await sharp(resolvedPath)
.resize(1200, 1200, {
fit: 'cover'
})
.composite([{
input: iconPath,
gravity: 'center'
}])
.jpeg({ quality: 90 })
.toBuffer();
res.set({
'Content-Type': 'image/jpeg',
'Cache-Control': 'public, max-age=2592000',
'Content-Disposition': 'inline; filename="preview_360.jpg"'
});
return res.send(imageBuffer);
} catch (sharpError) {
console.error("[Sharp Error]:", sharpError.message);
// Nếu lỗi sharp, fallback xuống sendFile bình thường ở dưới
}
}
}
// Sử dụng res.sendFile để tối ưu hóa việc truyền tải file lớn và hỗ trợ Caching (ETag)
res.sendFile(resolvedPath, {
maxAge: 2592000000, // 30 ngày (tính bằng ms)
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.