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.
 
 
 
 
 
 

156 lines
6.1 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"`
}
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)
// 强更必须有最低支持版本,否则「开了强更却挡不住任何人」,是配置期就能发现的错。
if req.ForceUpdate && req.MinVersion == "" {
writeErr(c, pb.ErrorCode_ReqParameterError, "开启强制更新时必须填「最低支持版本」")
return
}
// 最低支持版本不该高于线上版本,否则线上用户全被判定为需强更却又没有更高版本可更。
if req.MinVersion != "" && req.Version != "" && comm.CompareVersion(req.MinVersion, req.Version) > 0 {
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.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
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)
}