添加 main.go
This commit is contained in:
219
main.go
Normal file
219
main.go
Normal file
@@ -0,0 +1,219 @@
|
||||
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 := `<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>CHNRoute RSC 服务</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
|
||||
h1 { color: #333; }
|
||||
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }
|
||||
pre { background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }
|
||||
.link { margin: 10px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🇨🇳 CHNRoute RSC 转换服务</h1>
|
||||
<p>自动从 <a href="https://github.com/mayaxcn/china-ip-list">mayaxcn/china-ip-list</a> 获取最新数据并转换为 RouterOS 格式</p>
|
||||
|
||||
<h2>📥 下载地址</h2>
|
||||
<div class="link">
|
||||
<strong>IPv4:</strong> <a href="/chnroute.rsc">/chnroute.rsc</a>
|
||||
</div>
|
||||
<div class="link">
|
||||
<strong>IPv6:</strong> <a href="/chnroute_v6.rsc">/chnroute_v6.rsc</a>
|
||||
</div>
|
||||
|
||||
<h2>🔧 RouterOS 使用方法</h2>
|
||||
<pre>/tool fetch url="http://你的服务器IP:8080/chnroute.rsc" mode=http
|
||||
/import chnroute.rsc
|
||||
/ip firewall address-list print count-only where list=chnroute</pre>
|
||||
|
||||
<h2>♻️ 更新策略</h2>
|
||||
<ul>
|
||||
<li>缓存时间:1 小时</li>
|
||||
<li>上游更新:每小时</li>
|
||||
<li>自动刷新缓存</li>
|
||||
</ul>
|
||||
|
||||
<h2>📊 健康检查</h2>
|
||||
<div class="link"><a href="/health">/health</a></div>
|
||||
</body>
|
||||
</html>`
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user