Files
appDistribute/background/main.go

235 lines
5.3 KiB
Go
Raw Normal View History

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)
}