feat(core): 添加IP地址检测服务核心功能

- 实现配置文件加载和解析功能,支持服务器、MMDB数据库和日志配置
- 集成GeoLite2城市数据库查询,提供IP地理位置信息查询服务
- 添加私有IP地址检测逻辑,过滤本地网络地址段
- 构建HTTP路由处理器,返回JSON格式的IP位置信息
- 配置默认启动参数和错误处理机制
- 集成日志系统,记录请求处理过程和错误信息
This commit is contained in:
2026-02-02 17:20:51 +08:00
parent c4c6aefb5a
commit 6df8136e1b
6 changed files with 237 additions and 0 deletions

36
cmd/checkip/main.go Normal file
View File

@@ -0,0 +1,36 @@
package main
import (
"checkIP/internal/app"
"checkIP/internal/config"
"checkIP/internal/mmdb"
"fmt"
"github.com/sirupsen/logrus"
)
func main() {
cfg, err := config.Load("./configs/config.yaml")
if err != nil {
logrus.WithError(err).Fatal("load config failed")
}
if cfg.Log.Level != "" {
level, err := logrus.ParseLevel(cfg.Log.Level)
if err != nil {
logrus.WithError(err).Fatal("invalid log level")
}
logrus.SetLevel(level)
}
mmdbReader, err := mmdb.New(cfg.MMDB.FilePath)
if err != nil {
logrus.WithError(err).Fatal("init mmdb reader failed")
}
router := app.NewRouter(mmdbReader)
addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)
if err := router.Run(addr); err != nil {
logrus.WithError(err).Fatal("http server failed")
}
}

9
configs/config.yaml Normal file
View File

@@ -0,0 +1,9 @@
server:
host: 0.0.0.0
port: 8431
mmdb:
filePath: ./db/GeoLite2-City.mmdb
log:
level: debug

52
internal/app/router.go Normal file
View File

@@ -0,0 +1,52 @@
package app
import (
"net/http"
"checkIP/internal/mmdb"
"checkIP/internal/network"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
func NewRouter(reader *mmdb.Reader) *gin.Engine {
router := gin.New()
router.Use(gin.Recovery())
router.Use(gin.LoggerWithWriter(logrus.StandardLogger().Out))
router.GET("/", func(c *gin.Context) {
ip := c.ClientIP()
if ip == "" {
c.String(http.StatusBadRequest, "Invalid IP")
return
}
ipInfo := network.GetIPInformation(ip)
fields := logrus.Fields{
"ip": ipInfo.IP,
"is_private": ipInfo.IsPrivate,
}
if ipInfo.IsPrivate {
fields["private_cidr"] = ipInfo.PrivateCIDR
}
logrus.WithFields(fields).Info("ip check")
if ipInfo.IsPrivate {
c.String(http.StatusBadRequest, "Invalid IP")
return
}
ipLocation, err := reader.QueryIP(ipInfo.IP)
if err != nil {
logrus.WithError(err).Error("mmdb lookup failed")
c.Status(http.StatusInternalServerError)
return
}
logrus.WithField("ip_info", ipLocation).Info("mmdb lookup")
c.JSON(http.StatusOK, ipLocation)
})
return router
}

39
internal/config/config.go Normal file
View File

@@ -0,0 +1,39 @@
package config
import (
"fmt"
"os"
"gopkg.in/yaml.v3"
)
type Config struct {
Server ServerConfig `yaml:"server"`
MMDB MMDBConfig `yaml:"mmdb"`
Log LogConfig `yaml:"log"`
}
type ServerConfig struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
}
type MMDBConfig struct {
FilePath string `yaml:"filePath"`
}
type LogConfig struct {
Level string `yaml:"level"`
}
func Load(path string) (Config, error) {
var cfg Config
data, err := os.ReadFile(path)
if err != nil {
return cfg, fmt.Errorf("read config: %w", err)
}
if err := yaml.Unmarshal(data, &cfg); err != nil {
return cfg, fmt.Errorf("parse yaml: %w", err)
}
return cfg, nil
}

49
internal/mmdb/reader.go Normal file
View File

@@ -0,0 +1,49 @@
package mmdb
import (
"fmt"
"net"
"github.com/IncSW/geoip2"
)
type IpInfo struct {
Ip string `json:"ip"`
CountryIsoCode string `json:"country_iso_code"`
CountryName string `json:"country_name"`
City string `json:"city"`
TimeZone string `json:"time_zone"`
}
type Reader struct {
reader *geoip2.CityReader
}
func New(filePath string) (*Reader, error) {
if filePath == "" {
return nil, fmt.Errorf("mmdb file path is empty")
}
r, err := geoip2.NewCityReaderFromFile(filePath)
if err != nil {
return nil, fmt.Errorf("open mmdb: %w", err)
}
return &Reader{reader: r}, nil
}
func (r *Reader) QueryIP(ip string) (IpInfo, error) {
if r == nil || r.reader == nil {
return IpInfo{}, fmt.Errorf("mmdb reader not initialized")
}
record, err := r.reader.Lookup(net.ParseIP(ip))
if err != nil {
return IpInfo{}, fmt.Errorf("lookup ip: %w", err)
}
info := IpInfo{
Ip: ip,
CountryIsoCode: record.Country.ISOCode,
CountryName: record.Country.Names["en"],
City: record.City.Names["en"],
TimeZone: record.Location.TimeZone,
}
return info, nil
}

View File

@@ -0,0 +1,52 @@
package network
import (
"net"
"github.com/sirupsen/logrus"
)
type IPInformation struct {
IP string
IsPrivate bool
PrivateCIDR string
}
func GetIPInformation(ip string) IPInformation {
ipInfo := IPInformation{
IP: ip,
IsPrivate: false,
PrivateCIDR: "",
}
cidrs := []*net.IPNet{
parseCIDR("10.0.0.0/8"),
parseCIDR("172.16.0.0/12"),
parseCIDR("192.168.0.0/16"),
parseCIDR("127.0.0.1/16"),
}
ipAddr := net.ParseIP(ip)
if ipAddr == nil {
logrus.Warn("invalid ip address")
return ipInfo
}
for _, cidr := range cidrs {
if cidr.Contains(ipAddr) {
ipInfo.IsPrivate = true
ipInfo.PrivateCIDR = cidr.String()
break
}
}
return ipInfo
}
func parseCIDR(cidr string) *net.IPNet {
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
panic(err)
}
return ipNet
}