feat(deploy): 支持 tar 和 gz 文件格式
- 修改文件格式校验逻辑,增加对 tar 和 gz 文件的支持 - 添加文件 ID 生成逻辑,确保唯一性 - 实现文件内容校验功能,确保压缩包中包含 index.html - 优化项目创建流程,支持关联部署文件 -改进项目删除逻辑,使用事务确保数据一致性
This commit is contained in:
parent
d024f0c9e0
commit
73492a4add
|
@ -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"`
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue