diff --git a/internal/model/sys_deploy_project.go b/internal/model/sys_deploy_project.go index 3a74978..fa75181 100644 --- a/internal/model/sys_deploy_project.go +++ b/internal/model/sys_deploy_project.go @@ -8,6 +8,7 @@ type SysDeployProject struct { ProjectName string `gorm:"column:project_name;type:varchar(100);not null;comment:项目名称" json:"projectName" binding:"required"` Domain string `gorm:"column:domain;type:varchar(255);not null;comment:访问域名" json:"domain" binding:"required"` DeployPath string `gorm:"column:deploy_path;type:varchar(500);not null;comment:部署路径" json:"deployPath"` + FileId string `gorm:"-" json:"fileId" binding:"required"` Status string `gorm:"column:status;type:char(1);default:1;comment:状态(0停用 1正常 2部署中 3部署失败)" json:"status"` DeployStatus string `gorm:"column:deploy_status;type:char(1);default:0;comment:部署状态(0未部署 1部署成功 2部署失败)" json:"deployStatus"` ErrorMsg string `gorm:"column:error_msg;type:text;comment:错误信息" json:"errorMsg"` diff --git a/internal/service/sys_deploy_file_service.go b/internal/service/sys_deploy_file_service.go index 2f01719..36f7d19 100644 --- a/internal/service/sys_deploy_file_service.go +++ b/internal/service/sys_deploy_file_service.go @@ -9,7 +9,9 @@ import ( "fmt" "io" "os" + "os/exec" "path/filepath" + "runtime" "strings" "time" @@ -39,9 +41,17 @@ func (s *SysDeployFileService) Create(c *gin.Context) serializer.Response { } // 校验文件类型 - if !strings.HasSuffix(strings.ToLower(file.Filename), ".zip") { - logger.Error(c, "只支持zip格式文件!") - return serializer.ParamErr("只支持zip格式文件!", nil) + 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) } // 获取文件名(不包含扩展名) @@ -49,15 +59,12 @@ func (s *SysDeployFileService) Create(c *gin.Context) serializer.Response { var deployFile model.SysDeployFile deployFile.FileName = filename - // 生成文件ID - if id, err := SysSequenceServiceBuilder(deployFile.TableName()).GenerateId(); err == nil { - deployFile.FileId = id - } else { - return serializer.DBErr("序列生成失败!", err) - } + // 获取文件扩展名 + fileExt := filepath.Ext(file.Filename) + + // 生成新的文件名:使用文件ID + 原始文件扩展名 + newFileName := deployFile.FileId + fileExt - // 生成新的文件名:使用文件ID + .zip 后缀 - newFileName := deployFile.FileId + ".zip" // 设置文件保存路径 deployFile.FilePath = filepath.Join("/data", newFileName) @@ -67,6 +74,20 @@ func (s *SysDeployFileService) Create(c *gin.Context) serializer.Response { 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 { @@ -359,3 +380,43 @@ func (s *SysDeployFileService) CalculateFileHash(reader io.Reader) (string, erro } 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 +} diff --git a/internal/service/sys_deploy_project_service.go b/internal/service/sys_deploy_project_service.go index e2fe121..02e9f42 100644 --- a/internal/service/sys_deploy_project_service.go +++ b/internal/service/sys_deploy_project_service.go @@ -7,7 +7,6 @@ import ( "ego/pkg/logger" "fmt" "os" - "path/filepath" "time" "github.com/gin-gonic/gin" @@ -54,6 +53,9 @@ func (s *SysDeployProjectService) Create(c *gin.Context) serializer.Response { return serializer.DBErr("序列生成失败!", err) } + // 设置默认部署路径 + deployProject.DeployPath = fmt.Sprintf("/home/%s", deployProject.Domain) + // 设置默认值 now := time.Now() deployProject.CreateTime = &now @@ -66,11 +68,44 @@ func (s *SysDeployProjectService) Create(c *gin.Context) serializer.Response { deployProject.CreateBy = createBy } - if err := s.Db.Create(&deployProject).Error; err != nil { + // 开始事务处理 + tx := s.Db.Begin() + + // 创建部署项目记录 + if err := tx.Create(&deployProject).Error; err != nil { + tx.Rollback() logger.Error(c, "创建部署项目记录失败!") return serializer.DBErr("创建部署项目记录失败!", err) } + // 如果提供了FileId,则更新关联的部署文件记录的ParentId + if deployProject.FileId != "" { + // 检查文件是否存在且属于当前用户 + currentUserId := c.GetString("id") + var deployFile model.SysDeployFile + if err := tx.Where("file_id = ? AND del_flag = ? AND create_by = ?", + deployProject.FileId, "0", currentUserId).First(&deployFile).Error; err != nil { + tx.Rollback() + logger.Error(c, "关联的部署文件不存在或无权限访问!") + return serializer.ParamErr("关联的部署文件不存在或无权限访问!", err) + } + + // 更新部署文件的ParentId为当前项目的DeployId + if err := tx.Model(&model.SysDeployFile{}).Where("file_id = ?", deployProject.FileId). + Updates(map[string]interface{}{ + "parent_id": deployProject.DeployId, + "update_time": time.Now(), + "update_by": currentUserId, + }).Error; err != nil { + tx.Rollback() + logger.Error(c, "更新部署文件关联信息失败!") + return serializer.DBErr("更新部署文件关联信息失败!", err) + } + } + + // 提交事务 + tx.Commit() + return serializer.Succ("创建部署项目记录成功!", deployProject) } @@ -145,44 +180,52 @@ func (s *SysDeployProjectService) DeleteByID(c *gin.Context) serializer.Response return serializer.ParamErr("用户信息获取失败!", nil) } - // 软删除 - data := map[string]any{ - "del_flag": "1", - "update_time": time.Now(), - "update_by": currentUserId, - } + // 开始事务处理 + tx := s.Db.Begin() - // 删除已经部署的文件夹 + // 获取要删除的项目信息 deployProject := model.SysDeployProject{} - if err := s.Db.Where("deploy_id = ? AND create_by = ?", id, currentUserId).First(&deployProject).Error; err != nil { + if err := tx.Where("deploy_id = ? AND create_by = ?", id, currentUserId).First(&deployProject).Error; err != nil { + tx.Rollback() logger.Error(c, "获取部署项目记录失败或无权限访问!") return serializer.DBErr("获取部署项目记录失败或无权限访问!", err) } - // 删除 /home/:projectName - err := os.RemoveAll(filepath.Join("/home", deployProject.Domain)) + // 删除 /home/:domain 目录 + err := os.RemoveAll(deployProject.DeployPath) if err != nil { + tx.Rollback() logger.Error(c, "删除部署文件夹失败!") return serializer.DBErr("删除部署文件夹失败!", err) } - // 同时软删除关联的文件记录 + // 软删除关联的文件记录 fileData := map[string]any{ "del_flag": "1", "update_time": time.Now(), "update_by": currentUserId, } - if err := s.Db.Model(&model.SysDeployFile{}).Where("parent_id = ?", id).Updates(fileData).Error; err != nil { + if err := tx.Model(&model.SysDeployFile{}).Where("parent_id = ?", id).Updates(fileData).Error; err != nil { + tx.Rollback() logger.Error(c, "删除关联文件记录失败!") return serializer.DBErr("删除关联文件记录失败!", err) } - // 删除数据库记录 - if err := s.Db.Model(&model.SysDeployProject{}).Where("deploy_id = ? AND create_by = ?", id, currentUserId).Updates(data).Error; err != nil { + // 软删除项目记录 + projectData := map[string]any{ + "del_flag": "1", + "update_time": time.Now(), + "update_by": currentUserId, + } + if err := tx.Model(&model.SysDeployProject{}).Where("deploy_id = ? AND create_by = ?", id, currentUserId).Updates(projectData).Error; err != nil { + tx.Rollback() logger.Error(c, "删除部署项目记录失败!") return serializer.DBErr("删除部署项目记录失败!", err) } + // 提交事务 + tx.Commit() + return serializer.Succ("删除部署项目记录成功!", nil) }