这是对Pinata 挑战的提交_
我建造的东西我使用Golang和React构建了一个可以上传多个文件的工具,它可以上传多个到Pinata。
以下是它的功能分解。
- 它检查请求是否使用POST方法。如果不是,它将发送一个状态码为405(方法不允许的方法)的错误响应并返回。
- 它使用ParseMultipartForm函数解析请求中的多部分表单数据,最大文件大小为maxFileSize。如果解析表单数据时出错,它将发送一个状态码为400(错误请求)的错误响应并返回。
- 它从请求中使用MultipartForm.File["files"]获取上传的文件列表。如果没有上传文件,它将发送一个状态码为400(错误请求)的错误响应并返回。
- 它初始化了两个切片:responses用于存储成功的上传,errors用于存储上传过程中出现的任何错误。
- 它创建了一个sync.WaitGroup和一个sync.Mutex以同步并发文件上传。
- 它遍历每个上传的文件并为每个文件启动一个goroutine以使用uploadFileToPinata函数进行上传。wg.Add(1)增加了等待组计数器,在goroutine完成时使用defer wg.Done()减少计数器。
- uploadFileToPinata函数将每个文件上传到Pinata API端点并返回响应及上传过程中出现的任何错误。
- mu.Lock()和mu.Unlock()调用确保对responses和errors切片的更新是原子操作。
- 所有的goroutines完成后,该函数使用wg.Wait()等待它们完成。
- 它构造了一个JSON响应结构体result,包含成功的上传和错误。
- 它将响应头的内容类型设置为JSON。
- 它检查是否有错误。如果有错误,它将响应状态码设置为206(部分内容)。否则,将状态码设置为200(OK)。
- 它将结果结构体编码为JSON并将其写入响应体。
总体来说,这段代码负责处理文件上传的过程,执行并行上传,并发送一个包含成功上传的文件以及过程中出现的任何错误信息的JSON响应。
演示版 我的代码访问 Github 查看更多详情。
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"sync"
"github.com/joho/godotenv"
)
const (
maxFileSize = 10 << 20 // 10 MB
)
type PinataResponse struct {
IpfsHash string `json:"IpfsHash"`
PinSize int `json:"PinSize"`
Timestamp string `json:"Timestamp"`
}
type ErrorResponse struct {
Error string `json:"error"`
}
type Credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
func main() {
// 加载 .env 文件失败
err := godotenv.Load(".env")
if err != nil {
log.Fatal("加载 .env 文件失败")
}
// http.HandleFunc("/upload", handleUpload)
http.Handle("/upload", corsMiddleware(http.HandlerFunc(handleUpload)))
fmt.Println("服务运行在 http://localhost:9000")
log.Fatal(http.ListenAndServe(":9000", nil))
}
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, pinata_api_key, pinata_secret_api_key")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
func handleUpload(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
sendErrorResponse(w, "方法不允许", http.StatusMethodNotAllowed)
return
}
err := r.ParseMultipartForm(maxFileSize)
if err != nil {
sendErrorResponse(w, "解析多部分表单错误: "+err.Error(), http.StatusBadRequest)
return
}
files := r.MultipartForm.File["files"]
if len(files) == 0 {
sendErrorResponse(w, "没有上传任何文件", http.StatusBadRequest)
return
}
responses := make([]PinataResponse, 0, len(files))
errors := make([]string, 0)
var wg sync.WaitGroup
var mu sync.Mutex
for _, fileHeader := range files {
wg.Add(1)
go func(fh *multipart.FileHeader) {
defer wg.Done()
response, err := uploadFileToPinata(fh)
mu.Lock()
defer mu.Unlock()
if err != nil {
errors = append(errors, fmt.Sprintf("上传 %s 时出错: %v", fh.Filename, err))
} else {
responses = append(responses, response)
}
}(fileHeader)
}
wg.Wait()
result := struct {
SuccessfulUploads []PinataResponse `json:"successful_uploads"`
Errors []string `json:"errors,omitempty"`
}{
SuccessfulUploads: responses,
Errors: errors,
}
w.Header().Set("Content-Type", "application/json")
if len(errors) > 0 {
w.WriteHeader(http.StatusPartialContent)
} else {
w.WriteHeader(http.StatusOK)
}
json.NewEncoder(w).Encode(result)
}
func uploadFileToPinata(fileHeader *multipart.FileHeader) (PinataResponse, error) {
file, err := fileHeader.Open()
if err != nil {
return PinataResponse{}, fmt.Errorf("无法打开文件: %w", err)
}
defer file.Close()
var requestBody bytes.Buffer
writer := multipart.NewWriter(&requestBody)
part, err := writer.CreateFormFile("file", filepath.Base(fileHeader.Filename))
if err != nil {
return PinataResponse{}, fmt.Errorf("无法创建表单文件: %w", err)
}
_, err = io.Copy(part, file)
if err != nil {
return PinataResponse{}, fmt.Errorf("无法复制文件内容: %w", err)
}
err = writer.Close()
if err != nil {
return PinataResponse{}, fmt.Errorf("无法关闭多部分写入器: %w", err)
}
// 加载环境变量
pinataAPIKey := os.Getenv("PINATA_API_KEY")
pinataAPISecret := os.Getenv("PINATA_API_SECRET")
pinataAPIURL := os.Getenv("PINATA_API_URL")
req, err := http.NewRequest("POST", pinataAPIURL, &requestBody)
if err != nil {
return PinataResponse{}, fmt.Errorf("无法创建请求: %w", err)
}
req.Header.Set("Content-Type", writer.FormDataContentType())
req.Header.Set("pinata_api_key", pinataAPIKey)
req.Header.Set("pinata_secret_api_key", pinataAPISecret)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return PinataResponse{}, fmt.Errorf("无法发送请求: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return PinataResponse{}, fmt.Errorf("Pinata API 返回非 OK 状态: %s", resp.Status)
}
var pinataResp PinataResponse
err = json.NewDecoder(resp.Body).Decode(&pinataResp)
if err != nil {
return PinataResponse{}, fmt.Errorf("无法解析Pinata响应: %w", err)
}
return pinataResp, nil
}
func sendErrorResponse(w http.ResponseWriter, message string, statusCode int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(ErrorResponse{Error: message})
}
全屏, 退出全屏
在共享的GitHub主页上找前端代码
了解更多详情點擊查看更多內容
為 TA 點贊
評論
評論
共同學習,寫下你的評論
評論加載中...
作者其他優質文章
正在加載中
感謝您的支持,我會繼續努力的~
掃碼打賞,你說多少就多少
贊賞金額會直接到老師賬戶
支付方式
打開微信掃一掃,即可進行掃碼打賞哦