2025-12-23 23:28:09 +08:00
|
|
|
|
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"
|
2025-12-23 23:48:12 +08:00
|
|
|
|
port = ":6903" // 修改端口号为6903
|
2025-12-23 23:28:09 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 生成唯一文件名
|
|
|
|
|
|
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)
|
2025-12-23 23:48:12 +08:00
|
|
|
|
api.GET("/apps/:id/", downloadApp) // 处理以/结尾的URL
|
2025-12-23 23:28:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启动服务器
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-23 23:48:12 +08:00
|
|
|
|
// 使用FileAttachment直接提供文件下载,避免重定向
|
2025-12-23 23:28:09 +08:00
|
|
|
|
c.FileAttachment(filePath, targetApp.FileName)
|
|
|
|
|
|
}
|