refactor: Business logic and dependencies updates
- 核心服务代码更新 (db, server, auth, proxy) - Agent 相关模块更新 (mindspace, experience) - 前端组件和 hooks 更新 - 数据库 schema 更新 - 依赖版本更新
This commit is contained in:
+52
-1
@@ -32,7 +32,7 @@ const ALLOWED_EXTENSIONS = new Map([
|
||||
['.html', 'text/html'],
|
||||
['.htm', 'text/html'],
|
||||
]);
|
||||
const MAX_IMAGE_UPLOAD_BYTES = 1536 * 1024;
|
||||
const MAX_IMAGE_UPLOAD_BYTES = 2 * 1024 * 1024;
|
||||
const PUBLIC_TEMP_IMAGE_DIR = '.tmp-images';
|
||||
|
||||
const PUBLIC_IMAGE_EXTENSIONS = new Map([
|
||||
@@ -65,6 +65,54 @@ function expectedMimeType(filename) {
|
||||
return ALLOWED_EXTENSIONS.get(path.extname(filename).toLowerCase()) ?? null;
|
||||
}
|
||||
|
||||
function extractImageDimensions(buffer, mimeType) {
|
||||
if (mimeType === 'image/png' && buffer.length >= 24) {
|
||||
const width = buffer.readUInt32BE(16);
|
||||
const height = buffer.readUInt32BE(20);
|
||||
return { width, height };
|
||||
}
|
||||
if (mimeType === 'image/jpeg' && buffer.length >= 2) {
|
||||
let offset = 2;
|
||||
while (offset < buffer.length - 9) {
|
||||
if (buffer[offset] !== 0xff) break;
|
||||
const marker = buffer[offset + 1];
|
||||
if (marker === 0xd9) break;
|
||||
if (marker >= 0xd0 && marker <= 0xd8) {
|
||||
offset += 2;
|
||||
continue;
|
||||
}
|
||||
const segmentLength = buffer.readUInt16BE(offset + 2);
|
||||
if (marker === 0xc0 || marker === 0xc1 || marker === 0xc2) {
|
||||
const height = buffer.readUInt16BE(offset + 5);
|
||||
const width = buffer.readUInt16BE(offset + 7);
|
||||
return { width, height };
|
||||
}
|
||||
offset += segmentLength + 2;
|
||||
}
|
||||
}
|
||||
if (mimeType === 'image/webp' && buffer.length >= 30) {
|
||||
const width = buffer.readUInt32LE(24) + 1;
|
||||
const height = buffer.readUInt32LE(28) + 1;
|
||||
return { width, height };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateImagePixels(buffer, mimeType) {
|
||||
if (!mimeType?.startsWith('image/')) return null;
|
||||
const dims = extractImageDimensions(buffer, mimeType);
|
||||
if (!dims) return { ok: true };
|
||||
const { width, height } = dims;
|
||||
const megapixels = (width * height) / 1_000_000;
|
||||
if (megapixels > 25) {
|
||||
throw Object.assign(
|
||||
new Error(`图片像素超过限制(${megapixels.toFixed(1)}MP > 25MP)`),
|
||||
{ code: 'image_pixel_exceeded' }
|
||||
);
|
||||
}
|
||||
return { ok: true, width, height };
|
||||
}
|
||||
|
||||
function detectMimeType(buffer, filename) {
|
||||
const head = buffer.subarray(0, 256).toString('utf8').trimStart().toLowerCase();
|
||||
if (head.startsWith('<!doctype html') || head.startsWith('<html')) return 'text/html';
|
||||
@@ -386,6 +434,9 @@ export function createAssetService(pool, options = {}) {
|
||||
if (detectedMimeType.startsWith('image/') && buffer.length > MAX_IMAGE_UPLOAD_BYTES) {
|
||||
throw Object.assign(new Error('图片文件超过单文件大小限制'), { code: 'file_too_large' });
|
||||
}
|
||||
if (detectedMimeType.startsWith('image/')) {
|
||||
validateImagePixels(buffer, detectedMimeType);
|
||||
}
|
||||
|
||||
const target = absoluteStoragePath(upload.temporary_storage_key);
|
||||
await fs.mkdir(path.dirname(target), { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user