1005 lines
23 KiB
Go
1005 lines
23 KiB
Go
package controllers
|
||
|
||
import (
|
||
"crypto/md5"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"io"
|
||
"net/http"
|
||
"net/url"
|
||
"os"
|
||
"path"
|
||
"path/filepath"
|
||
"server/models"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
beego "github.com/beego/beego/v2/server/web"
|
||
)
|
||
|
||
// FileController 处理文件相关请求
|
||
type FileController struct {
|
||
beego.Controller
|
||
}
|
||
|
||
// GetAllFiles 获取所有文件信息
|
||
func (c *FileController) GetAllFiles() {
|
||
files, err := models.GetAllFiles()
|
||
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "获取文件列表失败",
|
||
"error": err.Error(),
|
||
}
|
||
} else {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "获取文件列表成功",
|
||
"data": files,
|
||
}
|
||
}
|
||
|
||
c.ServeJSON()
|
||
}
|
||
|
||
// GetFileById 根据ID获取文件信息
|
||
func (c *FileController) GetFileById() {
|
||
idStr := c.Ctx.Input.Param(":id")
|
||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "参数错误",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
file, err := models.GetFileById(id)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "文件不存在",
|
||
}
|
||
} else {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "获取文件成功",
|
||
"data": file,
|
||
}
|
||
}
|
||
|
||
c.ServeJSON()
|
||
}
|
||
|
||
// GetFilesByTenant 根据租户ID获取文件信息
|
||
func (c *FileController) GetFilesByTenant() {
|
||
tenantID := c.GetString("tenant_id")
|
||
if tenantID == "" {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "租户ID不能为空",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
files, err := models.GetFilesByTenant(tenantID)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "获取文件列表失败",
|
||
"error": err.Error(),
|
||
}
|
||
} else {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "获取文件列表成功",
|
||
"data": files,
|
||
}
|
||
}
|
||
|
||
c.ServeJSON()
|
||
}
|
||
|
||
// GetFilesByCategory 根据分类获取文件信息
|
||
func (c *FileController) GetFilesByCategory() {
|
||
category := c.GetString("category")
|
||
if category == "" {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "分类不能为空",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
files, err := models.GetFilesByCategory(category)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "获取文件列表失败",
|
||
"error": err.Error(),
|
||
}
|
||
} else {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "获取文件列表成功",
|
||
"data": files,
|
||
}
|
||
}
|
||
|
||
c.ServeJSON()
|
||
}
|
||
|
||
// GetFilesByStatus 根据状态获取文件信息
|
||
func (c *FileController) GetFilesByStatus() {
|
||
statusStr := c.GetString("status")
|
||
status, err := strconv.Atoi(statusStr)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "状态参数错误",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
files, err := models.GetFilesByStatus(int8(status))
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "获取文件列表失败",
|
||
"error": err.Error(),
|
||
}
|
||
} else {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "获取文件列表成功",
|
||
"data": files,
|
||
}
|
||
}
|
||
|
||
c.ServeJSON()
|
||
}
|
||
|
||
// CreateFile 创建新文件信息
|
||
func (c *FileController) CreateFile() {
|
||
var file models.FileInfo
|
||
|
||
// 解析请求体
|
||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &file); err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "请求参数错误",
|
||
"error": err.Error(),
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 从JWT中间件获取用户信息
|
||
if userID, ok := c.Ctx.Input.GetData("userId").(int); ok && userID > 0 {
|
||
file.UserID = userID
|
||
}
|
||
if username, ok := c.Ctx.Input.GetData("username").(string); ok && username != "" {
|
||
file.UploadBy = username
|
||
}
|
||
|
||
// 验证必填字段
|
||
if file.TenantID == "" || file.FileName == "" || file.OriginalName == "" ||
|
||
file.FilePath == "" || file.FileType == "" || file.FileExt == "" ||
|
||
file.Category == "" || file.UploadBy == "" {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "必填字段不能为空",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 添加文件信息
|
||
if _, err := models.AddFile(&file); err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "创建文件失败",
|
||
"error": err.Error(),
|
||
}
|
||
} else {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "创建文件成功",
|
||
"data": file,
|
||
}
|
||
}
|
||
|
||
c.ServeJSON()
|
||
}
|
||
|
||
// UpdateFile 更新文件信息
|
||
func (c *FileController) UpdateFile() {
|
||
idStr := c.Ctx.Input.Param(":id")
|
||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "参数错误",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
var file models.FileInfo
|
||
file.ID = id
|
||
|
||
// 解析请求体
|
||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &file); err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "请求参数错误",
|
||
"error": err.Error(),
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 更新文件信息
|
||
if err := models.UpdateFile(&file); err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "更新文件失败",
|
||
"error": err.Error(),
|
||
}
|
||
} else {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "更新文件成功",
|
||
"data": file,
|
||
}
|
||
}
|
||
|
||
c.ServeJSON()
|
||
}
|
||
|
||
// DeleteFile 删除文件信息(软删除)
|
||
func (c *FileController) DeleteFile() {
|
||
idStr := c.Ctx.Input.Param(":id")
|
||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "参数错误",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
if err := models.DeleteFile(id); err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "删除文件失败",
|
||
"error": err.Error(),
|
||
}
|
||
} else {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "删除文件成功",
|
||
}
|
||
}
|
||
|
||
c.ServeJSON()
|
||
}
|
||
|
||
// HardDeleteFile 硬删除文件信息
|
||
func (c *FileController) HardDeleteFile() {
|
||
idStr := c.Ctx.Input.Param(":id")
|
||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "参数错误",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
if err := models.HardDeleteFile(id); err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "删除文件失败",
|
||
"error": err.Error(),
|
||
}
|
||
} else {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "删除文件成功",
|
||
}
|
||
}
|
||
|
||
c.ServeJSON()
|
||
}
|
||
|
||
// GetFileStatistics 获取文件统计信息
|
||
func (c *FileController) GetFileStatistics() {
|
||
tenantID := c.GetString("tenant_id")
|
||
if tenantID == "" {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "租户ID不能为空",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
stats, err := models.GetFileStatistics(tenantID)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "获取统计信息失败",
|
||
"error": err.Error(),
|
||
}
|
||
} else {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "获取统计信息成功",
|
||
"data": stats,
|
||
}
|
||
}
|
||
|
||
c.ServeJSON()
|
||
}
|
||
|
||
// SearchFiles 搜索文件
|
||
func (c *FileController) SearchFiles() {
|
||
keyword := c.GetString("keyword")
|
||
tenantID := c.GetString("tenant_id")
|
||
|
||
if keyword == "" {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "搜索关键词不能为空",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
if tenantID == "" {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "租户ID不能为空",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
files, err := models.SearchFiles(keyword, tenantID)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "搜索文件失败",
|
||
"error": err.Error(),
|
||
}
|
||
} else {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "搜索文件成功",
|
||
"data": files,
|
||
}
|
||
}
|
||
|
||
c.ServeJSON()
|
||
}
|
||
|
||
// GetMyFiles 获取当前用户的文件列表
|
||
func (c *FileController) GetMyFiles() {
|
||
// 从JWT中间件获取用户信息
|
||
userID, ok := c.Ctx.Input.GetData("userId").(int)
|
||
if !ok || userID <= 0 {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "用户未登录或登录已过期",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
files, err := models.GetFilesByUserID(userID)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "获取文件列表失败: " + err.Error(),
|
||
}
|
||
} else {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "获取成功",
|
||
"data": files,
|
||
}
|
||
}
|
||
c.ServeJSON()
|
||
}
|
||
|
||
// GetFilesPublic 不需要认证的获取文件信息接口
|
||
func (c *FileController) GetFilesPublic() {
|
||
files, err := models.GetAllFiles()
|
||
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "获取文件列表失败",
|
||
"error": err.Error(),
|
||
}
|
||
} else {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "获取文件列表成功",
|
||
"data": files,
|
||
}
|
||
}
|
||
|
||
c.ServeJSON()
|
||
}
|
||
|
||
// Post 处理文件上传
|
||
func (c *FileController) Post() {
|
||
// 从JWT中间件获取用户信息
|
||
userID, ok := c.Ctx.Input.GetData("userId").(int)
|
||
if !ok || userID <= 0 {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "用户未登录或登录已过期",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
username, _ := c.Ctx.Input.GetData("username").(string)
|
||
if username == "" {
|
||
username = "unknown"
|
||
}
|
||
|
||
// 从JWT中间件获取租户ID
|
||
tenantID := "default"
|
||
if tid, ok := c.Ctx.Input.GetData("tenantId").(int); ok && tid > 0 {
|
||
tenantID = strconv.Itoa(tid)
|
||
}
|
||
|
||
// 获取上传的文件
|
||
file, header, err := c.GetFile("file")
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "获取上传文件失败: " + err.Error(),
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
defer file.Close()
|
||
|
||
// 获取文件基本信息
|
||
originalName := header.Filename
|
||
fileSize := header.Size
|
||
fileExt := strings.ToLower(filepath.Ext(originalName))
|
||
fileName := strings.TrimSuffix(originalName, fileExt)
|
||
|
||
// 读取文件内容到内存以计算MD5(对于大文件可能需要优化)
|
||
fileData := make([]byte, fileSize)
|
||
n, err := file.Read(fileData)
|
||
if err != nil && err != io.EOF {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "读取文件失败: " + err.Error(),
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
if int64(n) != fileSize {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "读取文件不完整",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 计算文件MD5值
|
||
hash := md5.New()
|
||
hash.Write(fileData)
|
||
fileMD5 := hex.EncodeToString(hash.Sum(nil))
|
||
|
||
// 检查是否已存在相同MD5的文件
|
||
existingFile, err := models.GetFileByMD5AndTenant(fileMD5, tenantID)
|
||
if err == nil && existingFile != nil {
|
||
// 文件已存在,不保存文件,只创建数据库记录
|
||
// 生成日期路径(年/月/日)
|
||
now := time.Now()
|
||
|
||
// 创建文件信息记录(使用已存在的文件路径)
|
||
fileInfo := models.FileInfo{
|
||
TenantID: tenantID,
|
||
UserID: userID,
|
||
FileName: fileName,
|
||
OriginalName: originalName,
|
||
FilePath: existingFile.FilePath, // 使用已存在文件的路径
|
||
FileURL: existingFile.FileURL, // 使用已存在文件的URL
|
||
FileSize: fileSize,
|
||
FileType: getFileTypeByExt(fileExt),
|
||
FileExt: fileExt,
|
||
MD5: fileMD5,
|
||
Category: c.GetString("category"),
|
||
Status: 1,
|
||
UploadBy: username,
|
||
UploadTime: now,
|
||
}
|
||
|
||
// 如果分类为空,使用默认分类
|
||
if fileInfo.Category == "" {
|
||
fileInfo.Category = "未分类"
|
||
}
|
||
|
||
// 保存到数据库(只保存记录,不保存文件)
|
||
id, err := models.AddFile(&fileInfo)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "保存文件信息失败: " + err.Error(),
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
fileInfo.ID = id
|
||
|
||
// 返回成功响应
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "文件上传成功(重复文件,使用已有文件)",
|
||
"data": fileInfo,
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 文件不存在,正常上传流程
|
||
// 获取分类(可选)
|
||
category := c.GetString("category")
|
||
if category == "" {
|
||
category = "未分类"
|
||
}
|
||
|
||
// 生成日期路径(年/月/日)
|
||
now := time.Now()
|
||
datePath := now.Format("2006/01/02")
|
||
|
||
// 目录改成 server 文件夹目录下的 uploads
|
||
uploadDir := filepath.Join("uploads", datePath)
|
||
|
||
// 清理路径
|
||
uploadDir = filepath.Clean(uploadDir)
|
||
|
||
// 确保目录存在
|
||
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "创建上传目录失败: " + err.Error(),
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 生成唯一文件名(时间戳 + 原始文件名)
|
||
timestamp := now.Format("20060102150405")
|
||
uniqueFileName := timestamp + "_" + originalName
|
||
savePath := path.Join(uploadDir, uniqueFileName)
|
||
|
||
// 计算相对路径(用于存储到数据库)
|
||
relativePath := path.Join("uploads", datePath, uniqueFileName)
|
||
|
||
// 保存文件(将已读取的数据写入文件)
|
||
if err := os.WriteFile(savePath, fileData, 0644); err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "保存文件失败: " + err.Error(),
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 获取文件类型
|
||
fileType := getFileTypeByExt(fileExt)
|
||
|
||
// 构造文件URL(相对路径)
|
||
fileURL := "/" + relativePath
|
||
|
||
// 创建文件信息记录
|
||
fileInfo := models.FileInfo{
|
||
TenantID: tenantID,
|
||
UserID: userID,
|
||
FileName: fileName,
|
||
OriginalName: originalName,
|
||
FilePath: relativePath,
|
||
FileURL: fileURL,
|
||
FileSize: fileSize,
|
||
FileType: fileType,
|
||
FileExt: fileExt,
|
||
MD5: fileMD5,
|
||
Category: category,
|
||
Status: 1, // 设置为正常状态
|
||
UploadBy: username,
|
||
UploadTime: now,
|
||
}
|
||
|
||
// 保存到数据库
|
||
id, err := models.AddFile(&fileInfo)
|
||
if err != nil {
|
||
// 如果数据库保存失败,删除已上传的文件
|
||
os.Remove(savePath)
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "保存文件信息失败: " + err.Error(),
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
fileInfo.ID = id
|
||
|
||
// 返回成功响应
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": true,
|
||
"message": "文件上传成功",
|
||
"data": fileInfo,
|
||
}
|
||
c.ServeJSON()
|
||
}
|
||
|
||
// DownloadFile 下载文件
|
||
func (c *FileController) DownloadFile() {
|
||
idStr := c.Ctx.Input.Param(":id")
|
||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "参数错误",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 获取文件信息
|
||
file, err := models.GetFileById(id)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "文件不存在",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 获取实际的文件记录(如果文件不存在,通过MD5查找)
|
||
actualFile, err := getActualFile(file)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "获取文件信息失败",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 检查文件是否存在(尝试多个可能的路径)
|
||
var filePath string
|
||
possiblePaths := []string{
|
||
actualFile.FilePath, // 直接使用相对路径
|
||
filepath.Join("server", actualFile.FilePath), // server目录前缀
|
||
filepath.Join(".", actualFile.FilePath), // 当前目录
|
||
}
|
||
|
||
for _, path := range possiblePaths {
|
||
if _, err := os.Stat(path); err == nil {
|
||
filePath = path
|
||
break
|
||
}
|
||
}
|
||
|
||
if filePath == "" {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "文件不存在于服务器",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 设置响应头
|
||
c.Ctx.Output.Header("Content-Description", "File Transfer")
|
||
c.Ctx.Output.Header("Content-Type", "application/octet-stream")
|
||
c.Ctx.Output.Header("Content-Disposition", "attachment; filename="+url.QueryEscape(actualFile.OriginalName))
|
||
c.Ctx.Output.Header("Content-Transfer-Encoding", "binary")
|
||
c.Ctx.Output.Header("Expires", "0")
|
||
c.Ctx.Output.Header("Cache-Control", "must-revalidate")
|
||
c.Ctx.Output.Header("Pragma", "public")
|
||
|
||
// 输出文件
|
||
http.ServeFile(c.Ctx.ResponseWriter, c.Ctx.Request, filePath)
|
||
}
|
||
|
||
// PreviewFile 预览文件
|
||
func (c *FileController) PreviewFile() {
|
||
idStr := c.Ctx.Input.Param(":id")
|
||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "参数错误",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 获取文件信息
|
||
file, err := models.GetFileById(id)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "文件不存在",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 获取实际的文件记录(如果文件不存在,通过MD5查找)
|
||
actualFile, err := getActualFile(file)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "获取文件信息失败",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 检查文件是否可预览
|
||
if !actualFile.CanPreview() {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "该文件类型不支持预览",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 检查文件是否存在(尝试多个可能的路径)
|
||
var filePath string
|
||
possiblePaths := []string{
|
||
actualFile.FilePath, // 直接使用相对路径
|
||
filepath.Join("server", actualFile.FilePath), // server目录前缀
|
||
filepath.Join(".", actualFile.FilePath), // 当前目录
|
||
}
|
||
|
||
for _, path := range possiblePaths {
|
||
if _, err := os.Stat(path); err == nil {
|
||
filePath = path
|
||
break
|
||
}
|
||
}
|
||
|
||
if filePath == "" {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "文件不存在于服务器",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 设置正确的 Content-Type
|
||
contentType := getContentType(actualFile.FileExt)
|
||
c.Ctx.Output.Header("Content-Type", contentType)
|
||
c.Ctx.Output.Header("Content-Disposition", "inline; filename="+url.QueryEscape(actualFile.OriginalName))
|
||
|
||
// 打开文件
|
||
fileHandle, err := os.Open(filePath)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "打开文件失败: " + err.Error(),
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
defer fileHandle.Close()
|
||
|
||
// 复制文件内容到响应
|
||
io.Copy(c.Ctx.ResponseWriter, fileHandle)
|
||
}
|
||
|
||
// PublicPreviewFile 公开预览文件(用于 Office Online Viewer,无需认证但仅用于预览)
|
||
func (c *FileController) PublicPreviewFile() {
|
||
idStr := c.Ctx.Input.Param(":id")
|
||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "参数错误",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 获取文件信息
|
||
file, err := models.GetFileById(id)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "文件不存在",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 获取实际的文件记录
|
||
actualFile, err := getActualFile(file)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "获取文件信息失败",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 检查文件是否可预览
|
||
if !actualFile.CanPreview() {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "该文件类型不支持预览",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 检查文件是否存在
|
||
var filePath string
|
||
possiblePaths := []string{
|
||
actualFile.FilePath,
|
||
filepath.Join("server", actualFile.FilePath),
|
||
filepath.Join(".", actualFile.FilePath),
|
||
}
|
||
|
||
for _, path := range possiblePaths {
|
||
if _, err := os.Stat(path); err == nil {
|
||
filePath = path
|
||
break
|
||
}
|
||
}
|
||
|
||
if filePath == "" {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "文件不存在于服务器",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 设置正确的 Content-Type
|
||
contentType := getContentType(actualFile.FileExt)
|
||
c.Ctx.Output.Header("Content-Type", contentType)
|
||
c.Ctx.Output.Header("Content-Disposition", "inline; filename="+url.QueryEscape(actualFile.OriginalName))
|
||
// 允许跨域访问(Office Online Viewer 需要)
|
||
c.Ctx.Output.Header("Access-Control-Allow-Origin", "*")
|
||
|
||
// 打开文件
|
||
fileHandle, err := os.Open(filePath)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"success": false,
|
||
"message": "打开文件失败: " + err.Error(),
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
defer fileHandle.Close()
|
||
|
||
// 复制文件内容到响应
|
||
io.Copy(c.Ctx.ResponseWriter, fileHandle)
|
||
}
|
||
|
||
// getFileTypeByExt 根据文件扩展名获取文件类型
|
||
func getFileTypeByExt(ext string) string {
|
||
ext = strings.ToLower(ext)
|
||
switch ext {
|
||
case ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp":
|
||
return "image"
|
||
case ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".txt":
|
||
return "document"
|
||
case ".mp4", ".avi", ".mov", ".wmv":
|
||
return "video"
|
||
case ".mp3", ".wav", ".flac":
|
||
return "audio"
|
||
case ".zip", ".rar", ".7z":
|
||
return "archive"
|
||
default:
|
||
return "other"
|
||
}
|
||
}
|
||
|
||
// getContentType 根据文件扩展名获取 Content-Type
|
||
func getContentType(ext string) string {
|
||
ext = strings.ToLower(ext)
|
||
contentTypes := map[string]string{
|
||
// 图片
|
||
".jpg": "image/jpeg",
|
||
".jpeg": "image/jpeg",
|
||
".png": "image/png",
|
||
".gif": "image/gif",
|
||
".bmp": "image/bmp",
|
||
".webp": "image/webp",
|
||
".svg": "image/svg+xml",
|
||
// 文档
|
||
".pdf": "application/pdf",
|
||
".txt": "text/plain; charset=utf-8",
|
||
".doc": "application/msword",
|
||
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||
".xls": "application/vnd.ms-excel",
|
||
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||
".ppt": "application/vnd.ms-powerpoint",
|
||
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||
// 视频
|
||
".mp4": "video/mp4",
|
||
".webm": "video/webm",
|
||
// 音频
|
||
".mp3": "audio/mpeg",
|
||
".wav": "audio/wav",
|
||
}
|
||
|
||
if contentType, ok := contentTypes[ext]; ok {
|
||
return contentType
|
||
}
|
||
return "application/octet-stream"
|
||
}
|
||
|
||
// getActualFile 获取实际的文件记录(如果当前记录的文件不存在,通过MD5查找唯一文件)
|
||
func getActualFile(file *models.FileInfo) (*models.FileInfo, error) {
|
||
// 检查当前记录的文件是否存在(尝试多个可能的路径)
|
||
possiblePaths := []string{
|
||
file.FilePath, // 直接使用相对路径
|
||
filepath.Join("server", file.FilePath), // server目录前缀
|
||
filepath.Join(".", file.FilePath), // 当前目录
|
||
}
|
||
|
||
for _, path := range possiblePaths {
|
||
if _, err := os.Stat(path); err == nil {
|
||
// 文件存在,返回当前记录
|
||
return file, nil
|
||
}
|
||
}
|
||
|
||
// 文件不存在,通过MD5查找唯一文件
|
||
if file.MD5 == "" {
|
||
return file, nil
|
||
}
|
||
|
||
actualFile, err := models.GetFileByMD5(file.MD5)
|
||
if err == nil && actualFile != nil {
|
||
// 检查找到的文件是否存在
|
||
possiblePaths = []string{
|
||
actualFile.FilePath,
|
||
filepath.Join("server", actualFile.FilePath),
|
||
filepath.Join(".", actualFile.FilePath),
|
||
}
|
||
for _, path := range possiblePaths {
|
||
if _, err := os.Stat(path); err == nil {
|
||
return actualFile, nil
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果找不到,返回原始记录
|
||
return file, nil
|
||
}
|