fix(adtracking): 收紧成交口径、点击去重与生产落地页白名单
成交 GMV 排除已关闭订单,避免未支付关单被计入;点击按访客 5 秒、IP 1 秒双重去重;生产禁止 localhost 落地页,失效短码改用 QueryEscape。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -28,7 +28,7 @@ var reservedCodes = map[string]bool{
|
||||
"pay": true, "notice": true, "chat": true, "dashboard": true,
|
||||
}
|
||||
|
||||
func validateTargetURL(rawURL string) (string, error) {
|
||||
func validateTargetURL(rawURL string, allowLocal bool) (string, error) {
|
||||
targetURL := strings.TrimSpace(rawURL)
|
||||
if targetURL == "" {
|
||||
return "https://hao858.com/", nil
|
||||
@@ -40,25 +40,37 @@ func validateTargetURL(rawURL string) (string, error) {
|
||||
targetURL = "https://" + targetURL
|
||||
}
|
||||
u, err := url.Parse(targetURL)
|
||||
if err != nil {
|
||||
if err != nil || u.Host == "" {
|
||||
return "", errors.New("目标落地页地址不合法")
|
||||
}
|
||||
scheme := strings.ToLower(u.Scheme)
|
||||
if scheme != "http" && scheme != "https" {
|
||||
return "", errors.New("目标落地页地址不合法")
|
||||
}
|
||||
host := strings.ToLower(u.Hostname())
|
||||
if host != "hao858.com" && !strings.HasSuffix(host, ".hao858.com") && host != "localhost" && host != "127.0.0.1" {
|
||||
if host == "localhost" || host == "127.0.0.1" {
|
||||
if !allowLocal {
|
||||
return "", ErrDisallowedTargetDomain
|
||||
}
|
||||
return targetURL, nil
|
||||
}
|
||||
if host != "hao858.com" && !strings.HasSuffix(host, ".hao858.com") {
|
||||
return "", ErrDisallowedTargetDomain
|
||||
}
|
||||
return targetURL, nil
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
repo *Repository
|
||||
redis *redis.Client
|
||||
repo *Repository
|
||||
redis *redis.Client
|
||||
allowLocalTargets bool
|
||||
}
|
||||
|
||||
func NewService(repo *Repository, redisClient *redis.Client) *Service {
|
||||
func NewService(repo *Repository, redisClient *redis.Client, allowLocalTargets bool) *Service {
|
||||
return &Service{
|
||||
repo: repo,
|
||||
redis: redisClient,
|
||||
repo: repo,
|
||||
redis: redisClient,
|
||||
allowLocalTargets: allowLocalTargets,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +89,7 @@ func (s *Service) CreateLink(ctx context.Context, adminID uint64, req CreateTrac
|
||||
return nil, ErrCodeAlreadyExists
|
||||
}
|
||||
|
||||
targetURL, err := validateTargetURL(req.TargetURL)
|
||||
targetURL, err := validateTargetURL(req.TargetURL, s.allowLocalTargets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -113,7 +125,7 @@ func (s *Service) UpdateLink(ctx context.Context, id uint64, req UpdateTrackingL
|
||||
return ErrLinkNotFound
|
||||
}
|
||||
|
||||
targetURL, err := validateTargetURL(req.TargetURL)
|
||||
targetURL, err := validateTargetURL(req.TargetURL, s.allowLocalTargets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -179,18 +191,13 @@ func (s *Service) ResolveRedirect(ctx context.Context, code, ip, ua, referer, vi
|
||||
bgCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 防刷频控:同一个访客/IP在 5 秒内重复点击相同短链,不重复记录流水
|
||||
// 防刷:同访客 5 秒去重;同 IP 1 秒去重,避免不带 Cookie 轮换 visitorID 刷库
|
||||
if s.redis != nil {
|
||||
dedupID := visitorID
|
||||
if dedupID == "" {
|
||||
dedupID = ip
|
||||
if isDuplicateClick(bgCtx, s.redis, "adtrack:dedup:v:"+link.Code+":"+visitorID, 5*time.Second) {
|
||||
return
|
||||
}
|
||||
if dedupID != "" {
|
||||
dedupKey := "adtrack:dedup:" + link.Code + ":" + dedupID
|
||||
ok, rErr := s.redis.SetNX(bgCtx, dedupKey, "1", 5*time.Second).Result()
|
||||
if rErr == nil && !ok {
|
||||
return
|
||||
}
|
||||
if isDuplicateClick(bgCtx, s.redis, "adtrack:dedup:ip:"+link.Code+":"+ip, time.Second) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +221,14 @@ func (s *Service) ResolveRedirect(ctx context.Context, code, ip, ua, referer, vi
|
||||
return finalURL, nil
|
||||
}
|
||||
|
||||
func isDuplicateClick(ctx context.Context, rdb *redis.Client, key string, ttl time.Duration) bool {
|
||||
if rdb == nil || key == "" || strings.HasSuffix(key, ":") {
|
||||
return false
|
||||
}
|
||||
ok, err := rdb.SetNX(ctx, key, "1", ttl).Result()
|
||||
return err == nil && !ok
|
||||
}
|
||||
|
||||
func appendTrackCode(targetURL, code string) string {
|
||||
u, err := url.Parse(targetURL)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user