feat(core): 添加IP地址检测服务核心功能
- 实现配置文件加载和解析功能,支持服务器、MMDB数据库和日志配置 - 集成GeoLite2城市数据库查询,提供IP地理位置信息查询服务 - 添加私有IP地址检测逻辑,过滤本地网络地址段 - 构建HTTP路由处理器,返回JSON格式的IP位置信息 - 配置默认启动参数和错误处理机制 - 集成日志系统,记录请求处理过程和错误信息
This commit is contained in:
36
cmd/checkip/main.go
Normal file
36
cmd/checkip/main.go
Normal 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
9
configs/config.yaml
Normal 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
52
internal/app/router.go
Normal 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
39
internal/config/config.go
Normal 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
49
internal/mmdb/reader.go
Normal 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
|
||||||
|
}
|
||||||
52
internal/network/private.go
Normal file
52
internal/network/private.go
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user