ipinfo

package
v1.7.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package ipinfo 实现第三方主题(LuminaPlus)所需的「IP 信息」HTTP 接口。

契约:GET /api/public/ip-info/v1/{status,lookup,latency} 与 POST /api/admin/ip-info/v1/refresh,详见 docs/IP-INFO-API.md。

注意:这里的响应外壳是 {ok,data,meta} / {error:{message}},与仓库内 RPC2 的 {status,message,data} 不同。主题用严格 schema 校验字段名、类型与可空性, 所以本包单独维护一套 DTO,刻意不复用 web/api 的 Respond* 外壳。

Index

Constants

View Source
const (
	// APIVersion 是接口的语义版本,经 /status 暴露给主题。
	APIVersion = "1.0.0"
	// SchemaVersion 是响应结构的版本号,所有 data 都会回显它。
	SchemaVersion = 1

	// CacheHit / CacheMiss 是 meta.cache 的两个取值。
	CacheHit  = "hit"
	CacheMiss = "miss"
)
View Source
const (
	ClassificationNative    = "native"
	ClassificationBroadcast = "broadcast"
	ClassificationUnknown   = "unknown"

	LabelNative    = "原生 IP"
	LabelBroadcast = "广播 IP"
	LabelUnknown   = "未知"
)

分类(classification.type)的三个取值与对应中文标签。

View Source
const (
	ClassificationSourceCountryComparison = "country_comparison"
	ClassificationSourceUnavailable       = "unavailable"
)

classification.source 的取值:说明本次判定是「怎么来的」,不是「用了哪个数据源」。

这两个字符串来自参考实现(shanyang242/Komari-IP-Info 的 normalizeNativeClassification),主题的 zod schema 把 source 定义成**必填**字符串:

source: d()   // z.string(),无 optional、无 default

缺了它整条 /lookup 响应会被主题判为无效 —— HTTP 仍然是 200,只是查询报错、 面板不渲染。所以这两个常量不能删,也不能留空。

View Source
const (
	LatencyStatusOK          = "ok"
	LatencyStatusTimeout     = "timeout"
	LatencyStatusUnavailable = "unavailable"
)

延迟节点状态,只能是这三个值之一。

Variables

View Source
var ErrBusy = errors.New("IP query capacity reached; retry later")

Functions

This section is empty.

Types

type Address

type Address struct {
	Value  string `json:"value"`
	Family int    `json:"family"` // 只允许 4 或 6
}

Address 是被查询地址本身。

type Classification

type Classification struct {
	Type                  string `json:"type"`
	Label                 string `json:"label"`
	GeolocatedCountryCode string `json:"geolocated_country_code"`
	RegisteredCountryCode string `json:"registered_country_code"`
	Source                string `json:"source"`
}

Classification 是原生/广播判定结果。

Source 是主题 schema 的**必填**字段(见 ClassificationSource* 的注释)。它描述判定 依据,取值 "country_comparison"(比较地理定位国家与 ASN 注册国家)或 "unavailable" (任一国家未知,无法比较)。

type Config

type Config struct {
	IPWhoIsBaseURL    string
	RIPEstatBaseURL   string
	IPAPIBaseURL      string
	GlobalpingBaseURL string

	// GlobalpingToken 可选:公共 API 免 token,限流时带 token 额度更高。
	GlobalpingToken string
	// GlobalLatencyDisabled 关闭 Globalping 测量,同时让 /status 声明不支持。
	GlobalLatencyDisabled bool

	// HTTPClient 是所有上游共用的客户端;必须带显式超时。
	HTTPClient *http.Client
	// Now 可注入时钟,测试用。
	Now func() time.Time

	// 缓存时长。
	PositiveTTL        time.Duration // 成功结果的新鲜期,契约要求 >= 1h
	StaleTTL           time.Duration // 新鲜期结束后仍保留用于兜底的时长
	NegativeTTL        time.Duration // 失败结果的新鲜期(短暂,避免反复打上游)
	LatencyTTL         time.Duration // 延迟结果的新鲜期
	LatencyNegativeTTL time.Duration // 延迟失败的短缓存
	LookupCacheSize    int
	LatencyCacheSize   int

	// Globalping 轮询参数。
	GlobalpingPollInterval time.Duration
	GlobalpingMaxWait      time.Duration
}

Config 是服务的可调参数。零值字段会在 normalized() 里补默认值, 因此测试可以只写关心的几项(例如只替换上游 baseURL)。

func DefaultConfig

func DefaultConfig() Config

DefaultConfig 返回生产默认配置。

type Envelope

type Envelope struct {
	OK   bool        `json:"ok"`
	Data interface{} `json:"data,omitempty"`
	Meta interface{} `json:"meta,omitempty"`
}

Envelope 是统一响应外壳:成功为 {ok:true,data,meta},失败为 {error:{message}}。

type ErrorBody

type ErrorBody struct {
	Error ErrorDetail `json:"error"`
}

ErrorBody 是失败响应体,主题读取 error.message。

type ErrorDetail

type ErrorDetail struct {
	Message string `json:"message"`
}

ErrorDetail 承载失败原因。

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler 把 Service 挂到 gin 路由上。

func Default

func Default() *Handler

Default 返回进程级单例处理器。 公开读接口与管理刷新接口必须共享同一份缓存,否则刷新接口刷不到读接口的缓存。

func NewHandler

func NewHandler() *Handler

NewHandler 用默认配置构造处理器。

func (*Handler) Latency

func (h *Handler) Latency(c *gin.Context)

Latency 处理 GET /api/public/ip-info/v1/latency?uuid=&ip=。 该接口不会硬失败:Globalping 不可用时返回空节点集合 + meta.warning。

func (*Handler) Lookup

func (h *Handler) Lookup(c *gin.Context)

Lookup 处理 GET /api/public/ip-info/v1/lookup?uuid=&ip=。

func (*Handler) NodeNetworkSummary added in v1.6.13

func (h *Handler) NodeNetworkSummary(ctx context.Context, address string) (*NetworkSummary, error)

func (*Handler) Refresh

func (h *Handler) Refresh(c *gin.Context)

Refresh 处理 POST /api/admin/ip-info/v1/refresh(管理员鉴权由路由中间件负责)。

force 缺省视为 true:这个接口的语义就是「刷新」。显式传 force=false 时才允许命中缓存。 include_latency=true 时顺带把延迟缓存也刷一遍,但延迟失败不影响本次返回。

func (*Handler) Status

func (h *Handler) Status(c *gin.Context)

Status 处理 GET /api/public/ip-info/v1/status。

type LatencyData

type LatencyData struct {
	UUID           string          `json:"uuid"`
	SchemaVersion  int             `json:"schema_version"`
	Address        Address         `json:"address"`
	Classification Classification  `json:"classification"`
	Latency        LatencyResult   `json:"latency"`
	Provider       LatencyProvider `json:"provider"`
}

LatencyData 是 /latency 的 data。

type LatencyNode

type LatencyNode struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	City        string `json:"city"`
	CountryCode string `json:"country_code"`
	LatencyMS   int    `json:"latency_ms"`
	Status      string `json:"status"`
}

LatencyNode 是单个探测节点的结果。

type LatencyProvider

type LatencyProvider struct {
	ID                      string `json:"id"`
	Name                    string `json:"name"`
	Homepage                string `json:"homepage"`
	ClassificationAvailable bool   `json:"classification_available"`
	LatencyAvailable        bool   `json:"latency_available"`
}

LatencyProvider 描述延迟数据来源。

type LatencyResult

type LatencyResult struct {
	Nodes          []LatencyNode `json:"nodes"`
	AvailableCount int           `json:"available_count"`
	TimeoutCount   int           `json:"timeout_count"`
	ProviderCached bool          `json:"provider_cached"`
}

LatencyResult 是延迟面板主体。

type Location

type Location struct {
	Continent             string  `json:"continent"`
	ContinentCode         string  `json:"continent_code"`
	Country               string  `json:"country"`
	CountryCode           string  `json:"country_code"`
	RegisteredCountry     string  `json:"registered_country"`
	RegisteredCountryCode string  `json:"registered_country_code"`
	Region                string  `json:"region"`
	RegionCode            string  `json:"region_code"`
	City                  string  `json:"city"`
	PostalCode            string  `json:"postal_code"`
	Timezone              string  `json:"timezone"`
	Latitude              float64 `json:"latitude"`
	Longitude             float64 `json:"longitude"`
	AccuracyRadius        int     `json:"accuracy_radius"`
}

Location 是地理信息,字段全部来自 ipwho.is。

type LookupCapabilities

type LookupCapabilities struct {
	MediaUnlock bool `json:"media_unlock"`
	AIUnlock    bool `json:"ai_unlock"`
}

LookupCapabilities 是 lookup 的 data.capabilities。

type LookupData

type LookupData struct {
	UUID           string             `json:"uuid"`
	SchemaVersion  int                `json:"schema_version"`
	Excluded       bool               `json:"excluded"`
	ExcludedReason *string            `json:"excluded_reason"`
	Address        Address            `json:"address"`
	Location       Location           `json:"location"`
	Network        Network            `json:"network"`
	Classification Classification     `json:"classification"`
	Reputation     Reputation         `json:"reputation"`
	Capabilities   LookupCapabilities `json:"capabilities"`
	// Unlock 是节点探针上报的解锁快照。主题的 zod 对象会丢弃未知键,所以老主题
	// 拿到它不会报错,只是不显示。
	Unlock   *UnlockData    `json:"unlock,omitempty"`
	Provider LookupProvider `json:"provider"`
}

LookupData 是 /lookup 与 /refresh 的 data。

type LookupProvider

type LookupProvider struct {
	ID                    string   `json:"id"`
	Name                  string   `json:"name"`
	Homepage              string   `json:"homepage"`
	BaseSource            string   `json:"base_source"`
	QualitySources        []string `json:"quality_sources"`
	SecurityDataAvailable bool     `json:"security_data_available"`
}

LookupProvider 描述本次数据由哪些上游提供。

type Meta

type Meta struct {
	Cache      string  `json:"cache"`
	Stale      bool    `json:"stale"`
	UpdatedAt  string  `json:"updated_at"`
	ExpiresAt  string  `json:"expires_at"`
	StaleUntil string  `json:"stale_until"`
	Warning    *string `json:"warning"`
}

Meta 是所有 data 的元信息。warning 的 schema 是 string | null, 因此用指针并禁用 omitempty:无警告时显式输出 null。

type Network

type Network struct {
	ASN          string `json:"asn"`        // 形如 "AS123",无数据时为空串
	ASNNumber    int    `json:"asn_number"` // 形如 123,无数据时为 0
	Organization string `json:"organization"`
	Operator     string `json:"operator"`
	NetworkType  string `json:"network_type"`
	CompanyType  string `json:"company_type"`
	Route        string `json:"route"` // 形如 "1.2.3.0/24"
	RIR          string `json:"rir"`
	Domain       string `json:"domain"`
	Datacenter   string `json:"datacenter"`
}

Network 是网络归属信息,ASN 来自 ipwho.is,route 来自 RIPEstat。

type NetworkSummary added in v1.6.13

type NetworkSummary struct {
	Network      string `json:"network"`
	Organization string `json:"organization"`
	Country      string `json:"country"`
	Source       string `json:"source"`
	Stale        bool   `json:"stale"`
}

NetworkSummary intentionally omits the queried IP and detailed location. It reuses the existing bounded provider cache instead of probing from a browser.

type RefreshRequest

type RefreshRequest struct {
	UUID           string `json:"uuid"`
	IP             string `json:"ip"`
	Force          *bool  `json:"force"`
	IncludeLatency bool   `json:"include_latency"`
}

RefreshRequest 是 POST /api/admin/ip-info/v1/refresh 的请求体。

type Reputation

type Reputation struct {
	Available           bool              `json:"available"`
	PurityScore         int               `json:"purity_score"`
	RiskScore           int               `json:"risk_score"`
	PollutionScore      int               `json:"pollution_score"`
	RiskLevel           string            `json:"risk_level"`
	PollutionLevel      string            `json:"pollution_level"`
	PositiveSignalCount int               `json:"positive_signal_count"`
	ValidSignalCount    int               `json:"valid_signal_count"`
	Signals             ReputationSignals `json:"signals"`
	DatabaseScores      map[string]any    `json:"database_scores"`
	DatabaseSignals     map[string]any    `json:"database_signals"`
	AvailableSources    []string          `json:"available_sources"`
	FailedSources       []string          `json:"failed_sources"`
	Method              ReputationMethod  `json:"method"`
}

Reputation 是信誉/纯净度面板数据。

注意 database_scores / database_signals / available_sources / failed_sources 必须是 JSON 对象/数组而非 null,因此构造时一律初始化为空集合。

type ReputationMethod

type ReputationMethod struct {
	ID     string `json:"id"`
	Status string `json:"status"`
}

ReputationMethod 描述风险分的算法与本次计算状态。

type ReputationSignals

type ReputationSignals struct {
	CountryCode string `json:"country_code"`
	Proxy       bool   `json:"proxy"`
	Tor         bool   `json:"tor"`
	VPN         bool   `json:"vpn"`
	Datacenter  bool   `json:"datacenter"`
	Abuser      bool   `json:"abuser"`
	Crawler     bool   `json:"crawler"`
}

ReputationSignals 是逐项风险信号。

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service 聚合各上游并对外提供带缓存的查询。

func NewService

func NewService(cfg Config) *Service

NewService 构造服务。所有上游共享同一个 HTTP 客户端(各自带超时), 服务本身不起后台 goroutine、不注册定时器,因此不需要 Close。

func (*Service) Latency

func (s *Service) Latency(ctx context.Context, ip net.IP, force bool) (latencySnapshot, Meta)

Latency 返回全球延迟快照。任何上游失败都只降级成「节点为空 + warning」, 不返回错误 —— 契约要求这个接口不能硬失败。

func (*Service) Lookup

func (s *Service) Lookup(ctx context.Context, ip net.IP, force bool) (lookupSnapshot, Meta, error)

Lookup 返回某个 IP 的完整信息快照。

缓存策略:

  • force=false 且命中新鲜条目 -> 直接返回,meta.cache=hit;
  • 否则并发查询上游:只要有任意一个上游给出数据就算成功,写入正缓存(meta.cache=miss);
  • 上游全挂时,若还有「过期但保留」的条目就兜底返回(meta.stale=true), 否则写入短暂负缓存并返回错误。

func (*Service) Status

func (s *Service) Status() StatusData

Status 组装 /status 的 data。能力位反映本后端的真实能力: geo/network/reputation 由固定上游提供,native_classification 由 「地理国家 vs ASN 注册国家」推导,global_latency 取决于 Globalping 是否启用; media_unlock / ai_unlock 没有数据源,恒为 false。

type StatusCapabilities

type StatusCapabilities struct {
	Geo                  bool `json:"geo"`
	Network              bool `json:"network"`
	Reputation           bool `json:"reputation"`
	NativeClassification bool `json:"native_classification"`
	GlobalLatency        bool `json:"global_latency"`
	MediaUnlock          bool `json:"media_unlock"`
	AIUnlock             bool `json:"ai_unlock"`
}

StatusCapabilities 声明本后端支持的能力,主题据此决定显示哪些面板。

type StatusData

type StatusData struct {
	Available             bool               `json:"available"`
	Version               string             `json:"version"`
	SchemaVersion         int                `json:"schema_version"`
	MainlandChinaExcluded bool               `json:"mainland_china_excluded"`
	Capabilities          StatusCapabilities `json:"capabilities"`
}

StatusData 是 /status 的 data。

type UnlockData added in v0.1.9

type UnlockData struct {
	EgressIP     string          `json:"egress_ip,omitempty"`
	EgressRegion string          `json:"egress_region,omitempty"`
	ProbedAt     string          `json:"probed_at"`
	Results      []v2.UnlockItem `json:"results"`
}

UnlockData 是「流媒体 / AI 解锁」面板数据。

它由节点上的探针测量并上报,服务端只负责存与转发:解锁取决于发起请求的那个 IP, 服务端在别的机房,只能测到它自己的出口。没有探测记录时整个字段为 null,面板据此 显示「等待探测」而不是「未解锁」—— 这两件事必须分得开。

EgressIP 是探测实际用的出口地址,不一定等于节点地址:本机群里有主机经由另一台 节点出网,不带上它就会把结论算到错误的节点上。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL