DeployHelper/internal/service/sys_deploy_file_service.go

423 lines
13 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 service
import (
"crypto/md5"
"ego/internal/model"
"ego/internal/serializer"
"ego/internal/types"
"ego/pkg/logger"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// SysDeployFileService 部署文件服务
type SysDeployFileService struct {
Db *gorm.DB
}
// NewSysDeployFileService 构建部署文件服务
func NewSysDeployFileService(db *gorm.DB) *SysDeployFileService {
return &SysDeployFileService{
Db: db,
}
}
// Create 创建部署文件记录
func (s *SysDeployFileService) Create(c *gin.Context) serializer.Response {
// 获取上传的文件
file, err := c.FormFile("file")
if err != nil {
logger.Error(c, "获取上传文件失败!")
return serializer.ParamErr("获取上传文件失败!", err)
}
// 校验文件类型
supportedExtensions := []string{".zip", ".tar", ".gz"}
valid := false
for _, ext := range supportedExtensions {
if strings.HasSuffix(strings.ToLower(file.Filename), ext) {
valid = true
break
}
}
if !valid {
logger.Error(c, "只支持zip、tar、gz格式文件!")
return serializer.ParamErr("只支持zip、tar、gz格式文件!", nil)
}
// 获取文件名(不包含扩展名)
filename := strings.TrimSuffix(file.Filename, filepath.Ext(file.Filename))
var deployFile model.SysDeployFile
deployFile.FileName = filename
// 获取文件扩展名
fileExt := filepath.Ext(file.Filename)
// 生成新的文件名使用文件ID + 原始文件扩展名
newFileName := deployFile.FileId + fileExt
// 设置文件保存路径
deployFile.FilePath = filepath.Join("/data", newFileName)
// 保存文件到指定路径
if err := c.SaveUploadedFile(file, deployFile.FilePath); err != nil {
logger.Error(c, "保存文件失败!")
return serializer.ParamErr("保存文件失败!", err)
}
// 校验文件
err = s.ValidateArchiveFile(deployFile.FilePath, fileExt, file.Filename)
if err != nil {
logger.Error(c, "校验文件失败!")
return serializer.ParamErr("校验文件失败!", err)
}
// 生成文件ID
if id, err := SysSequenceServiceBuilder(deployFile.TableName()).GenerateId(); err == nil {
deployFile.FileId = id
} else {
return serializer.DBErr("序列生成失败!", err)
}
// 打开保存的文件以计算哈希值
savedFile, err := os.Open(deployFile.FilePath)
if err != nil {
logger.Error(c, "打开保存的文件失败!")
return serializer.ParamErr("打开保存的文件失败!", err)
}
defer savedFile.Close()
// 计算文件哈希值
deployFile.FileHash, err = s.CalculateFileHash(savedFile)
if err != nil {
logger.Error(c, "计算文件哈希值失败!")
return serializer.ParamErr("计算文件哈希值失败!", err)
}
// 获取文件大小(从保存的文件获取更准确)
fileInfo, err := savedFile.Stat()
if err != nil {
logger.Error(c, "获取文件信息失败!")
return serializer.ParamErr("获取文件信息失败!", err)
}
deployFile.FileSize = fileInfo.Size()
// 设置默认值
now := time.Now()
deployFile.CreateTime = &now
deployFile.Status = model.FileStatusNotUsed
deployFile.DelFlag = "0"
// 获取当前用户
if createBy := c.GetString("id"); createBy != "" {
deployFile.CreateBy = createBy
}
if err := s.Db.Create(&deployFile).Error; err != nil {
logger.Error(c, "创建部署文件记录失败!")
return serializer.DBErr("创建部署文件记录失败!", err)
}
return serializer.Succ("创建部署文件记录成功!", deployFile)
}
// GetByID 根据ID获取部署文件记录
func (s *SysDeployFileService) GetByID(c *gin.Context) serializer.Response {
// 获取当前用户ID
currentUserId := c.GetString("id")
if currentUserId == "" {
return serializer.ParamErr("用户信息获取失败!", nil)
}
var deployFile model.SysDeployFile
if err := s.Db.Where("file_id = ? AND del_flag = ? AND create_by = ?", c.Param("id"), "0", currentUserId).First(&deployFile).Error; err != nil {
logger.Error(c, "获取部署文件记录失败!")
return serializer.DBErr("获取部署文件记录失败!", err)
}
return serializer.Succ("查询成功!", deployFile)
}
// UpdateByID 根据ID更新部署文件记录
func (s *SysDeployFileService) UpdateByID(c *gin.Context) serializer.Response {
var deployFile model.SysDeployFile
if err := c.ShouldBind(&deployFile); err != nil {
logger.Error(c, "参数绑定失败!")
return serializer.ParamErr("参数绑定失败!", err)
}
id := deployFile.FileId
if id == "" {
logger.Error(c, "id 不可为空!")
return serializer.ParamErr("id不可为空!", fmt.Errorf("id不可为空"))
}
if deployFile.ParentId == "" {
logger.Error(c, "项目ID不能为空!")
return serializer.ParamErr("项目ID不能为空!", fmt.Errorf("项目ID不能为空"))
}
// 获取当前用户ID
currentUserId := c.GetString("id")
if currentUserId == "" {
return serializer.ParamErr("用户信息获取失败!", nil)
}
// 检查权限:只能更新自己创建的数据
var existingDeployFile model.SysDeployFile
if err := s.Db.Where("file_id = ? AND del_flag = ? AND create_by = ?", id, "0", currentUserId).First(&existingDeployFile).Error; err != nil {
logger.Error(c, "部署文件记录不存在或无权限访问!")
return serializer.ParamErr("部署文件记录不存在或无权限访问!", err)
}
// 设置更新时间
now := time.Now()
deployFile.UpdateTime = &now
// 获取当前用户
deployFile.UpdateBy = currentUserId
if err := s.Db.Model(&deployFile).Where("file_id = ? AND del_flag = ? AND create_by = ?", id, "0", currentUserId).Updates(&deployFile).Error; err != nil {
logger.Error(c, "更新部署文件记录失败!")
return serializer.DBErr("更新部署文件记录失败!", err)
}
return serializer.Succ("更新部署文件记录成功!", deployFile)
}
// DeleteByID 根据ID删除部署文件记录
func (s *SysDeployFileService) DeleteByID(c *gin.Context) serializer.Response {
id := c.Param("id")
if id == "" {
logger.Error(c, "id 不可为空!")
return serializer.ParamErr("id不可为空!", fmt.Errorf("id不可为空"))
}
// 获取当前用户ID
currentUserId := c.GetString("id")
if currentUserId == "" {
return serializer.ParamErr("用户信息获取失败!", nil)
}
// 软删除
data := map[string]any{
"del_flag": "1",
"update_time": time.Now(),
"update_by": currentUserId,
}
// 删除数据库记录
if err := s.Db.Model(&model.SysDeployFile{}).Where("file_id = ? AND create_by = ?", id, currentUserId).Updates(data).Error; err != nil {
logger.Error(c, "删除部署文件记录失败!")
return serializer.DBErr("删除部署文件记录失败!", err)
}
return serializer.Succ("删除部署文件记录成功!", nil)
}
// GetByCondition 条件查询部署文件记录
func (s *SysDeployFileService) GetByCondition(c *gin.Context) serializer.Response {
var p types.Params
if err := c.ShouldBind(&p); err != nil {
return serializer.ParamErr("参数绑定失败!", err)
}
queryStr, args, err := p.ConvertToGormConditions()
if err != nil {
logger.Error(c, "参数绑定失败!")
return serializer.ParamErr("参数绑定失败!", err)
}
var total int64
var deployFiles []model.SysDeployFile
offset := (p.Page - 1) * p.Limit
// 获取当前用户ID
currentUserId := c.GetString("id")
if currentUserId == "" {
return serializer.ParamErr("用户信息获取失败!", nil)
}
// 构建基础查询
db := s.Db.Model(&model.SysDeployFile{})
// 如果有查询条件,添加条件
if queryStr != "" {
db = db.Where(queryStr, args...)
}
// 添加用户权限过滤:只能查询自己创建的数据
db = db.Where("create_by = ?", currentUserId)
// 排序
if p.Sort != "" {
db = db.Order(p.Sort)
} else {
db = db.Order("create_time DESC")
}
// 执行分页查询
if err := db.Where("del_flag = ?", "0").Offset(offset).Limit(p.Limit).Find(&deployFiles).Error; err != nil {
logger.Error(c, "获取部署文件记录失败!")
return serializer.DBErr("获取部署文件记录失败!", err)
}
// 执行总数查询
if err := db.Where("del_flag = ?", "0").Count(&total).Error; err != nil {
logger.Error(c, "获取部署文件记录总数失败!")
return serializer.DBErr("获取部署文件记录总数失败!", err)
}
return serializer.Succ("查询成功!", gin.H{
"total": total,
"items": deployFiles,
"page": p.Page,
"limit": p.Limit,
})
}
// GetByParentID 根据项目ID获取文件列表
func (s *SysDeployFileService) GetByParentID(c *gin.Context) serializer.Response {
parentId := c.Param("parentId")
if parentId == "" {
logger.Error(c, "项目ID不可为空!")
return serializer.ParamErr("项目ID不可为空!", fmt.Errorf("项目ID不可为空"))
}
// 获取当前用户ID
currentUserId := c.GetString("id")
if currentUserId == "" {
return serializer.ParamErr("用户信息获取失败!", nil)
}
var deployFiles []model.SysDeployFile
if err := s.Db.Where("parent_id = ? AND del_flag = ? AND create_by = ?", parentId, "0", currentUserId).
Order("create_time DESC").Find(&deployFiles).Error; err != nil {
logger.Error(c, "获取文件列表失败!")
return serializer.DBErr("获取文件列表失败!", err)
}
return serializer.Succ("查询成功!", deployFiles)
}
// SetActiveFile 设置活跃文件
func (s *SysDeployFileService) SetActiveFile(c *gin.Context) serializer.Response {
fileId := c.Param("id")
if fileId == "" {
logger.Error(c, "文件ID不可为空!")
return serializer.ParamErr("文件ID不可为空!", fmt.Errorf("文件ID不可为空"))
}
// 获取当前用户ID
currentUserId := c.GetString("id")
if currentUserId == "" {
return serializer.ParamErr("用户信息获取失败!", nil)
}
// 获取文件信息
var deployFile model.SysDeployFile
if err := s.Db.Where("file_id = ? AND del_flag = ? AND create_by = ?", fileId, "0", currentUserId).First(&deployFile).Error; err != nil {
logger.Error(c, "文件不存在或无权限访问!")
return serializer.ParamErr("文件不存在或无权限访问!", err)
}
// 开始事务
tx := s.Db.Begin()
// 将同项目下的其他文件设为未使用状态
if err := tx.Model(&model.SysDeployFile{}).
Where("parent_id = ? AND del_flag = ? AND create_by = ?", deployFile.ParentId, "0", currentUserId).
Updates(map[string]any{
"status": model.FileStatusNotUsed,
"update_time": time.Now(),
"update_by": currentUserId,
}).Error; err != nil {
tx.Rollback()
logger.Error(c, "更新文件状态失败!")
return serializer.DBErr("更新文件状态失败!", err)
}
// 将当前文件设为使用中状态
if err := tx.Model(&model.SysDeployFile{}).
Where("file_id = ?", fileId).
Updates(map[string]any{
"status": model.FileStatusInUse,
"update_time": time.Now(),
"update_by": currentUserId,
}).Error; err != nil {
tx.Rollback()
logger.Error(c, "设置活跃文件失败!")
return serializer.DBErr("设置活跃文件失败!", err)
}
tx.Commit()
return serializer.Succ("设置活跃文件成功!", nil)
}
// GetActiveFileByParentID 根据项目ID获取当前使用中的文件
func (s *SysDeployFileService) GetActiveFileByParentID(parentId string) (*model.SysDeployFile, error) {
var deployFile model.SysDeployFile
err := s.Db.Where("parent_id = ? AND status = ? AND del_flag = ?",
parentId, model.FileStatusInUse, "0").First(&deployFile).Error
if err != nil {
return nil, err
}
return &deployFile, nil
}
// CalculateFileHash 计算文件哈希值
func (s *SysDeployFileService) CalculateFileHash(reader io.Reader) (string, error) {
hash := md5.New()
if _, err := io.Copy(hash, reader); err != nil {
return "", err
}
return fmt.Sprintf("%x", hash.Sum(nil)), nil
}
// ValidateArchiveFile 校验压缩文件是否包含 index.html
func (s *SysDeployFileService) ValidateArchiveFile(filePath, fileExt, originalFilename string) error {
// 只在linux下检查
if runtime.GOOS != "linux" {
return nil // 非Linux系统跳过校验
}
var cmd *exec.Cmd
switch fileExt {
case ".zip":
cmd = exec.Command("unzip", "-l", filePath)
case ".tar":
cmd = exec.Command("tar", "-tf", filePath)
case ".gz":
// 对于 .tar.gz 文件,需要特殊处理
if strings.HasSuffix(strings.ToLower(originalFilename), ".tar.gz") {
cmd = exec.Command("tar", "-tzf", filePath)
} else {
// 单独的 .gz 文件不支持列表查看
// 这里假设是 .tar.gz 文件
cmd = exec.Command("tar", "-tzf", filePath)
}
default:
return fmt.Errorf("不支持的文件格式")
}
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("检查文件内容失败: %v", err)
}
// 检查输出中是否包含 index.html
outputStr := string(output)
if !strings.Contains(outputStr, "index.html") {
return fmt.Errorf("压缩包中必须包含 index.html 文件")
}
return nil
}