欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

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

完整代碼如下

后端

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"`
}

// 返回給前端的響應(yīng)結(jié)構(gòu)
type UploadResponse struct {
	OriginalName string `json:"originalName"`
	SavedPath    string `json:"savedPath"`
	ReceivedMD5  string `json:"receivedMD5"`
	IsVerified   bool   `json:"isVerified"` // 是否通過驗(yàn)證
}

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. 驗(yàn)證文件數(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. 驗(yàn)證基本元數(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. 記錄結(jié)果(實(shí)際項(xiàng)目中這里應(yīng)該做MD5校驗(yàn))
			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("服務(wù)啟動在 :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>支持多文件上傳,自動計(jì)算MD5校驗(yàn)值</p>
    </div>

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

    <div class="progress-container" id="progressContainer" style="display: none;">
        <h3>上傳進(jìn)度</h3>
        <div class="progress-bar">
            <div class="progress" id="progressBar"></div>
        </div>
        <div id="progressText">準(zhǔn)備上傳...</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;

        // 為每個(gè)文件計(jì)算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;
    }

    // 計(jì)算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>上傳結(jié)果</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);

            // 進(jìn)度監(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('網(wǎng)絡(luò)錯(cuò)誤,上傳失敗');
            };

            xhr.send(formData);

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

    // 顯示上傳結(jié)果
    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校驗(yàn): ${result.receivedMD5 || '無'} -
                            <span style="color: ${result.isVerified ? '#2ecc71' : '#e74c3c'}">
                                ${result.isVerified ? '? 驗(yàn)證通過' : '× 驗(yàn)證失敗'}
                            </span>
                        </div>
                    `;

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

    // 顯示錯(cuò)誤
    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>

上傳截圖

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

相關(guān)文章

  • Golang map實(shí)現(xiàn)原理淺析

    Golang map實(shí)現(xiàn)原理淺析

    Go中Map是一個(gè)KV對集合,下面這篇文章主要給大家介紹了關(guān)于Golang中map探究的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2022-12-12
  • Go log庫的使用示例詳解

    Go log庫的使用示例詳解

    Go語言內(nèi)置的log庫提供了基本的日志記錄功能,支持日志的格式化輸出、設(shè)置日志前綴、配置輸出位置等,可以通過標(biāo)準(zhǔn)logger或創(chuàng)建新的Logger對象來使用,log庫簡單易用,但功能有限,可能需要配合第三方日志庫如logrus、zap等來滿足復(fù)雜需求
    2024-09-09
  • 源碼分析Go語言使用cgo導(dǎo)致線程增長的原因

    源碼分析Go語言使用cgo導(dǎo)致線程增長的原因

    這篇文章主要從一個(gè)cgo調(diào)用開始解析Go語言源碼,從而分析一下造成線程增長的原因,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一學(xué)習(xí)一下
    2023-06-06
  • Golang中日志使用詳解

    Golang中日志使用詳解

    這篇文章記錄了Golang項(xiàng)目中日志使用,以及結(jié)合Gin框架記錄請求日志,文中通過代碼示例介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-01-01
  • 一文帶你深入理解Golang Context包

    一文帶你深入理解Golang Context包

    在 Go 語言中,Context 包是一種非常常用的工具,它被用來管理 goroutine 之間的通信和取消。本文將深入探討Context 包的基本原理,包括使用場景、原理和一些最佳實(shí)踐,需要的可以參考下
    2023-05-05
  • go語言中基本數(shù)據(jù)類型及應(yīng)用快速了解

    go語言中基本數(shù)據(jù)類型及應(yīng)用快速了解

    這篇文章主要為大家介紹了go語言中基本數(shù)據(jù)類型應(yīng)用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • Go channel發(fā)送方和接收方如何相互阻塞等待源碼解讀

    Go channel發(fā)送方和接收方如何相互阻塞等待源碼解讀

    這篇文章主要為大家介紹了Go channel發(fā)送方和接收方如何相互阻塞等待源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • 一文帶你搞懂Go如何讀寫Excel文件

    一文帶你搞懂Go如何讀寫Excel文件

    Excelize是一個(gè)用純Go語言編寫的庫,提供了一組函數(shù),可以對XLAM?/?XLSM?/?XLSX?/?XLTM?/?XLTX文件進(jìn)行讀寫。支持讀寫由Microsoft?Excel?2007及以后版本生成的電子表格文檔。本文就將用它實(shí)現(xiàn)讀寫Excel文件操作,感興趣的可以學(xué)習(xí)一下
    2022-11-11
  • 使用自定義錯(cuò)誤碼攔截grpc內(nèi)部狀態(tài)碼問題

    使用自定義錯(cuò)誤碼攔截grpc內(nèi)部狀態(tài)碼問題

    這篇文章主要介紹了使用自定義錯(cuò)誤碼攔截grpc內(nèi)部狀態(tài)碼問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • 詳解golang中模板的常用語法

    詳解golang中模板的常用語法

    這篇文章主要介紹了golang模板中的常用語法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-08-08

最新評論