Revert "feat(deps): implement batch installation and manifest-based (requirements.txt/package.json) parser and importer"
This reverts commit cbdcbed7a0.
This commit is contained in:
@@ -6,7 +6,6 @@ import (
|
|||||||
"github.com/engigu/baihu-panel/internal/models"
|
"github.com/engigu/baihu-panel/internal/models"
|
||||||
"github.com/engigu/baihu-panel/internal/models/vo"
|
"github.com/engigu/baihu-panel/internal/models/vo"
|
||||||
"github.com/engigu/baihu-panel/internal/services"
|
"github.com/engigu/baihu-panel/internal/services"
|
||||||
"github.com/engigu/baihu-panel/internal/services/deps"
|
|
||||||
"github.com/engigu/baihu-panel/internal/utils"
|
"github.com/engigu/baihu-panel/internal/utils"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -304,96 +303,3 @@ func (c *DependencyController) GetInstalled(ctx *gin.Context) {
|
|||||||
|
|
||||||
utils.Success(ctx, packages)
|
utils.Success(ctx, packages)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBatchInstallCommand 获取批量安装依赖包的命令
|
|
||||||
func (c *DependencyController) GetBatchInstallCommand(ctx *gin.Context) {
|
|
||||||
var req struct {
|
|
||||||
Items []struct {
|
|
||||||
Name string `json:"name" binding:"required"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
Language string `json:"language" binding:"required"`
|
|
||||||
LangVersion string `json:"lang_version"`
|
|
||||||
} `json:"items" binding:"required,gt=0"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
|
||||||
utils.BadRequest(ctx, "参数错误: items 不能为空且必须包含 name 和 language")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var depsList []models.Dependency
|
|
||||||
for _, item := range req.Items {
|
|
||||||
depsList = append(depsList, models.Dependency{
|
|
||||||
Name: item.Name,
|
|
||||||
Version: item.Version,
|
|
||||||
Language: item.Language,
|
|
||||||
LangVersion: item.LangVersion,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd, err := c.service.GetBatchInstallCommand(depsList)
|
|
||||||
if err != nil {
|
|
||||||
utils.ServerError(ctx, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
utils.Success(ctx, gin.H{"command": cmd})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseAndImport 解析上传/粘贴的清单文件内容并批量导入至数据库
|
|
||||||
func (c *DependencyController) ParseAndImport(ctx *gin.Context) {
|
|
||||||
var req struct {
|
|
||||||
Language string `json:"language" binding:"required"`
|
|
||||||
LangVersion string `json:"lang_version"`
|
|
||||||
Content string `json:"content" binding:"required"`
|
|
||||||
ImportDB bool `json:"import_db"` // 是否持久化到数据库做可视化管理
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
|
||||||
utils.BadRequest(ctx, "参数错误: language 和 content 必填")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 解析文本清单内容
|
|
||||||
parsedDeps, err := deps.ParseManifest(req.Language, req.Content)
|
|
||||||
if err != nil {
|
|
||||||
utils.ServerError(ctx, "清单文件解析失败: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(parsedDeps) == 0 {
|
|
||||||
utils.BadRequest(ctx, "未解析到任何有效依赖包")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 补全语言和版本属性
|
|
||||||
for i := range parsedDeps {
|
|
||||||
parsedDeps[i].Language = req.Language
|
|
||||||
parsedDeps[i].LangVersion = req.LangVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 根据需求决定是否导入数据库
|
|
||||||
var finalDeps []models.Dependency
|
|
||||||
if req.ImportDB {
|
|
||||||
imported, err := c.service.ImportDependencies(parsedDeps)
|
|
||||||
if err != nil {
|
|
||||||
utils.ServerError(ctx, "导入依赖记录至数据库失败: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
finalDeps = imported
|
|
||||||
} else {
|
|
||||||
finalDeps = parsedDeps
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 为这一批包生成合并批量安装命令
|
|
||||||
cmd, err := c.service.GetBatchInstallCommand(finalDeps)
|
|
||||||
if err != nil {
|
|
||||||
utils.ServerError(ctx, "生成安装命令失败: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
utils.Success(ctx, gin.H{
|
|
||||||
"dependencies": vo.ToDependencyVOListFromModels(finalDeps),
|
|
||||||
"command": cmd,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -193,8 +193,6 @@ func registerDependencyRoutes(g *gin.RouterGroup, c *Controllers) {
|
|||||||
deps.POST("/reinstall/:id", c.Dependency.Reinstall)
|
deps.POST("/reinstall/:id", c.Dependency.Reinstall)
|
||||||
deps.POST("/reinstall-all", c.Dependency.ReinstallAll)
|
deps.POST("/reinstall-all", c.Dependency.ReinstallAll)
|
||||||
deps.POST("/reinstall-all-cmd", c.Dependency.GetReinstallAllCommand)
|
deps.POST("/reinstall-all-cmd", c.Dependency.GetReinstallAllCommand)
|
||||||
deps.POST("/batch-install-cmd", c.Dependency.GetBatchInstallCommand)
|
|
||||||
deps.POST("/import", c.Dependency.ParseAndImport)
|
|
||||||
deps.GET("/installed", c.Dependency.GetInstalled)
|
deps.GET("/installed", c.Dependency.GetInstalled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,30 +111,3 @@ func (s *DependencyService) GetVerifyCommand(language, langVersion string) (stri
|
|||||||
}
|
}
|
||||||
return m.GetVerifyCommand(langVersion)
|
return m.GetVerifyCommand(langVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBatchInstallCommand 获取批量安装命令
|
|
||||||
func (s *DependencyService) GetBatchInstallCommand(depsList []models.Dependency) (string, error) {
|
|
||||||
if len(depsList) == 0 {
|
|
||||||
return "", errors.New("依赖包列表不能为空")
|
|
||||||
}
|
|
||||||
|
|
||||||
firstDep := depsList[0]
|
|
||||||
m := deps.GetManager(firstDep.Language)
|
|
||||||
if m == nil {
|
|
||||||
return "", errors.New("不支持的依赖类型: " + firstDep.Language)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.GetBatchInstallCommand(depsList)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImportDependencies 批量导入依赖并自动入库去重
|
|
||||||
func (s *DependencyService) ImportDependencies(depsList []models.Dependency) ([]models.Dependency, error) {
|
|
||||||
var imported []models.Dependency
|
|
||||||
for i := range depsList {
|
|
||||||
dep := &depsList[i]
|
|
||||||
if err := s.Create(dep); err == nil {
|
|
||||||
imported = append(imported, *dep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return imported, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ type Manager interface {
|
|||||||
Uninstall(dep *models.Dependency) error
|
Uninstall(dep *models.Dependency) error
|
||||||
GetInstalledPackages(language, langVersion string) ([]models.Dependency, error)
|
GetInstalledPackages(language, langVersion string) ([]models.Dependency, error)
|
||||||
GetInstallCommand(dep *models.Dependency) (string, error)
|
GetInstallCommand(dep *models.Dependency) (string, error)
|
||||||
GetBatchInstallCommand(deps []models.Dependency) (string, error)
|
|
||||||
GetReinstallAllCommand(deps []models.Dependency) (string, error)
|
GetReinstallAllCommand(deps []models.Dependency) (string, error)
|
||||||
GetVerifyCommand(langVersion string) (string, error)
|
GetVerifyCommand(langVersion string) (string, error)
|
||||||
}
|
}
|
||||||
@@ -75,33 +74,6 @@ func (m *BaseManager) GetInstallCommand(dep *models.Dependency) (string, error)
|
|||||||
return fullCmd + " && echo \"__INSTALL_SUCCESS__\" || echo \"__INSTALL_FAILED__\"", nil
|
return fullCmd + " && echo \"__INSTALL_SUCCESS__\" || echo \"__INSTALL_FAILED__\"", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *BaseManager) GetBatchInstallCommand(deps []models.Dependency) (string, error) {
|
|
||||||
if len(deps) == 0 {
|
|
||||||
return "echo \"没有需要安装的依赖\"", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var packageSpecs []string
|
|
||||||
var langVersion string
|
|
||||||
var language string
|
|
||||||
for _, dep := range deps {
|
|
||||||
language = dep.Language
|
|
||||||
if dep.LangVersion != "" {
|
|
||||||
langVersion = dep.LangVersion
|
|
||||||
}
|
|
||||||
if dep.Version != "" {
|
|
||||||
packageSpecs = append(packageSpecs, dep.Name+m.Separator+dep.Version)
|
|
||||||
} else {
|
|
||||||
packageSpecs = append(packageSpecs, dep.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args := append([]string{}, m.InstallCmd...)
|
|
||||||
args = append(args, packageSpecs...)
|
|
||||||
|
|
||||||
fullCmd := utils.BuildMiseCommandSimple(strings.Join(args, " "), language, langVersion)
|
|
||||||
return fullCmd + " && echo \"__INSTALL_SUCCESS__\" || echo \"__INSTALL_FAILED__\"", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *BaseManager) GetReinstallAllCommand(deps []models.Dependency) (string, error) {
|
func (m *BaseManager) GetReinstallAllCommand(deps []models.Dependency) (string, error) {
|
||||||
if len(deps) == 0 {
|
if len(deps) == 0 {
|
||||||
return "echo \"没有需要安装的依赖\"", nil
|
return "echo \"没有需要安装的依赖\"", nil
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
package deps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/engigu/baihu-panel/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseManifest 根据语言解析依赖清单文件内容
|
|
||||||
func ParseManifest(language, content string) ([]models.Dependency, error) {
|
|
||||||
lang := strings.ToLower(language)
|
|
||||||
if strings.Contains(lang, "python") {
|
|
||||||
return ParseRequirements(content), nil
|
|
||||||
}
|
|
||||||
if strings.Contains(lang, "node") {
|
|
||||||
return ParsePackageJson(content)
|
|
||||||
}
|
|
||||||
return []models.Dependency{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseRequirements 解析 Python requirements.txt
|
|
||||||
func ParseRequirements(content string) []models.Dependency {
|
|
||||||
var deps []models.Dependency
|
|
||||||
// 使用正则表达式按行分割,兼容 Windows 和 Linux 的换行符
|
|
||||||
lines := regexp.MustCompile(`\r?\n`).Split(content, -1)
|
|
||||||
|
|
||||||
// 用于分割包名和版本的正则 (支持 ==, >=, <=, ~=, >, <, @)
|
|
||||||
versionRegex := regexp.MustCompile(`[=><~@]+`)
|
|
||||||
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
// 忽略空行、注释行以及参数行 (以 - 开头的行如 -i, -r)
|
|
||||||
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "-") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分割名称与版本
|
|
||||||
parts := versionRegex.Split(line, 2)
|
|
||||||
name := strings.TrimSpace(parts[0])
|
|
||||||
version := ""
|
|
||||||
if len(parts) > 1 {
|
|
||||||
// 清除可能存在的后续参数,比如 requests==2.31.0 --hash=sha256:...
|
|
||||||
versionPart := strings.TrimSpace(parts[1])
|
|
||||||
|
|
||||||
// 如果有逗号分隔的多个范围限制,比如 >=1.20,<2.0,只取第一个范围作为参考版本号
|
|
||||||
if idx := strings.Index(versionPart, ","); idx != -1 {
|
|
||||||
versionPart = versionPart[:idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
versionFields := strings.Fields(versionPart)
|
|
||||||
if len(versionFields) > 0 {
|
|
||||||
version = strings.TrimSpace(versionFields[0])
|
|
||||||
// 清除可能残留的首部版本符号
|
|
||||||
version = strings.TrimLeft(version, "=><~@ ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if name != "" {
|
|
||||||
deps = append(deps, models.Dependency{
|
|
||||||
Name: name,
|
|
||||||
Version: version,
|
|
||||||
Language: "python3",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return deps
|
|
||||||
}
|
|
||||||
|
|
||||||
// PackageJson 代表 package.json 的结构定义
|
|
||||||
type PackageJson struct {
|
|
||||||
Dependencies map[string]string `json:"dependencies"`
|
|
||||||
DevDependencies map[string]string `json:"devDependencies"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParsePackageJson 解析 Node.js package.json
|
|
||||||
func ParsePackageJson(content string) ([]models.Dependency, error) {
|
|
||||||
var pkg PackageJson
|
|
||||||
if err := json.Unmarshal([]byte(content), &pkg); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var deps []models.Dependency
|
|
||||||
collect := func(m map[string]string, isDev bool) {
|
|
||||||
for name, versionRange := range m {
|
|
||||||
// 移除 npm 常见版本范围修饰符(如 ^1.2.3 或 ~2.3.0,保留底线版本号)
|
|
||||||
version := strings.TrimLeft(versionRange, "^~>=<* ")
|
|
||||||
remark := ""
|
|
||||||
if isDev {
|
|
||||||
remark = "devDependencies"
|
|
||||||
}
|
|
||||||
deps = append(deps, models.Dependency{
|
|
||||||
Name: name,
|
|
||||||
Version: version,
|
|
||||||
Language: "node",
|
|
||||||
Remark: remark,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
collect(pkg.Dependencies, false)
|
|
||||||
collect(pkg.DevDependencies, true)
|
|
||||||
return deps, nil
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package deps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseRequirements(t *testing.T) {
|
|
||||||
content := `
|
|
||||||
# This is a comment
|
|
||||||
requests==2.31.0
|
|
||||||
numpy>=1.20,<2.0
|
|
||||||
gunicorn
|
|
||||||
-r other-requirements.txt
|
|
||||||
pandas ~= 1.3.0
|
|
||||||
`
|
|
||||||
deps := ParseRequirements(content)
|
|
||||||
if len(deps) != 4 {
|
|
||||||
t.Fatalf("expected 4 dependencies, got %d", len(deps))
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []struct {
|
|
||||||
name string
|
|
||||||
version string
|
|
||||||
}{
|
|
||||||
{"requests", "2.31.0"},
|
|
||||||
{"numpy", "1.20"},
|
|
||||||
{"gunicorn", ""},
|
|
||||||
{"pandas", "1.3.0"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, exp := range expected {
|
|
||||||
if deps[i].Name != exp.name {
|
|
||||||
t.Errorf("expected name %s, got %s", exp.name, deps[i].Name)
|
|
||||||
}
|
|
||||||
if deps[i].Version != exp.version {
|
|
||||||
t.Errorf("expected version %s, got %s", exp.version, deps[i].Version)
|
|
||||||
}
|
|
||||||
if deps[i].Language != "python3" {
|
|
||||||
t.Errorf("expected language python3, got %s", deps[i].Language)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParsePackageJson(t *testing.T) {
|
|
||||||
content := `{
|
|
||||||
"dependencies": {
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"express": "~4.18.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"typescript": "^5.0.4"
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
deps, err := ParsePackageJson(content)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to parse package.json: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(deps) != 3 {
|
|
||||||
t.Fatalf("expected 3 dependencies, got %d", len(deps))
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []struct {
|
|
||||||
name string
|
|
||||||
version string
|
|
||||||
remark string
|
|
||||||
}{
|
|
||||||
{"lodash", "4.17.21", ""},
|
|
||||||
{"express", "4.18.2", ""},
|
|
||||||
{"typescript", "5.0.4", "devDependencies"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, exp := range expected {
|
|
||||||
if deps[i].Name != exp.name {
|
|
||||||
t.Errorf("expected name %s, got %s", exp.name, deps[i].Name)
|
|
||||||
}
|
|
||||||
if deps[i].Version != exp.version {
|
|
||||||
t.Errorf("expected version %s, got %s", exp.version, deps[i].Version)
|
|
||||||
}
|
|
||||||
if deps[i].Remark != exp.remark {
|
|
||||||
t.Errorf("expected remark %s, got %s", exp.remark, deps[i].Remark)
|
|
||||||
}
|
|
||||||
if deps[i].Language != "node" {
|
|
||||||
t.Errorf("expected language node, got %s", deps[i].Language)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user