| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>AWS S3 文件夹下载工具</title>
- <script src="https://sdk.amazonaws.com/js/aws-sdk-2.938.0.min.js"></script>
- <style>
- * {
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- }
-
- body {
- background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
- color: #333;
- line-height: 1.6;
- padding: 20px;
- min-height: 100vh;
- }
-
- .container {
- max-width: 900px;
- margin: 0 auto;
- background-color: rgba(255, 255, 255, 0.95);
- border-radius: 12px;
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
- padding: 30px;
- }
-
- h1 {
- text-align: center;
- margin-bottom: 25px;
- color: #2c3e50;
- font-weight: 600;
- }
-
- .description {
- text-align: center;
- margin-bottom: 30px;
- color: #555;
- font-size: 16px;
- }
-
- .input-section {
- display: flex;
- flex-direction: column;
- gap: 15px;
- margin-bottom: 25px;
- }
-
- .folder-input {
- display: flex;
- gap: 10px;
- }
-
- input[type="text"] {
- flex: 1;
- padding: 12px 15px;
- border: 1px solid #ddd;
- border-radius: 6px;
- font-size: 16px;
- transition: border 0.3s;
- }
-
- input[type="text"]:focus {
- border-color: #3498db;
- outline: none;
- box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
- }
-
- button {
- padding: 12px 20px;
- background-color: #3498db;
- color: white;
- border: none;
- border-radius: 6px;
- cursor: pointer;
- font-size: 16px;
- font-weight: 500;
- transition: background-color 0.3s, transform 0.2s;
- }
-
- button:hover {
- background-color: #2980b9;
- transform: translateY(-2px);
- }
-
- button:active {
- transform: translateY(0);
- }
-
- button:disabled {
- background-color: #95a5a6;
- cursor: not-allowed;
- transform: none;
- }
-
- .action-buttons {
- display: flex;
- gap: 10px;
- justify-content: center;
- margin-top: 20px;
- }
-
- .download-all {
- background-color: #2ecc71;
- }
-
- .download-all:hover {
- background-color: #27ae60;
- }
-
- .clear {
- background-color: #e74c3c;
- }
-
- .clear:hover {
- background-color: #c0392b;
- }
-
- .folders-list {
- margin-top: 25px;
- }
-
- .folder-item {
- background-color: #f8f9fa;
- border-radius: 8px;
- padding: 15px;
- margin-bottom: 15px;
- border-left: 4px solid #3498db;
- box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);
- }
-
- .folder-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 10px;
- }
-
- .folder-name {
- font-weight: 600;
- font-size: 18px;
- color: #2c3e50;
- }
-
- .file-list {
- margin-top: 10px;
- max-height: 0;
- overflow: hidden;
- transition: max-height 0.3s ease;
- }
-
- .file-list.expanded {
- max-height: 500px;
- }
-
- .file-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 10px;
- border-bottom: 1px solid #eee;
- }
-
- .file-item:last-child {
- border-bottom: none;
- }
-
- .file-name {
- flex: 1;
- }
-
- .file-size {
- color: #7f8c8d;
- font-size: 14px;
- margin-right: 15px;
- }
-
- .progress-bar {
- height: 6px;
- background-color: #ecf0f1;
- border-radius: 3px;
- margin-top: 8px;
- overflow: hidden;
- }
-
- .progress {
- height: 100%;
- background-color: #2ecc71;
- width: 0%;
- transition: width 0.3s;
- }
-
- .status {
- margin-top: 10px;
- font-size: 14px;
- color: #7f8c8d;
- }
-
- .completed {
- color: #2ecc71;
- }
-
- .error {
- color: #e74c3c;
- }
-
- .expand-btn {
- background: none;
- border: none;
- color: #3498db;
- cursor: pointer;
- font-size: 14px;
- padding: 5px 10px;
- }
-
- .no-folders {
- text-align: center;
- padding: 20px;
- color: #7f8c8d;
- font-style: italic;
- }
-
- .summary {
- margin-top: 20px;
- padding: 15px;
- background-color: #f8f9fa;
- border-radius: 8px;
- text-align: center;
- }
-
- @media (max-width: 600px) {
- .container {
- padding: 15px;
- }
-
- .folder-input {
- flex-direction: column;
- }
-
- .action-buttons {
- flex-direction: column;
- }
- }
- </style>
- </head>
- <body>
- <div class="container">
- <h1>AWS S3 文件夹下载工具</h1>
- <p class="description">输入文件夹路径,批量下载S3存储桶中的文件</p>
-
- <div class="input-section">
- <div class="folder-input">
- <input type="text" id="folderPath" placeholder="输入文件夹路径,例如: documents/reports/">
- <button id="addFolder">添加文件夹</button>
- </div>
- </div>
-
- <div class="action-buttons">
- <button id="downloadAll" class="download-all" disabled>全部下载</button>
- <button id="clearAll" class="clear">清空列表</button>
- </div>
-
- <div class="folders-list" id="foldersList">
- <div class="no-folders" id="noFoldersMessage">暂无文件夹,请添加文件夹路径</div>
- </div>
-
- <div class="summary" id="summary" style="display: none;">
- <div id="summaryText"></div>
- <div class="progress-bar">
- <div class="progress" id="overallProgress"></div>
- </div>
- </div>
- </div>
- <script>
- // AWS S3配置 - 请替换为您的实际配置
- const awsConfig = {
- region: 'cn-northwest-1'; //設置區域
- credentials: {
- accessKeyId: 'AKIATLPEDU37QV5CHLMH',
- secretAccessKey: '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'
- }
- };
-
- // 初始化S3客户端
- AWS.config.update(awsConfig);
- const s3 = new AWS.S3();
- const bucketName = 'ccrb'; // 您的存储桶名称
-
- // 存储文件夹数据
- let folders = ["411325201408040121","440104201312168333","440604201604210182","440604201512070039","440305201207071553","411503201201270459","440305201208211554","440114200712210420","421181200905305829","440305201006230060","H10192830","36042320100705006x","445122201001140920","440304200911101831","430702201004080100","441422201507140015","440304201608261810","441523201403146764","440104201504076716","440307201308140020","150422201211302119","350305200910182398","511322201008261122","445381201002020040","440304201101091816","440303201409054513","42122420130806584x","460107201410300425","440881201301096726","610722201212110641","450422201211302119","362322200907013620","44051220171107001X","440306201602225117","440307201409126519","430724201606220113","440306201310090433","411302201312150399","511322200809261312","411622200810091026","610702200911200929","440304200909272041","420116201003150422","410823201310070203","440303201310117764","500234201312302184","442000201402214621","442000201501254634","360735201306230513","411423201212280230","440104201204136529","44010420120116652X","440303201402288528","42102320130828245X","441422201211232631","42130220130426841X","440305201712231239","440305201709281235","440305201802090142","42000201402214621","44158120140208901X ","360731201310017613","430903201207130312","441781201111186996","430621201205050219","445281201407234391","430902201403030136","440305201407099016","440104201712246118","440103201709182719","46902820090622071X","441623201003023410","420103201008124416","421181201008213531","360721200911050059","370212201509262531","44030620151119081","411626200905066531","420801201004044019","440305200902141512","44030620106094333","441324200809181616","430321201005030153","430981200909240254","44030520151214005X","440306201304270518","441501200909275018","810000201001300331","440306200911291017","440306201402061015","43020220140417","441424201210175121","140624198706080534","441622201210056665","440304201704101825","440105201212115425","44030520100106784X","440307201001193128","441623200903025521","440306200909140527","44182720081125610X","440303201404088132","440305201807160170","140105201903120222","321088200910315250","341225200911208210","522425201008120095","44030520170807121X","440106201706223326","445281201202283018","441501201203214090","431122201007290246","44158120100211882X","640221201606255730","360921201401250312","440303201312027340","440304201408050090","440183201410162416","440305201407081553","421023201412251012","440303201305232717","43052120090518017X","441827201006070112","44030620090905002X","450821200909241021","440304200910291821","440306201302193918","450703201211132751","440306201211192012","220102201703285218","430104201002160078","45132320090321511X","420506200910181817","441323201405100013","440304201406090099","43022320130908019X","440104201606306527","440104201510206513","44030620110627071X","440304201106171831","61072120160107262X","430621201411020045","440306201512280913","412824201008124416","440305201708241223","440981201110064612","440305201112311534","440304201205267417","440307201608200339","42088120160704583X","440306201006094333","440305201402120015","451021201003091399","440511201411174750","440113201503062116","411330200911181833","440306200911271315","440304201205150059","500116201112254351","44528120120718043x","360924201402152828","421281201310080720","450481201312201640","440304201206297618","440307201703220514","620702201310141210","430581201312210477","440515201403081939","430626201503160493","440823201406284914","431022201312050016","44030420140701635X","440307201401281821","445222201404092922","440306201402184568","440307201011162819","441702200410231726","430527200409116361","350824201402054614","44030420131008185X","361128201607072090","510824200811185063","440282200906022200","440305201006180032","440104201704116938","440103201508217815","44030620160514091X","430421201010050592","440305201005081518","442000201501254645","440304201209171818","440306201906213692","440513201004211609","440513201008055084","440511201304296744","440514200405200822","430481201602270229","440303200810068546","441302201211082038","360722201210162112","370283201308061552","441502201306061126","445224201209195160","44522420130321572X","360982201407026816","440104201504176514","430528201310220231","440305201312207811","440303201604298118","445281201209030830","620622201007295217","511324200911243551","430722201001160178","429004200403151355","440305201304051589","440233200809268017","440304201611031813","511011200910065062","421381201410289594","411622201503206510","44030620151119081X","340506201510100517","440604201508210078","440604201409070022","440604201608240194","431025201407220224","430223201408230106","50010120140112062X","440823201508132014","440305200901053414","230306200911094057","440305201005063416","620102201006175341","421024201001060824","440514201403174944","440303200908262726","43062601503160493","440104201204056713","445222201609115235","44060420161206017X","440604201604130019","440604201708210320","440514201412212359","411328201306280019","440306201212053911","420321201208120018","42900420040315","230183200910011311","411421200910280230","610125201004203516","430523201008120238","431102200901080016","340104200905039010","220102200811273316","430722201109200231","341003201205182610","511381201202223496","18923782659","440306201402011018","440307201606270050","440305201109211532","421081201203152150","441622201112087417","440304200908274627","621124200907122372","440981200912054630","44030420160715006X","23011020190710082X","421125201506070026","441427201409201517","440306201409193547","44030320140131451X","210204201003037033","445281200909171074","440304201005316317","440305200412151224","440103201805252422","440304201407017619","440304201310167610","440304201211281848","420822200311113720","430102201501120094","440103201606057413","440513200911033039","450923200912257479","440305201603241534","440511201105116210","440304201510221845","440304201510301861","440606201503270330","440304201703110041","371521201612040025","440303201405088521","440305201309278221","411102201006120099","440303200909164511","441424201007260320","441223200911252626","411622201606131515","421124201607031011","CAN440118050505","440304200912257133","371722200912033866","450921201211260415","440306201607080834","441203201510191219","430626200310030022","440402201308229176"];
- folders = ["411325201408040121"];
- // DOM元素
- const folderPathInput = document.getElementById('folderPath');
- const addFolderBtn = document.getElementById('addFolder');
- const downloadAllBtn = document.getElementById('downloadAll');
- const clearAllBtn = document.getElementById('clearAll');
- const foldersList = document.getElementById('foldersList');
- const noFoldersMessage = document.getElementById('noFoldersMessage');
- const summary = document.getElementById('summary');
- const summaryText = document.getElementById('summaryText');
- const overallProgress = document.getElementById('overallProgress');
-
- // 添加文件夹
- addFolderBtn.addEventListener('click', () => {
- const folderPath = folderPathInput.value.trim();
- if (!folderPath) {
- alert('请输入文件夹路径');
- return;
- }
-
- // 确保路径以斜杠结尾
- const normalizedPath = folderPath.endsWith('/') ? folderPath : folderPath + '/';
-
- // 检查是否已存在
- if (folders.some(f => f.path === normalizedPath)) {
- alert('该文件夹已存在列表中');
- return;
- }
-
- // 添加到文件夹列表
- const folder = {
- id: Date.now().toString(),
- path: normalizedPath,
- files: [],
- status: 'pending', // pending, loading, ready, downloading, completed, error
- expanded: false
- };
-
- folders.push(folder);
- renderFolders();
- folderPathInput.value = '';
-
- // 获取文件夹中的文件列表
- loadFolderFiles(folder);
- });
-
- // 加载文件夹中的文件
- async function loadFolderFiles(folder) {
- folder.status = 'loading';
- updateFolderUI(folder);
-
- try {
- const params = {
- Bucket: bucketName,
- Prefix: folder.path
- };
-
- const data = await s3.listObjectsV2(params).promise();
-
- // 过滤掉文件夹本身(如果存在)
- folder.files = data.Contents.filter(item =>
- item.Key !== folder.path
- ).map(item => ({
- key: item.Key,
- name: item.Key.replace(folder.path, ''),
- size: formatFileSize(item.Size),
- status: 'pending',
- progress: 0,
- url: null
- }));
-
- folder.status = folder.files.length > 0 ? 'ready' : 'empty';
- updateFolderUI(folder);
- updateDownloadButtonState();
- } catch (error) {
- console.error('加载文件列表失败:', error);
- folder.status = 'error';
- folder.error = error.message;
- updateFolderUI(folder);
- }
- }
-
- // 下载单个文件
- async function downloadFile(file, folder) {
- file.status = 'downloading';
- updateFolderUI(folder);
-
- try {
- const params = {
- Bucket: bucketName,
- Key: file.key
- };
-
- // 获取预签名URL
- const url = await s3.getSignedUrlPromise('getObject', params);
- file.url = url;
-
- // 创建隐藏的下载链接并触发点击
- const a = document.createElement('a');
- a.href = url;
- a.download = file.name;
- a.style.display = 'none';
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
-
- file.status = 'completed';
- file.progress = 100;
- updateFolderUI(folder);
-
- // 检查文件夹是否全部完成
- checkFolderCompletion(folder);
- } catch (error) {
- console.error('下载文件失败:', error);
- file.status = 'error';
- file.error = error.message;
- updateFolderUI(folder);
- }
- }
-
- // 下载文件夹中的所有文件
- async function downloadFolder(folder) {
- folder.status = 'downloading';
- updateFolderUI(folder);
-
- for (const file of folder.files) {
- if (file.status !== 'completed') {
- await downloadFile(file, folder);
- // 添加延迟以避免请求过于频繁
- await new Promise(resolve => setTimeout(resolve, 500));
- }
- }
- }
-
- // 下载所有文件夹
- async function downloadAllFolders() {
- summary.style.display = 'block';
- const totalFiles = folders.reduce((sum, folder) => sum + folder.files.length, 0);
- let completedFiles = 0;
-
- summaryText.textContent = `正在下载 ${totalFiles} 个文件...`;
-
- for (const folder of folders) {
- if (folder.status === 'ready' || folder.status === 'partial') {
- folder.status = 'downloading';
- updateFolderUI(folder);
-
- for (const file of folder.files) {
- if (file.status !== 'completed') {
- await downloadFile(file, folder);
- completedFiles++;
-
- // 更新总进度
- const progress = (completedFiles / totalFiles) * 100;
- overallProgress.style.width = `${progress}%`;
- summaryText.textContent = `已下载 ${completedFiles}/${totalFiles} 个文件 (${Math.round(progress)}%)`;
-
- // 添加延迟以避免请求过于频繁
- await new Promise(resolve => setTimeout(resolve, 500));
- }
- }
- }
- }
-
- summaryText.textContent = `下载完成!共下载 ${completedFiles} 个文件`;
- }
-
- // 检查文件夹是否全部完成
- function checkFolderCompletion(folder) {
- const completedCount = folder.files.filter(f => f.status === 'completed').length;
- const totalCount = folder.files.length;
-
- if (completedCount === totalCount) {
- folder.status = 'completed';
- } else if (completedCount > 0) {
- folder.status = 'partial';
- }
-
- updateFolderUI(folder);
- updateDownloadButtonState();
- }
-
- // 更新文件夹UI
- function updateFolderUI(folder) {
- const folderElement = document.getElementById(`folder-${folder.id}`);
- if (!folderElement) return;
-
- const statusElement = folderElement.querySelector('.status');
- const progressBar = folderElement.querySelector('.progress');
- const downloadBtn = folderElement.querySelector('.download-btn');
- const fileList = folderElement.querySelector('.file-list');
-
- // 更新状态
- let statusText = '';
- let statusClass = '';
-
- switch (folder.status) {
- case 'pending':
- statusText = '等待中...';
- break;
- case 'loading':
- statusText = '加载文件列表中...';
- break;
- case 'ready':
- statusText = `准备下载 (${folder.files.length} 个文件)`;
- statusClass = 'completed';
- downloadBtn.disabled = false;
- break;
- case 'empty':
- statusText = '文件夹为空';
- break;
- case 'downloading':
- const completed = folder.files.filter(f => f.status === 'completed').length;
- statusText = `下载中... ${completed}/${folder.files.length}`;
- downloadBtn.disabled = true;
- break;
- case 'completed':
- statusText = `下载完成 (${folder.files.length} 个文件)`;
- statusClass = 'completed';
- downloadBtn.disabled = true;
- break;
- case 'partial':
- const completedPartial = folder.files.filter(f => f.status === 'completed').length;
- statusText = `部分完成 (${completedPartial}/${folder.files.length})`;
- downloadBtn.disabled = false;
- break;
- case 'error':
- statusText = `错误: ${folder.error}`;
- statusClass = 'error';
- break;
- }
-
- statusElement.textContent = statusText;
- statusElement.className = `status ${statusClass}`;
-
- // 更新进度条
- if (folder.files.length > 0) {
- const completedFiles = folder.files.filter(f => f.status === 'completed').length;
- const progress = (completedFiles / folder.files.length) * 100;
- progressBar.style.width = `${progress}%`;
- }
-
- // 更新文件列表
- if (folder.expanded) {
- fileList.classList.add('expanded');
- renderFileList(folder, fileList);
- }
- }
-
- // 渲染文件列表
- function renderFileList(folder, container) {
- container.innerHTML = '';
-
- folder.files.forEach(file => {
- const fileItem = document.createElement('div');
- fileItem.className = 'file-item';
-
- let fileStatus = '';
- let fileStatusClass = '';
-
- switch (file.status) {
- case 'pending':
- fileStatus = '等待下载';
- break;
- case 'downloading':
- fileStatus = '下载中...';
- break;
- case 'completed':
- fileStatus = '已完成';
- fileStatusClass = 'completed';
- break;
- case 'error':
- fileStatus = `错误: ${file.error}`;
- fileStatusClass = 'error';
- break;
- }
-
- fileItem.innerHTML = `
- <div class="file-name">${file.name}</div>
- <div class="file-size">${file.size}</div>
- <div class="status ${fileStatusClass}">${fileStatus}</div>
- `;
-
- container.appendChild(fileItem);
- });
- }
-
- // 渲染文件夹列表
- function renderFolders() {
- if (folders.length === 0) {
- noFoldersMessage.style.display = 'block';
- foldersList.innerHTML = '';
- foldersList.appendChild(noFoldersMessage);
- return;
- }
-
- noFoldersMessage.style.display = 'none';
- foldersList.innerHTML = '';
-
- folders.forEach(folder => {
- const folderElement = document.createElement('div');
- folderElement.className = 'folder-item';
- folderElement.id = `folder-${folder.id}`;
-
- folderElement.innerHTML = `
- <div class="folder-header">
- <div class="folder-name">${folder.path}</div>
- <div>
- <button class="expand-btn">${folder.expanded ? '收起' : '展开'}</button>
- <button class="download-btn" ${folder.status !== 'ready' && folder.status !== 'partial' ? 'disabled' : ''}>下载文件夹</button>
- </div>
- </div>
- <div class="progress-bar">
- <div class="progress"></div>
- </div>
- <div class="status">等待中...</div>
- <div class="file-list"></div>
- `;
-
- // 添加事件监听器
- const expandBtn = folderElement.querySelector('.expand-btn');
- const downloadBtn = folderElement.querySelector('.download-btn');
- const fileList = folderElement.querySelector('.file-list');
-
- expandBtn.addEventListener('click', () => {
- folder.expanded = !folder.expanded;
- if (folder.expanded) {
- fileList.classList.add('expanded');
- renderFileList(folder, fileList);
- } else {
- fileList.classList.remove('expanded');
- }
- expandBtn.textContent = folder.expanded ? '收起' : '展开';
- });
-
- downloadBtn.addEventListener('click', () => {
- downloadFolder(folder);
- });
-
- foldersList.appendChild(folderElement);
- updateFolderUI(folder);
- });
- }
-
- // 更新下载按钮状态
- function updateDownloadButtonState() {
- const hasReadyFolders = folders.some(f =>
- f.status === 'ready' || f.status === 'partial'
- );
- downloadAllBtn.disabled = !hasReadyFolders;
- }
-
- // 格式化文件大小
- function formatFileSize(bytes) {
- if (bytes === 0) return '0 B';
-
- const k = 1024;
- const sizes = ['B', 'KB', 'MB', 'GB'];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
-
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
- }
-
- // 事件监听器
- downloadAllBtn.addEventListener('click', downloadAllFolders);
-
- clearAllBtn.addEventListener('click', () => {
- folders = [];
- renderFolders();
- summary.style.display = 'none';
- updateDownloadButtonState();
- });
-
- folderPathInput.addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- addFolderBtn.click();
- }
- });
-
- // 初始化
- renderFolders();
- </script>
- </body>
- </html>
|