package main import ( "crypto/md5" "encoding/hex" "encoding/json" "fmt" "io" "math/rand" "net/http" "os" "path/filepath" "sync" "time" "github.com/gin-gonic/gin" ) // App 结构体定义 type App struct { ID string `json:"id"` Name string `json:"name"` FileName string `json:"fileName"` Date string `json:"date"` FilePath string `json:"-"` // 内部使用,不序列化 } // 全局变量 var ( apps []App appsMutex sync.RWMutex // 读写锁,提高并发性能 filesDir = "./files" jsonFile = "./apps.json" port = ":6903" // 修改端口号为6903 ) // 生成唯一文件名 func generateUniqueFileName(originalName string) string { // 获取文件扩展名 ext := filepath.Ext(originalName) // 生成基于时间、随机数和原文件名的MD5哈希 timestamp := time.Now().UnixNano() random := rand.Int63() hash := md5.Sum([]byte(fmt.Sprintf("%d%d%s", timestamp, random, originalName))) // 组合成新的唯一文件名 return fmt.Sprintf("%s%s", hex.EncodeToString(hash[:]), ext) } // 加载apps数据 func loadApps() { appsMutex.Lock() defer appsMutex.Unlock() file, err := os.Open(jsonFile) if err != nil { // 如果文件不存在,初始化空切片 apps = []App{} return } defer file.Close() decoder := json.NewDecoder(file) if err := decoder.Decode(&apps); err != nil { apps = []App{} } } // 保存apps数据 func saveApps() error { appsMutex.RLock() defer appsMutex.RUnlock() file, err := os.Create(jsonFile) if err != nil { return fmt.Errorf("failed to create apps.json: %w", err) } defer file.Close() encoder := json.NewEncoder(file) encoder.SetIndent("", " ") if err := encoder.Encode(apps); err != nil { return fmt.Errorf("failed to encode apps: %w", err) } return nil } func main() { // 初始化随机数生成器 rand.Seed(time.Now().UnixNano()) // 创建files目录 if err := os.MkdirAll(filesDir, 0755); err != nil { panic(fmt.Sprintf("failed to create files directory: %v", err)) } // 加载apps数据 loadApps() // 创建Gin引擎 // 生产环境中应该使用gin.ReleaseMode // gin.SetMode(gin.ReleaseMode) r := gin.Default() // 添加CORS中间件 r.Use(func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() }) // API路由 api := r.Group("/api") { api.GET("/apps", getApps) api.POST("/apps", uploadApp) api.GET("/apps/:id", downloadApp) api.GET("/apps/:id/", downloadApp) // 处理以/结尾的URL } // 启动服务器 fmt.Printf("Server is running on http://localhost%s\n", port) if err := r.Run(port); err != nil { panic(fmt.Sprintf("failed to start server: %v", err)) } } // 获取App列表 func getApps(c *gin.Context) { appsMutex.RLock() defer appsMutex.RUnlock() c.JSON(http.StatusOK, apps) } // 上传App func uploadApp(c *gin.Context) { // 获取表单数据 name := c.PostForm("name") if name == "" { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "App名称不能为空"}) return } // 获取上传的文件 file, header, err := c.Request.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "文件上传失败: " + err.Error()}) return } defer file.Close() // 生成唯一ID和文件名 id := fmt.Sprintf("%d", time.Now().UnixNano()) originalFileName := header.Filename uniqueFileName := generateUniqueFileName(originalFileName) filePath := filepath.Join(filesDir, uniqueFileName) // 创建目标文件 dst, err := os.Create(filePath) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "文件保存失败: " + err.Error()}) return } defer dst.Close() // 复制文件内容 if _, err = io.Copy(dst, file); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "文件复制失败: " + err.Error()}) return } // 添加到App列表 newApp := App{ ID: id, Name: name, FileName: originalFileName, FilePath: uniqueFileName, Date: time.Now().Format("2006-01-02 15:04:05"), } appsMutex.Lock() apps = append(apps, newApp) appsMutex.Unlock() // 保存到文件 if err := saveApps(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "保存数据失败: " + err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true}) } // 下载指定App func downloadApp(c *gin.Context) { // 获取ID id := c.Param("id") // 查找App appsMutex.RLock() var targetApp *App for i := range apps { if apps[i].ID == id { targetApp = &apps[i] break } } appsMutex.RUnlock() if targetApp == nil { c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "App不存在"}) return } // 构建文件路径 filePath := filepath.Join(filesDir, targetApp.FilePath) // 检查文件是否存在 if _, err := os.Stat(filePath); os.IsNotExist(err) { c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "文件不存在"}) return } // 使用FileAttachment直接提供文件下载,避免重定向 c.FileAttachment(filePath, targetApp.FileName) }