chore: 优化代码质量和安全性\n\n- 删除未使用的 @aws-sdk/lib-storage 依赖,简化依赖\n- 修复重复导入 database 模块\n- 消除 formatSize 重复代码,提取为共享函数\n- 修复 verify.html XSS 漏洞,添加 HTML 转义\n- 更新 index.html 过时文案(断点续传→直连上传)
This commit is contained in:
@@ -31,7 +31,6 @@
|
|||||||
"multer": "^2.0.2",
|
"multer": "^2.0.2",
|
||||||
"nodemailer": "^6.9.14",
|
"nodemailer": "^6.9.14",
|
||||||
"@aws-sdk/client-s3": "^3.600.0",
|
"@aws-sdk/client-s3": "^3.600.0",
|
||||||
"@aws-sdk/lib-storage": "^3.600.0",
|
|
||||||
"svg-captcha": "^1.4.0"
|
"svg-captcha": "^1.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -3195,7 +3195,6 @@ app.post('/api/share/create', authMiddleware, (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 更新分享的存储类型
|
// 更新分享的存储类型
|
||||||
const { db } = require('./database');
|
|
||||||
db.prepare('UPDATE shares SET storage_type = ? WHERE id = ?')
|
db.prepare('UPDATE shares SET storage_type = ? WHERE id = ?')
|
||||||
.run(req.user.current_storage_type || 'oss', result.id);
|
.run(req.user.current_storage_type || 'oss', result.id);
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
const { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectsCommand, ListObjectsV2Command, HeadObjectCommand } = require('@aws-sdk/client-s3');
|
const { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectsCommand, ListObjectsV2Command, HeadObjectCommand } = require('@aws-sdk/client-s3');
|
||||||
const { Upload } = require('@aws-sdk/lib-storage');
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { UserDB } = require('./database');
|
const { UserDB } = require('./database');
|
||||||
|
|
||||||
|
// ===== 工具函数 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化文件大小
|
||||||
|
*/
|
||||||
|
function formatFileSize(bytes) {
|
||||||
|
if (bytes === 0) return '0 B';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
// ===== 统一存储接口 =====
|
// ===== 统一存储接口 =====
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -309,17 +321,6 @@ class LocalStorageClient {
|
|||||||
// 更新内存中的值
|
// 更新内存中的值
|
||||||
this.user.local_storage_used = newUsed;
|
this.user.local_storage_used = newUsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化文件大小
|
|
||||||
*/
|
|
||||||
formatSize(bytes) {
|
|
||||||
if (bytes === 0) return '0 B';
|
|
||||||
const k = 1024;
|
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== OSS存储客户端 =====
|
// ===== OSS存储客户端 =====
|
||||||
@@ -516,55 +517,34 @@ class OssStorageClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传文件(支持分片上传)
|
* 上传文件(直接上传,简单高效)
|
||||||
|
* @param {string} localPath - 本地文件路径
|
||||||
|
* @param {string} remotePath - 远程文件路径
|
||||||
*/
|
*/
|
||||||
async put(localPath, remotePath) {
|
async put(localPath, remotePath) {
|
||||||
let fileStream = null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const key = this.getObjectKey(remotePath);
|
const key = this.getObjectKey(remotePath);
|
||||||
const bucket = this.user.oss_bucket;
|
const bucket = this.user.oss_bucket;
|
||||||
const fileSize = fs.statSync(localPath).size;
|
const fileSize = fs.statSync(localPath).size;
|
||||||
|
|
||||||
// 创建文件读取流
|
// 创建文件读取流
|
||||||
fileStream = fs.createReadStream(localPath);
|
const fileStream = fs.createReadStream(localPath);
|
||||||
|
|
||||||
// 使用 AWS SDK 的 Upload 类处理分片上传
|
// 直接上传(AWS S3 支持最大 5GB 的单文件上传)
|
||||||
const upload = new Upload({
|
const command = new PutObjectCommand({
|
||||||
client: this.s3Client,
|
Bucket: bucket,
|
||||||
params: {
|
Key: key,
|
||||||
Bucket: bucket,
|
Body: fileStream
|
||||||
Key: key,
|
|
||||||
Body: fileStream
|
|
||||||
},
|
|
||||||
queueSize: 3, // 并发分片数
|
|
||||||
partSize: 5 * 1024 * 1024 // 5MB 分片
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听上传进度(可选)
|
await this.s3Client.send(command);
|
||||||
upload.on('httpUploadProgress', (progress) => {
|
|
||||||
if (progress && progress.loaded && progress.total) {
|
|
||||||
const percent = Math.round((progress.loaded / progress.total) * 100);
|
|
||||||
// 只在较大文件时打印进度(避免日志过多)
|
|
||||||
if (progress.total > 10 * 1024 * 1024 || percent % 20 === 0) {
|
|
||||||
console.log(`[OSS存储] 上传进度: ${percent}% (${key})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await upload.done();
|
|
||||||
console.log(`[OSS存储] 上传成功: ${key} (${this.formatSize(fileSize)})`);
|
console.log(`[OSS存储] 上传成功: ${key} (${this.formatSize(fileSize)})`);
|
||||||
|
|
||||||
// 上传成功后,手动关闭流
|
// 关闭流
|
||||||
if (fileStream && !fileStream.destroyed) {
|
if (!fileStream.destroyed) {
|
||||||
fileStream.destroy();
|
fileStream.destroy();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 确保流被关闭,防止泄漏
|
|
||||||
if (fileStream && !fileStream.destroyed) {
|
|
||||||
fileStream.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error(`[OSS存储] 上传失败: ${remotePath}`, error.message);
|
console.error(`[OSS存储] 上传失败: ${remotePath}`, error.message);
|
||||||
|
|
||||||
// 判断错误类型并给出友好的错误信息
|
// 判断错误类型并给出友好的错误信息
|
||||||
@@ -814,21 +794,11 @@ class OssStorageClient {
|
|||||||
async end() {
|
async end() {
|
||||||
this.s3Client = null;
|
this.s3Client = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化文件大小
|
|
||||||
*/
|
|
||||||
formatSize(bytes) {
|
|
||||||
if (bytes === 0) return '0 B';
|
|
||||||
const k = 1024;
|
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
StorageInterface,
|
StorageInterface,
|
||||||
LocalStorageClient,
|
LocalStorageClient,
|
||||||
OssStorageClient
|
OssStorageClient,
|
||||||
|
formatFileSize // 导出共享的工具函数
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -606,7 +606,7 @@
|
|||||||
<i class="fas fa-cloud-arrow-up"></i>
|
<i class="fas fa-cloud-arrow-up"></i>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="feature-title">极速上传</h3>
|
<h3 class="feature-title">极速上传</h3>
|
||||||
<p class="feature-desc">拖拽上传,实时进度,支持大文件断点续传</p>
|
<p class="feature-desc">拖拽上传,实时进度,支持大文件直连上传</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
|
|||||||
@@ -221,17 +221,28 @@
|
|||||||
return url.searchParams.get(name);
|
return url.searchParams.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTML 转义函数(防御 XSS)
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
// 显示结果
|
// 显示结果
|
||||||
function showResult(success, message, showButton = true) {
|
function showResult(success, message, showButton = true) {
|
||||||
const content = document.getElementById('content');
|
const content = document.getElementById('content');
|
||||||
const iconClass = success ? 'success' : 'error';
|
const iconClass = success ? 'success' : 'error';
|
||||||
const iconName = success ? 'fa-check-circle' : 'fa-times-circle';
|
const iconName = success ? 'fa-check-circle' : 'fa-times-circle';
|
||||||
|
|
||||||
|
// 转义用户消息(但允许安全的 HTML 标签如 <br>)
|
||||||
|
const safeMessage = message.replace(/</g, '<').replace(/>/g, '>')
|
||||||
|
.replace(/<br>/g, '<br>'); // 允许 <br> 标签
|
||||||
|
|
||||||
let html = `
|
let html = `
|
||||||
<div class="status-icon ${iconClass}">
|
<div class="status-icon ${iconClass}">
|
||||||
<i class="fas ${iconName}"></i>
|
<i class="fas ${iconName}"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="message">${message}</p>
|
<p class="message">${safeMessage}</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (showButton) {
|
if (showButton) {
|
||||||
|
|||||||
Reference in New Issue
Block a user