Files
Ant-Browser/backend/internal/browser/core.go
T
Ant Browser Release Bot fc5a6fadbe publish: 1.1.0 snapshot (ff46de5)
channel: master
version: 1.1.0
source-ref: master
published-at-utc: 2026-03-20T14:14:07Z
2026-03-20 22:14:08 +08:00

366 lines
9.9 KiB
Go

package browser
import (
"ant-chrome/backend/internal/fsutil"
"ant-chrome/backend/internal/logger"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/google/uuid"
)
func normalizeProfileCoreID(coreId string) string {
coreId = strings.TrimSpace(coreId)
if strings.EqualFold(coreId, "default") {
return ""
}
return coreId
}
// GetCore 根据 coreId 获取内核配置
func (m *Manager) GetCore(coreId string) (Core, bool) {
coreId = normalizeProfileCoreID(coreId)
if coreId == "" {
return Core{}, false
}
for _, core := range m.ListCores() {
if strings.EqualFold(core.CoreId, coreId) {
return core, true
}
}
return Core{}, false
}
// GetDefaultCore 获取默认内核
func (m *Manager) GetDefaultCore() (Core, bool) {
cores := m.ListCores()
for _, core := range cores {
if core.IsDefault {
return core, true
}
}
if len(cores) > 0 {
return cores[0], true
}
return Core{}, false
}
// ResolveCoreExecutable 解析内核可执行文件路径
func (m *Manager) ResolveCoreExecutable(core Core) (string, error) {
corePath := strings.TrimSpace(core.CorePath)
if corePath == "" {
return "", fmt.Errorf("浏览器内核路径为空,请在“内核管理”中补充内核目录")
}
baseDir := m.ResolveRelativePath(corePath)
exePath, _, ok := FindCoreExecutable(baseDir)
if !ok {
return "", fmt.Errorf("浏览器内核目录无效:未找到可执行文件(候选:%s)。请检查内核目录是否完整或重新下载内核", strings.Join(CoreExecutableCandidates(), ", "))
}
if err := fsutil.EnsureExecutable(exePath); err != nil {
return "", fmt.Errorf("浏览器内核文件不可执行:%s。原因:%w。请检查文件权限或重新解压内核", exePath, err)
}
return exePath, nil
}
// ValidateCorePath 验证内核路径是否有效
func (m *Manager) ValidateCorePath(corePath string) CoreValidateResult {
corePath = strings.TrimSpace(corePath)
if corePath == "" {
return CoreValidateResult{Valid: false, Message: "路径不能为空"}
}
baseDir := m.ResolveRelativePath(corePath)
if _, err := os.Stat(baseDir); os.IsNotExist(err) {
return CoreValidateResult{Valid: false, Message: fmt.Sprintf("目录不存在: %s", baseDir)}
}
exePath, _, ok := FindCoreExecutable(baseDir)
if !ok {
return CoreValidateResult{Valid: false, Message: fmt.Sprintf("未找到浏览器可执行文件(候选:%s)", strings.Join(CoreExecutableCandidates(), ", "))}
}
if err := fsutil.ValidateExecutable(exePath); err != nil {
return CoreValidateResult{Valid: false, Message: fmt.Sprintf("浏览器可执行文件不可用:%v", err)}
}
return CoreValidateResult{Valid: true, Message: fmt.Sprintf("路径有效: %s", exePath)}
}
// ListCores 获取所有内核配置
func (m *Manager) ListCores() []Core {
if m.CoreDAO != nil {
cores, err := m.CoreDAO.List()
if err == nil {
// 同步到内存 config,供其他逻辑使用
m.Config.Browser.Cores = cores
return cores
}
}
return m.Config.Browser.Cores
}
// SaveCore 保存内核配置(新增或更新)
func (m *Manager) SaveCore(input CoreInput) error {
log := logger.New("Browser")
coreId := strings.TrimSpace(input.CoreId)
coreName := strings.TrimSpace(input.CoreName)
corePath := strings.TrimSpace(input.CorePath)
if coreName == "" {
return fmt.Errorf("内核名称不能为空")
}
if corePath == "" {
return fmt.Errorf("内核路径不能为空")
}
if m.CoreDAO != nil {
if coreId == "" {
coreId = uuid.NewString()
}
if input.IsDefault {
if err := m.CoreDAO.SetDefault(""); err != nil {
// SetDefault 空串只清除,忽略错误
_ = err
}
}
core := Core{CoreId: coreId, CoreName: coreName, CorePath: corePath, IsDefault: input.IsDefault}
if err := m.CoreDAO.Upsert(core); err != nil {
return err
}
// 同步内存
m.syncCoresFromDAO()
log.Info("内核配置保存", logger.F("core_id", coreId), logger.F("core_name", coreName))
return nil
}
// 降级:写 config.yaml
existingIndex := -1
for i, core := range m.Config.Browser.Cores {
if coreId != "" && strings.EqualFold(core.CoreId, coreId) {
existingIndex = i
break
}
}
if existingIndex >= 0 {
m.Config.Browser.Cores[existingIndex].CoreName = coreName
m.Config.Browser.Cores[existingIndex].CorePath = corePath
if input.IsDefault {
m.clearDefaultCore()
m.Config.Browser.Cores[existingIndex].IsDefault = true
}
} else {
if coreId == "" {
coreId = uuid.NewString()
}
newCore := Core{CoreId: coreId, CoreName: coreName, CorePath: corePath,
IsDefault: input.IsDefault || len(m.Config.Browser.Cores) == 0}
if newCore.IsDefault {
m.clearDefaultCore()
}
m.Config.Browser.Cores = append(m.Config.Browser.Cores, newCore)
}
log.Info("内核配置保存(文件)", logger.F("core_id", coreId))
return m.Config.Save(m.ResolveRelativePath("config.yaml"))
}
// DeleteCore 删除内核配置
func (m *Manager) DeleteCore(coreId string) error {
log := logger.New("Browser")
coreId = strings.TrimSpace(coreId)
if coreId == "" {
return fmt.Errorf("内核ID不能为空")
}
if m.CoreDAO != nil {
if err := m.CoreDAO.Delete(coreId); err != nil {
return err
}
m.syncCoresFromDAO()
log.Info("内核配置删除", logger.F("core_id", coreId))
return nil
}
// 降级
index := -1
for i, core := range m.Config.Browser.Cores {
if strings.EqualFold(core.CoreId, coreId) {
index = i
break
}
}
if index < 0 {
return fmt.Errorf("内核不存在: %s", coreId)
}
wasDefault := m.Config.Browser.Cores[index].IsDefault
m.Config.Browser.Cores = append(m.Config.Browser.Cores[:index], m.Config.Browser.Cores[index+1:]...)
if wasDefault && len(m.Config.Browser.Cores) > 0 {
m.Config.Browser.Cores[0].IsDefault = true
}
log.Info("内核配置删除(文件)", logger.F("core_id", coreId))
return m.Config.Save(m.ResolveRelativePath("config.yaml"))
}
// SetDefaultCore 设置默认内核
func (m *Manager) SetDefaultCore(coreId string) error {
log := logger.New("Browser")
coreId = strings.TrimSpace(coreId)
if coreId == "" {
return fmt.Errorf("内核ID不能为空")
}
if m.CoreDAO != nil {
if err := m.CoreDAO.SetDefault(coreId); err != nil {
return err
}
m.syncCoresFromDAO()
log.Info("设置默认内核", logger.F("core_id", coreId))
return nil
}
// 降级
found := false
for i := range m.Config.Browser.Cores {
if strings.EqualFold(m.Config.Browser.Cores[i].CoreId, coreId) {
m.Config.Browser.Cores[i].IsDefault = true
found = true
} else {
m.Config.Browser.Cores[i].IsDefault = false
}
}
if !found {
return fmt.Errorf("内核不存在: %s", coreId)
}
log.Info("设置默认内核(文件)", logger.F("core_id", coreId))
return m.Config.Save(m.ResolveRelativePath("config.yaml"))
}
// syncCoresFromDAO 从 DAO 同步内核列表到内存 config
func (m *Manager) syncCoresFromDAO() {
if m.CoreDAO == nil {
return
}
if cores, err := m.CoreDAO.List(); err == nil {
m.Config.Browser.Cores = cores
}
}
// clearDefaultCore 清除所有默认标记
func (m *Manager) clearDefaultCore() {
for i := range m.Config.Browser.Cores {
m.Config.Browser.Cores[i].IsDefault = false
}
}
// ResolveChromeBinary 解析 Chrome 二进制路径(简化版)
func (m *Manager) ResolveChromeBinary(profile *Profile) (string, error) {
log := logger.New("Browser")
coreId := normalizeProfileCoreID(profile.CoreId)
var core Core
var found bool
if coreId != "" {
core, found = m.GetCore(coreId)
}
if !found {
core, found = m.GetDefaultCore()
}
if !found {
return "", fmt.Errorf("未配置可用浏览器内核。请先在“内核管理”中添加内核并设置默认内核")
}
exePath, err := m.ResolveCoreExecutable(core)
if err != nil {
log.Error("内核路径解析失败", logger.F("core_id", core.CoreId), logger.F("error", err.Error()))
return "", err
}
log.Debug("使用内核", logger.F("core_id", core.CoreId), logger.F("path", exePath))
return exePath, nil
}
// GetChromeVersion 从 manifest.json 读取 Chrome 版本号
func (m *Manager) GetChromeVersion(corePath string) string {
corePath = strings.TrimSpace(corePath)
if corePath == "" {
return ""
}
baseDir := m.ResolveRelativePath(corePath)
// 尝试读取 manifest.json 或 *.manifest 文件
manifestPath := filepath.Join(baseDir, "manifest.json")
data, err := os.ReadFile(manifestPath)
if err != nil {
// 尝试查找 *.manifest 文件
matches, _ := filepath.Glob(filepath.Join(baseDir, "*.manifest"))
if len(matches) > 0 {
// 从文件名提取版本号,如 "142.0.7444.175.manifest"
baseName := filepath.Base(matches[0])
version := strings.TrimSuffix(baseName, ".manifest")
if version != "" {
return version
}
}
return ""
}
// 解析 JSON
var manifest struct {
Version string `json:"version"`
}
if err := json.Unmarshal(data, &manifest); err != nil {
return ""
}
return manifest.Version
}
// CountInstancesByCore 统计使用指定内核的实例数量
func (m *Manager) CountInstancesByCore(coreId string) int {
coreId = strings.TrimSpace(coreId)
count := 0
countByCoreID := func(profileCoreId string) {
// 如果实例的 CoreId 为空,则使用默认内核
if profileCoreId == "" {
defaultCore, found := m.GetDefaultCore()
if found && strings.EqualFold(defaultCore.CoreId, coreId) {
count++
}
} else if strings.EqualFold(profileCoreId, coreId) {
count++
}
}
if len(m.Profiles) > 0 {
for _, profile := range m.Profiles {
countByCoreID(normalizeProfileCoreID(profile.CoreId))
}
return count
}
for _, profile := range m.Config.Browser.Profiles {
countByCoreID(normalizeProfileCoreID(profile.CoreId))
}
return count
}
// GetCoresExtendedInfo 获取所有内核的扩展信息
func (m *Manager) GetCoresExtendedInfo() []CoreExtendedInfo {
cores := m.ListCores()
result := make([]CoreExtendedInfo, 0, len(cores))
for _, core := range cores {
info := CoreExtendedInfo{
CoreId: core.CoreId,
ChromeVersion: m.GetChromeVersion(core.CorePath),
InstanceCount: m.CountInstancesByCore(core.CoreId),
}
result = append(result, info)
}
return result
}