@@ -0,0 +1,176 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"91porn-server/common/log"
|
||||
"91porn-server/common/redis"
|
||||
"91porn-server/skd/skdg"
|
||||
"91porn-server/services/srv_im"
|
||||
"91porn-server/web/webg"
|
||||
)
|
||||
|
||||
const (
|
||||
phaseQueued = "queued"
|
||||
phaseScanningDialogs = "scanning_chat_sessions"
|
||||
phaseRegisteringUsers = "registering_users"
|
||||
phaseAddingFriends = "adding_friends"
|
||||
phaseDone = "done"
|
||||
|
||||
syncIMUsersTriggerKey = "im:sync_users:trigger"
|
||||
syncIMUsersLockKey = "im:sync_users:lock"
|
||||
syncIMUsersStatusKey = "im:sync_users:status"
|
||||
|
||||
syncIMUsersTriggerTTL = 10 * time.Minute
|
||||
syncIMUsersLockTTL = 1 * time.Hour
|
||||
)
|
||||
|
||||
// SyncIMUsersTaskStatus 旧私聊会话用户同步任务状态。
|
||||
// 状态完全落在 Redis 里——web 进程写 trigger、读 status;skd 进程消费 trigger、执行任务、写 status。
|
||||
type SyncIMUsersTaskStatus struct {
|
||||
Running bool `json:"running"`
|
||||
Phase string `json:"phase,omitempty"`
|
||||
StartedAt *time.Time `json:"startedAt,omitempty"`
|
||||
FinishedAt *time.Time `json:"finishedAt,omitempty"`
|
||||
LastStats srv_im.SyncDialogUsersStats `json:"lastStats"`
|
||||
LastError string `json:"lastError,omitempty"`
|
||||
}
|
||||
|
||||
// imSyncRedis 拿当前进程可用的 Redis 客户端:
|
||||
// skd 主进程用 skdg.Redis;web 主进程用 webg.Redis。
|
||||
// 两端写的是同一个 Redis 实例,靠 key 协议互通。
|
||||
func imSyncRedis() *redis.Client {
|
||||
if skdg.Redis != nil {
|
||||
return skdg.Redis
|
||||
}
|
||||
if webg.Redis != nil {
|
||||
return webg.Redis
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RequestSyncIMUsers 由 web admin 接口调用:写 trigger key,由 skd 接管执行。
|
||||
// 返回 accepted=true 表示成功排队;false 表示已有任务在跑或在排队。
|
||||
func RequestSyncIMUsers() (accepted bool, status SyncIMUsersTaskStatus) {
|
||||
status = GetSyncIMUsersStatus()
|
||||
if status.Running {
|
||||
return false, status
|
||||
}
|
||||
r := imSyncRedis()
|
||||
if r == nil {
|
||||
status.LastError = "redis unavailable"
|
||||
return false, status
|
||||
}
|
||||
if existing, _ := r.Get(syncIMUsersTriggerKey); existing != nil && *existing != "" {
|
||||
// 已经有 trigger 在排队,等 skd 消费
|
||||
return false, status
|
||||
}
|
||||
if err := r.Set(syncIMUsersTriggerKey, "1", syncIMUsersTriggerTTL); err != nil {
|
||||
status.LastError = err.Error()
|
||||
return false, status
|
||||
}
|
||||
now := time.Now()
|
||||
status = SyncIMUsersTaskStatus{
|
||||
Phase: phaseQueued,
|
||||
StartedAt: &now,
|
||||
LastStats: srv_im.SyncDialogUsersStats{Enabled: true},
|
||||
}
|
||||
saveSyncIMUsersStatus(status)
|
||||
return true, status
|
||||
}
|
||||
|
||||
// GetSyncIMUsersStatus 读 Redis 里最新状态,任何进程都能调
|
||||
func GetSyncIMUsersStatus() SyncIMUsersTaskStatus {
|
||||
r := imSyncRedis()
|
||||
if r == nil {
|
||||
return SyncIMUsersTaskStatus{}
|
||||
}
|
||||
raw, err := r.Get(syncIMUsersStatusKey)
|
||||
if err != nil || raw == nil || *raw == "" {
|
||||
return SyncIMUsersTaskStatus{}
|
||||
}
|
||||
var status SyncIMUsersTaskStatus
|
||||
if err := json.Unmarshal([]byte(*raw), &status); err != nil {
|
||||
return SyncIMUsersTaskStatus{}
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
// RunPendingSyncIMUsers 由 skd cron 定时调用:检测 trigger 并接管执行同步
|
||||
func RunPendingSyncIMUsers() {
|
||||
r := imSyncRedis()
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
// 没有 trigger 就直接退
|
||||
trig, _ := r.Get(syncIMUsersTriggerKey)
|
||||
if trig == nil || *trig == "" {
|
||||
return
|
||||
}
|
||||
// 加锁防止 skd 多副本同时跑
|
||||
ok, err := r.Setnx_NewOK(syncIMUsersLockKey, 1, syncIMUsersLockTTL)
|
||||
if err != nil || !ok {
|
||||
return
|
||||
}
|
||||
defer func() { _, _ = r.Del(syncIMUsersLockKey) }()
|
||||
// 消费 trigger,避免下个 tick 重复拉起
|
||||
_, _ = r.Del(syncIMUsersTriggerKey)
|
||||
|
||||
now := time.Now()
|
||||
status := SyncIMUsersTaskStatus{
|
||||
Running: true,
|
||||
Phase: phaseScanningDialogs,
|
||||
StartedAt: &now,
|
||||
LastStats: srv_im.SyncDialogUsersStats{Enabled: true},
|
||||
}
|
||||
saveSyncIMUsersStatus(status)
|
||||
|
||||
stats, runErr := runSyncIMUsersOnce(&status)
|
||||
|
||||
finished := time.Now()
|
||||
status.Running = false
|
||||
status.Phase = phaseDone
|
||||
status.FinishedAt = &finished
|
||||
status.LastStats = stats
|
||||
if runErr != nil {
|
||||
status.LastError = runErr.Error()
|
||||
log.Error("RunPendingSyncIMUsers failed", log.E(runErr), log.Any("stats", stats))
|
||||
} else {
|
||||
status.LastError = ""
|
||||
log.Info("RunPendingSyncIMUsers done", log.Any("stats", stats))
|
||||
}
|
||||
saveSyncIMUsersStatus(status)
|
||||
}
|
||||
|
||||
func runSyncIMUsersOnce(status *SyncIMUsersTaskStatus) (stats srv_im.SyncDialogUsersStats, err error) {
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
err = fmt.Errorf("sync panic: %v", rec)
|
||||
}
|
||||
}()
|
||||
status.Phase = phaseRegisteringUsers
|
||||
saveSyncIMUsersStatus(*status)
|
||||
|
||||
stats, err = srv_im.SyncDialogUsers()
|
||||
|
||||
status.Phase = phaseAddingFriends
|
||||
status.LastStats = stats
|
||||
saveSyncIMUsersStatus(*status)
|
||||
return stats, err
|
||||
}
|
||||
|
||||
func saveSyncIMUsersStatus(status SyncIMUsersTaskStatus) {
|
||||
r := imSyncRedis()
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
raw, err := json.Marshal(status)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err := r.Set(syncIMUsersStatusKey, string(raw), 0); err != nil {
|
||||
log.Warn("saveSyncIMUsersStatus set failed", log.E(err))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user