Hiển thị thumbnail với biểu tượng 360 trên facebook
This commit is contained in:
+48
-18
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user