当前位置: 首页 > news >正文

Go+Gin实现多文件上传

后端

package main

import (
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"

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

const (
	uploadDir     = "uploads"
	maxUploadSize = 10 << 20 // 10MB
)

type UploadRes struct {
	OriginFileName string `json:"originFileName"`
	SavedFileName  string `json:"savedFileName"`
}

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

	// 更完善的CORS配置
	r.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"*"},
		AllowMethods:     []string{"POST", "OPTIONS"},
		AllowHeaders:     []string{"Origin", "Content-Type"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: true,
		MaxAge:           12 * time.Hour,
	}))

	// 确保上传目录存在
	if err := os.MkdirAll(uploadDir, 0755); err != nil {
		log.Fatalf("创建上传目录失败: %v", err)
	}

	r.Static("./static", ".")
	// 文件上传接口
	r.POST("/upload", func(c *gin.Context) {
		// 获取文件
		form, err := c.MultipartForm()
		if err != nil {
			if errors.Is(err, http.ErrNotMultipart) {
				c.JSON(http.StatusBadRequest, gin.H{"error": "请求不是multipart格式"})
			} else if errors.Is(http.ErrMissingBoundary, err) {
				c.JSON(http.StatusBadRequest, gin.H{"error": "缺少multipart边界"})
			} else {
				c.JSON(http.StatusBadRequest, gin.H{"error": "文件获取失败: " + err.Error()})
			}
			return
		}

		files := form.File["files"]
		if len(files) == 0 {
			c.JSON(http.StatusBadRequest, gin.H{"error": "没有上传文件"})
			return
		}
		var ress = make([]UploadRes, 0, len(files))
		for _, file := range files {

			if file.Size > maxUploadSize {
				c.JSON(http.StatusBadRequest, gin.H{"error": "文件大小超过限制"})
				return
			}
			// 安全的文件名生成
			savedName := fmt.Sprintf("%d_%s%s",
				time.Now().UnixNano(),
				uuid.New().String(), // 添加随机字符串防止冲突
				filepath.Ext(file.Filename))
			savePath := filepath.Join(uploadDir, savedName)

			// 保存文件
			if err := c.SaveUploadedFile(file, savePath); err != nil {
				log.Printf("保存文件失败: %v", err)
				c.JSON(http.StatusInternalServerError, gin.H{"error": "保存文件失败"})
				return
			}
			ress = append(ress, UploadRes{OriginFileName: file.Filename, SavedFileName: savedName})

		}
		c.JSON(http.StatusOK, gin.H{
			"message": "上传成功",
			"results": ress,
		})
	})

	log.Println("服务启动在 :8080")
	if err := r.Run(":8080"); err != nil {
		log.Fatalf("服务启动失败: %v", err)
	}
}

前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件上传</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        .upload-container {
            max-width: 600px;
            margin: auto;
        }
        .file-input {
            display: block;
            margin-bottom: 20px;
        }
        .submit-btn {
            padding: 10px 20px;
            background-color: #007bff;
            color: white;
            border: none;
            cursor: pointer;
        }
        .submit-btn:hover {
            background-color: #0056b3;
        }
        .response-message {
            margin-top: 20px;
            font-weight: bold;
        }
        .selected-files {
            margin-top: 20px;
        }
        .uploaded-files {
            margin-top: 20px;
        }
    </style>
</head>
<body>
<div class="upload-container">
    <h1>文件上传</h1>
    <form id="upload-form" enctype="multipart/form-data">
        <input type="file" name="files" multiple class="file-input" id="file-input">
        <button type="submit" class="submit-btn">上传文件</button>
    </form>
    <div class="selected-files" id="selected-files"></div>
    <div class="response-message" id="response-message"></div>
    <div class="uploaded-files" id="uploaded-files"></div>
</div>

<script>
    document.getElementById('file-input').addEventListener('change', function(event) {
        const files = event.target.files;
        const selectedFilesElement = document.getElementById('selected-files');
        if (files.length > 0) {
            selectedFilesElement.innerHTML = '<p><strong>已选择的文件:</strong></p>';
            const ul = document.createElement('ul');
            for (let i = 0; i < files.length; i++) {
                const li = document.createElement('li');
                li.textContent = files[i].name;
                ul.appendChild(li);
            }
            selectedFilesElement.appendChild(ul);
        } else {
            selectedFilesElement.innerHTML = '';
        }
    });

    document.getElementById('upload-form').addEventListener('submit', function(event) {
        event.preventDefault();

        const form = event.target;
        const formData = new FormData(form);

        fetch('/upload', {
            method: 'POST',
            body: formData
        })
            .then(response => response.json())
            .then(data => {
                const messageElement = document.getElementById('response-message');
                const uploadedFilesElement = document.getElementById('uploaded-files');

                if (data.error) {
                    messageElement.style.color = 'red';
                    messageElement.textContent = data.error;
                    uploadedFilesElement.innerHTML = '';
                } else {
                    messageElement.style.color = 'green';
                    messageElement.textContent = '上传成功!';

                    // 清空文件输入字段
                    document.getElementById('file-input').value = '';
                    document.getElementById('selected-files').innerHTML = '';

                    // 展示上传结果
                    uploadedFilesElement.innerHTML = '<p><strong>已保存的文件:</strong></p>';
                    const ul = document.createElement('ul');
                    data.results.forEach(result => {
                        const li = document.createElement('li');
                        li.textContent = `原始文件名: ${result.originFileName}, 保存文件名: ${result.savedFileName}`;
                        ul.appendChild(li);
                    });
                    uploadedFilesElement.appendChild(ul);
                }
            })
            .catch(error => {
                console.error('Error:', error);
                document.getElementById('response-message').textContent = '发生错误,请重试。';
                document.getElementById('uploaded-files').innerHTML = '';
            });
    });
</script>
</body>
</html>

运行截图

在这里插入图片描述

http://www.dtcms.com/a/112153.html

相关文章:

  • Linux: 系统内核中的信号
  • 【NLP 53、投机采样加速推理】
  • 【CMake】《CMake构建实战:项目开发卷》笔记-Chapter8-生成器表达式
  • LIO-SAM跑自己的数据集
  • 局域网:电脑或移动设备作为主机实现局域网访问
  • MyBatis操作数据库(1)
  • linux安装redis
  • PyTorch使用(7)-张量常见运算函数
  • AIGC实战——CycleGAN详解与实现
  • NVIDIA AgentIQ 详细介绍
  • 从Keep-Alive到页面关闭:解决Vue和React生命周期函数不触发的实战技巧
  • 相干光信号处理的一些基础知识
  • Spring依赖注入最佳实践:应对接口多实现的挑战
  • Centos7.9怎样安装Mysql 5.7
  • MySQL数据库如何在线修改表结构及字段类型?
  • FreeRTOS/任务创建和删除的API函数
  • HTML表单属性1
  • 线程同步与互斥(上)
  • 计算机通识
  • NB-IoT单灯控制器:智慧照明的“神经末梢”
  • 蓝桥杯嵌入式第15届真题-个人理解+解析
  • 【系统】换硬盘不换系统,使用WIN PE Ghost镜像给电脑无损扩容换硬盘
  • Python3.13安装教程-2025最新版超级详细图文安装教程(附所需安装包环境)
  • PhotoShop学习04
  • 详解大模型四类漏洞
  • Vue2+Vue3 45-90集学习笔记
  • P12013 [Ynoi April Fool‘s Round 2025] 牢夸 Solution
  • CMAKE中使用外部动态库
  • C++中,应尽可能将引用形参声明为const
  • Smart Link 技术全面解析