feat(backend): 实现Go后端API并添加测试脚本
This commit is contained in:
233
background/main.go
Normal file
233
background/main.go
Normal file
@@ -0,0 +1,233 @@
|
||||
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 = ":6902"
|
||||
)
|
||||
|
||||
// 生成唯一文件名
|
||||
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)
|
||||
}
|
||||
|
||||
// 启动服务器
|
||||
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
|
||||
}
|
||||
|
||||
// 提供文件下载,设置原始文件名
|
||||
c.FileAttachment(filePath, targetApp.FileName)
|
||||
}
|
||||
Reference in New Issue
Block a user