最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go+Gin實現(xiàn)安全多文件上傳功能

 更新時間:2025年04月02日 15:18:53   作者:赴前塵  
這篇文章主要為大家詳細介紹了Go如何利用Gin框架實現(xiàn)安全多文件上傳功能,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下

Go+Gin實現(xiàn)安全多文件上傳:帶MD5校驗的完整解決方案

完整代碼如下

后端

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"

	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
)

// 前端傳來的文件元數(shù)據(jù)
type FileMetaRequest struct {
	FileName     string `json:"fileName" binding:"required"`
	FileSize     int64  `json:"fileSize" binding:"required"`
	FileType     string `json:"fileType" binding:"required"`
	FileMD5      string `json:"fileMD5" binding:"required"`
}

// 返回給前端的響應結構
type UploadResponse struct {
	OriginalName string `json:"originalName"`
	SavedPath    string `json:"savedPath"`
	ReceivedMD5  string `json:"receivedMD5"`
	IsVerified   bool   `json:"isVerified"` // 是否通過驗證
}

func main() {
	r := gin.Default()

	// 配置CORS
	r.Use(cors.New(cors.Config{
		AllowOrigins: []string{"*"},
		AllowMethods: []string{"POST"},
	}))

	// 上傳目錄
	uploadDir := "uploads"
	if _, err := os.Stat(uploadDir); os.IsNotExist(err) {
		os.Mkdir(uploadDir, 0755)
	}

	r.POST("/upload", func(c *gin.Context) {
		// 1. 獲取元數(shù)據(jù)JSON
		metaJson := c.PostForm("metadata")
		var fileMetas []FileMetaRequest
		if err := json.Unmarshal([]byte(metaJson), &fileMetas); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "元數(shù)據(jù)解析失敗"})
			return
		}

		// 2. 獲取文件
		form, err := c.MultipartForm()
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "文件獲取失敗"})
			return
		}
		files := form.File["files"]

		// 3. 驗證文件數(shù)量匹配
		if len(files) != len(fileMetas) {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": fmt.Sprintf("元數(shù)據(jù)與文件數(shù)量不匹配(元數(shù)據(jù):%d 文件:%d)",
					len(fileMetas), len(files)),
			})
			return
		}

		var results []UploadResponse
		for i, file := range files {
			meta := fileMetas[i]

			// 4. 驗證基本元數(shù)據(jù)
			if file.Filename != meta.FileName ||
				file.Size != meta.FileSize {
				results = append(results, UploadResponse{
					OriginalName: file.Filename,
					IsVerified:   false,
				})
				continue
			}

			// 5. 保存文件
			savedName := fmt.Sprintf("%s%s", meta.FileMD5, filepath.Ext(file.Filename))
			savePath := filepath.Join(uploadDir, savedName)

			if err := c.SaveUploadedFile(file, savePath); err != nil {
				results = append(results, UploadResponse{
					OriginalName: file.Filename,
					IsVerified:   false,
				})
				continue
			}

			// 6. 記錄結果(實際項目中這里應該做MD5校驗)
			results = append(results, UploadResponse{
				OriginalName: file.Filename,
				SavedPath:    savePath,
				ReceivedMD5:  meta.FileMD5,
				IsVerified:   true,
			})
		}

		c.JSON(http.StatusOK, gin.H{
			"success": true,
			"results": results,
		})
	})

	log.Println("服務啟動在 :8080")
	r.Run(":8080")
}

前端

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件上傳系統(tǒng)</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js"></script>

    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        h1 {
            color: #2c3e50;
            text-align: center;
            margin-bottom: 30px;
        }
        .upload-container {
            background-color: white;
            padding: 25px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .file-drop-area {
            border: 2px dashed #3498db;
            border-radius: 5px;
            padding: 30px;
            text-align: center;
            margin-bottom: 20px;
            transition: all 0.3s;
        }
        .file-drop-area.highlight {
            background-color: #f0f8ff;
            border-color: #2980b9;
        }
        #fileInput {
            display: none;
        }
        .file-label {
            display: inline-block;
            padding: 10px 20px;
            background-color: #3498db;
            color: white;
            border-radius: 5px;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        .file-label:hover {
            background-color: #2980b9;
        }
        .file-list {
            margin-top: 20px;
        }
        .file-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 10px;
            border-bottom: 1px solid #eee;
        }
        .file-info {
            flex: 1;
        }
        .file-name {
            font-weight: bold;
        }
        .file-meta {
            font-size: 0.8em;
            color: #7f8c8d;
        }
        .file-type {
            display: inline-block;
            padding: 2px 8px;
            border-radius: 4px;
            font-size: 0.8em;
            margin-left: 10px;
        }
        .type-body {
            background-color: #2ecc71;
            color: white;
        }
        .type-attachment {
            background-color: #e74c3c;
            color: white;
        }
        .progress-container {
            margin-top: 20px;
        }
        .progress-bar {
            height: 20px;
            background-color: #ecf0f1;
            border-radius: 4px;
            margin-bottom: 10px;
            overflow: hidden;
        }
        .progress {
            height: 100%;
            background-color: #3498db;
            width: 0%;
            transition: width 0.3s;
        }
        .results {
            margin-top: 30px;
        }
        .result-item {
            padding: 10px;
            margin-bottom: 10px;
            border-radius: 4px;
            background-color: #f8f9fa;
        }
        .success {
            border-left: 4px solid #2ecc71;
        }
        .error {
            border-left: 4px solid #e74c3c;
        }
        button {
            padding: 10px 20px;
            background-color: #3498db;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            transition: background-color 0.3s;
        }
        button:hover {
            background-color: #2980b9;
        }
        button:disabled {
            background-color: #95a5a6;
            cursor: not-allowed;
        }
    </style>
</head>
<body>
<h1>郵件文件上傳系統(tǒng)</h1>

<div class="upload-container">
    <div class="file-drop-area" id="dropArea">
        <input type="file" id="fileInput" multiple>
        <label for="fileInput" class="file-label">選擇文件或拖放到此處</label>
        <p>支持多文件上傳,自動計算MD5校驗值</p>
    </div>

    <div class="file-list" id="fileList"></div>

    <div class="progress-container" id="progressContainer" style="display: none;">
        <h3>上傳進度</h3>
        <div class="progress-bar">
            <div class="progress" id="progressBar"></div>
        </div>
        <div id="progressText">準備上傳...</div>
    </div>

    <button id="uploadBtn" disabled>開始上傳</button>
    <button id="clearBtn">清空列表</button>
</div>

<div class="results" id="results"></div>

<script>
    // 全局變量
    let files = [];
    const dropArea = document.getElementById('dropArea');
    const fileInput = document.getElementById('fileInput');
    const fileList = document.getElementById('fileList');
    const uploadBtn = document.getElementById('uploadBtn');
    const clearBtn = document.getElementById('clearBtn');
    const progressContainer = document.getElementById('progressContainer');
    const progressBar = document.getElementById('progressBar');
    const progressText = document.getElementById('progressText');
    const resultsContainer = document.getElementById('results');

    // 拖放功能
    dropArea.addEventListener('dragover', (e) => {
        e.preventDefault();
        dropArea.classList.add('highlight');
    });

    dropArea.addEventListener('dragleave', () => {
        dropArea.classList.remove('highlight');
    });

    dropArea.addEventListener('drop', (e) => {
        e.preventDefault();
        dropArea.classList.remove('highlight');
        if (e.dataTransfer.files.length) {
            fileInput.files = e.dataTransfer.files;
            handleFiles();
        }
    });

    // 文件選擇處理
    fileInput.addEventListener('change', handleFiles);

    async function handleFiles() {
        const newFiles = Array.from(fileInput.files);
        if (newFiles.length === 0) return;

        // 為每個文件計算MD5并創(chuàng)建元數(shù)據(jù)
        for (const file of newFiles) {
            const fileMeta = {
                file: file,
                name: file.name,
                size: file.size,
                type: file.type,
                md5: await calculateMD5(file),
            };
            files.push(fileMeta);
        }

        renderFileList();
        uploadBtn.disabled = false;
    }

    // 計算MD5
    async function calculateMD5(file) {
        return new Promise((resolve) => {
            const reader = new FileReader();
            reader.onload = (e) => {
                const hash = md5(e.target.result);
                resolve(hash);
            };
            reader.readAsBinaryString(file); // 注意這里使用 readAsBinaryString
        });
    }

    // 渲染文件列表
    function renderFileList() {
        fileList.innerHTML = '';

        if (files.length === 0) {
            fileList.innerHTML = '<p>沒有選擇文件</p>';
            uploadBtn.disabled = true;
            return;
        }

        files.forEach((fileMeta, index) => {
            const fileItem = document.createElement('div');
            fileItem.className = 'file-item';

            fileItem.innerHTML = `
                    <div class="file-info">
                        <div class="file-name">${fileMeta.name}</div>
                        <div class="file-meta">
                            大小: ${formatFileSize(fileMeta.size)} |
                            MD5: ${fileMeta.md5.substring(0, 8)}... |
                            類型: ${fileMeta.type || '未知'}
                        </div>
                    </div>
                    <div>
                        <button onclick="toggleFileType(${index})" class="file-type ${fileMeta.isAttachment ? 'type-attachment' : 'type-body'}">
                            ${fileMeta.isAttachment ? '附件' : '正文'}
                        </button>
                    </div>
                `;

            fileList.appendChild(fileItem);
        });
    }

    // 格式化文件大小
    function formatFileSize(bytes) {
        if (bytes === 0) return '0 Bytes';
        const k = 1024;
        const sizes = ['Bytes', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }

    // 上傳文件
    uploadBtn.addEventListener('click', async () => {
        if (files.length === 0) return;

        uploadBtn.disabled = true;
        progressContainer.style.display = 'block';
        resultsContainer.innerHTML = '<h3>上傳結果</h3>';

        try {
            const formData = new FormData();

            // 添加元數(shù)據(jù)
            const metadata = files.map(f => ({
                fileName: f.name,
                fileSize: f.size,
                fileType: f.type,
                fileMD5: f.md5,
            }));
            formData.append('metadata', JSON.stringify(metadata));

            // 添加文件
            files.forEach(f => formData.append('files', f.file));

            // 使用Fetch API上傳
            const xhr = new XMLHttpRequest();
            xhr.open('POST', 'http://localhost:8080/upload', true);

            // 進度監(jiān)聽
            xhr.upload.onprogress = (e) => {
                if (e.lengthComputable) {
                    const percent = Math.round((e.loaded / e.total) * 100);
                    progressBar.style.width = percent + '%';
                    progressText.textContent = `上傳中: ${percent}% (${formatFileSize(e.loaded)}/${formatFileSize(e.total)})`;
                }
            };

            xhr.onload = () => {
                if (xhr.status === 200) {
                    const response = JSON.parse(xhr.responseText);
                    showResults(response);
                } else {
                    showError('上傳失敗: ' + xhr.statusText);
                }
            };

            xhr.onerror = () => {
                showError('網絡錯誤,上傳失敗');
            };

            xhr.send(formData);

        } catch (error) {
            showError('上傳出錯: ' + error.message);
        }
    });

    // 顯示上傳結果
    function showResults(response) {
        progressText.textContent = '上傳完成!';

        if (response.success) {
            response.results.forEach(result => {
                const resultItem = document.createElement('div');
                resultItem.className = `result-item ${result.isVerified ? 'success' : 'error'}`;

                resultItem.innerHTML = `
                        <div><strong>${result.originalName}</strong></div>
                        <div>保存路徑: ${result.savedPath || '無'}</div>
                        <div>MD5校驗: ${result.receivedMD5 || '無'} -
                            <span style="color: ${result.isVerified ? '#2ecc71' : '#e74c3c'}">
                                ${result.isVerified ? '? 驗證通過' : '× 驗證失敗'}
                            </span>
                        </div>
                    `;

                resultsContainer.appendChild(resultItem);
            });
        } else {
            showError(response.error || '上傳失敗');
        }
    }

    // 顯示錯誤
    function showError(message) {
        const errorItem = document.createElement('div');
        errorItem.className = 'result-item error';
        errorItem.textContent = message;
        resultsContainer.appendChild(errorItem);
    }

    // 清空列表
    clearBtn.addEventListener('click', () => {
        files = [];
        fileInput.value = '';
        renderFileList();
        progressContainer.style.display = 'none';
        resultsContainer.innerHTML = '';
        uploadBtn.disabled = true;
    });
</script>
</body>
</html>

上傳截圖

到此這篇關于Go+Gin實現(xiàn)安全多文件上傳功能的文章就介紹到這了,更多相關Go Gin多文件上傳內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 解讀golang中的const常量和iota

    解讀golang中的const常量和iota

    這篇文章主要介紹了golang中的const常量和iota,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • Go語言學習之結構體和方法使用詳解

    Go語言學習之結構體和方法使用詳解

    這篇文章主要為大家詳細介紹了Go語言中結構體和方法的使用,文中的示例代碼講解詳細,對我們學習Go語言有一定的幫助,需要的可以參考一下
    2022-04-04
  • Go構建高性能的命令行工具使例詳解

    Go構建高性能的命令行工具使例詳解

    這篇文章主要為大家介紹了Go構建高性能的命令行工具使例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • Go高級特性之并發(fā)處理http詳解

    Go高級特性之并發(fā)處理http詳解

    Golang?作為一種高效的編程語言,提供了多種方法來實現(xiàn)并發(fā)發(fā)送?HTTP?請求,本文將深入探討?Golang?中并發(fā)發(fā)送?HTTP?請求的最佳技術和實踐,希望對大家有所幫助
    2024-02-02
  • Golang實現(xiàn)web文件共享服務的示例代碼

    Golang實現(xiàn)web文件共享服務的示例代碼

    這篇文章主要介紹了Golang實現(xiàn)web文件共享服務的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-10-10
  • golang sql連接池的實現(xiàn)方法詳解

    golang sql連接池的實現(xiàn)方法詳解

    database/sql是golang的標準庫之一,它提供了一系列接口方法,用于訪問關系數(shù)據(jù)庫。下面這篇文章主要給大家介紹了關于golang sql連接池用法的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起看看吧
    2018-09-09
  • Golang學習筆記(六):struct

    Golang學習筆記(六):struct

    這篇文章主要介紹了Golang學習筆記(六):struct,本文講解了struct的聲明及初始化、struct的匿名字段(繼承)、method、method繼承和重寫等內容,需要的朋友可以參考下
    2015-05-05
  • 一文搞懂Go?Exec?僵尸與孤兒進程

    一文搞懂Go?Exec?僵尸與孤兒進程

    本文主要介紹了Go?Exec?僵尸與孤兒進程,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • Go語言中字符串賦值中的問題與解決方法

    Go語言中字符串賦值中的問題與解決方法

    這篇文章主要為大家詳細介紹了Go語言中字符串賦值會出現(xiàn)的一些問題以及解決方法,文中的示例代碼講解詳細,感興趣的小伙伴可以參考一下
    2024-12-12
  • Go語言的GOPATH與工作目錄詳解

    Go語言的GOPATH與工作目錄詳解

    這篇文章主要介紹了Go語言的GOPATH與工作目錄詳解,本文詳細講解了GOPATH設置、應用目錄結構、編譯應用等內容,需要的朋友可以參考下
    2014-10-10

最新評論

巫山县| 巩留县| 贵州省| 龙陵县| 中山市| 临桂县| 西乌珠穆沁旗| 巫山县| 汤原县| 汝城县| 繁昌县| 那坡县| 文登市| 龙游县| 东宁县| 揭西县| 精河县| 龙海市| 井陉县| 三都| 阳东县| 淮北市| 胶州市| 夏邑县| 敦煌市| 天柱县| 界首市| 双柏县| 观塘区| 达尔| 沈丘县| 三穗县| 玛纳斯县| 巩留县| 麟游县| 长丰县| 白朗县| 上杭县| 商城县| 阜阳市| 当雄县|