这是对Pinata 挑战的提交_
我建造的东西我使用Golang和React构建了一个可以上传多个文件的工具,它可以上传多个到Pinata。
以下是它的功能分解。
总体来说,这段代码负责处理文件上传的过程,执行并行上传,并发送一个包含成功上传的文件以及过程中出现的任何错误信息的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主页上找前端代码
了解更多详情