package comm import ( "strconv" "strings" ) // 渠道分发配置(表 TableChannelAppCfg,存 console 共享库 postgres)。 // // 与 svcpool.go / agentcfg.go / calltranslate.go 同一模式:console 后台读写(管理端 CRUD), // 业务服务(home 的 user 模块)运行时按 (app_name=? OR app_name='') 直读、下发客户端。 // // 取代旧的 channelapp 表(存各应用业务库 mysql、主键只有 channel):现在按 app_name // 分作用域统一收在 console 主库。版本与游客登录那几项后来又进一步拆到 AppRelease // (按应用、不分渠道),本表只留真正因渠道而异的:下载地址、上架状态、支付渠道、跳过绑定。 // 渠道枚举(与 pb.UserChannel 一致,此处冗余一份便于 comm 层独立使用)。 const ( ChannelUnknown int32 = 0 ChannelChinaHuawei int32 = 1 ChannelChinaXiaomi int32 = 2 ChannelChinaOppo int32 = 3 ChannelChinaVivo int32 = 4 ChannelChinaHonor int32 = 5 ChannelOfficial int32 = 100 ChannelGlobalApple int32 = 101 ChannelGlobalGoogle int32 = 102 ) // 上架状态。 const ( ChannelStatusDraft int32 = 0 // 草稿:后台配着,不下发 ChannelStatusOnline int32 = 1 // 已上架 ChannelStatusReviewing int32 = 2 // 审核中:ReviewVersion 那一版正在过审 ChannelStatusOffline int32 = 3 // 已下架 ) // 支付渠道取值(PayChannels 逗号分隔)。与 comm.ModuleCatalog 里「支付」分组的模块一一对应, // 后台据此校验「开了某支付渠道但对应凭据没配」。 const ( PayWechat = "wxpay" PayAlipay = "alipay" PayPaypal = "paypal" PayApple = "apple" PayGoogle = "google" ) // PayChannelModule 支付渠道 → 业务功能配置里的模块键(app_module_config.module)。 var PayChannelModule = map[string]string{ PayWechat: "wechatpay", PayAlipay: "alipay", PayPaypal: "paypal", PayApple: "appleiap", PayGoogle: "googleiap", } // ChannelApp 一个应用在一个分发渠道上的配置。(app_name, channel) 唯一。 // // ⚠️ 版本号 / 最低支持版本 / 强制更新 / 更新日志 / 送审版本 / 游客登录开关这 7 项 // **已搬到 [AppRelease]**(后台「服务配置 → 版本控制 / 游客显隐控制」),本结构不再持有。 // 它们本来就是「整个应用一套」,按渠道各存一份意味着同一个开关有 8 处能改而客户端只读一处 —— // 别再加回来。数据库里的旧列不会被删除(gorm 从不删列),只是不再读写。 type ChannelApp struct { RowId uint64 `gorm:"primaryKey;autoIncrement;column:row_id" json:"row_id"` AppName string `gorm:"column:app_name;size:64;uniqueIndex:uidx_channel_app,priority:1" json:"app_name"` // ''=全局默认 Channel int32 `gorm:"column:channel;uniqueIndex:uidx_channel_app,priority:2" json:"channel"` // 下载地址是**本渠道**的商店链接,留在这里:华为/小米/App Store 各不相同, // 客户端直接把它当 URL 打开,一套地址会把小米用户引到华为商店。 // 留空则回退到 AppRelease.Address(见 AppRelease.DownloadAddress)。 Address string `gorm:"column:address" json:"address"` Status int32 `gorm:"column:status" json:"status"` // 见 ChannelStatus*,各商店各自的上架进度 Description string `gorm:"column:description" json:"description"` // 能力开关(沿用旧 DBChannelApp 语义) AllowSkipDeviceBinding bool `gorm:"column:allowskipdevicebinding" json:"allowskipdevicebinding"` // 允许跳过设备绑定 PayChannels string `gorm:"column:paychannels;size:128" json:"paychannels"` // 逗号分隔,见 Pay* 常量 Enable bool `gorm:"column:enable" json:"enable"` Createtime int64 `gorm:"column:createtime" json:"createtime"` Updatetime int64 `gorm:"column:updatetime" json:"updatetime"` } // SplitPayChannels 拆分支付渠道串。 func SplitPayChannels(s string) []string { out := make([]string, 0, 4) for _, x := range strings.Split(s, ",") { if v := strings.TrimSpace(x); v != "" { out = append(out, v) } } return out } // CompareVersion 比较点分版本号,ab 返回 1。 // // 逐段按整数比较而非字符串比较——"1.10.0" 必须大于 "1.9.0",字符串比较会判反。 // 段数不同时短的按 0 补齐("1.2" == "1.2.0");非数字段(如 "1.2.0-beta" 的 "0-beta") // 取其前导数字,取不到按 0 处理,保证脏数据不会 panic 也不会误判成强更。 func CompareVersion(a, b string) int { as, bs := strings.Split(strings.TrimSpace(a), "."), strings.Split(strings.TrimSpace(b), ".") n := len(as) if len(bs) > n { n = len(bs) } for i := 0; i < n; i++ { x, y := 0, 0 if i < len(as) { x = leadingInt(as[i]) } if i < len(bs) { y = leadingInt(bs[i]) } if x != y { if x < y { return -1 } return 1 } } return 0 } // leadingInt 取字符串前导的十进制数字,没有则 0("0-beta" → 0、"12rc1" → 12)。 func leadingInt(s string) int { s = strings.TrimSpace(s) end := 0 for end < len(s) && s[end] >= '0' && s[end] <= '9' { end++ } if end == 0 { return 0 } n, err := strconv.Atoi(s[:end]) if err != nil { return 0 } return n } // 升级/游客登录的判定方法已随字段一起搬到 AppRelease: // NeedForceUpdate / NeedUpdate / IsReviewing / ShowTourists 见 comm/apprelease.go。 // CompareVersion 仍留在本文件,两边共用。 // PickChannelApp 从「应用行 + 全局行」里挑出该渠道生效的一条:应用行覆盖全局行,都没有返回 nil。 // 与 agent / 第三方服务下发同语义(应用优先、无则回退全局)。 func PickChannelApp(rows []*ChannelApp, channel int32) *ChannelApp { var global, scoped *ChannelApp for _, r := range rows { if r == nil || r.Channel != channel { continue } if r.AppName == "" { global = r } else { scoped = r } } if scoped != nil { return scoped } return global }