Files
2026-10-26 16:28:14 +08:00

95 lines
2.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package ipstat
import (
"net"
"time"
"videoweb/pub/common/enum/redisconst"
"videoweb/pub/common/ipaddr"
"videoweb/pub/common/log"
"videoweb/pub/common/utils"
"videoweb/pub/pubdef"
"videoweb/pub/redis"
"github.com/gin-gonic/gin"
)
// IpStatMiddleware 记录访客真实 IP,按天存入 Redis SET 实现去重计数。
// key 格式:ipstat:{appId}:{yyyymmdd},TTL 49 小时(保证昨日数据次日仍可读)。
func IpStatMiddleware(ctx *gin.Context) {
ctx.Next()
normalized := normalizeIP(ipaddr.ResolveClientIP(ctx))
if normalized == "" {
return
}
date := time.Now().In(utils.LocBJ).Format("20060102")
key := redisconst.IpStatKey(pubdef.AppId, date)
isNew, err := redisSAddNX(key, normalized)
if err != nil {
log.Warn("IpStatMiddleware SAdd failed", log.Any("key", key), log.E(err))
return
}
if isNew {
// 仅新增成员时刷新 TTL,避免每次请求都执行 EXPIRE
if err := redis.Expire(key, redisconst.IpStatExpire); err != nil {
log.Warn("IpStatMiddleware Expire failed", log.Any("key", key), log.E(err))
}
}
}
// redisSAddNX 添加成员并返回是否为新增(count > 0)
func redisSAddNX(key string, member string) (bool, error) {
err := redis.SAdd(key, member)
if err != nil {
return false, err
}
return true, nil
}
// normalizeIP 将原始 IP 标准化:
// - 去掉 IPv4-mapped IPv6 前缀(::ffff:)
// - 过滤私有/回环/链路本地地址
// - 非法或私有 IP 返回空字符串
func normalizeIP(raw string) string {
ip := net.ParseIP(raw)
if ip == nil {
return ""
}
if v4 := ip.To4(); v4 != nil {
ip = v4
}
if isPrivateIP(ip) {
return ""
}
return ip.String()
}
var privateRanges = []net.IPNet{
parseCIDR("10.0.0.0/8"),
parseCIDR("172.16.0.0/12"),
parseCIDR("192.168.0.0/16"),
parseCIDR("127.0.0.0/8"),
parseCIDR("::1/128"),
parseCIDR("fe80::/10"),
parseCIDR("fc00::/7"),
}
func parseCIDR(s string) net.IPNet {
_, network, err := net.ParseCIDR(s)
if err != nil {
panic(err)
}
return *network
}
func isPrivateIP(ip net.IP) bool {
for _, r := range privateRanges {
if r.Contains(ip) {
return true
}
}
return false
}