Files
appDistribute/background/main.go
sakurai b49fdeaa9d feat: 增强前端健壮性并添加刷新功能
- 添加刷新按钮和加载状态指示器
- 改进API请求错误处理和用户反馈
- 优化表单验证和登录逻辑
- 更新后端端口号并处理URL结尾斜杠
- 添加按钮悬停效果和移动端适配
2025-12-23 23:48:12 +08:00

235 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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