feat: update config, geoip, server, and videoapp frontend
This commit is contained in:
@@ -44,13 +44,15 @@ func init() {
|
||||
rootCmd.PersistentFlags().IntP("port", "p", 8080, "监听端口")
|
||||
rootCmd.PersistentFlags().String("mode", "release", "运行模式 (debug/release/test)")
|
||||
rootCmd.PersistentFlags().Bool("geoip", false, "启用 IP 地理位置解析")
|
||||
rootCmd.PersistentFlags().String("geoip-db", "./data/ip2region.xdb", "ip2region 数据库路径")
|
||||
rootCmd.PersistentFlags().String("geoip-db-v4", "./data/ip2region_v4.xdb", "ip2region IPv4 数据库路径")
|
||||
rootCmd.PersistentFlags().String("geoip-db-v6", "./data/ip2region_v6.xdb", "ip2region IPv6 数据库路径")
|
||||
|
||||
viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("host"))
|
||||
viper.BindPFlag("port", rootCmd.PersistentFlags().Lookup("port"))
|
||||
viper.BindPFlag("mode", rootCmd.PersistentFlags().Lookup("mode"))
|
||||
viper.BindPFlag("geoip", rootCmd.PersistentFlags().Lookup("geoip"))
|
||||
viper.BindPFlag("geoip_db", rootCmd.PersistentFlags().Lookup("geoip-db"))
|
||||
viper.BindPFlag("geoip_db_v4", rootCmd.PersistentFlags().Lookup("geoip-db-v4"))
|
||||
viper.BindPFlag("geoip_db_v6", rootCmd.PersistentFlags().Lookup("geoip-db-v6"))
|
||||
|
||||
// 添加子命令
|
||||
rootCmd.AddCommand(serveCmd)
|
||||
|
||||
@@ -6,15 +6,15 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/service"
|
||||
)
|
||||
|
||||
// GeoIP 基于 ip2region 的 IP 地理位置解析器
|
||||
// GeoIP 基于 ip2region 的 IP 地理位置解析器(IPv4 + IPv6)
|
||||
type GeoIP struct {
|
||||
mu sync.RWMutex
|
||||
searcher *xdb.Searcher
|
||||
enabled bool
|
||||
cache map[string]*GeoInfo
|
||||
mu sync.RWMutex
|
||||
engine *service.Ip2Region
|
||||
enabled bool
|
||||
cache map[string]*GeoInfo
|
||||
}
|
||||
|
||||
// GeoInfo IP 地理位置信息
|
||||
@@ -26,27 +26,36 @@ type GeoInfo struct {
|
||||
Region string
|
||||
}
|
||||
|
||||
// NewGeoIP 创建解析器,dbPath 是 ip2region.xdb 路径
|
||||
func NewGeoIP(dbPath string, enableCache bool) (*GeoIP, error) {
|
||||
// NewGeoIP 创建解析器,v4Path 和 v6Path 为 ip2region xdb 路径
|
||||
// 两个路径均可为空(为空则禁用对应版本),但至少需要一个有效
|
||||
func NewGeoIP(v4Path, v6Path string) (*GeoIP, error) {
|
||||
g := &GeoIP{
|
||||
enabled: true,
|
||||
cache: make(map[string]*GeoInfo),
|
||||
}
|
||||
|
||||
searcher, err := xdb.NewWithFileOnly(xdb.IPv4, dbPath)
|
||||
engine, err := service.NewIp2RegionWithPath(v4Path, v6Path)
|
||||
if err != nil {
|
||||
log.Printf("[geoip] 无法加载 %s: %v,禁用 IP 解析", dbPath, err)
|
||||
log.Printf("[geoip] 加载失败 (v4=%s, v6=%s): %v,禁用 IP 解析", v4Path, v6Path, err)
|
||||
g.enabled = false
|
||||
return g, nil
|
||||
}
|
||||
g.searcher = searcher
|
||||
log.Printf("[geoip] 已加载 IP 地理位置库: %s", dbPath)
|
||||
g.engine = engine
|
||||
|
||||
loaded := []string{}
|
||||
if v4Path != "" {
|
||||
loaded = append(loaded, "IPv4")
|
||||
}
|
||||
if v6Path != "" {
|
||||
loaded = append(loaded, "IPv6")
|
||||
}
|
||||
log.Printf("[geoip] 已加载 IP 地理位置库: %s", strings.Join(loaded, " + "))
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// Lookup 查询 IP 对应的地理位置
|
||||
// Lookup 查询 IP 对应的地理位置(自动识别 IPv4/IPv6)
|
||||
func (g *GeoIP) Lookup(ip string) *GeoInfo {
|
||||
if !g.enabled || g.searcher == nil {
|
||||
if !g.enabled || g.engine == nil {
|
||||
return &GeoInfo{Region: "未知"}
|
||||
}
|
||||
|
||||
@@ -61,7 +70,7 @@ func (g *GeoIP) Lookup(ip string) *GeoInfo {
|
||||
}
|
||||
g.mu.RUnlock()
|
||||
|
||||
region, err := g.searcher.Search(ip)
|
||||
region, err := g.engine.Search(ip)
|
||||
if err != nil {
|
||||
return &GeoInfo{Region: "未知"}
|
||||
}
|
||||
@@ -118,7 +127,9 @@ func isPrivateIP(ip string) bool {
|
||||
strings.HasPrefix(ip, "192.168.") ||
|
||||
strings.HasPrefix(ip, "127.") ||
|
||||
ip == "::1" ||
|
||||
strings.HasPrefix(ip, "fe80:") {
|
||||
strings.HasPrefix(ip, "fe80:") ||
|
||||
strings.HasPrefix(ip, "fc") ||
|
||||
strings.HasPrefix(ip, "fd") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -126,8 +137,8 @@ func isPrivateIP(ip string) bool {
|
||||
|
||||
// Close 释放资源
|
||||
func (g *GeoIP) Close() {
|
||||
if g.searcher != nil {
|
||||
g.searcher.Close()
|
||||
if g.engine != nil {
|
||||
g.engine.Close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,20 +19,21 @@ type ServerConfig struct {
|
||||
Port int
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
Mode string // debug / release / test
|
||||
Mode string
|
||||
}
|
||||
|
||||
// AnalyticsConfig 统计配置
|
||||
type AnalyticsConfig struct {
|
||||
Retention time.Duration
|
||||
CleanupInterval time.Duration
|
||||
Buckets int // 时间序列分桶数
|
||||
Buckets int
|
||||
}
|
||||
|
||||
// GeoIPConfig IP 地理位置配置
|
||||
type GeoIPConfig struct {
|
||||
Enabled bool
|
||||
DBPath string
|
||||
Enabled bool
|
||||
DBPathV4 string // ip2region_v4.xdb
|
||||
DBPathV6 string // ip2region_v6.xdb
|
||||
}
|
||||
|
||||
// Default 默认配置
|
||||
@@ -51,8 +52,9 @@ func Default() *Config {
|
||||
Buckets: 60,
|
||||
},
|
||||
GeoIP: GeoIPConfig{
|
||||
Enabled: true,
|
||||
DBPath: "./data/ip2region.xdb",
|
||||
Enabled: true,
|
||||
DBPathV4: "./data/ip2region_v4.xdb",
|
||||
DBPathV6: "./data/ip2region_v6.xdb",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -79,8 +81,11 @@ func Load(v *viper.Viper) *Config {
|
||||
if v.IsSet("geoip") {
|
||||
cfg.GeoIP.Enabled = v.GetBool("geoip")
|
||||
}
|
||||
if v.IsSet("geoip_db") {
|
||||
cfg.GeoIP.DBPath = v.GetString("geoip_db")
|
||||
if v.IsSet("geoip_db_v4") {
|
||||
cfg.GeoIP.DBPathV4 = v.GetString("geoip_db_v4")
|
||||
}
|
||||
if v.IsSet("geoip_db_v6") {
|
||||
cfg.GeoIP.DBPathV6 = v.GetString("geoip_db_v6")
|
||||
}
|
||||
|
||||
return cfg
|
||||
|
||||
@@ -39,8 +39,8 @@ func New(cfg *config.Config) (*Server, error) {
|
||||
// 设置 gin 模式
|
||||
gin.SetMode(cfg.Server.Mode)
|
||||
|
||||
// 初始化 IP 地理位置
|
||||
geoip, err := analytics.NewGeoIP(cfg.GeoIP.DBPath, true)
|
||||
// 初始化 IP 地理位置(IPv4 + IPv6)
|
||||
geoip, err := analytics.NewGeoIP(cfg.GeoIP.DBPathV4, cfg.GeoIP.DBPathV6)
|
||||
if err != nil {
|
||||
log.Printf("[server] 初始化 GeoIP 失败: %v", err)
|
||||
geoip = nil
|
||||
|
||||
@@ -68,6 +68,12 @@ function navigate(hash) {
|
||||
|
||||
function handleRoute() {
|
||||
const hash = window.location.hash || '#/home';
|
||||
|
||||
// 离开 watch 页面时停止视频播放
|
||||
if (!hash.startsWith('#/watch/')) {
|
||||
stopVideo();
|
||||
}
|
||||
|
||||
const pages = document.querySelectorAll('.page');
|
||||
pages.forEach(p => p.classList.add('hidden'));
|
||||
|
||||
@@ -347,6 +353,23 @@ async function extractCurrent() {
|
||||
await loadVideoDetail(currentViewkey);
|
||||
}
|
||||
|
||||
function stopVideo() {
|
||||
const video = document.getElementById('videoPlayer');
|
||||
if (!video) return;
|
||||
video.pause();
|
||||
video.removeAttribute('src');
|
||||
document.getElementById('videoSource').src = '';
|
||||
video.load();
|
||||
// 重置UI状态
|
||||
document.getElementById('playerBox').classList.remove('hidden');
|
||||
document.getElementById('videoPlayer').classList.add('hidden');
|
||||
document.getElementById('playerError').classList.add('hidden');
|
||||
document.getElementById('playerLabel').textContent = '正在提取视频源...';
|
||||
document.getElementById('playerStatus').classList.add('hidden');
|
||||
currentViewkey = null;
|
||||
extractedVideoURL = null;
|
||||
}
|
||||
|
||||
function copyVideoURL() {
|
||||
if (extractedVideoURL) {
|
||||
navigator.clipboard.writeText(extractedVideoURL).then(() => {
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
<div class="video-actions">
|
||||
<button class="btn btn-primary" id="btnExtract" onclick="extractCurrent()">🔗 重新提取</button>
|
||||
<button class="btn btn-outline" id="btnCopyURL" onclick="copyVideoURL()" disabled>📋 复制源地址</button>
|
||||
<a class="btn btn-outline hidden" id="btnOpenNew" target="_blank" rel="noopener">🔗 新窗口打开</a>
|
||||
<a class="btn btn-outline hidden" id="btnOpenNew" target="_blank" rel="noopener">📥 下载</a>
|
||||
</div>
|
||||
<div class="video-source hidden" id="videoSourceBox">
|
||||
<label>视频源地址:</label>
|
||||
|
||||
Reference in New Issue
Block a user