package analyze import ( "fmt" "sync/atomic" "time" "yunyan/utils" ) // statLoc 统计日界时区,由模块 Init 经 setStatLoc 写入(默认 Asia/Shanghai)。 // 埋点(Report)与同步(sync)两侧共用,保证 0 点切日口径一致;未初始化时回退固定 UTC+8。 var statLoc atomic.Value // *time.Location func setStatLoc(loc *time.Location) { statLoc.Store(loc) } func statLocation() *time.Location { if v := statLoc.Load(); v != nil { return v.(*time.Location) } return time.FixedZone("UTC+8", 8*3600) } // dayNumber 把时间转成 YYYYMMDD 形式的统计日(按固定统计时区,跨部署口径一致)。 func dayNumber(t time.Time) uint32 { return utils.StatDayNumber(t, statLocation()) } // ============================ Redis 键 ============================ // keyPrefix 形如 {集群标签}:analyze:{应用ID}:{区域},由 setupKeys 写入; // 多集群 / 多应用 / 多区域共用一套 Redis 时互不串台。 // 用 atomic 保证 setupKeys(启动期) 与 Report(运行期) 间无竞态。 var keyPrefix atomic.Value // string func prefix() string { if p := keyPrefix.Load(); p != nil { return p.(string) } return "" } // indexKey 活跃统计分区的索引集合:成员为分区标识(见 partition)。 func indexKey() string { return prefix() + ":idx" } // hashKey / hllKey 都由「分区标识」直接拼出,而不是各自按字段重新拼—— // 这样分区标识升级(加渠道商段)后,老格式成员算出的仍是老 key, // 切换版本当天残留在 Redis 里的老分区数据照样能被读出、推送、清理,不丢当日统计。 // hashKey 某分区累加计数的哈希。 func hashKey(part string) string { return prefix() + ":" + part } // hllKey 某分区某去重指标的 HyperLogLog。 func hllKey(part, metric string) string { return prefix() + ":" + part + ":u:" + metric } // emptyChannelMark 分区标识里「无渠道归属」的占位符。渠道商短码字符集为 // A-Z2-9(见 comm.ShortCodeCharset),不含 '-',故不会与真实渠道商冲突。 const emptyChannelMark = "-" // partition 索引集合的成员,标识一个 产品:渠道商:日 分区。 // 无渠道归属(历史设备、未挂设备的应用级指标)用 emptyChannelMark 占位, // 避免出现连续冒号导致解析歧义。 func partition(productId uint32, channelId string, statDay uint32) string { if channelId == "" { channelId = emptyChannelMark } return fmt.Sprintf("%d:%s:%d", productId, channelId, statDay) } // 统计哈希的字段名,与 stats_global_day 表列名保持一致,便于追溯。 const ( fLoginCount = "login_count" fNewUserCount = "new_user_count" fActiveDeviceCount = "active_device_count" fBindDeviceCount = "bind_device_count" fQRActiveCount = "qr_active_count" fOrderCreateCount = "order_create_count" fOrderPaidCount = "order_paid_count" fOrderFailedCount = "order_failed_count" fOrderCreateAmount = "order_create_amount" fOrderPaidAmount = "order_paid_amount" fGrantVipDay = "grant_vip_day" fGrantAiIntegral = "grant_ai_integral" fGrantTradeSecond = "grant_trade_second" fGrantMeetSecond = "grant_meet_second" fAiChatCount = "ai_chat_count" fAiUpToken = "ai_up_token" fAiDownToken = "ai_down_token" fTradeCount = "trade_count" fTradeSecond = "trade_second" fTradeWords = "trade_words" fMeetCount = "meet_count" fMeetSecond = "meet_second" ) // 去重指标的 HLL 名(对应 stats_global_day 的 *_user_count 列)。 const ( uLogin = "login" uActive = "active" uPay = "pay" )