开发者日记:2023年X月X日 星期X 武汉 阴
项目背景
今日正式启动客户的大文件传输系统项目,需求明确:支持20G文件/文件夹上传下载、跨平台(Windows/macOS/Linux)、全浏览器兼容(含IE8)、断点续传、保留文件夹层级结构。后端使用PHP + MySQL,存储用阿里云OSS,前端为Vue3 CLI + WebUploader/H5。客户强调高频文件夹上传场景,需极致优化用户体验。免费开源代码和7*24支持的压力依旧,但技术栈熟悉度较高,信心渐增。
技术选型与调整
-
前端框架
- Vue3 CLI:组件化开发,配合
@babel/polyfill
兼容IE8。 - WebUploader:核心上传组件,深度定制文件夹解析逻辑。
- H5 File API:现代浏览器备用方案,IE8回退到Flash上传。
- Vue3 CLI:组件化开发,配合
-
后端架构
- PHP 8.1:处理分片上传、MySQL元数据存储、OSS直传。
- MySQL 8.0:存储文件路径、分片状态、用户ID(InnoDB引擎保证事务)。
-
核心难点
- 高频文件夹上传:优化递归解析性能,避免前端卡顿。
- 断点续传稳定性:MySQL事务保证分片记录一致性。
- IE8兼容性:Flash上传需处理跨域问题(
crossdomain.xml
)。
前端代码实现(Vue3 + WebUploader)
1. 文件夹上传组件
// src/components/FolderUploader.vue
import { ref, onMounted } from 'vue';
import WebUploader from 'webuploader';
import 'webuploader/dist/webuploader.css';
export default {
setup() {
const uploader = ref(null);
const folderTree = ref([]);
onMounted(() => {
// 动态加载Flash(IE8兼容)
if (!WebUploader.Uploader.support('HTML5')) {
WebUploader.Uploader.register({
name: 'flash',
fn: () => '/assets/Uploader.swf'
});
}
uploader.value = new WebUploader.Uploader({
swf: '/assets/Uploader.swf',
server: '/api/upload.php',
chunked: true,
chunkSize: 4 * 1024 * 1024, // 4MB分片
formData: {
fileId: '',
relativePath: '' // 关键:记录文件相对路径
}
});
});
const triggerFolderInput = () => {
document.getElementById('folderInput').click();
};
// 递归解析文件夹(兼容现代浏览器)
const handleFolderSelect = (e) => {
const items = e.target.files;
if (items) {
const tree = [];
for (let i = 0; i < items.length; i++) {
const file = items[i];
const path = file.webkitRelativePath || file.name; // 相对路径
const segments = path.split('/');
// 构建树形结构
let currentLevel = tree;
segments.slice(0, -1).forEach(segment => {
let dir = currentLevel.find(item => item.name === segment);
if (!dir) {
dir = { name: segment, type: 'directory', children: [] };
currentLevel.push(dir);
}
currentLevel = dir.children;
});
// 添加文件节点
currentLevel.push({
name: segments.pop(),
type: 'file',
file: file,
relativePath: path
});
}
folderTree.value = tree;
uploadFolder(tree);
}
};
// 上传文件夹内容
const uploadFolder = (tree) => {
tree.forEach(node => {
if (node.type === 'directory') {
uploadFolder(node.children); // 递归上传子目录
} else {
const formData = uploader.value.option('formData');
formData.relativePath = node.relativePath;
uploader.value.addFile(node.file, node.relativePath);
}
});
uploader.value.upload(); // 触发上传
};
return { triggerFolderInput, folderTree };
}
};
2. IE8兼容性处理
后端代码实现(PHP + MySQL)
1. 分片上传接口
setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare("
INSERT INTO upload_progress (file_id, chunk_index, total_chunks, uploaded_at)
VALUES (?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE updated_at = NOW()
");
$stmt->execute([$fileId, $chunkIndex, $totalChunks]);
// 如果是最后一块,合并并上传OSS
if ($chunkIndex === $totalChunks - 1) {
$finalPath = "{$tempDir}/final_file";
$fp = fopen($finalPath, 'wb');
for ($i = 0; $i < $totalChunks; $i++) {
$chunk = file_get_contents("{$tempDir}/chunk_{$i}");
fwrite($fp, $chunk);
}
fclose($fp);
// 上传OSS(需引入阿里云OSS SDK)
require_once 'oss-sdk/autoload.php';
$ossClient = new OSS\OssClient($OSS_KEY, $OSS_SECRET, $OSS_ENDPOINT);
$ossClient->putObject($OSS_BUCKET, "uploads/{$fileId}", $finalPath);
// 清理临时文件
array_map('unlink', glob("{$tempDir}/*"));
rmdir($tempDir);
// 保存文件元数据到MySQL
$metaStmt = $pdo->prepare("
INSERT INTO file_metadata (file_id, relative_path, size, uploaded_at)
VALUES (?, ?, ?, NOW())
");
$metaStmt->execute([$fileId, $relativePath, filesize($finalPath)]);
}
echo json_encode(['success' => true, 'fileId' => $fileId]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
?>
2. MySQL表结构
-- 文件分片进度表
CREATE TABLE `upload_progress` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`file_id` varchar(64) NOT NULL COMMENT '文件唯一ID',
`chunk_index` int(11) NOT NULL COMMENT '分片索引',
`total_chunks` int(11) NOT NULL COMMENT '总分片数',
`uploaded_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_file_chunk` (`file_id`, `chunk_index`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 文件元数据表
CREATE TABLE `file_metadata` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`file_id` varchar(64) NOT NULL COMMENT '文件唯一ID',
`relative_path` varchar(512) NOT NULL COMMENT '文件相对路径',
`size` bigint(20) NOT NULL COMMENT '文件大小(字节)',
`uploaded_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_file_path` (`file_id`, `relative_path`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
断点续传实现
1. 前端恢复逻辑
// 检查未完成上传
const checkResume = async (fileId) => {
const res = await fetch(`/api/progress.php?fileId=${fileId}`);
const data = await res.json();
if (data.completedChunks < data.totalChunks) {
uploader.value.option('formData', {
fileId,
chunk: data.completedChunks
});
uploader.value.upload();
}
};
// 本地存储fileId(即使浏览器关闭)
window.addEventListener('beforeunload', () => {
if (uploader.value.getFiles().length > 0) {
localStorage.setItem('lastUploadId', uploader.value.option('formData').fileId);
}
});
2. 后端进度查询
'fileId is required']);
exit;
}
try {
$pdo = new PDO("mysql:host={$DB_HOST};dbname={$DB_NAME}", $DB_USER, $DB_PASS);
$stmt = $pdo->prepare("
SELECT COUNT(*) AS completed, MAX(total_chunks) AS total
FROM upload_progress
WHERE file_id = ?
");
$stmt->execute([$fileId]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
echo json_encode([
'completedChunks' => (int)$result['completed'],
'totalChunks' => (int)$result['total']
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
?>
今日总结
- 进展:完成文件夹层级解析和PHP分片上传逻辑,IE8兼容性方案验证通过。
- 问题:
- WebUploader在IE8下对大文件夹性能较差,需优化DOM操作。
- MySQL事务需加强,避免分片记录残留。
- 明日计划:
- 实现OSS分片上传(避免本地合并临时文件)。
- 编写完整的开发文档和API接口说明。
求助:若有熟悉PHP文件处理或MySQL优化的高手,欢迎加入QQ群374992201指导!代码将完全开源回馈社区。
(注:实际项目需补充安全校验、OSS直传和性能监控代码。)
安装环境
PHP:7.2.14
调整块大小
NOSQL
NOSQL不需要任何配置,可以直接访问测试
SQL
创建数据库
您可以直接复制脚本进行创建
配置数据库连接
安装依赖
访问页面进行测试
数据表中的数据
效果预览
文件上传
文件刷新续传
支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传
文件夹上传
支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。
批量下载
支持文件批量下载
下载续传
文件下载支持离线保存进度信息,刷新页面,关闭页面,重启系统均不会丢失进度信息。
文件夹下载
支持下载文件夹,并保留层级结构,不打包,不占用服务器资源。
免费下载示例
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/weixin_52050572/article/details/151252137