package console import ( "strings" "time" "yunyan/comm" "yunyan/lego/sys/postgres" "yunyan/pb" "github.com/gin-gonic/gin" ) // ============================ 渠道分发 (channelapp) ============================ // // 「SaaS 管理 → 渠道分发」的后端:管理各应用在各分发渠道(华为/小米/OPPO/VIVO/荣耀/官方/苹果/谷歌) // 上**因渠道而异**的东西——本渠道下载地址、上架状态、支付渠道、是否允许跳过设备绑定。 // 模型见 comm/channelapp.go,存 console 主库(postgres.GetSys()),按 app_name 作用域, // 业务服务(home 的 user 模块)运行时按 (app_name=? OR app_name='') 直读下发。 // // ⚠️ 版本号/最低支持版本/强制更新/更新日志/送审版本/游客登录开关**不在这里**:它们是 // 「整个应用一套」的,已搬到「服务配置 → 版本控制 / 游客显隐控制」(api_apprelease.go)。 // 别再往本页加这些字段——按渠道各存一份的直接后果是同一个开关有 8 处能改、客户端只读一处。 // // 旧实现(deep_server modules/api/api_*channelapp.go + 各应用业务库 mysql 的 channelapp 表)已下线: // 数据统一收到 console 主库,后台一个库即可总览全部应用的渠道版本。 // // 支付:本页只管「这个渠道开放哪几种支付」(PayChannels);各支付方式的商户凭据仍在 // 「应用中心 → 业务功能配置」的支付分组(app_module_config)里配,不在此重复。 // getChannelPayStatus 负责把两边对上——告诉前端哪些已开的支付渠道其实还没配凭据。 // getChannelApps 列出某作用域下的渠道配置(app_name=”为全局默认)。 func (this *serverComp) getChannelApps(c *gin.Context) { var req struct { AppName string `json:"app_name"` } _ = c.ShouldBindJSON(&req) items := make([]*comm.ChannelApp, 0) if err := postgres.Find(comm.TableChannelAppCfg, &items, "app_name=?", req.AppName); err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } writeOK(c, gin.H{"items": items}) } // saveChannelApp 新增或更新一个渠道配置(按 app_name+channel upsert)。 func (this *serverComp) saveChannelApp(c *gin.Context) { var m comm.ChannelApp if err := c.ShouldBindJSON(&m); err != nil { writeErr(c, pb.ErrorCode_ReqParameterError, err.Error()) return } if m.Channel == 0 { writeErr(c, pb.ErrorCode_ReqParameterError, "渠道 必选") return } // 版本/强更/游客登录的校验已随字段搬到 api_apprelease.go(按应用配,不分渠道)。 now := time.Now().UnixMilli() m.Updatetime = now old := &comm.ChannelApp{} err := postgres.FindOne(comm.TableChannelAppCfg, old, "app_name=? AND channel=?", m.AppName, m.Channel) if err != nil { m.RowId = 0 m.Createtime = now err = postgres.Insert(comm.TableChannelAppCfg, &m) } else { m.RowId = old.RowId m.Createtime = old.Createtime err = postgres.Save(comm.TableChannelAppCfg, &m) } if err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } broadcastConfigChanged(comm.ConfigKindChannelApp, "update", 0) writeOK(c, &m) } // delChannelApp 删除某作用域下某渠道的配置。 func (this *serverComp) delChannelApp(c *gin.Context) { var req struct { AppName string `json:"app_name"` Channel int32 `json:"channel"` } _ = c.ShouldBindJSON(&req) if req.Channel == 0 { writeErr(c, pb.ErrorCode_ReqParameterError, "channel 必填") return } if err := postgres.Delete(comm.TableChannelAppCfg, "app_name=? AND channel=?", req.AppName, req.Channel); err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } broadcastConfigChanged(comm.ConfigKindChannelApp, "delete", 0) writeOK(c, gin.H{"channel": req.Channel}) } // getChannelPayStatus 查这个应用的支付凭据配了哪几种。 // // 渠道配置里的 PayChannels 只是「开放哪几种支付入口」,真正的商户凭据在**应用业务库**的 // app_module_config 里。两者分处不同库,运营很容易开了入口却忘了配凭据,故后台直接把结论算好: // 遍历该 app_name 下的部署,连上任意一个业务库,读出已配置的支付模块键。 // 全局作用域(app_name=”)没有对应业务库,返回空表示「无法判定」,前端据此不告警。 func (this *serverComp) getChannelPayStatus(c *gin.Context) { var req struct { AppName string `json:"app_name"` } _ = c.ShouldBindJSON(&req) resp := gin.H{"configured": []string{}, "checked": false} if strings.TrimSpace(req.AppName) == "" { writeOK(c, resp) return } regs := make([]*AppRegistry, 0) if err := postgres.Find(comm.TableAppRegistry, ®s, "app_name=?", req.AppName); err != nil || len(regs) == 0 { writeOK(c, resp) return } payModules := map[string]bool{} for _, m := range comm.PayChannelModule { payModules[m] = true } for _, r := range regs { db, err := this.module.registry.getServiceDB(r.Id) if err != nil { continue // 这个部署连不上,试下一个 } rows, err := listModuleConfig(db, "") if err != nil { continue } configured := make([]string, 0, len(payModules)) seen := map[string]bool{} for _, row := range rows { if !payModules[row.Module] || seen[row.Module] || strings.TrimSpace(row.Value) == "" { continue } seen[row.Module] = true configured = append(configured, row.Module) } writeOK(c, gin.H{"configured": configured, "checked": true, "app_id": r.Id}) return } writeOK(c, resp) }