package utils import ( "fmt" "io" "mime/multipart" "os" "path/filepath" "strings" "time" ) // StorageType 存储类型 type StorageType string const ( StorageLocal StorageType = "local" StorageQCloud StorageType = "qcloud" StorageAliyun StorageType = "aliyun" StorageQiniu StorageType = "qiniu" ) // OSSConfig OSS配置 type OSSConfig struct { StorageType string AccessKey string SecretKey string Bucket string Region string Domain string } // OSSUploader OSS上传接口 type OSSUploader interface { Upload(file multipart.File, filename string, size int64) (string, string, error) // 返回 filePath, fileURL, error Delete(filePath string) error } // GetOSSUploader 根据存储类型获取上传器 func GetOSSUploader(config *OSSConfig) (OSSUploader, error) { switch StorageType(config.StorageType) { case StorageLocal: return &LocalUploader{ BasePath: "./uploads", BaseURL: "/uploads", }, nil case StorageQCloud: // TODO: 实现腾讯云COS上传 return nil, fmt.Errorf("qcloud storage not implemented yet") case StorageAliyun: // TODO: 实现阿里云OSS上传 return nil, fmt.Errorf("aliyun storage not implemented yet") case StorageQiniu: // TODO: 实现七牛云上传 return nil, fmt.Errorf("qiniu storage not implemented yet") default: return nil, fmt.Errorf("unsupported storage type: %s", config.StorageType) } } // LocalUploader 本地存储上传器 type LocalUploader struct { BasePath string BaseURL string } // Upload 上传文件到本地 func (l *LocalUploader) Upload(file multipart.File, filename string, size int64) (string, string, error) { // 生成唯一文件名 ext := filepath.Ext(filename) timestamp := time.Now().Unix() randomStr := fmt.Sprintf("%d", timestamp) newFilename := fmt.Sprintf("%s_%s%s", strings.TrimSuffix(filename, ext), randomStr, ext) // 按日期创建目录 dateDir := time.Now().Format("2006/01/02") uploadDir := filepath.Join(l.BasePath, dateDir) // 创建目录 if err := os.MkdirAll(uploadDir, 0755); err != nil { return "", "", fmt.Errorf("failed to create upload directory: %v", err) } // 完整文件路径 filePath := filepath.Join(uploadDir, newFilename) // 创建目标文件 dst, err := os.Create(filePath) if err != nil { return "", "", fmt.Errorf("failed to create file: %v", err) } defer dst.Close() // 复制文件内容 if _, err := io.Copy(dst, file); err != nil { return "", "", fmt.Errorf("failed to copy file: %v", err) } // 生成访问URL fileURL := fmt.Sprintf("%s/%s/%s", l.BaseURL, dateDir, newFilename) return filePath, fileURL, nil } // Delete 删除本地文件 func (l *LocalUploader) Delete(filePath string) error { // 确保文件路径在BasePath内(安全措施) absBasePath, err := filepath.Abs(l.BasePath) if err != nil { return err } absFilePath, err := filepath.Abs(filePath) if err != nil { return err } if !strings.HasPrefix(absFilePath, absBasePath) { return fmt.Errorf("invalid file path: outside base directory") } return os.Remove(filePath) } // GetFileType 根据MIME类型判断文件类型 func GetFileType(mimeType string) string { if strings.HasPrefix(mimeType, "image/") { return "image" } else if strings.HasPrefix(mimeType, "video/") { return "video" } else if strings.HasPrefix(mimeType, "application/pdf") || strings.HasPrefix(mimeType, "application/msword") || strings.HasPrefix(mimeType, "application/vnd.openxmlformats") || strings.HasPrefix(mimeType, "text/") { return "document" } return "other" }