package utils import ( "fmt" "os" "os/user" "path/filepath" "regexp" "runtime" "sort" "strings" "time" ) // GetHostsPath 获取系统hosts文件路径 func GetHostsPath() string { switch runtime.GOOS { case "windows": return "C:\\Windows\\System32\\drivers\\etc\\hosts" case "linux", "darwin": return "/etc/hosts" default: return "/etc/hosts" } } // CheckAdmin 检查是否以管理员权限运行 func CheckAdmin() { if !isRunningAsAdmin() { if runtime.GOOS == "windows" { LogError("需要管理员权限运行") LogInfo("请右键点击命令提示符或PowerShell,选择'以管理员身份运行'") } else { LogError("需要root权限,请使用sudo运行") } os.Exit(1) } if runtime.GOOS == "windows" { LogSuccess("已以管理员权限运行") } } // ValidateBlockName 验证块名称是否有效 func ValidateBlockName(name string) bool { // 块名称只能包含字母、数字、下划线和连字符 matched, _ := regexp.MatchString(`^[a-zA-Z0-9_-]+$`, name) return matched && len(name) > 0 && len(name) <= 50 } // ValidateDomain 验证域名格式 func ValidateDomain(domain string) bool { // 简单的域名格式验证 matched, _ := regexp.MatchString(`^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](\.[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9])*$`, domain) return matched } // ValidateIP 验证IP地址格式 func ValidateIP(ip string) bool { // 简单的IP地址格式验证 parts := strings.Split(ip, ".") if len(parts) != 4 { return false } for _, part := range parts { if len(part) == 0 || len(part) > 3 { return false } // 检查是否为数字且在0-255范围内 num := 0 for _, c := range part { if c < '0' || c > '9' { return false } num = num*10 + int(c-'0') } if num > 255 { return false } } return true } // TrimString 去除字符串前后空白字符 func TrimString(s string) string { return strings.TrimSpace(s) } // IsComment 检查行是否为注释 func IsComment(line string) bool { trimmed := TrimString(line) return strings.HasPrefix(trimmed, "#") } // FileExists 检查文件是否存在 func FileExists(path string) bool { _, err := os.Stat(path) return !os.IsNotExist(err) } // WriteFile 写入文件 func WriteFile(path string, data []byte) error { // 创建目录 dir := strings.ReplaceAll(path, "\\", "/") if idx := strings.LastIndex(dir, "/"); idx != -1 { if err := os.MkdirAll(dir[:idx], 0755); err != nil { return err } } return os.WriteFile(path, data, 0644) } // ReadFile 读取文件 func ReadFile(path string) ([]byte, error) { return os.ReadFile(path) } // BackupFile 备份文件到用户备份目录 func BackupFile(path string) error { data, err := ReadFile(path) if err != nil { return err } // 导入config包获取备份目录 // 注意:这里需要确保config已经初始化 backupDir := getBackupDirectory() // 确保备份目录存在 if err := os.MkdirAll(backupDir, 0755); err != nil { return fmt.Errorf("创建备份目录失败: %v", err) } // 生成备份文件名(包含时间戳) filename := filepath.Base(path) timestamp := time.Now().Format("20060102_150405") backupFileName := fmt.Sprintf("%s.%s.backup", filename, timestamp) backupPath := filepath.Join(backupDir, backupFileName) // 写入备份文件 if err := WriteFile(backupPath, data); err != nil { return fmt.Errorf("写入备份文件失败: %v", err) } // 清理旧备份文件,只保留最新的几个 if err := cleanOldBackups(backupDir, filename, 5); err != nil { // 清理失败不影响备份操作,只记录日志 LogWarning("清理旧备份文件失败: %v", err) } return nil } // IsEmptyLine 检查是否为空行 func IsEmptyLine(line string) bool { return len(TrimString(line)) == 0 } // ParseHostsLine 解析hosts文件行 func ParseHostsLine(line string) (ip, domain, comment string) { line = TrimString(line) // 检查是否为注释行 if IsComment(line) { return "", "", line } // 分离注释部分 commentIndex := strings.Index(line, "#") if commentIndex != -1 { comment = TrimString(line[commentIndex:]) line = TrimString(line[:commentIndex]) } // 分离IP和域名 parts := strings.Fields(line) if len(parts) >= 2 { ip = parts[0] domain = parts[1] } return ip, domain, comment } // FormatHostsLine 格式化hosts文件行 func FormatHostsLine(ip, domain, comment string) string { if ip == "" || domain == "" { if comment != "" { return comment } return "" } line := fmt.Sprintf("%-15s %s", ip, domain) if comment != "" && !strings.HasPrefix(comment, "#") { line += " # " + comment } else if comment != "" { line += " " + comment } return line } // getBackupDirectory 获取备份目录 func getBackupDirectory() string { // 获取用户配置目录 currentUser, err := user.Current() if err != nil { return "backup" // 回退到相对路径 } return filepath.Join(currentUser.HomeDir, ".hostsync", "backup") } // cleanOldBackups 清理旧备份文件,只保留最新的count个 func cleanOldBackups(backupDir, originalFileName string, keepCount int) error { // 读取备份目录中的文件 files, err := os.ReadDir(backupDir) if err != nil { return err } // 筛选出匹配原文件名的备份文件 var backupFiles []os.DirEntry prefix := originalFileName + "." suffix := ".backup" for _, file := range files { if !file.IsDir() && strings.HasPrefix(file.Name(), prefix) && strings.HasSuffix(file.Name(), suffix) { backupFiles = append(backupFiles, file) } } // 如果备份文件数量不超过保留数量,直接返回 if len(backupFiles) <= keepCount { return nil } // 按修改时间排序(最新的在前) sort.Slice(backupFiles, func(i, j int) bool { infoI, _ := backupFiles[i].Info() infoJ, _ := backupFiles[j].Info() return infoI.ModTime().After(infoJ.ModTime()) }) // 删除多余的旧备份文件 for i := keepCount; i < len(backupFiles); i++ { oldBackupPath := filepath.Join(backupDir, backupFiles[i].Name()) if err := os.Remove(oldBackupPath); err != nil { LogWarning("删除旧备份文件失败: %s, %v", oldBackupPath, err) } } return nil } // DisplayWidth 计算字符串的显示宽度(中文字符占2个宽度) func DisplayWidth(s string) int { width := 0 for _, r := range s { if r > 127 { // 非ASCII字符 width += 2 } else { width += 1 } } return width } // PadString 根据显示宽度填充字符串 func PadString(s string, width int) string { currentWidth := DisplayWidth(s) if currentWidth >= width { return s } padding := strings.Repeat(" ", width-currentWidth) return s + padding } // TruncateWithWidth 根据显示宽度截断字符串 func TruncateWithWidth(s string, maxWidth int) string { if DisplayWidth(s) <= maxWidth { return s } width := 0 var result []rune for _, r := range s { charWidth := 1 if r > 127 { charWidth = 2 } if width+charWidth > maxWidth-3 { // 为"..."预留3个字符 break } result = append(result, r) width += charWidth } return string(result) + "..." } // FormatTable 格式化表格输出 func FormatTable(headers []string, rows [][]string, columnWidths []int) { // 如果没有指定列宽,自动计算 if len(columnWidths) == 0 { columnWidths = make([]int, len(headers)) // 计算标题宽度 for i, header := range headers { columnWidths[i] = DisplayWidth(header) } // 计算数据行宽度 for _, row := range rows { for i, cell := range row { if i < len(columnWidths) { width := DisplayWidth(cell) if width > columnWidths[i] { columnWidths[i] = width } } } } // 设置最小和最大宽度 for i := range columnWidths { if columnWidths[i] < 8 { columnWidths[i] = 8 } if columnWidths[i] > 40 { columnWidths[i] = 40 } } } // 打印顶部边框 printTableBorder(columnWidths, "┌", "┬", "┐") // 打印标题行 fmt.Print("│") for i, header := range headers { paddedHeader := PadString(" "+header+" ", columnWidths[i]+2) fmt.Print(paddedHeader + "│") } fmt.Println() // 打印标题分隔线 printTableBorder(columnWidths, "├", "┼", "┤") // 打印数据行 for _, row := range rows { fmt.Print("│") for i, cell := range row { if i < len(columnWidths) { // 截断过长的内容 truncatedCell := TruncateWithWidth(cell, columnWidths[i]) paddedCell := PadString(" "+truncatedCell+" ", columnWidths[i]+2) fmt.Print(paddedCell + "│") } } fmt.Println() } // 打印底部边框 printTableBorder(columnWidths, "└", "┴", "┘") } // printTableBorder 打印表格边框 func printTableBorder(columnWidths []int, left, middle, right string) { fmt.Print(left) for i, width := range columnWidths { fmt.Print(strings.Repeat("─", width+2)) if i < len(columnWidths)-1 { fmt.Print(middle) } } fmt.Println(right) }