65 lines
1.8 KiB
Go
65 lines
1.8 KiB
Go
// Package appcode 集中处理多 App 租户键的归一化和 context 透传。
|
||
package appcode
|
||
|
||
import (
|
||
"context"
|
||
"crypto/rand"
|
||
"fmt"
|
||
"strings"
|
||
)
|
||
|
||
const (
|
||
// Default 是当前开发阶段的种子 App;旧请求未显式携带 app_code 时统一落到 lalu。
|
||
Default = "lalu"
|
||
)
|
||
|
||
type contextKey struct{}
|
||
|
||
// Normalize 把外部输入收敛成数据库使用的短 app_code。
|
||
func Normalize(value string) string {
|
||
value = strings.ToLower(strings.TrimSpace(value))
|
||
if value == "" {
|
||
// 开发阶段不保留历史兼容表结构,但测试和后台任务仍需要一个稳定默认租户。
|
||
return Default
|
||
}
|
||
|
||
return value
|
||
}
|
||
|
||
// WithContext 把 app_code 写入 context,供 service/repository 在不扩散方法签名的情况下取用。
|
||
func WithContext(ctx context.Context, value string) context.Context {
|
||
return context.WithValue(ctx, contextKey{}, Normalize(value))
|
||
}
|
||
|
||
// FromContext 从 context 读取 app_code;缺失时返回当前开发默认 App。
|
||
func FromContext(ctx context.Context) string {
|
||
if ctx == nil {
|
||
return Default
|
||
}
|
||
value, _ := ctx.Value(contextKey{}).(string)
|
||
return Normalize(value)
|
||
}
|
||
|
||
// NewScopedID 生成对客户端暴露的 App 长 ID。
|
||
// 格式固定为 app_code + "_" + uuid,保证同一个 ID 可直接作为用户长 ID、房间 ID 和腾讯 IM/RTC 字符串标识。
|
||
func NewScopedID(value string) string {
|
||
app := Normalize(value)
|
||
var bytes [16]byte
|
||
if _, err := rand.Read(bytes[:]); err != nil {
|
||
// crypto/rand 失败极少见;这里退化为全 0 仍保留格式,后续数据库唯一键会兜底冲突。
|
||
return fmt.Sprintf("%s_00000000-0000-4000-8000-000000000000", app)
|
||
}
|
||
|
||
bytes[6] = (bytes[6] & 0x0f) | 0x40
|
||
bytes[8] = (bytes[8] & 0x3f) | 0x80
|
||
|
||
return fmt.Sprintf("%s_%08x-%04x-%04x-%04x-%012x",
|
||
app,
|
||
bytes[0:4],
|
||
bytes[4:6],
|
||
bytes[6:8],
|
||
bytes[8:10],
|
||
bytes[10:16],
|
||
)
|
||
}
|