95 lines
2.1 KiB
Go
95 lines
2.1 KiB
Go
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
|
||
}
|