Files
sakuraiKiyoshi 20ccfbc205 refactor(backend): 修改App结构体并调整互斥锁使用
将App.FilePath字段改为可序列化以便服务器重启后恢复
将saveApps函数中的RLock改为Lock以确保数据一致性
清理未使用的文档文件
2025-12-25 14:12:02 +08:00

301 lines
6.8 KiB
Go
Raw Permalink 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:"filePath"` // 序列化到JSON中用于服务器重启后恢复
}
type Doc struct {
DocBody string `json:"docBody"`
}
// 全局变量
var (
docs []Doc
apps []App
appsMutex sync.RWMutex // 读写锁,提高并发性能
filesDir = "./files"
docsDir = "./docs"
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.Lock()
defer appsMutex.Unlock()
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和docs目录
if err := os.MkdirAll(filesDir, 0755); err != nil {
panic(fmt.Sprintf("failed to create files directory: %v", err))
}
if err := os.MkdirAll(docsDir, 0755); err != nil {
panic(fmt.Sprintf("failed to create docs 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
api.GET("/docs", getDocs) // 新增获取文档的API端点
}
// 启动服务器
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)
}
// 读取文档内容
func readDocFile(docName string) (string, error) {
filePath := filepath.Join(docsDir, docName+".txt")
content, err := os.ReadFile(filePath)
if err != nil {
return "", err
}
return string(content), nil
}
// 获取文档内容
func getDocs(c *gin.Context) {
// 读取5个文档文件
doc1, err1 := readDocFile("doc1")
if err1 != nil {
doc1 = "" // 如果文件不存在或读取失败,返回空字符串
}
doc2, err2 := readDocFile("doc2")
if err2 != nil {
doc2 = ""
}
doc3, err3 := readDocFile("doc3")
if err3 != nil {
doc3 = ""
}
doc4, err4 := readDocFile("doc4")
if err4 != nil {
doc4 = ""
}
doc5, err5 := readDocFile("doc5")
if err5 != nil {
doc5 = ""
}
if len(docs) == 0 {
docs = append(docs, Doc{DocBody: doc1})
docs = append(docs, Doc{DocBody: doc2})
docs = append(docs, Doc{DocBody: doc3})
docs = append(docs, Doc{DocBody: doc4})
docs = append(docs, Doc{DocBody: doc5})
} else {
docs[0].DocBody = doc1
docs[1].DocBody = doc2
docs[2].DocBody = doc3
docs[3].DocBody = doc4
docs[4].DocBody = doc5
}
// 返回包含message1-5的JSON
c.JSON(http.StatusOK, docs)
}