Pārlūkot izejas kodu

新增IP地址显示

537yaha 1 mēnesi atpakaļ
vecāks
revīzija
8effb7e178

+ 3 - 3
.env.example

@@ -69,11 +69,11 @@ REDIS_WS_CHANNEL=ai_cs:ws_events
 # =========================
 # 访客 IP 地理位置(ip2region 离线库,可选)
 # =========================
-# true 时关闭解析;Docker 镜像内已内置 data/ip2region_v4.xdb
+# true 时关闭解析;Docker 镜像内已内置 v4+v6 数据文件
 IP2REGION_DISABLED=false
-# 自定义 xdb 路径(本地开发可下载到 backend/data/,见 backend/data/README.md
+# 自定义 xdb 路径(本地开发:sh scripts/download-ip2region-xdb.sh
 # IP2REGION_V4_XDB=backend/data/ip2region_v4.xdb
-# IP2REGION_V6_XDB=
+# IP2REGION_V6_XDB=backend/data/ip2region_v6.xdb
 
 # =========================
 # 端口映射(非必须)

+ 6 - 2
backend/Dockerfile

@@ -18,8 +18,11 @@ RUN go mod download
 COPY . .
 
 # 离线 IP 库(访客地理位置,见 backend/data/README.md)
-RUN mkdir -p data && curl -fsSL -o data/ip2region_v4.xdb \
-    https://github.com/lionsoul2014/ip2region/raw/master/data/ip2region_v4.xdb
+RUN mkdir -p data && \
+    curl -fsSL -o data/ip2region_v4.xdb \
+      https://github.com/lionsoul2014/ip2region/raw/master/data/ip2region_v4.xdb && \
+    curl -fsSL -o data/ip2region_v6.xdb \
+      https://github.com/lionsoul2014/ip2region/raw/master/data/ip2region_v6.xdb
 
 # 构建应用
 # CGO_ENABLED=0: 禁用 CGO,生成纯 Go 二进制文件
@@ -42,6 +45,7 @@ COPY --from=builder /app/backend .
 
 # ip2region 数据文件
 COPY --from=builder /app/data/ip2region_v4.xdb /app/data/ip2region_v4.xdb
+COPY --from=builder /app/data/ip2region_v6.xdb /app/data/ip2region_v6.xdb
 
 # 从构建阶段复制时区数据(可选)
 COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

+ 4 - 6
backend/data/README.md

@@ -4,16 +4,14 @@
 
 ## 获取数据文件
 
-将 **`ip2region_v4.xdb`** 放到本目录(与本文档同级):
+将 **`ip2region_v4.xdb`** 与 **`ip2region_v6.xdb`** 放到本目录(与本文档同级)。  
+访客 IP 若为 **IPv6**(如 `2403:...`),必须提供 v6 库才能显示位置。
 
 ```bash
-# 在项目根目录执行(需 curl)
-curl -L -o backend/data/ip2region_v4.xdb \
-  https://github.com/lionsoul2014/ip2region/raw/master/data/ip2region_v4.xdb
+# 在项目根目录执行(推荐脚本,一次下齐 v4+v6)
+sh scripts/download-ip2region-xdb.sh
 ```
 
-可选 IPv6:下载 `ip2region_v6.xdb` 并设置环境变量 `IP2REGION_V6_XDB`。
-
 ## 环境变量
 
 | 变量 | 说明 |

+ 26 - 11
backend/infra/geoip/resolver.go

@@ -86,10 +86,13 @@ func InitFromEnv() {
 	v4Path := strings.TrimSpace(os.Getenv("IP2REGION_V4_XDB"))
 	v6Path := strings.TrimSpace(os.Getenv("IP2REGION_V6_XDB"))
 	if v4Path == "" {
-		v4Path = findDefaultV4XDB()
+		v4Path = findDefaultXDB("ip2region_v4.xdb")
 	}
-	if v4Path == "" {
-		log.Println("⚠️ 未找到 ip2region xdb 文件,访客「位置」将保持为空;可将 data/ip2region_v4.xdb 放到 backend/data/ 或设置 IP2REGION_V4_XDB")
+	if v6Path == "" {
+		v6Path = findDefaultXDB("ip2region_v6.xdb")
+	}
+	if v4Path == "" && v6Path == "" {
+		log.Println("⚠️ 未找到 ip2region xdb,访客「位置」将保持为空;请放置 backend/data/ip2region_v4.xdb(及可选 v6)或设置 IP2REGION_V4_XDB / IP2REGION_V6_XDB")
 		return
 	}
 
@@ -99,22 +102,34 @@ func InitFromEnv() {
 		return
 	}
 	globalResolver = &ip2regionResolver{svc: svc}
-	log.Printf("✅ ip2region 已加载 (v4: %s)", v4Path)
+	if v4Path != "" {
+		log.Printf("✅ ip2region 已加载 (v4: %s)", v4Path)
+	}
 	if v6Path != "" {
-		log.Printf("   IPv6 库: %s", v6Path)
+		log.Printf("✅ ip2region 已加载 (v6: %s)", v6Path)
+	} else {
+		log.Println("ℹ️ 未加载 ip2region IPv6 库;访客若为 IPv6 地址将无法解析位置,可下载 ip2region_v6.xdb")
+	}
+}
+
+// LookupLocation 解析 IP;已有 location 非空时原样返回。
+func LookupLocation(ip, currentLocation string) string {
+	if strings.TrimSpace(currentLocation) != "" {
+		return currentLocation
 	}
+	return Get().Lookup(ip)
 }
 
-func findDefaultV4XDB() string {
+func findDefaultXDB(filename string) string {
 	candidates := []string{
-		"data/ip2region_v4.xdb",
-		"backend/data/ip2region_v4.xdb",
-		"/app/data/ip2region_v4.xdb",
+		"data/" + filename,
+		"backend/data/" + filename,
+		"/app/data/" + filename,
 	}
 	if wd, err := os.Getwd(); err == nil {
 		candidates = append(candidates,
-			filepath.Join(wd, "data", "ip2region_v4.xdb"),
-			filepath.Join(wd, "..", "backend", "data", "ip2region_v4.xdb"),
+			filepath.Join(wd, "data", filename),
+			filepath.Join(wd, "..", "backend", "data", filename),
 		)
 	}
 	for _, p := range candidates {

+ 17 - 5
backend/service/conversation_service.go

@@ -109,7 +109,7 @@ func (s *ConversationService) InitConversation(input InitConversationInput) (*In
 				OS:               input.OS,
 				Language:         input.Language,
 				IPAddress:        input.IPAddress,
-				Location:         geoip.Get().Lookup(input.IPAddress),
+				Location:         geoip.LookupLocation(input.IPAddress, ""),
 				LastSeenAt:       &now,
 				ChatMode:         chatMode,
 				AIConfigID:       aiConfigID,
@@ -161,10 +161,15 @@ func (s *ConversationService) InitConversation(input InitConversationInput) (*In
 		}
 		if input.IPAddress != "" && conv.IPAddress == "" {
 			updates["ip_address"] = input.IPAddress
-			if conv.Location == "" {
-				if loc := geoip.Get().Lookup(input.IPAddress); loc != "" {
-					updates["location"] = loc
-				}
+		}
+		// 补全地理位置:新 IP、或历史会话仅有 IP 无 location(如升级前创建、或当时缺 v6 库)
+		ipForGeo := conv.IPAddress
+		if input.IPAddress != "" {
+			ipForGeo = input.IPAddress
+		}
+		if conv.Location == "" && ipForGeo != "" {
+			if loc := geoip.LookupLocation(ipForGeo, ""); loc != "" {
+				updates["location"] = loc
 			}
 		}
 
@@ -432,6 +437,13 @@ func (s *ConversationService) GetConversationDetail(id uint, userID uint) (*Conv
 		return nil, gorm.ErrRecordNotFound
 	}
 
+	if conv.Location == "" && conv.IPAddress != "" {
+		if loc := geoip.LookupLocation(conv.IPAddress, ""); loc != "" {
+			_ = s.conversations.UpdateFields(conv.ID, map[string]interface{}{"location": loc})
+			conv.Location = loc
+		}
+	}
+
 	summary, err := s.buildSummary(*conv, userID)
 	if err != nil {
 		return nil, err

+ 9 - 6
scripts/download-ip2region-xdb.sh

@@ -1,9 +1,12 @@
 #!/usr/bin/env sh
-# 下载 ip2region v4 xdb 到 backend/data/(本地开发用)
+# 下载 ip2region v4/v6 xdb 到 backend/data/(本地开发用)
 set -e
 ROOT="$(cd "$(dirname "$0")/.." && pwd)"
-DEST="$ROOT/backend/data/ip2region_v4.xdb"
-mkdir -p "$(dirname "$DEST")"
-curl -fsSL -o "$DEST" \
-  https://github.com/lionsoul2014/ip2region/raw/master/data/ip2region_v4.xdb
-echo "OK: $DEST ($(wc -c < "$DEST" | tr -d ' ') bytes)"
+DIR="$ROOT/backend/data"
+mkdir -p "$DIR"
+BASE="https://github.com/lionsoul2014/ip2region/raw/master/data"
+for f in ip2region_v4.xdb ip2region_v6.xdb; do
+  DEST="$DIR/$f"
+  curl -fsSL -o "$DEST" "$BASE/$f"
+  echo "OK: $DEST ($(wc -c < "$DEST" | tr -d ' ') bytes)"
+done