You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
6.2 KiB
129 lines
6.2 KiB
package comm
|
|
|
|
import "strings"
|
|
|
|
// 应用发版与入口控制配置(表 TableAppRelease,存 console 共享库 postgres)。
|
|
//
|
|
// 一行 = 一个应用(`app_name=”` 为全局默认兜底),**不分渠道**。对应后台
|
|
// 「服务配置 → 版本控制」与「服务配置 → 游客显隐控制」两个页面。
|
|
//
|
|
// 为什么从 ChannelApp 里拆出来:版本号、强更、游客开关这几项本来就是「整个应用一套」,
|
|
// 按渠道各存一份的直接后果是同一个开关有 8 处能改、客户端只读其中一处 —— 线上真出过
|
|
// 「后台明明开了游客登录、App 就是不显示」的事故。拆出来之后这些字段**只有这一个来源**。
|
|
//
|
|
// 仍留在 ChannelApp 里的是真正因渠道而异的东西:下载地址、上架状态、支付渠道、
|
|
// 是否允许跳过设备绑定。其中下载地址在这里也有一份**默认值**,渠道没单独填时用它 ——
|
|
// 华为/小米/App Store 的商店链接各不相同,客户端是直接把它当 URL 打开的,
|
|
// 一套地址会把小米用户引到华为商店,所以渠道级覆盖必须保留。
|
|
type AppRelease struct {
|
|
RowId uint64 `gorm:"primaryKey;autoIncrement;column:row_id" json:"row_id"`
|
|
AppName string `gorm:"column:app_name;size:64;uniqueIndex:uidx_app_release" json:"app_name"` // ''=全局默认
|
|
|
|
// ── 版本控制 ──
|
|
Version string `gorm:"column:version;size:32" json:"version"` // 当前线上版本
|
|
MinVersion string `gorm:"column:min_version;size:32" json:"min_version"` // 最低支持版本,低于它的客户端必须更新
|
|
ForceUpdate bool `gorm:"column:force_update" json:"force_update"` // 强制更新总开关(配合 MinVersion)
|
|
Changelog string `gorm:"column:changelog" json:"changelog"` // 更新日志,下发给客户端展示
|
|
Address string `gorm:"column:address" json:"address"` // 默认下载地址;渠道填了自己的则以渠道为准
|
|
ReviewVersion string `gorm:"column:review_version;size:32" json:"review_version"` // 送审版本号;客户端报这一版时隐藏未过审功能
|
|
|
|
// ── 游客显隐 ──
|
|
Tourists bool `gorm:"column:tourists" json:"tourists"` // 允许游客登录(总开关)
|
|
TouristsVersion string `gorm:"column:touristsversion;size:32" json:"touristsversion"` // 生效版本,白名单语义见 ShowTourists
|
|
|
|
Createtime int64 `gorm:"column:createtime" json:"createtime"`
|
|
Updatetime int64 `gorm:"column:updatetime" json:"updatetime"`
|
|
}
|
|
|
|
// NeedForceUpdate 判断某客户端版本是否必须更新:开了强更开关、配了最低版本、且客户端版本低于它。
|
|
//
|
|
// 客户端版本为空(老客户端没上报)时不强更,避免把所有人挡在门外 —— 这个判定一旦判宽只是
|
|
// 少弹一次强更框,判严就是全员无法使用,失败方向必须偏向放行。
|
|
func (r *AppRelease) NeedForceUpdate(clientVersion string) bool {
|
|
if r == nil || !r.ForceUpdate || strings.TrimSpace(r.MinVersion) == "" {
|
|
return false
|
|
}
|
|
if strings.TrimSpace(clientVersion) == "" {
|
|
return false
|
|
}
|
|
return CompareVersion(clientVersion, r.MinVersion) < 0
|
|
}
|
|
|
|
// NeedUpdate 判断是否有更新可用:线上版本高于客户端版本。
|
|
func (r *AppRelease) NeedUpdate(clientVersion string) bool {
|
|
if r == nil || strings.TrimSpace(r.Version) == "" || strings.TrimSpace(clientVersion) == "" {
|
|
return false
|
|
}
|
|
return CompareVersion(clientVersion, r.Version) < 0
|
|
}
|
|
|
|
// IsReviewing 判断某客户端版本是否正是「送审版本」——客户端据此隐藏未过审的功能。
|
|
func (r *AppRelease) IsReviewing(clientVersion string) bool {
|
|
if r == nil || strings.TrimSpace(r.ReviewVersion) == "" {
|
|
return false
|
|
}
|
|
return CompareVersion(clientVersion, r.ReviewVersion) == 0
|
|
}
|
|
|
|
// ShowTourists 判断某客户端版本是否该看到「游客登录」入口。
|
|
//
|
|
// 判定一律在服务端做:客户端只拿结论,不拿规则。各端各写一遍版本比较必然出现实现不一致,
|
|
// 而这个开关直接关系到应用商店过审,判错代价很高。
|
|
//
|
|
// 语义是「白名单」:**只有被点名的那一个版本可见,其余一律隐藏**。
|
|
// - Tourists=false → 一律隐藏(总开关关掉)
|
|
// - Tourists=true 且 TouristsVersion="" → 一律隐藏(没点名任何版本 = 谁都不放行)
|
|
// - Tourists=true 且 TouristsVersion=X → 只有版本号等于 X 的客户端可见
|
|
//
|
|
// 送审场景:只给正在审核的那一版开游客登录,线上其它版本不受影响。
|
|
//
|
|
// ⚠️ 留空按「全隐藏」而非「全放行」处理,是刻意选的失败方向:这个开关一旦判宽,
|
|
// 后果是不该露出游客入口的版本露了出去(过审风险、白嫖开户礼);判严只是入口少显示。
|
|
// 后台保存时会拦住「勾了开关却没填版本」这种等于没开的配置,避免运营踩空。
|
|
//
|
|
// 客户端没上报版本时同样隐藏——点名了版本却认不出来是哪一版,只能从严。
|
|
func (r *AppRelease) ShowTourists(clientVersion string) bool {
|
|
if r == nil || !r.Tourists {
|
|
return false
|
|
}
|
|
want := strings.TrimSpace(r.TouristsVersion)
|
|
if want == "" {
|
|
return false
|
|
}
|
|
if strings.TrimSpace(clientVersion) == "" {
|
|
return false
|
|
}
|
|
return CompareVersion(clientVersion, want) == 0
|
|
}
|
|
|
|
// DownloadAddress 取该渠道实际生效的下载地址:渠道填了就用渠道的,没填回退到应用默认。
|
|
func (r *AppRelease) DownloadAddress(channelAddress string) string {
|
|
if s := strings.TrimSpace(channelAddress); s != "" {
|
|
return channelAddress
|
|
}
|
|
if r == nil {
|
|
return ""
|
|
}
|
|
return r.Address
|
|
}
|
|
|
|
// PickAppRelease 从「应用行 + 全局行」里挑生效的一条:应用行覆盖全局行,都没有返回 nil。
|
|
// 与 agent / 第三方服务 / 渠道分发下发同语义(应用优先、无则回退全局)。
|
|
func PickAppRelease(rows []*AppRelease, app string) *AppRelease {
|
|
var global, scoped *AppRelease
|
|
for _, r := range rows {
|
|
if r == nil {
|
|
continue
|
|
}
|
|
switch r.AppName {
|
|
case app:
|
|
scoped = r
|
|
case "":
|
|
global = r
|
|
}
|
|
}
|
|
if scoped != nil {
|
|
return scoped
|
|
}
|
|
return global
|
|
}
|
|
|