301 lines
6.7 KiB
Go
301 lines
6.7 KiB
Go
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:"-"` // 内部使用,不序列化
|
||
}
|
||
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.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和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)
|
||
}
|