69 lines
2.0 KiB
Go
69 lines
2.0 KiB
Go
package mysql
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
const (
|
||
// StatTZUTC 是历史默认口径,保留它可以让旧接口、旧报表和跨服务 UTC 统计规则继续稳定工作。
|
||
StatTZUTC = "UTC"
|
||
// StatTZAsiaShanghai 是 Databi 需要的中国自然日口径;中国当前无夏令时,固定 +08:00 能避免容器缺 tzdata 时切日失败。
|
||
StatTZAsiaShanghai = "Asia/Shanghai"
|
||
)
|
||
|
||
var asiaShanghaiLocation = time.FixedZone(StatTZAsiaShanghai, 8*60*60)
|
||
|
||
type statDayScope struct {
|
||
tz string
|
||
day string
|
||
}
|
||
|
||
type statTZScopeContextKey struct{}
|
||
|
||
// WithStatTZScope 限制当前聚合只写一个 stat_tz;实时消费不使用它,回灌任务用它避免重放历史事件时污染 UTC 口径。
|
||
func WithStatTZScope(ctx context.Context, statTZ string) context.Context {
|
||
return context.WithValue(ctx, statTZScopeContextKey{}, normalizeStatTZ(statTZ))
|
||
}
|
||
|
||
func normalizeStatTZ(value string) string {
|
||
switch strings.TrimSpace(value) {
|
||
case StatTZAsiaShanghai:
|
||
return StatTZAsiaShanghai
|
||
case StatTZUTC, "":
|
||
return StatTZUTC
|
||
default:
|
||
return StatTZUTC
|
||
}
|
||
}
|
||
|
||
func statLocation(statTZ string) *time.Location {
|
||
if normalizeStatTZ(statTZ) == StatTZAsiaShanghai {
|
||
return asiaShanghaiLocation
|
||
}
|
||
return time.UTC
|
||
}
|
||
|
||
func statDayIn(ms int64, statTZ string) string {
|
||
return time.UnixMilli(ms).In(statLocation(statTZ)).Format("2006-01-02")
|
||
}
|
||
|
||
func statDayScopes(ms int64) []statDayScope {
|
||
// 同一条业务事实必须一次事务内写出两份读模型:UTC 行维持历史聚合,Asia/Shanghai 行给中国自然日大屏使用。
|
||
return []statDayScope{
|
||
{tz: StatTZUTC, day: statDayIn(ms, StatTZUTC)},
|
||
{tz: StatTZAsiaShanghai, day: statDayIn(ms, StatTZAsiaShanghai)},
|
||
}
|
||
}
|
||
|
||
func statDayScopesFromContext(ctx context.Context, ms int64) []statDayScope {
|
||
if ctx != nil {
|
||
if statTZ, ok := ctx.Value(statTZScopeContextKey{}).(string); ok {
|
||
statTZ = normalizeStatTZ(statTZ)
|
||
return []statDayScope{{tz: statTZ, day: statDayIn(ms, statTZ)}}
|
||
}
|
||
}
|
||
return statDayScopes(ms)
|
||
}
|