压缩包网页预览(zip-html-preview)

zip-html-preview

项目介绍

这是一个基于 Spring Boot 开发的在线 ZIP 文件预览工具,主要用于预览 ZIP 压缩包中的 HTML 文件及其相关资源。

主要功能

  • 支持拖拽上传或点击选择多个 ZIP 文件
  • 自动解压并提取 ZIP 文件中的 HTML 文件
  • 在线预览 HTML 文件及其相关的 CSS、JavaScript 和图片等资源
  • 支持多文件批量处理

技术栈

  • 后端:Spring Boot 2.3.4
  • 前端:HTML5 + JavaScript
  • 构建工具:Maven

快速开始

环境要求

  • JDK 8+
  • Maven 3.x

配置说明

application.properties 中配置解压文件存储路径:

zip.extract.path=D:/unzip

运行步骤

  1. 克隆项目
git clone https://gitee.com/anxwefndu/zip-html-preview.git
  1. 进入项目目录
cd zip-html-preview/SpringBoot
  1. 编译打包
mvn clean package
  1. 运行项目
java -jar target/SpringBoot-1.0-SNAPSHOT-execute.jar
  1. 访问应用
    打开浏览器访问: http://localhost:8080

使用说明

  1. 打开网页后,可以通过拖拽或点击选择按钮上传 ZIP 文件
  2. 支持同时选择多个 ZIP 文件
  3. 上传完成后会自动显示可预览的 HTML 文件列表
  4. 点击文件名即可在新标签页中预览对应的 HTML 文件

源码下载

zip-html-preview

演示截图

1.系统首页
在这里插入图片描述

2.预览压缩包
在这里插入图片描述

3.预览页面1
在这里插入图片描述

4.预览页面2
在这里插入图片描述

核心源码

SpringBoot/src/main/resources/static/index.html

<!DOCTYPE html>
<html lang="zh-CN">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>zip-html-preview</title>
        <link rel="preconnect" href="https://fonts.googleapis.com">
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
        <link href="https://fonts.googleapis.com/css2?family=Pacifico&display=swap" rel="stylesheet">
        <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
        <script src="https://cdn.tailwindcss.com"></script>
        <script>
            tailwind.config = {
                theme: {
                    extend: {
                        colors: {
                            primary: '#4F46E5',
                            secondary: '#6366F1'
                        },
                        borderRadius: {
                            'none': '0px',
                            'sm': '2px',
                            DEFAULT: '4px',
                            'md': '8px',
                            'lg': '12px',
                            'xl': '16px',
                            '2xl': '20px',
                            '3xl': '24px',
                            'full': '9999px',
                            'button': '4px'
                        }
                    }
                }
            }
        </script>
        <style>
            body::-webkit-scrollbar {
                width: 0.5rem;
            }

            body::-webkit-scrollbar-track {
                background: #f1f1f1;
                border-radius: 0.5rem;
            }

            body::-webkit-scrollbar-thumb {
                background: #3498db;
                border-radius: 0.5rem;
            }

            body::-webkit-scrollbar-thumb:hover {
                background: #2980b9;
            }

            .drag-area {
                border: 2px dashed #E5E7EB;
                transition: all 0.3s ease;
            }

            .drag-area:hover {
                border-color: #4F46E5;
            }

            .file-input {
                display: none;
            }

            .button-hover {
                transition: transform 0.2s ease;
            }

            .button-hover:active {
                transform: translateY(2px);
            }
        </style>
    </head>

    <body class="bg-gray-50">
        <div class="w-[1200px] mx-auto">
            <nav class="h-16 bg-white shadow-sm flex items-center justify-between px-8">
                <div class="flex items-center space-x-2">
                    <span class="text-2xl font-['Pacifico'] text-primary">logo</span>
                    <span class="text-lg font-medium">zip-html-preview</span>
                </div>
                <button class="w-10 h-10 rounded-button flex items-center justify-center hover:bg-gray-100 transition-colors">
                    <i class="fas fa-sun text-gray-600"></i>
                </button>
            </nav>

            <main class="px-8 py-12">
                <div class="max-w-3xl mx-auto">
                    <div class="bg-white rounded-lg shadow-sm p-8">
                        <div id="dropArea" class="drag-area rounded-lg p-12 text-center">
                            <input type="file" id="fileInput" class="file-input" multiple accept=".zip">
                            <div class="space-y-4">
                                <div class="w-20 h-20 mx-auto bg-gray-50 rounded-full flex items-center justify-center">
                                    <i class="fas fa-cloud-upload-alt text-4xl text-gray-400"></i>
                                </div>
                                <div>
                                    <h3 class="text-lg font-medium">拖拽文件到这里,或</h3>
                                    <button class="mt-2 px-6 py-2 bg-primary text-white rounded-button hover:bg-primary/90 button-hover whitespace-nowrap">
                                        点击选择文件
                                    </button>
                                </div>
                                <p class="text-sm text-gray-500">
                                    支持的文件格式:ZIP
                                </p>
                            </div>
                        </div>

                        <div id="fileList" class="hidden mt-8 space-y-4">
                            <div class="text-lg font-medium mb-4">已选择的文件</div>
                            <div class="space-y-3"></div>
                            <div class="flex justify-end space-x-3 mt-6">
                                <button id="clearButton" class="px-6 py-2 text-gray-600 bg-gray-100 rounded-button hover:bg-gray-200 button-hover whitespace-nowrap">
                                    清空列表
                                </button>
                                <button id="previewButton" class="px-6 py-2 text-white bg-primary rounded-button hover:bg-primary/90 button-hover whitespace-nowrap">
                                    预览
                                </button>
                            </div>
                        </div>

                        <div id="previewLinks" class="hidden mt-8 space-y-4">
                            <div class="text-lg font-medium mb-4">可预览的文件</div>
                            <ul class="space-y-2" id="htmlFilesList">
                            </ul>
                        </div>
                    </div>

                    <div class="mt-12 bg-white rounded-lg shadow-sm p-8">
                        <h2 class="text-xl font-medium mb-6">使用说明</h2>
                        <div class="space-y-4 text-gray-600">
                            <div class="flex items-start space-x-3">
                                <div class="w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0 mt-0.5">
                                    <i class="fas fa-check text-sm text-primary"></i>
                                </div>
                                <p>支持拖拽上传或点击选择文件,可以一次选择多个ZIP文件</p>
                            </div>
                            <div class="flex items-start space-x-3">
                                <div class="w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0 mt-0.5">
                                    <i class="fas fa-check text-sm text-primary"></i>
                                </div>
                                <p>系统会自动解析ZIP文件中的HTML文件,并提供在线预览功能</p>
                            </div>
                            <div class="flex items-start space-x-3">
                                <div class="w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0 mt-0.5">
                                    <i class="fas fa-check text-sm text-primary"></i>
                                </div>
                                <p>支持预览ZIP包中的HTML文件及其相关的CSS、JavaScript和图片等资源</p>
                            </div>
                            <div class="flex items-start space-x-3">
                                <div class="w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0 mt-0.5">
                                    <i class="fas fa-check text-sm text-primary"></i>
                                </div>
                                <p>提供文件树视图,方便浏览ZIP包中的文件结构和快速切换不同的HTML文件</p>
                            </div>
                        </div>
                    </div>
                </div>
            </main>
        </div>

        <script>
            const dropArea = document.getElementById('dropArea');
            const fileInput = document.getElementById('fileInput');
            const fileList = document.getElementById('fileList');
            const previewButton = document.getElementById('previewButton');
            const clearButton = document.getElementById('clearButton');
            const previewLinks = document.getElementById('previewLinks');
            const htmlFilesList = document.getElementById('htmlFilesList');

            // 存储所有文件的数组
            let selectedFiles = [];

            // 点击拖拽区域触发文件选择
            dropArea.addEventListener('click', () => {
                fileInput.click();
            });

            // 拖拽进入时改变样式
            dropArea.addEventListener('dragover', (e) => {
                e.preventDefault();
                dropArea.classList.add('border-primary');
            });

            // 拖拽离开时恢复样式
            dropArea.addEventListener('dragleave', () => {
                dropArea.classList.remove('border-primary');
            });

            // 拖拽放下时处理文件
            dropArea.addEventListener('drop', (e) => {
                e.preventDefault();
                dropArea.classList.remove('border-primary');
                const files = e.dataTransfer.files;
                handleFiles(files);
            });

            // 文件输入框变化时处理文件
            fileInput.addEventListener('change', () => {
                const files = fileInput.files;
                handleFiles(files);
                fileInput.value = null;
            });

            // 处理文件并更新文件列表
            function handleFiles(files) {
                for (let i = 0; i < files.length; i++) {
                    const file = files[i];
                    if (!selectedFiles.some(f => f.name === file.name)) {
                        selectedFiles.push(file);
                    }
                }
                updateFileList();
            }

            // 更新文件列表 DOM
            function updateFileList() {
                if (selectedFiles.length > 0) {
                    fileList.classList.remove('hidden');
                } else {
                    fileList.classList.add('hidden');
                }

                const listContainer = fileList.querySelector('.space-y-3');
                listContainer.innerHTML = ''; // 清空旧的文件列表

                selectedFiles.forEach((file, index) => {
                    const fileType = file.type || 'text/plain';
                    const fileSize = formatFileSize(file.size);

                    const listItem = document.createElement('div');
                    listItem.className = 'flex items-center justify-between p-4 bg-gray-50 rounded-lg';

                    // 左侧:文件图标和信息
                    const leftSide = document.createElement('div');
                    leftSide.className = 'flex items-center space-x-3';

                    const icon = document.createElement('i');
                    icon.className = getIconClass(fileType);
                    icon.style.color = '#4F46E5'; // 设置颜色

                    const fileInfo = document.createElement('div');
                    fileInfo.innerHTML = `
                      <div class="font-medium">${escapeHTML(file.name)}</div>
                      <div class="text-sm text-gray-500">${fileSize}</div>
                    `;

                    leftSide.appendChild(icon);
                    leftSide.appendChild(fileInfo);

                    // 右侧:操作按钮
                    const rightSide = document.createElement('div');
                    rightSide.className = 'flex items-center space-x-2';

                    // 删除按钮
                    const deleteButton = document.createElement('button');
                    deleteButton.className = 'p-2 text-gray-400 hover:text-gray-600 rounded-button button-hover';
                    deleteButton.innerHTML = '<i class="fas fa-times"></i>';
                    deleteButton.addEventListener('click', () => removeFile(index));

                    rightSide.appendChild(deleteButton);

                    listItem.appendChild(leftSide);
                    listItem.appendChild(rightSide);

                    listContainer.appendChild(listItem);
                });
            }

            // 格式化文件大小
            function formatFileSize(size) {
                const units = ['B', 'KB', 'MB', 'GB'];
                let unitIndex = 0;

                while (size >= 1024 && unitIndex < units.length - 1) {
                    size /= 1024;
                    unitIndex++;
                }

                return `${size.toFixed(2)} ${units[unitIndex]}`;
            }

            // 获取文件类型的图标类名
            function getIconClass(type) {
                if (type.startsWith('image/')) {
                    return 'fas fa-file-image text-primary w-8 h-8 flex items-center justify-center';
                } else if (type === 'text/plain' || type.endsWith('.md')) {
                    return 'fas fa-file-alt text-primary w-8 h-8 flex items-center justify-center';
                } else {
                    return 'fas fa-file text-primary w-8 h-8 flex items-center justify-center';
                }
            }

            // 移除指定索引的文件
            function removeFile(index) {
                selectedFiles.splice(index, 1);
                updateFileList();
            }

            // 清空所有文件
            document.querySelector('#fileList .px-6.py-2.text-gray-600').addEventListener('click', () => {
                selectedFiles = [];
                updateFileList();
            });

            // 转义 HTML 特殊字符
            function escapeHTML(str) {
                return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
            }

            // 清空所有文件
            clearButton.addEventListener('click', () => {
                selectedFiles = [];
                updateFileList();
                previewLinks.classList.add('hidden');
                htmlFilesList.innerHTML = '';
            });

            // 预览按钮点击事件
            previewButton.addEventListener('click', () => {
                if (selectedFiles.length === 0) return;

                const formData = new FormData();
                selectedFiles.forEach(file => formData.append('file', file));

                fetch('/api/upload', {
                    method: 'POST',
                    body: formData
                }).then(response => response.json())
                    .then(data => {
                        previewLinks.classList.remove('hidden');
                        htmlFilesList.innerHTML = '';

                        data.forEach(htmlFile => {
                            const listItem = document.createElement('li');
                            listItem.className = 'flex items-center justify-between p-4 bg-gray-50 rounded-lg';

                            const fileName = document.createElement('a');
                            fileName.href = htmlFile.filePath;
                            fileName.target = '_blank';
                            fileName.className = 'text-blue-500 hover:underline';
                            fileName.textContent = htmlFile.fileName;

                            listItem.appendChild(fileName);
                            htmlFilesList.appendChild(listItem);
                        });
                    }).catch(error => {
                    alert('上传失败,请稍后再试。');
                    console.error('Error:', error);
                });
            });
        </script>
    </body>

</html>

SpringBoot/src/main/java/com/boot/service/ZipService.java

package com.boot.service;

import com.boot.config.ZipConfig;
import com.boot.model.HtmlFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

@Service
public class ZipService {

    @Autowired
    private ZipConfig zipConfig;

    public List<HtmlFile> processZipFile(MultipartFile file) throws IOException {

        String extractDir = zipConfig.getExtract().getPath();
        File dir = new File(extractDir);
        if (!dir.exists()) {
            dir.mkdirs();
        }

        // 解压文件
        try (ZipInputStream zis = new ZipInputStream(file.getInputStream(), Charset.forName("GBK"))) {
            ZipEntry zipEntry;
            byte[] buffer = new byte[1024];
            while ((zipEntry = zis.getNextEntry()) != null) {
                File newFile = new File(extractDir + File.separator + zipEntry.getName());
                if (zipEntry.isDirectory()) {
                    newFile.mkdirs();
                    continue;
                }
                // 创建父目录
                new File(newFile.getParent()).mkdirs();
                // 写入文件
                try (FileOutputStream fos = new FileOutputStream(newFile)) {
                    int len;
                    while ((len = zis.read(buffer)) > 0) {
                        fos.write(buffer, 0, len);
                    }
                }
            }
        }

        // 查找第一层的HTML文件
        List<HtmlFile> htmlFiles = new ArrayList<>();

        // 提取文件夹名称(去掉扩展名)
        String folderName = file.getOriginalFilename().substring(0, file.getOriginalFilename().lastIndexOf('.'));
        extractDir = extractDir + File.separator + folderName;

        try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(extractDir))) {
            for (Path path : stream) {
                if (Files.isRegularFile(path) && path.toString().toLowerCase().endsWith(".html")) {
                    htmlFiles.add(new HtmlFile(
                            path.getFileName().toString(), "/preview" + path.toString().substring(zipConfig.getExtract().getPath().length())
                    ));
                }
            }
        }

        return htmlFiles;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值