network-infra-utility 0.5.0 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +6 -0
- data/CHANGELOG.md +31 -0
- data/GUIDE.md +201 -0
- data/README.md +1 -6
- data/Rakefile +1 -1
- data/bin/dns-query +4 -2
- data/bin/gen-get +144 -0
- data/bin/geo-api +40 -1
- data/bin/geo-get +18 -36
- data/bin/geo-update +429 -0
- data/bin/ngeo-get +253 -0
- data/bin/packbit +227 -0
- data/bin/probe +134 -0
- data/document/reference/DNS/346/212/200/346/234/257/346/226/207/346/241/243.md +912 -0
- data/network-infra-utility.gemspec +6 -4
- data/network.rb +19 -1
- data/service/geodb/GeoAPI.md +64 -0
- data/service/geodb/api.rb +77 -19
- data/service/geoquery/GeoQuery.md +309 -0
- data/service/geoquery/cache.rb +78 -0
- data/service/geoquery/geo_cache.rb +233 -0
- data/service/geoquery/geoquery.rb +327 -0
- data/service/geoquery/local.rb +151 -0
- data/service/geoquery/merge.rb +52 -0
- data/service/geoquery/normalize.rb +102 -0
- data/service/geoquery/online.rb +136 -0
- data/service/simlab/SimLab.md +140 -0
- data/service/simlab/bin/simctl +152 -0
- data/service/simlab/lib/simlab.rb +124 -0
- data/service/simlab/lib/simlab_engine.rb +146 -0
- data/service/simlab/lib/simlab_observer.rb +176 -0
- data/service/simlab/lib/simlab_router.rb +330 -0
- data/service/simlab/lib/simlab_scenario.rb +200 -0
- data/service/simlab/lib/simlab_switch.rb +182 -0
- data/service/simlab/lib/simlab_topology.rb +196 -0
- data/service/simlab/lib/simlab_traffic.rb +167 -0
- data/service/ssh/README.md +50 -37
- data/service/ssh/ext/ssh_core/src/ssh_infra_sup.erl +14 -1
- data/support/basic/packet.rb +167 -0
- data/support/routing/bgp.rb +279 -0
- data/support/routing/lpm_trie.rb +145 -0
- data/support/routing/ospf.rb +243 -0
- data/support/routing/prefix.rb +155 -0
- data/support/routing/rib.rb +167 -0
- data/support/routing/rip.rb +223 -0
- data/support/routing/static_route.rb +102 -0
- data/support/switching/frame_forward.rb +87 -0
- data/support/switching/mac_table.rb +127 -0
- data/support/switching/stp.rb +230 -0
- data/support/switching/vlan.rb +155 -0
- data/tool/packbit/README.md +136 -0
- data/tool/packbit/config/five-tuple.yml +25 -0
- data/tool/packbit/config/flow.yml +17 -0
- data/tool/packbit/config/packbit.yml +41 -0
- data/tool/packbit/erl/packbit.erl +588 -0
- data/tool/probe/README.md +88 -0
- data/tool/probe/lib/probe.rb +54 -0
- data/tool/probe/lib/probe_base.rb +98 -0
- data/tool/probe/lib/probe_dns.rb +64 -0
- data/tool/probe/lib/probe_icmp.rb +71 -0
- data/tool/probe/lib/probe_ntp.rb +51 -0
- data/tool/probe/lib/probe_radius.rb +58 -0
- data/tool/probe/lib/probe_registry.rb +44 -0
- data/tool/probe/lib/probe_result.rb +75 -0
- data/tool/probe/lib/probe_snmp.rb +114 -0
- data/tool/probe/lib/probe_tcp.rb +82 -0
- data/tool/probe/lib/probe_version.rb +7 -0
- data/version.rb +1 -1
- metadata +78 -2
|
@@ -18,16 +18,18 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
spec.metadata["source_code_uri"] = "https://github.com/ChenMeng1365/network-infra-utility"
|
|
19
19
|
spec.metadata["changelog_uri"] = "https://github.com/ChenMeng1365/network-infra-utility/blob/main/CHANGELOG.md"
|
|
20
20
|
|
|
21
|
+
# example 用例随目录重组移至 document/example/, 排除规则同步覆盖新旧路径
|
|
21
22
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
22
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features|example)/}) }
|
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features|example|document/example)/}) }
|
|
23
24
|
end
|
|
24
25
|
# bin/ 既放对外命令 (geo-api) 也放开发脚本 (console/setup),
|
|
25
26
|
# executables 显式声明,避免把开发脚本当作系统命令安装到用户 PATH。
|
|
26
27
|
spec.bindir = "bin"
|
|
27
|
-
spec.executables = %w[geo-api geo-get geo-load geo-doc]
|
|
28
|
-
spec.require_paths = ["document", "service", "service/ssh/lib", "support", "tool", "."]
|
|
28
|
+
spec.executables = %w[geo-api geo-get geo-load geo-doc geo-update gen-get ngeo-get dns-query packbit probe]
|
|
29
|
+
spec.require_paths = ["document", "service", "service/ssh/lib", "service/simlab/lib", "support", "tool", "tool/probe/lib", "."]
|
|
29
30
|
|
|
30
|
-
#
|
|
31
|
+
# 仿真场景 DSL 需要的运行时 gem
|
|
32
|
+
spec.add_runtime_dependency "yaml", "~> 0.1"
|
|
31
33
|
spec.add_runtime_dependency "roda", "~> 3.0"
|
|
32
34
|
spec.add_runtime_dependency "rackup", "~> 2.0"
|
|
33
35
|
spec.add_runtime_dependency "puma", "~> 6.0"
|
data/network.rb
CHANGED
|
@@ -15,12 +15,30 @@ module NetworkInfraUtility
|
|
|
15
15
|
require_relative "support/basic/ip"
|
|
16
16
|
require_relative "support/basic/as_num"
|
|
17
17
|
require_relative "support/basic/mac_address"
|
|
18
|
+
require_relative "support/basic/packet"
|
|
19
|
+
|
|
20
|
+
# 路由原理层:依赖 support/basic
|
|
21
|
+
require_relative "support/routing/prefix"
|
|
22
|
+
require_relative "support/routing/lpm_trie"
|
|
23
|
+
require_relative "support/routing/rib"
|
|
24
|
+
require_relative "support/routing/static_route"
|
|
25
|
+
require_relative "support/routing/rip"
|
|
26
|
+
require_relative "support/routing/ospf"
|
|
27
|
+
require_relative "support/routing/bgp"
|
|
28
|
+
|
|
29
|
+
# 交换原理层:依赖 support/basic
|
|
30
|
+
require_relative "support/switching/mac_table"
|
|
31
|
+
require_relative "support/switching/frame_forward"
|
|
32
|
+
require_relative "support/switching/vlan"
|
|
33
|
+
require_relative "support/switching/stp"
|
|
18
34
|
|
|
19
35
|
# 工具层:依赖 support
|
|
20
|
-
|
|
36
|
+
require_relative "tool/probe/lib/probe"
|
|
21
37
|
|
|
22
38
|
# 服务层:依赖 tool / support
|
|
39
|
+
require_relative "service/simlab/lib/simlab"
|
|
23
40
|
require_relative "service/ssh/lib/network_infra_utility/ssh"
|
|
41
|
+
require_relative "service/geoquery/geoquery"
|
|
24
42
|
|
|
25
43
|
# 文档层:依赖 service / tool,最后加载
|
|
26
44
|
# require_relative "document/xxx"
|
data/service/geodb/GeoAPI.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
AIGC:
|
|
3
|
+
ContentProducer: '001191110102MAD55U9H0F10002'
|
|
4
|
+
ContentPropagator: '001191110102MAD55U9H0F10002'
|
|
5
|
+
Label: '1'
|
|
6
|
+
ProduceID: 'f8a5515f-8063-4c0f-82de-6f5d3921a8d0'
|
|
7
|
+
PropagateID: 'f8a5515f-8063-4c0f-82de-6f5d3921a8d0'
|
|
8
|
+
ReservedCode1: 'e9dc29f2-2c00-4f09-a5c5-2d487e36268f'
|
|
9
|
+
ReservedCode2: 'e9dc29f2-2c00-4f09-a5c5-2d487e36268f'
|
|
10
|
+
---
|
|
1
11
|
|
|
2
12
|
# GeoAPI 测试用例
|
|
3
13
|
|
|
@@ -219,6 +229,54 @@ IP 合法性说明:含 `:` 按 IPv6 解析,否则按 IPv4 解析;非法地
|
|
|
219
229
|
|
|
220
230
|
---
|
|
221
231
|
|
|
232
|
+
## (7) GEO_CACHE 外带缓存兜底 — `geo-api -a`
|
|
233
|
+
|
|
234
|
+
服务端以 `geo-api -d GEODB_PATH -a GEO_CACHE [--priority local,cache]` 启动后,
|
|
235
|
+
`addr` 类查询按顺位编排:先查本地 GeoLite2 库,本地查不到(404)时用 GEO_CACHE
|
|
236
|
+
目录内 `geocacheYYYYMMDD.json`(`ngeo-get -c` 产出)的缓存定位信息兜底,统一
|
|
237
|
+
schema 转换为各接口的 GeoLite2 风格响应,并附加 `"cached": true` 标记。
|
|
238
|
+
`id` / `num` 类查询不适用缓存(非 IP 键)。目录内文件增删改自动感知,无需重启。
|
|
239
|
+
|
|
240
|
+
### 7.1 启动与根路由
|
|
241
|
+
|
|
242
|
+
| # | 命令 | 预期结果 |
|
|
243
|
+
|---|------|----------|
|
|
244
|
+
| 7.1.1 | `geo-api -d ./geodb -a ./GEO_CACHE` | 启动横幅显示缓存目录与文件数、兑底顺位 `local,cache` |
|
|
245
|
+
| 7.1.2 | `curl -s "localhost:9292/"` | 200,含 `cache_dir` 与 `cache_order:"local,cache"` |
|
|
246
|
+
| 7.1.3 | `geo-api -a ./不存在的目录` | 警告但继续启动(目录后续创建自动感知) |
|
|
247
|
+
| 7.1.4 | `geo-api --priority cache,local` | 兑底顺位改为缓存优先 |
|
|
248
|
+
|
|
249
|
+
### 7.2 缓存兜底查询
|
|
250
|
+
|
|
251
|
+
预置 `GEO_CACHE/geocache20260101.json`:
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{ "203.0.113.7": { "state": "local", "country": "中国", "province": "浙江",
|
|
255
|
+
"city": "杭州", "asn": "4134", "asn_org": "Chinanet",
|
|
256
|
+
"network": "203.0.113.0/24", "ts": 100 } }
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
| # | curl 命令 | 预期结果 |
|
|
260
|
+
|---|-----------|----------|
|
|
261
|
+
| 7.2.1 | `curl -s "localhost:9292/geo/city?addr=203.0.113.7"` | 200,`geoname.subdivision_1_name:"浙江"`、`geoname.city_namezh:"杭州"`、`cached:true`(本地库无此 TEST-NET-3 地址,走缓存兜底)|
|
|
262
|
+
| 7.2.2 | `curl -s "localhost:9292/geo/asn?addr=203.0.113.7"` | 200,`autonomous_system_number:"4134"`、`cached:true` |
|
|
263
|
+
| 7.2.3 | `curl -s "localhost:9292/geo/country?addr=203.0.113.7"` | 200,`geoname.country_name:"中国"`、`cached:true` |
|
|
264
|
+
| 7.2.4 | `curl -s "localhost:9292/geo/asn?addr=8.8.8.8"` | 200,Google LLC,**无** `cached` 字段(本地库命中,不走缓存,local 优先)|
|
|
265
|
+
| 7.2.5 | 缓存记录 `state:"empty"` 的 IP 查 city | 404(缓存确认无归属,无兜底数据)|
|
|
266
|
+
| 7.2.6 | 缓存记录无 `asn` 字段的 IP 查 asn | 404(对应字段缺失,该接口无数据)|
|
|
267
|
+
|
|
268
|
+
### 7.3 兜底响应结构(缓存命中)
|
|
269
|
+
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"network": "203.0.113.0/24",
|
|
273
|
+
"geoname": { "country_name": "中国", "subdivision_1_name": "浙江", "city_namezh": "杭州" },
|
|
274
|
+
"cached": true
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
222
280
|
## 备注
|
|
223
281
|
|
|
224
282
|
1. **ASN 的 `num` 非数字**:需求第(1)点只要求 city/country 的 id 做数字校验,未规定 asn 的 num。当前实现在 `num=abc` 时返回 `count:0`(空结果,状态 200)。如需返回 `400 ASN编号不合法`,可在 `api.rb` 中加一行校验。
|
|
@@ -226,3 +284,9 @@ IP 合法性说明:含 `:` 按 IPv6 解析,否则按 IPv4 解析;非法地
|
|
|
226
284
|
2. **city-IPv6.json**:当前 `geodb/` 目录下已生成此文件,IPv6 city 查询可正常命中。文件缺失时服务端会容错返回 `无结果`(404),不影响其他接口。
|
|
227
285
|
|
|
228
286
|
3. **加载耗时(首次,含 JSON 解析)**:asn 约 3.5s / country-IPv4 约 2.9s / country-IPv6 约 3.6s / city-IPv4 约 22.8s,之后常驻内存走缓存,查询耗时约 0ms。
|
|
287
|
+
|
|
288
|
+
4. **GEO_CACHE 与服务端缓存(`@store`)的区别**:`@store` 是数据文件加载内存缓存(进程内常驻);GEO_CACHE 是外带定位信息缓存目录(ngeo-get 产出的查询结果),用于 addr 查询兜底,两者互不影响。
|
|
289
|
+
|
|
290
|
+
5. **geo-api 的顺位参数是 `--priority`**(不是 `-p`):`-p` 已被监听端口占用。服务端仅支持 `local,cache` 排列(无互联网源);客户端 `ngeo-get -p` 支持 `cache,local,internet` 三源排列。
|
|
291
|
+
|
|
292
|
+
> AI生成
|
data/service/geodb/api.rb
CHANGED
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
#
|
|
7
7
|
# 数据文件目录通过环境变量 GEODB_DATA_DIR 指定,默认 ./geodb/
|
|
8
8
|
# 用 -d/--data-dir 参数 (命令行) 或设置该环境变量可指向任意位置。
|
|
9
|
+
#
|
|
10
|
+
# GEO_CACHE 外带缓存 (bin/geo-api -a 或环境变量 GEODB_CACHE_DIR 指定目录):
|
|
11
|
+
# 目录内 geocacheYYYYMMDD.json (ngeo-get -c 产出) 作为缓存定位信息。
|
|
12
|
+
# addr 查询按 GEODB_CACHE_ORDER 顺位 (默认 local,cache) 编排:
|
|
13
|
+
# 先查本地 GeoLite2 库, 查不到时用缓存记录兑底 (统一 schema 转为
|
|
14
|
+
# 各接口的 GeoLite2 风格响应, 对客户端透明)。目录内文件增删改自动感知。
|
|
9
15
|
['cc','CasetDown/casetdown','network','roda'].each{|mod| require mod}
|
|
16
|
+
require_relative '../geoquery/geo_cache'
|
|
10
17
|
|
|
11
18
|
module GeoDB
|
|
12
19
|
module_function
|
|
@@ -26,6 +33,26 @@ module GeoDB
|
|
|
26
33
|
# asn 反向索引: autonomous_system_number => [record, ...]
|
|
27
34
|
@asn_index = {}
|
|
28
35
|
@lock = Mutex.new
|
|
36
|
+
# GEO_CACHE 外带缓存实例 (GEODB_CACHE_DIR 设置时启用)
|
|
37
|
+
@geo_cache = nil
|
|
38
|
+
|
|
39
|
+
# ---- GEO_CACHE 外带缓存 -------------------------------------------------
|
|
40
|
+
|
|
41
|
+
# 缓存实例 (惰性建立; 目录不存在时仍建立, 后续创建/产出可自动感知)
|
|
42
|
+
def geo_cache
|
|
43
|
+
@geo_cache ||= begin
|
|
44
|
+
d = ENV['GEODB_CACHE_DIR'].to_s
|
|
45
|
+
d.empty? ? nil : GeoQuery::GeoCache.new(d)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# 查询顺位: local (GeoLite2 库) 与 cache (GEO_CACHE) 的适用顺序。
|
|
50
|
+
# GEODB_CACHE_ORDER 指定 (bin/geo-api --priority), 默认 local,cache。
|
|
51
|
+
def cache_order
|
|
52
|
+
parts = ENV['GEODB_CACHE_ORDER'].to_s.split(/[,\s]+/).map(&:strip).reject(&:empty?)
|
|
53
|
+
parts = %w[local cache] if parts.empty?
|
|
54
|
+
parts.select { |p| %w[local cache].include?(p) }.uniq
|
|
55
|
+
end
|
|
29
56
|
|
|
30
57
|
# ---- 加载 ---------------------------------------------------------------
|
|
31
58
|
|
|
@@ -112,6 +139,44 @@ module GeoDB
|
|
|
112
139
|
(number >= s && number <= e) ? r : nil
|
|
113
140
|
end
|
|
114
141
|
|
|
142
|
+
# ---- addr 查询编排 (本地库 + GEO_CACHE) ---------------------------------
|
|
143
|
+
|
|
144
|
+
# 按 cache_order 顺位查本地库与 GEO_CACHE (kind: :asn/:city/:country)
|
|
145
|
+
# 返回: :invalid(IP不合法) / nil(无结果) / record
|
|
146
|
+
def lookup_addr(kind, addr)
|
|
147
|
+
num = ip_number(addr)
|
|
148
|
+
return :invalid unless num
|
|
149
|
+
cache_order.each do |src|
|
|
150
|
+
r = src == 'cache' ? cache_lookup(kind, addr) : local_lookup(kind, addr, num)
|
|
151
|
+
return r if r
|
|
152
|
+
end
|
|
153
|
+
nil
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# 本地 GeoLite2 库查询 (原 addr 查询逻辑)
|
|
157
|
+
def local_lookup(kind, addr, num)
|
|
158
|
+
case kind
|
|
159
|
+
when :asn
|
|
160
|
+
find_range('asn', num)
|
|
161
|
+
when :city
|
|
162
|
+
r = find_range(ipv6?(addr) ? 'city-IPv6' : 'city-IPv4', num)
|
|
163
|
+
r ? enrich(r, 'geo-city') : nil
|
|
164
|
+
when :country
|
|
165
|
+
r = find_range(ipv6?(addr) ? 'country-IPv6' : 'country-IPv4', num)
|
|
166
|
+
r ? enrich(r, 'geo-country') : nil
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# GEO_CACHE 兑底: 命中则把统一 schema 转为该接口的 GeoLite2 风格响应
|
|
171
|
+
# (对应字段缺失时 to_geolite 返回 nil, 继续下一数据源)
|
|
172
|
+
def cache_lookup(kind, addr)
|
|
173
|
+
gc = geo_cache
|
|
174
|
+
return nil unless gc
|
|
175
|
+
hit = gc.lookup(addr)
|
|
176
|
+
return nil unless hit
|
|
177
|
+
GeoQuery::GeoCache.to_geolite(kind, hit)
|
|
178
|
+
end
|
|
179
|
+
|
|
115
180
|
# ---- ASN 接口 (1) -------------------------------------------------------
|
|
116
181
|
|
|
117
182
|
# /geo/asn?num=XXX 查出该 AS 的所有地址段
|
|
@@ -120,12 +185,10 @@ module GeoDB
|
|
|
120
185
|
@asn_index[num.to_s] || []
|
|
121
186
|
end
|
|
122
187
|
|
|
123
|
-
# /geo/asn?addr=X.X.X.X 按 IP 查所属 AS
|
|
188
|
+
# /geo/asn?addr=X.X.X.X 按 IP 查所属 AS (本地库 → GEO_CACHE 兑底)
|
|
124
189
|
# 返回: :invalid(IP不合法) / nil(无结果) / record
|
|
125
190
|
def asn_by_addr(addr)
|
|
126
|
-
|
|
127
|
-
return :invalid unless num
|
|
128
|
-
find_range('asn', num)
|
|
191
|
+
lookup_addr(:asn, addr)
|
|
129
192
|
end
|
|
130
193
|
|
|
131
194
|
# ---- City 接口 (2)(3) ---------------------------------------------------
|
|
@@ -138,15 +201,11 @@ module GeoDB
|
|
|
138
201
|
g ? g[id.to_s] : nil
|
|
139
202
|
end
|
|
140
203
|
|
|
141
|
-
# /geo/city?addr=X.X.X.X 先查 city-IPv4/city-IPv6, 再关联 geo-city
|
|
204
|
+
# /geo/city?addr=X.X.X.X 先查 city-IPv4/city-IPv6, 再关联 geo-city;
|
|
205
|
+
# 无结果时 GEO_CACHE 兑底
|
|
142
206
|
# 返回: :invalid / nil / enriched_record
|
|
143
207
|
def city_by_addr(addr)
|
|
144
|
-
|
|
145
|
-
return :invalid unless num
|
|
146
|
-
name = ipv6?(addr) ? 'city-IPv6' : 'city-IPv4'
|
|
147
|
-
r = find_range(name, num)
|
|
148
|
-
return nil unless r
|
|
149
|
-
enrich(r, 'geo-city')
|
|
208
|
+
lookup_addr(:city, addr)
|
|
150
209
|
end
|
|
151
210
|
|
|
152
211
|
# ---- Country 接口 (4)(5) ------------------------------------------------
|
|
@@ -158,14 +217,10 @@ module GeoDB
|
|
|
158
217
|
g ? g[id.to_s] : nil
|
|
159
218
|
end
|
|
160
219
|
|
|
161
|
-
# /geo/country?addr=X.X.X.X 先查 country-IPv4/country-IPv6, 再关联
|
|
220
|
+
# /geo/country?addr=X.X.X.X 先查 country-IPv4/country-IPv6, 再关联
|
|
221
|
+
# geo-country; 无结果时 GEO_CACHE 兑底
|
|
162
222
|
def country_by_addr(addr)
|
|
163
|
-
|
|
164
|
-
return :invalid unless num
|
|
165
|
-
name = ipv6?(addr) ? 'country-IPv6' : 'country-IPv4'
|
|
166
|
-
r = find_range(name, num)
|
|
167
|
-
return nil unless r
|
|
168
|
-
enrich(r, 'geo-country')
|
|
223
|
+
lookup_addr(:country, addr)
|
|
169
224
|
end
|
|
170
225
|
|
|
171
226
|
# ---- 关联聚合 -----------------------------------------------------------
|
|
@@ -203,15 +258,18 @@ class GeoAPI < Roda
|
|
|
203
258
|
|
|
204
259
|
route do |r|
|
|
205
260
|
r.root do
|
|
206
|
-
{
|
|
261
|
+
info = {
|
|
207
262
|
service: 'GeoDB API',
|
|
208
263
|
data_dir: GeoDB.data_dir,
|
|
264
|
+
cache_order: GeoDB.cache_order.join(','),
|
|
209
265
|
endpoints: {
|
|
210
266
|
'asn' => '/geo/asn?num=XXX | /geo/asn?addr=X.X.X.X',
|
|
211
267
|
'city' => '/geo/city?id=XXX | /geo/city?addr=X.X.X.X',
|
|
212
268
|
'country' => '/geo/country?id=XXX | /geo/country?addr=X.X.X.X'
|
|
213
269
|
}
|
|
214
270
|
}
|
|
271
|
+
info[:cache_dir] = GeoDB.geo_cache.dir if GeoDB.geo_cache
|
|
272
|
+
info
|
|
215
273
|
end
|
|
216
274
|
|
|
217
275
|
r.on 'geo' do
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
---
|
|
2
|
+
AIGC:
|
|
3
|
+
ContentProducer: '001191110102MAD55U9H0F10002'
|
|
4
|
+
ContentPropagator: '001191110102MAD55U9H0F10002'
|
|
5
|
+
Label: '1'
|
|
6
|
+
ProduceID: 'aacf6d8c-3f16-44a6-ad5e-9dec92704459'
|
|
7
|
+
PropagateID: 'aacf6d8c-3f16-44a6-ad5e-9dec92704459'
|
|
8
|
+
ReservedCode1: 'c7410cd1-7a15-4cf5-b8f2-5c2ed30d33f1'
|
|
9
|
+
ReservedCode2: 'c7410cd1-7a15-4cf5-b8f2-5c2ed30d33f1'
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# GeoQuery 综合查询接口
|
|
13
|
+
|
|
14
|
+
> 模块目录:`service/geoquery/`
|
|
15
|
+
> 命令行入口:`bin/gen-get`(纯互联网)、`bin/ngeo-get`(缓存 + 本地 + 互联网综合)
|
|
16
|
+
> 设计蓝本:`ip_geo_lookup.py`(多级回退 + 缓存 + 归一化),按"叠加综合"需求重构为 Ruby 实现
|
|
17
|
+
|
|
18
|
+
## 一、命令总览
|
|
19
|
+
|
|
20
|
+
| 命令 | 数据源 | 说明 |
|
|
21
|
+
|------|--------|------|
|
|
22
|
+
| `geo-get` | 本地 geo-api (GeoLite2) | 三接口齐查,纯本地;为 `GeoQuery::LocalClient` 的瘦封装(三接口拉取与汇总逻辑收口在 geoquery)|
|
|
23
|
+
| `gen-get` | 互联网 ip-api.com | 免费接口(限 45 req/min,无需 Key) |
|
|
24
|
+
| `ngeo-get` | 缓存 + 本地 + 互联网 | 按 `-p` 顺位逐源查询(默认 cache→local→internet),满意即停,字段级叠加 |
|
|
25
|
+
|
|
26
|
+
三个查询命令共享同一套客户端实现(`GeoQuery::LocalClient` / `GeoQuery::OnlineClient` /
|
|
27
|
+
`GeoQuery::GeoCache`),无双实现。
|
|
28
|
+
|
|
29
|
+
## 二、ngeo-get 查询模型(三源顺位)
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
1. 会话缓存 (ngeo-cache.json) 命中 → 直接返回
|
|
33
|
+
2. 按 -p 顺位逐源查询 (默认 cache → local → internet):
|
|
34
|
+
cache GEO_CACHE 外带缓存目录 (geocacheYYYYMMDD.json, -a 指定)
|
|
35
|
+
local 本地 geo-api 服务 (GeoLite2)
|
|
36
|
+
internet 互联网 ip-api.com (限速 + 熔断保护)
|
|
37
|
+
3. 每源结果按顺位折叠: 先查的源字段优先, 后查的补空缺
|
|
38
|
+
4. 折叠后归属满意 (省/市/ASN 齐全) → 提前终止, 不再查后续源
|
|
39
|
+
5. 全部源查完仍不满意 → 组装终态 (叠加/部分/空/不可达)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 结果状态 (state) 可信度排序
|
|
43
|
+
|
|
44
|
+
**`unreachable` < `local-partial` < `cache-partial` < `empty` < `cache` < `local` < `online` < `merged`**
|
|
45
|
+
|
|
46
|
+
| state | 含义 | 触发条件 |
|
|
47
|
+
|-------|------|----------|
|
|
48
|
+
| `unreachable` | 各源均无结果且有不联通 (最低) | 本地无结果 + 互联网不可达 + 缓存未命中 |
|
|
49
|
+
| `local-partial` | 本地部分结果 | 本地归属不满意 + 其余源无补充,保留本地字段 |
|
|
50
|
+
| `cache-partial` | 缓存部分结果 | 缓存归属不满意 + 其余源无补充,保留缓存字段 |
|
|
51
|
+
| `empty` | 各源均确认无归属 | 如私有/保留地址 (ip-api 返回 `private range`) |
|
|
52
|
+
| `cache` | 缓存命中且满意 | GEO_CACHE 内记录省市 ASN 齐全 |
|
|
53
|
+
| `local` | 本地结果满意 | 省/市/ASN 齐全,无需互联网 |
|
|
54
|
+
| `online` | 纯互联网结果 | 本地无结果或 geo-api 未启动,互联网查询成功 |
|
|
55
|
+
| `merged` | 多源叠加综合 (最高) | 缓存/本地部分结果 + 互联网或其他源补全,字段级择优 |
|
|
56
|
+
|
|
57
|
+
### 字段叠加规则 (Merge)
|
|
58
|
+
|
|
59
|
+
重点准确字段:**省 (Province) / 城市 (City) / 用途 (IDC、云)**,越准确越好。
|
|
60
|
+
按顺位折叠:先查的源为 base,后查的源为 supp。
|
|
61
|
+
|
|
62
|
+
| 字段 | 择优规则 |
|
|
63
|
+
|------|----------|
|
|
64
|
+
| country / province / city / asn / asn_org | base 优先,base 空取 supp (GeoLite2 网段级数据有值时可信;本地省市缺失率高,由 ip-api.com / GEO_CACHE 补全) |
|
|
65
|
+
| network (网段 CIDR) | base 优先,base 空取 supp (互联网源无网段,实际补给来自本地库与缓存) |
|
|
66
|
+
| isp | 按合并后的 asn_org 重新归一化 (长名 → 电信/联通/移动/腾讯云… 简称) |
|
|
67
|
+
| usage | 两方组织名综合推断: 云 / IDC / CDN / 教育网 / 运营商 / 企业 / 未知 |
|
|
68
|
+
|
|
69
|
+
## 三、GEO_CACHE 外带缓存
|
|
70
|
+
|
|
71
|
+
ngeo-get 与 geo-api 均可外带一个缓存目录,目录内的定位信息作为缓存数据源。
|
|
72
|
+
|
|
73
|
+
### 3.1 格式
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
# 服务器端: GEO_CACHE 内的缓存定位信息作为 addr 查询兑底
|
|
77
|
+
geo-api -d GEODB_PATH -a GEO_CACHE
|
|
78
|
+
|
|
79
|
+
# 客户端: 查询时缓存目录数据纳入备选
|
|
80
|
+
ngeo-get X.X.X.X -a GEO_CACHE
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
缓存文件为 `geocacheYYYYMMDD.json`(XXXXXXXX 为 8 位日期时间标签),
|
|
84
|
+
内容与 `ngeo-cache.json` 同构:`{ "ip" => { 统一 schema 结果, "ts" => 时间戳 } }`。
|
|
85
|
+
目录内文件增删改自动感知,无需重启;多文件同 IP 冲突取 `ts` 最新。
|
|
86
|
+
|
|
87
|
+
### 3.2 顺位 (-p)
|
|
88
|
+
|
|
89
|
+
`-p` 决定本地库 (local) / 互联网库 (internet) / 缓存库 (cache) 的适用顺序:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
ngeo-get X.X.X.X -a GEO_CACHE -p local,internet,cache # 本地优先, 互联网次之, 缓存兑底
|
|
93
|
+
ngeo-get X.X.X.X -a GEO_CACHE # 默认 -p cache,local,internet
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
| 顺位 | 语义 |
|
|
97
|
+
|------|------|
|
|
98
|
+
| `cache,local,internet` (默认) | 优先缓存 → 本地库 → 互联网 |
|
|
99
|
+
| `local,internet,cache` | 本地优先,本地查不到才找互联网,互联网查不到或不通再查缓存 |
|
|
100
|
+
|
|
101
|
+
顺位即优先级:先查的源字段优先,后查的补空缺;折叠后满意 (省/市/ASN 齐全)
|
|
102
|
+
即停,不再查后续源。源不可用自动跳过(无 `-a` 时 cache 源不存在;
|
|
103
|
+
`--refresh` 跳过全部缓存;`--no-local` 跳过本地)。
|
|
104
|
+
|
|
105
|
+
服务端 `geo-api` 的顺位参数为 `--priority`(`-p` 已被端口占用),
|
|
106
|
+
仅支持 `local,cache` 排列,默认 `local,cache`。
|
|
107
|
+
|
|
108
|
+
### 3.3 产出 (-c / -nc)
|
|
109
|
+
|
|
110
|
+
每次查询的结果默认保存下来用于后续缓存:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
ngeo-get 1.2.3.4 -a GEO_CACHE # 查询结果默认保存到 GEO_CACHE/geocacheYYYYMMDD.json
|
|
114
|
+
ngeo-get 1.2.3.4 -a GEO_CACHE -nc # 不保存产出
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
- 保存位置:`-a` 指定的目录;未指定时为当前目录(可用环境变量 `GEO_CACHE_DIR` 覆盖)
|
|
118
|
+
- 命名:`geocacheYYYYMMDD.json`,同一天多次查询自动归并到同一文件,同 IP 以新结果覆盖
|
|
119
|
+
- 仅保存可缓存状态 (`local` / `merged` / `online` / `empty` / `cache`);
|
|
120
|
+
`unreachable` / `local-partial` / `cache-partial` 不落盘(下次重查自动补全)
|
|
121
|
+
- `-c` 为默认行为(显式指定等效),`-nc` 关闭
|
|
122
|
+
|
|
123
|
+
### 3.4 整理 (-m)
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
ngeo-get -m GEO_CACHE
|
|
127
|
+
# 合并完成: 3 个缓存文件 → 128 条记录
|
|
128
|
+
# 输出文件: /path/GEO_CACHE/geocacheYYYYMMDD.json (生成时间为时间标签)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
将 GEO_CACHE 目录下所有 `geocacheYYYYMMDD.json` 格式的数据合并,生成一个新的
|
|
132
|
+
`geocacheYYYYMMDD.json`(新生成时间为合并生成时间),同 IP 冲突取 `ts` 最新。
|
|
133
|
+
源文件保留不删除,重复执行幂等。
|
|
134
|
+
|
|
135
|
+
## 四、gen-get 接口与参数
|
|
136
|
+
|
|
137
|
+
默认调用 ip-api.com 免费接口(与 `ip_geo_lookup.py` 的默认配置一致,`message` 字段为诊断扩展):
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
http://ip-api.com/json/{ip}?lang=zh-CN&fields=status,message,country,regionName,city,isp,as,query
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
| gen-get state | 触发条件 | 缓存 |
|
|
144
|
+
|---------------|----------|------|
|
|
145
|
+
| `ok` | `status: success` | 落盘 |
|
|
146
|
+
| `empty` | `status: fail` (如私有地址 `private range`) | 落盘 |
|
|
147
|
+
| `unreachable` | 网络异常 / 超时 / HTTP 429 限速 / 非 200 | 不落盘 (下次重试) |
|
|
148
|
+
| `invalid` | IP 参数不合法 | 不落盘 |
|
|
149
|
+
|
|
150
|
+
内置限速:两次请求间隔 ≥ 1.4s(对齐 45 req/min 免费限额);`--timeout` 控制连接/读取超时(默认 10s)。
|
|
151
|
+
|
|
152
|
+
## 五、命令行用法
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
# gen-get — 互联网查询
|
|
156
|
+
gen-get 8.8.8.8 # 文字格式
|
|
157
|
+
gen-get 8.8.8.8 -j # JSON 格式
|
|
158
|
+
gen-get 1.1.1.1 8.8.8.8 # 多 IP (自动限速)
|
|
159
|
+
cat ips.txt | gen-get -j # stdin 逐行读 IP
|
|
160
|
+
gen-get 8.8.8.8 --refresh # 忽略缓存强刷
|
|
161
|
+
gen-get --cache-stats # 缓存统计
|
|
162
|
+
|
|
163
|
+
# ngeo-get — 综合查询 (三源顺位)
|
|
164
|
+
ngeo-get 111.8.44.6 # 默认顺位 cache,local,internet
|
|
165
|
+
ngeo-get 111.8.44.6 -j # JSON
|
|
166
|
+
ngeo-get 111.8.44.6 -a ./GEO_CACHE # 外带缓存目录
|
|
167
|
+
ngeo-get 111.8.44.6 -p local,internet,cache -a ./GEO_CACHE # 本地优先, 缓存兑底
|
|
168
|
+
ngeo-get 111.8.44.6 -nc # 查询但不产出缓存文件
|
|
169
|
+
ngeo-get -m ./GEO_CACHE # 合并目录下全部 geocache*.json
|
|
170
|
+
ngeo-get 111.8.44.6 --no-local # 跳过本地, 仅互联网
|
|
171
|
+
ngeo-get 111.8.44.6 --geoapi http://127.0.0.1:9292 # 覆盖本地服务地址
|
|
172
|
+
ngeo-get 111.8.44.6 --api "http://ip-api.com/json/{ip}?lang=zh-CN&fields=..." # 覆盖互联网接口
|
|
173
|
+
ngeo-get --cache-stats # 会话缓存统计 (-a 时附加 GEO_CACHE 统计)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
退出码:`0` 查询完成(含 `empty` / `local-partial` / `cache-partial`);`1` IP 不合法或参数错误;`2` 互联网不可达(且其余源无结果)。
|
|
177
|
+
|
|
178
|
+
## 六、结果 schema(ngeo-get -j)
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"ip": "111.8.44.6",
|
|
183
|
+
"state": "merged",
|
|
184
|
+
"message": "",
|
|
185
|
+
"country": "中国",
|
|
186
|
+
"province": "湖南",
|
|
187
|
+
"city": "青园",
|
|
188
|
+
"isp": "移动",
|
|
189
|
+
"asn": "56047",
|
|
190
|
+
"asn_org": "China Mobile communications corporation",
|
|
191
|
+
"network": "111.8.0.0/15",
|
|
192
|
+
"usage": "运营商",
|
|
193
|
+
"source": "ngeo(geo-get+gen-get)",
|
|
194
|
+
"sources": {
|
|
195
|
+
"cache": { "state": "miss", "message": "GEO_CACHE 缓存未命中" },
|
|
196
|
+
"local": { "state": "ok", "verdict": "不满意" },
|
|
197
|
+
"online": { "state": "ok" }
|
|
198
|
+
},
|
|
199
|
+
"ts": 1789370023
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`sources` 键:`cache` / `local` / `online`,未参与查询的源标 `skipped`;
|
|
204
|
+
`source` 字段按参与叠加的源组合,如 `ngeo(geo-cache+geo-get)`。
|
|
205
|
+
|
|
206
|
+
## 七、测试用例
|
|
207
|
+
|
|
208
|
+
### 7.1 ngeo-get 状态机
|
|
209
|
+
|
|
210
|
+
| # | 命令 | 预期结果 |
|
|
211
|
+
|---|------|----------|
|
|
212
|
+
| 7.1.1 | `ngeo-get 219.140.0.1 -j` | `state: local`(本地省市 ASN 齐全,`online: skipped`) |
|
|
213
|
+
| 7.1.2 | `ngeo-get 111.8.44.6 -j` | `state: merged`,省/市来自互联网,`network` 保留本地网段 |
|
|
214
|
+
| 7.1.3 | `ngeo-get 219.140.0.1 --geoapi http://127.0.0.1:9999 -j` | `state: online`(本地不可用,纯互联网) |
|
|
215
|
+
| 7.1.4 | `ngeo-get 111.8.44.6 --api http://192.0.2.1/x/{ip} --timeout 2` | `state: local-partial`,message 含"互联网查询不可达",本地 ASN/网段保留 |
|
|
216
|
+
| 7.1.5 | `ngeo-get 10.20.30.40 --api http://192.0.2.1/x/{ip} --timeout 2 -j` | `state: unreachable`,全部字段为空,退出码 2 |
|
|
217
|
+
| 7.1.6 | `ngeo-get 10.20.30.40 -j` | `state: empty`(`private range`) |
|
|
218
|
+
| 7.1.7 | `ngeo-get 999.1.1.1 -j` | `state: invalid`,退出码 1 |
|
|
219
|
+
| 7.1.8 | 连续查询同一 IP 两次 | 第二次 `cached: true`(JSON)或"缓存命中"(文字) |
|
|
220
|
+
|
|
221
|
+
### 7.2 gen-get
|
|
222
|
+
|
|
223
|
+
| # | 命令 | 预期结果 |
|
|
224
|
+
|---|------|----------|
|
|
225
|
+
| 7.2.1 | `gen-get 8.8.8.8` | `state: ok`,国家/省/市/ASN 齐全,`usage: 云` |
|
|
226
|
+
| 7.2.2 | `gen-get 10.20.30.40 -j` | `state: empty`,message 为 `private range` |
|
|
227
|
+
| 7.2.3 | `gen-get 8.8.8.8 --api http://192.0.2.1/x/{ip} --timeout 2` | `state: unreachable`,退出码 2 |
|
|
228
|
+
| 7.2.4 | `echo 8.8.4.4 \| gen-get -j` | stdin 输入,正常查询 |
|
|
229
|
+
|
|
230
|
+
### 7.3 GEO_CACHE 外带缓存
|
|
231
|
+
|
|
232
|
+
预置 `./GEO_CACHE/geocache20260101.json` 含某本地库没有的 IP(如 `203.0.113.7`)的完整定位记录。
|
|
233
|
+
|
|
234
|
+
| # | 命令 | 预期结果 |
|
|
235
|
+
|---|------|----------|
|
|
236
|
+
| 7.3.1 | `ngeo-get 203.0.113.7 -a ./GEO_CACHE -j` | `state: cache`,`cached: true`,`sources.local/online: skipped`(默认顺位缓存命中) |
|
|
237
|
+
| 7.3.2 | `ngeo-get 203.0.113.7 -p local,internet,cache -a ./GEO_CACHE --api http://192.0.2.1/x/{ip} --timeout 2 -j` | local 空 + internet 不通 + 缓存兑底,`state: cache` |
|
|
238
|
+
| 7.3.3 | `ngeo-get 203.0.113.7 -a ./GEO_CACHE -nc -j` | 查询正常,不修改产出文件(`-nc`) |
|
|
239
|
+
| 7.3.4 | `ngeo-get 1.2.3.4 -a ./GEO_CACHE -j` | 查询后 `./GEO_CACHE/geocacheYYYYMMDD.json` 生成,含本次结果 |
|
|
240
|
+
| 7.3.5 | `ngeo-get -m ./GEO_CACHE` | 合并目录内全部 geocache 文件,输出新文件,退出码 0 |
|
|
241
|
+
| 7.3.6 | `ngeo-get 1.2.3.4 -p bogus -j` | 报"无效顺位"退出码 1 |
|
|
242
|
+
| 7.3.7 | `geo-api -d ./geodb -a ./GEO_CACHE` 后 `curl "localhost:9292/geo/city?addr=203.0.113.7"` | 200,缓存兑底,`cached: true`(详见 GeoAPI.md 第 7 节) |
|
|
243
|
+
|
|
244
|
+
## 八、缓存与配置
|
|
245
|
+
|
|
246
|
+
| 项 | 说明 |
|
|
247
|
+
|----|------|
|
|
248
|
+
| 会话缓存目录 | `ENV["NGEO_CACHE_DIR"]`,默认 `~/.network-infra-utility/` |
|
|
249
|
+
| 会话缓存文件 | `gen-cache.json`(gen-get 互联网层)/ `ngeo-cache.json`(ngeo 综合层) |
|
|
250
|
+
| 会话缓存策略 | `local` / `merged` / `online` / `empty` / `cache` 落盘;`unreachable` / `local-partial` / `cache-partial` 不落盘(下次重查,互联网恢复后自动补全);`--refresh` 强制重查 |
|
|
251
|
+
| GEO_CACHE 外带缓存 | `-a DIR` 指定;产出目录未指定时 `ENV["GEO_CACHE_DIR"]` > 当前目录 |
|
|
252
|
+
| GEO_CACHE 产出 | `-c` 默认开启:结果写入 `geocacheYYYYMMDD.json`;`-nc` 关闭 |
|
|
253
|
+
| GEO_CACHE 整理 | `-m DIR` 合并目录内全部缓存文件为新文件 |
|
|
254
|
+
| 本地服务地址 | `--geoapi` 或 `ENV["GEO_API_BASE"]`,默认 `http://127.0.0.1:9292` |
|
|
255
|
+
| 互联网接口 | `--api`(`{ip}` 占位符),默认 ip-api.com 免费接口 |
|
|
256
|
+
| 熔断 | 连续 3 次互联网不可达后,60s 内跳过在线查询直接返回 unreachable |
|
|
257
|
+
|
|
258
|
+
## 九、代码级用法
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
require "geoquery/geoquery" # gem 安装后 (require_paths 含 service/)
|
|
262
|
+
|
|
263
|
+
# 纯互联网
|
|
264
|
+
online = GeoQuery::OnlineClient.new(timeout: 10)
|
|
265
|
+
r = online.lookup("8.8.8.8") # → state: ok/empty/unreachable/invalid
|
|
266
|
+
|
|
267
|
+
# 本地原始接口查询 (bin/geo-get 底座)
|
|
268
|
+
local = GeoQuery::LocalClient.new # base 默认 9292 (geo-get 另支持 ENV["GEO_API_BASE"])
|
|
269
|
+
raw = local.fetch_raw("8.8.8.8") # → { country: body|nil, city: body|nil, asn: body|nil }
|
|
270
|
+
raw = local.fetch_raw("8.8.8.8", endpoints: %i[asn]) # → 单接口子集
|
|
271
|
+
# fetch_raw 返回 :unreachable 表示 geo-api 服务不可达
|
|
272
|
+
|
|
273
|
+
# 本地归一化查询 (ngeo 内部同款)
|
|
274
|
+
r = local.lookup("8.8.8.8") # → 统一 schema, state: ok/empty/unavailable/invalid
|
|
275
|
+
|
|
276
|
+
# 综合 (三源顺位, 默认 cache,local,internet)
|
|
277
|
+
ngeo = GeoQuery::NGeo.new(geo_cache_dir: "./GEO_CACHE") # -a 外带缓存
|
|
278
|
+
r = ngeo.lookup("111.8.44.6") # → 统一 schema (见第六节)
|
|
279
|
+
ngeo.lookup("1.2.3.4", refresh: true, no_local: false)
|
|
280
|
+
ngeo.save_cache
|
|
281
|
+
|
|
282
|
+
# 顺位自定义 (等效 -p)
|
|
283
|
+
ngeo = GeoQuery::NGeo.new(geo_cache_dir: "./GEO_CACHE",
|
|
284
|
+
order: "local,internet,cache")
|
|
285
|
+
|
|
286
|
+
# GEO_CACHE 外带缓存 (service/geoquery/geo_cache.rb)
|
|
287
|
+
gc = GeoQuery::GeoCache.new("./GEO_CACHE")
|
|
288
|
+
gc.lookup("8.8.8.8") # → 缓存记录 (附加 cached: true) 或 nil
|
|
289
|
+
gc.put_batch([{ "ip" => "8.8.8.8", "state" => "merged", ... }]) # 产出
|
|
290
|
+
gc.merge! # → { "files" =>, "entries" =>, "output" => }
|
|
291
|
+
GeoQuery::GeoCache.parse_order("cache,local,internet") # → 顺位数组或 nil
|
|
292
|
+
GeoQuery::GeoCache.to_geolite(:city, hit) # 统一 schema → GeoLite2 风格 (服务端兑底)
|
|
293
|
+
|
|
294
|
+
# 归一化工具
|
|
295
|
+
GeoQuery::Normalize.isp_from_asn("CHINATELECOM Hubei province 5G network") # => "电信"
|
|
296
|
+
GeoQuery::Normalize.usage_from_asn("Tencent cloud") # => "云"
|
|
297
|
+
GeoQuery::Merge.fields(local_result, online_result) # => 字段叠加
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## 备注
|
|
301
|
+
|
|
302
|
+
1. **gen-get 的 `unreachable` 语义**:表示"互联网查询不可达"(网络不通/超时/限速),区别于 `empty`(互联网确认无归属,如私有地址)。不可达结果不缓存,恢复后重查即可。
|
|
303
|
+
2. **`local-partial` / `cache-partial` 的取舍**:互联网不可达时,本地/缓存的部分结果(如 ASN/网段)仍有价值,予以保留并标注;完全无数据时才降级为 `unreachable` 空状态。
|
|
304
|
+
3. **本地 geo-api 未启动**:ngeo-get 不视为错误(`local: unavailable`),自动走纯互联网路径,返回 `state: online`;geo-get 则直接报错并提示启动命令(可用 `GEO_API_BASE` 环境变量覆盖服务地址)。
|
|
305
|
+
4. **ip-api.com 免费接口限制**:45 req/min、仅 HTTP(无 HTTPS)、仅限自用。批量场景已内置 1.4s 最小间隔;更高需求可 `--api` 切换付费批量接口(pro.ip-api.com)。
|
|
306
|
+
5. **GEO_CACHE 与会话缓存的分工**:`ngeo-cache.json` 是进程自动维护的会话缓存(命中直接返回,不参与 `-p` 顺位);GEO_CACHE 是用户显式外带的缓存目录(作为顺位中的 `cache` 数据源,产出可拷贝/合并/供服务端兑底)。`--refresh` 两者均跳过。
|
|
307
|
+
6. **服务端顺位参数为何是 `--priority`**:`geo-api -p` 已被监听端口占用,故服务端顺位用 `--priority`(仅 `local,cache` 排列);客户端 `ngeo-get -p` 无冲突,支持三源排列。
|
|
308
|
+
|
|
309
|
+
> AI生成
|