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.
167 lines
6.7 KiB
167 lines
6.7 KiB
package console
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"yunyan/comm"
|
|
"yunyan/lego/sys/postgres"
|
|
"yunyan/pb"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// ==================== 应用发版与入口控制 (app_release) ====================
|
|
//
|
|
// 后台「服务配置 → 版本控制」与「服务配置 → 游客显隐控制」两个页面的后端。
|
|
// 模型见 comm/apprelease.go:一行 = 一个应用(app_name='' 为全局默认),**不分渠道**。
|
|
//
|
|
// ⚠️ 两个页面共用同一行,各自只写自己那组字段。所以保存一律是
|
|
// 「先读出整行 → 只覆盖本组字段 → 整行写回」,绝不能直接把请求体当整行存 ——
|
|
// 那样在「版本控制」页点一次保存就会把游客登录配置清空(反之亦然)。
|
|
|
|
// getAppRelease 取某作用域的发版配置。没配过时返回一行空值,前端不必区分「没有」和「空」。
|
|
func (this *serverComp) getAppRelease(c *gin.Context) {
|
|
var req struct {
|
|
AppName string `json:"app_name"`
|
|
}
|
|
_ = c.ShouldBindJSON(&req)
|
|
row, err := this.loadAppRelease(req.AppName)
|
|
if err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
writeOK(c, gin.H{"item": row})
|
|
}
|
|
|
|
// loadAppRelease 读某作用域的整行;没配过返回只带 AppName 的空行(RowId=0,保存时走插入)。
|
|
//
|
|
// 走主库(FindOnePrimary)而非只读副本:两个页面共用同一行、保存是「读整行→改一组→写回」,
|
|
// 读到副本的延迟旧行就会把另一组字段回滚成旧值 —— 刚在游客页保存完再去版本页保存,
|
|
// 游客配置可能被打回上一版。未注册副本时它等同 FindOne,没有额外代价。
|
|
//
|
|
// ⚠️ 「没查到」和「查不了」必须分开:连不上库时若也当成空行,接下来的保存会走插入分支、
|
|
// 撞上 app_name 唯一索引,用户看到的是一句莫名其妙的约束冲突而不是「数据库连不上」。
|
|
func (this *serverComp) loadAppRelease(appName string) (*comm.AppRelease, error) {
|
|
row := &comm.AppRelease{}
|
|
err := postgres.FindOnePrimary(comm.TableAppRelease, row, "app_name=?", appName)
|
|
if err == nil {
|
|
return row, nil
|
|
}
|
|
if err == postgres.ErrNoDocuments {
|
|
return &comm.AppRelease{AppName: appName}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// saveAppRelease 整行落库(存在则更新,不存在则插入)。调用方负责先做读改写。
|
|
func (this *serverComp) saveAppRelease(c *gin.Context, m *comm.AppRelease) {
|
|
now := time.Now().UnixMilli()
|
|
m.Updatetime = now
|
|
var err error
|
|
if m.RowId == 0 {
|
|
m.Createtime = now
|
|
err = postgres.Insert(comm.TableAppRelease, m)
|
|
} else {
|
|
err = postgres.Save(comm.TableAppRelease, m)
|
|
}
|
|
if err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
broadcastConfigChanged(comm.ConfigKindAppRelease, "update", 0)
|
|
writeOK(c, m)
|
|
}
|
|
|
|
// saveAppVersion 保存「版本控制」那一组:线上版本 / 最低支持版本 / 强更 / 更新日志 /
|
|
// 默认下载地址 / 送审版本。不触碰游客登录字段。
|
|
func (this *serverComp) saveAppVersion(c *gin.Context) {
|
|
var req struct {
|
|
AppName string `json:"app_name"`
|
|
Version string `json:"version"`
|
|
MinVersion string `json:"min_version"`
|
|
ForceUpdate bool `json:"force_update"`
|
|
Changelog string `json:"changelog"`
|
|
Address string `json:"address"`
|
|
ReviewVersion string `json:"review_version"`
|
|
IosVersion string `json:"ios_version"`
|
|
IosMinVersion string `json:"ios_min_version"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, err.Error())
|
|
return
|
|
}
|
|
req.Version = strings.TrimSpace(req.Version)
|
|
req.MinVersion = strings.TrimSpace(req.MinVersion)
|
|
req.ReviewVersion = strings.TrimSpace(req.ReviewVersion)
|
|
req.IosVersion = strings.TrimSpace(req.IosVersion)
|
|
req.IosMinVersion = strings.TrimSpace(req.IosMinVersion)
|
|
|
|
// 2026-09-23 起最低支持版本可以留空:留空 = 线上版本,即「开了强更就对所有低版本强更」。
|
|
// 但两个平台的线上版本都没填时强更没有任何效果,这是配置期就能发现的错。
|
|
if req.ForceUpdate && req.Version == "" && req.IosVersion == "" {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "开启强制更新时至少要填一个平台的「当前线上版本」")
|
|
return
|
|
}
|
|
// 最低支持版本不该高于线上版本,否则线上用户全被判定为需强更却又没有更高版本可更。
|
|
if req.MinVersion != "" && req.Version != "" && comm.CompareVersion(req.MinVersion, req.Version) > 0 {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "Android「最低支持版本」不能高于「当前线上版本」")
|
|
return
|
|
}
|
|
if req.IosMinVersion != "" && req.IosVersion != "" && comm.CompareVersion(req.IosMinVersion, req.IosVersion) > 0 {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "iOS「最低支持版本」不能高于「当前线上版本」")
|
|
return
|
|
}
|
|
|
|
m, err := this.loadAppRelease(req.AppName)
|
|
if err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
m.AppName = req.AppName
|
|
m.Version = req.Version
|
|
m.MinVersion = req.MinVersion
|
|
m.ForceUpdate = req.ForceUpdate
|
|
m.Changelog = req.Changelog
|
|
m.Address = strings.TrimSpace(req.Address)
|
|
m.ReviewVersion = req.ReviewVersion
|
|
m.IosVersion = req.IosVersion
|
|
m.IosMinVersion = req.IosMinVersion
|
|
this.saveAppRelease(c, m)
|
|
}
|
|
|
|
// saveAppTourists 保存「游客显隐控制」那一组:游客登录开关 + 生效版本。不触碰版本字段。
|
|
func (this *serverComp) saveAppTourists(c *gin.Context) {
|
|
var req struct {
|
|
AppName string `json:"app_name"`
|
|
Tourists bool `json:"tourists"`
|
|
TouristsVersion string `json:"touristsversion"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, err.Error())
|
|
return
|
|
}
|
|
req.TouristsVersion = strings.TrimSpace(req.TouristsVersion)
|
|
|
|
// 白名单语义:只有被点名的那一版可见。勾了开关却不填版本 = 谁都看不到,等于没开 ——
|
|
// 这是配置期就能发现的错,别让运营以为「已经开了」再回头去查客户端。
|
|
// 要全部隐藏就直接取消勾选,不要靠留空版本号来表达。
|
|
if req.Tourists && req.TouristsVersion == "" {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "开启游客登录时必须填「生效版本」——留空等于所有版本都看不到入口;要全部隐藏请直接取消勾选")
|
|
return
|
|
}
|
|
if req.TouristsVersion != "" && !req.Tourists {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "填了「游客登录生效版本」就必须同时勾选「允许游客登录」,否则该版本仍然看不到入口")
|
|
return
|
|
}
|
|
|
|
m, err := this.loadAppRelease(req.AppName)
|
|
if err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
m.AppName = req.AppName
|
|
m.Tourists = req.Tourists
|
|
m.TouristsVersion = req.TouristsVersion
|
|
this.saveAppRelease(c, m)
|
|
}
|
|
|