package job import ( "fmt" "strconv" "time" "91porn-server/common/enum/imad" "91porn-server/common/log" "91porn-server/common/stderr" "91porn-server/models/cache/sysconfdata" "91porn-server/models/v/sysconfmod" "91porn-server/skd/skdg" "91porn-server/services/srv_im" ) const ( imAdNotifyLockKey = "skd:im_ad_notify:online:lock" imAdNotifyLastRunKey = "skd:im_ad_notify:online:last_run" imAdNotifyLockTTL = 10 * time.Minute ) type imAdNotifyConfig struct { Enable bool IntervalMinutes int64 Positions []string MaxUsers int } // IMAdNotifyOnline 按 sysconf.imConfig 定时向当前 IM 在线用户透传广告刷新通知。 func IMAdNotifyOnline() { if err := imAdNotifyOnline(); err != nil { log.Error("IMAdNotifyOnline failed", log.E(err)) } } func imAdNotifyOnline() error { conf := loadIMAdNotifyConfig() now := time.Now() lastRunAt, err := loadIMAdNotifyLastRunAt() if err != nil { return err } if !shouldRunIMAdNotify(conf, lastRunAt, now) { return nil } acquired, err := skdg.Redis.Setnx_NewOK(imAdNotifyLockKey, 1, imAdNotifyLockTTL) if err != nil { return err } if !acquired { log.Info("IMAdNotifyOnline skipped: lock held") return nil } defer func() { if _, err := skdg.Redis.Del(imAdNotifyLockKey); err != nil { log.Warn("IMAdNotifyOnline release lock failed", log.E(err)) } }() lastRunAt, err = loadIMAdNotifyLastRunAt() if err != nil { return err } if !shouldRunIMAdNotify(conf, lastRunAt, now) { return nil } resp, code, msg := srv_im.SendAdNotify(srv_im.AdNotifyReq{ Target: "online", Positions: conf.Positions, MaxUsers: conf.MaxUsers, }) if code != stderr.Success { return fmt.Errorf("im ad notify failed: code=%d msg=%s", code, msg) } log.Info("IMAdNotifyOnline done", log.Any("resp", resp)) return saveIMAdNotifyLastRunAt(now) } func loadIMAdNotifyConfig() imAdNotifyConfig { conf := imAdNotifyConfig{IntervalMinutes: 60, MaxUsers: 1000} all, err := sysconfdata.GetAllFromCache() if err != nil { log.Warn("load im sysconf failed", log.E(err)) return conf } conf.Enable = all.GetBool(sysconfmod.VCodeIMAdEnable) if v := all.GetInt(sysconfmod.VCodeIMAdIntervalMinutes); v > 0 { conf.IntervalMinutes = v } if v := all.GetInt(sysconfmod.VCodeIMAdMaxUsers); v > 0 { conf.MaxUsers = int(v) } // 每个广告位独立开关,开了就加进去 conf.Positions = collectEnabledIMAdPositions(all) return conf } // collectEnabledIMAdPositions 把启用了的广告位 code 收集成列表 func collectEnabledIMAdPositions(all sysconfmod.ConfMap) []string { positions := make([]string, 0, 4) if all.GetBool(sysconfmod.VCodeIMAdPosChatNotificationBar) { positions = append(positions, imad.ChatNotificationBar) } if all.GetBool(sysconfmod.VCodeIMAdPosChatFloatingGifBall) { positions = append(positions, imad.ChatFloatingGifBall) } if all.GetBool(sysconfmod.VCodeIMAdPosListFeedNative) { positions = append(positions, imad.ListFeedNative) } if all.GetBool(sysconfmod.VCodeIMAdPosListBanner) { positions = append(positions, imad.ListBanner) } return positions } func shouldRunIMAdNotify(conf imAdNotifyConfig, lastRunAt time.Time, now time.Time) bool { if !conf.Enable || len(conf.Positions) == 0 { return false } if lastRunAt.IsZero() { return true } return !now.Before(lastRunAt.Add(normalizeIMAdNotifyInterval(conf.IntervalMinutes))) } func normalizeIMAdNotifyInterval(intervalMinutes int64) time.Duration { if intervalMinutes <= 0 { intervalMinutes = 60 } return time.Duration(intervalMinutes) * time.Minute } func loadIMAdNotifyLastRunAt() (time.Time, error) { val, err := skdg.Redis.Get(imAdNotifyLastRunKey) if err != nil || val == nil || *val == "" { return time.Time{}, err } unixSec, err := strconv.ParseInt(*val, 10, 64) if err != nil || unixSec <= 0 { return time.Time{}, nil } return time.Unix(unixSec, 0), nil } func saveIMAdNotifyLastRunAt(now time.Time) error { return skdg.Redis.Set(imAdNotifyLastRunKey, strconv.FormatInt(now.Unix(), 10), 0) }