package main import ( "bufio" "fmt" "io" "log" "net/http" "strings" "sync" "time" ) const ( // 数据源 URL urlChnrouteIPv4 = "https://raw.githubusercontent.com/mayaxcn/china-ip-list/master/chnroute.txt" urlChnrouteIPv6 = "https://raw.githubusercontent.com/mayaxcn/china-ip-list/master/chnroute_v6.txt" // 缓存时间(1小时) cacheDuration = 1 * time.Hour ) // 缓存结构 type Cache struct { data string timestamp time.Time mu sync.RWMutex } var ( cacheIPv4 = &Cache{} cacheIPv6 = &Cache{} ) // 从 URL 获取数据 func fetchData(url string) (string, error) { resp, err := http.Get(url) if err != nil { return "", fmt.Errorf("fetch error: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("HTTP %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("read error: %w", err) } return string(body), nil } // 转换为 RouterOS RSC 格式 func convertToRSC(cidrList string, listName string, ipVersion string) string { var sb strings.Builder scanner := bufio.NewScanner(strings.NewReader(cidrList)) // 添加头部注释 sb.WriteString(fmt.Sprintf("# China IP List - RouterOS Script\n")) sb.WriteString(fmt.Sprintf("# Generated: %s\n", time.Now().Format("2006-01-02 15:04:05"))) sb.WriteString(fmt.Sprintf("# Source: https://github.com/mayaxcn/china-ip-list\n\n")) // 根据 IP 版本选择命令前缀 prefix := "/ip firewall address-list" if ipVersion == "6" { prefix = "/ipv6 firewall address-list" } count := 0 for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } sb.WriteString(fmt.Sprintf("%s add list=%s address=%s\n", prefix, listName, line)) count++ } sb.WriteString(fmt.Sprintf("\n# Total: %d entries\n", count)) return sb.String() } // 获取或更新缓存 func getOrUpdateCache(cache *Cache, url string, listName string, ipVersion string) (string, error) { cache.mu.RLock() if time.Since(cache.timestamp) < cacheDuration && cache.data != "" { defer cache.mu.RUnlock() return cache.data, nil } cache.mu.RUnlock() // 需要更新缓存 cache.mu.Lock() defer cache.mu.Unlock() // 双重检查 if time.Since(cache.timestamp) < cacheDuration && cache.data != "" { return cache.data, nil } log.Printf("Fetching data from %s...", url) cidrData, err := fetchData(url) if err != nil { return "", err } rscData := convertToRSC(cidrData, listName, ipVersion) cache.data = rscData cache.timestamp = time.Now() log.Printf("Cache updated: %d bytes", len(rscData)) return rscData, nil } // HTTP 处理器 func handleIPv4(w http.ResponseWriter, r *http.Request) { data, err := getOrUpdateCache(cacheIPv4, urlChnrouteIPv4, "chnroute", "4") if err != nil { http.Error(w, fmt.Sprintf("Error: %v", err), http.StatusInternalServerError) log.Printf("IPv4 error: %v", err) return } w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Disposition", "attachment; filename=chnroute.rsc") w.Write([]byte(data)) log.Printf("IPv4 request served: %s", r.RemoteAddr) } func handleIPv6(w http.ResponseWriter, r *http.Request) { data, err := getOrUpdateCache(cacheIPv6, urlChnrouteIPv6, "chnroute6", "6") if err != nil { http.Error(w, fmt.Sprintf("Error: %v", err), http.StatusInternalServerError) log.Printf("IPv6 error: %v", err) return } w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Disposition", "attachment; filename=chnroute_v6.rsc") w.Write([]byte(data)) log.Printf("IPv6 request served: %s", r.RemoteAddr) } func handleHealth(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"status":"ok","cache_age_ipv4":"%s","cache_age_ipv6":"%s"}`, time.Since(cacheIPv4.timestamp).Round(time.Second), time.Since(cacheIPv6.timestamp).Round(time.Second)) } func handleRoot(w http.ResponseWriter, r *http.Request) { html := ` CHNRoute RSC 服务

🇨🇳 CHNRoute RSC 转换服务

自动从 mayaxcn/china-ip-list 获取最新数据并转换为 RouterOS 格式

📥 下载地址

🔧 RouterOS 使用方法

/tool fetch url="http://你的服务器IP:8080/chnroute.rsc" mode=http
/import chnroute.rsc
/ip firewall address-list print count-only where list=chnroute

♻️ 更新策略

📊 健康检查

` w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write([]byte(html)) } func main() { // 启动时预热缓存 go func() { log.Println("Warming up cache...") getOrUpdateCache(cacheIPv4, urlChnrouteIPv4, "chnroute", "4") getOrUpdateCache(cacheIPv6, urlChnrouteIPv6, "chnroute6", "6") log.Println("Cache warmed up") }() // 路由 http.HandleFunc("/", handleRoot) http.HandleFunc("/chnroute.rsc", handleIPv4) http.HandleFunc("/chnroute_v6.rsc", handleIPv6) http.HandleFunc("/health", handleHealth) port := ":8080" log.Printf("🚀 Server starting on http://0.0.0.0%s", port) if err := http.ListenAndServe(port, nil); err != nil { log.Fatal(err) } }