mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
channel: master version: 1.0.0 source-ref: master published-at-utc: 2026-03-13T15:19:28Z
238 lines
6.9 KiB
Go
238 lines
6.9 KiB
Go
package backend
|
||
|
||
import (
|
||
"ant-chrome/backend/internal/browser"
|
||
"ant-chrome/backend/internal/config"
|
||
"ant-chrome/backend/internal/logger"
|
||
"fmt"
|
||
"net"
|
||
"os"
|
||
"path/filepath"
|
||
"time"
|
||
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
// ============================================================================
|
||
// 工具函数
|
||
// ============================================================================
|
||
|
||
// resolveAppPath 将相对路径解析为绝对路径(基于 appRoot)。
|
||
// 如果传入的已经是绝对路径则直接返回。
|
||
func (a *App) resolveAppPath(p string) string {
|
||
if filepath.IsAbs(p) {
|
||
return p
|
||
}
|
||
if a.appRoot != "" {
|
||
return filepath.Join(a.appRoot, p)
|
||
}
|
||
if cwd, err := os.Getwd(); err == nil {
|
||
return filepath.Join(cwd, p)
|
||
}
|
||
return p
|
||
}
|
||
|
||
func generateUUID() string {
|
||
return uuid.NewString()
|
||
}
|
||
|
||
func nextAvailablePort() (int, error) {
|
||
// 二次验证策略:分配端口后立即再次绑定确认未被抢占,最多重试 10 次
|
||
for i := 0; i < 10; i++ {
|
||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||
if err != nil {
|
||
continue
|
||
}
|
||
port := l.Addr().(*net.TCPAddr).Port
|
||
l.Close()
|
||
// 短暂等待 OS 释放端口
|
||
time.Sleep(5 * time.Millisecond)
|
||
// 二次验证端口未被其他进程抢占
|
||
v, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||
if err != nil {
|
||
continue
|
||
}
|
||
v.Close()
|
||
return port, nil
|
||
}
|
||
return 0, fmt.Errorf("无法分配可用端口")
|
||
}
|
||
|
||
// ============================================================================
|
||
// 内核初始化
|
||
// ============================================================================
|
||
|
||
func (a *App) ensureDefaultCores() {
|
||
log := logger.New("Browser")
|
||
|
||
// 扫描 chrome/ 目录,无论配置是否已有内核都执行一次,确保新增子目录被发现
|
||
detected := a.scanChromeDir("chrome")
|
||
|
||
if len(a.config.Browser.Cores) == 0 {
|
||
// 配置为空:直接用扫描结果,或兜底写一个占位
|
||
if len(detected) > 0 {
|
||
a.config.Browser.Cores = detected
|
||
} else {
|
||
a.config.Browser.Cores = []browser.Core{}
|
||
}
|
||
if err := a.config.Save(a.resolveAppPath("config.yaml")); err != nil {
|
||
log.Error("内核配置初始化失败", logger.F("error", err))
|
||
return
|
||
}
|
||
log.Info("内核配置初始化完成", logger.F("count", len(a.config.Browser.Cores)))
|
||
return
|
||
}
|
||
|
||
// 配置已有内核:将扫描到的新目录追加进去(不覆盖已有的)
|
||
changed := false
|
||
for _, newCore := range detected {
|
||
exists := false
|
||
for _, existing := range a.config.Browser.Cores {
|
||
if existing.CorePath == newCore.CorePath {
|
||
exists = true
|
||
break
|
||
}
|
||
}
|
||
if !exists {
|
||
a.config.Browser.Cores = append(a.config.Browser.Cores, newCore)
|
||
log.Info("发现新内核,已注册", logger.F("path", newCore.CorePath))
|
||
changed = true
|
||
}
|
||
}
|
||
if changed {
|
||
if err := a.config.Save(a.resolveAppPath("config.yaml")); err != nil {
|
||
log.Error("新内核注册保存失败", logger.F("error", err))
|
||
}
|
||
}
|
||
}
|
||
|
||
func (a *App) autoDetectCores() {
|
||
log := logger.New("Browser")
|
||
// ensureDefaultCores 已完成扫描注册,这里只做路径有效性日志。
|
||
// SQLite 模式下以内核表为准,避免与 config.yaml 历史条目不一致。
|
||
cores := a.config.Browser.Cores
|
||
if a.browserMgr != nil {
|
||
cores = a.browserMgr.ListCores()
|
||
}
|
||
for _, core := range cores {
|
||
result := a.browserMgr.ValidateCorePath(core.CorePath)
|
||
if result.Valid {
|
||
log.Debug("内核路径有效", logger.F("core_id", core.CoreId), logger.F("path", core.CorePath))
|
||
} else {
|
||
log.Warn("内核路径无效", logger.F("core_id", core.CoreId), logger.F("path", core.CorePath), logger.F("message", result.Message))
|
||
}
|
||
}
|
||
}
|
||
|
||
// scanChromeDir 扫描指定目录,将包含 chrome.exe 的子文件夹识别为内核。
|
||
// 如果目录本身包含 chrome.exe(旧版单内核结构),则直接返回该目录作为内核。
|
||
func (a *App) scanChromeDir(chromeRoot string) []browser.Core {
|
||
log := logger.New("Browser")
|
||
|
||
baseDir := a.resolveAppPath(chromeRoot)
|
||
|
||
if _, err := os.Stat(baseDir); os.IsNotExist(err) {
|
||
return nil
|
||
}
|
||
|
||
// 如果根目录本身就有 chrome.exe,视为单内核结构
|
||
if _, err := os.Stat(filepath.Join(baseDir, "chrome.exe")); err == nil {
|
||
return []browser.Core{
|
||
{
|
||
CoreId: "default",
|
||
CoreName: "默认内核",
|
||
CorePath: chromeRoot,
|
||
IsDefault: true,
|
||
},
|
||
}
|
||
}
|
||
|
||
// 扫描子文件夹
|
||
entries, err := os.ReadDir(baseDir)
|
||
if err != nil {
|
||
log.Warn("扫描 chrome 目录失败", logger.F("path", baseDir), logger.F("error", err.Error()))
|
||
return nil
|
||
}
|
||
|
||
var cores []browser.Core
|
||
for _, entry := range entries {
|
||
if !entry.IsDir() {
|
||
continue
|
||
}
|
||
subPath := filepath.Join(chromeRoot, entry.Name())
|
||
absExe := filepath.Join(baseDir, entry.Name(), "chrome.exe")
|
||
if _, err := os.Stat(absExe); err != nil {
|
||
continue // 没有 chrome.exe,跳过
|
||
}
|
||
isDefault := len(cores) == 0
|
||
cores = append(cores, browser.Core{
|
||
CoreId: fmt.Sprintf("core-%s", entry.Name()),
|
||
CoreName: fmt.Sprintf("Chrome %s", entry.Name()),
|
||
CorePath: subPath,
|
||
IsDefault: isDefault,
|
||
})
|
||
log.Debug("发现内核", logger.F("name", entry.Name()), logger.F("path", subPath))
|
||
}
|
||
return cores
|
||
}
|
||
|
||
// ============================================================================
|
||
// 代理数据加载
|
||
// ============================================================================
|
||
|
||
// loadProxies 启动时加载代理数据。
|
||
// 优先从 ProxyDAO(SQLite)读取;若 DAO 未注入则降级到 proxies.yaml,最后降级到 config.yaml。
|
||
func (a *App) loadProxies() {
|
||
log := logger.New("Browser")
|
||
|
||
builtins := []browser.Proxy{
|
||
{ProxyId: "__direct__", ProxyName: "直连(不走代理)", ProxyConfig: "direct://"},
|
||
{ProxyId: "__local__", ProxyName: "本地代理", ProxyConfig: "http://127.0.0.1:7890"},
|
||
}
|
||
|
||
ensureBuiltins := func(list []browser.Proxy) []browser.Proxy {
|
||
for _, b := range builtins {
|
||
found := false
|
||
for _, p := range list {
|
||
if p.ProxyId == b.ProxyId {
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
if !found {
|
||
list = append([]browser.Proxy{b}, list...)
|
||
}
|
||
}
|
||
return list
|
||
}
|
||
|
||
// 优先从 SQLite 读取
|
||
if a.browserMgr.ProxyDAO != nil {
|
||
list, err := a.browserMgr.ProxyDAO.List()
|
||
if err != nil {
|
||
log.Error("从数据库读取代理失败", logger.F("error", err.Error()))
|
||
} else if len(list) > 0 {
|
||
a.config.Browser.Proxies = list
|
||
log.Info("代理数据从数据库加载完成", logger.F("count", len(list)))
|
||
return
|
||
}
|
||
}
|
||
|
||
// 降级:从 proxies.yaml 加载
|
||
loaded, err := config.LoadProxies(a.resolveAppPath("proxies.yaml"))
|
||
if err != nil {
|
||
log.Warn("读取 proxies.yaml 失败", logger.F("error", err.Error()))
|
||
}
|
||
if loaded != nil {
|
||
proxies := ensureBuiltins(loaded)
|
||
a.config.Browser.Proxies = proxies
|
||
log.Info("代理数据从 proxies.yaml 加载完成", logger.F("count", len(proxies)))
|
||
return
|
||
}
|
||
|
||
// 最终降级:使用 config.yaml 中的数据
|
||
proxies := ensureBuiltins(a.config.Browser.Proxies)
|
||
a.config.Browser.Proxies = proxies
|
||
log.Info("代理数据使用 config.yaml 默认值", logger.F("count", len(proxies)))
|
||
}
|