Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
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)
}