2026-05-05 03:05:06 +08:00

239 lines
5.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package appconfig 读取后台管理维护的 App 运行时配置。
package appconfig
import (
"context"
"database/sql"
"errors"
"strings"
_ "github.com/go-sql-driver/mysql"
)
const h5LinkGroup = "h5-links"
const listH5LinksSQL = "SELECT `key`, COALESCE(value, ''), updated_at FROM admin_app_configs WHERE `group` = ?"
const listAppBannersSQL = `
SELECT id, app_code, cover_url, banner_type, param, platform, sort_order, region_id, country_code, description, updated_at
FROM admin_app_banners
WHERE app_code = ? AND status = 'active'
AND (? = '' OR platform = ?)
AND (region_id = 0 OR region_id = ?)
AND (country_code = '' OR country_code = ?)
ORDER BY sort_order ASC, id DESC`
// H5Link 是 gateway 下发给 App 的 H5 入口配置。
type H5Link struct {
Key string `json:"key"`
Label string `json:"label"`
URL string `json:"url"`
UpdatedAtMs int64 `json:"updated_at_ms"`
}
// BannerQuery 是 App 首页 banner 的公开筛选条件。
type BannerQuery struct {
AppCode string
Platform string
RegionID int64
Country string
}
// Banner 是 gateway 下发给 App 首页的 banner 配置。
type Banner struct {
ID uint `json:"id"`
CoverURL string `json:"cover_url"`
BannerType string `json:"type"`
Param string `json:"param"`
Platform string `json:"platform"`
SortOrder int `json:"sort_order"`
RegionID int64 `json:"region_id,omitempty"`
CountryCode string `json:"country_code,omitempty"`
Description string `json:"description,omitempty"`
UpdatedAtMs int64 `json:"updated_at_ms"`
}
// Reader 是 HTTP 层读取 H5 配置的最小依赖。
type Reader interface {
ListH5Links(ctx context.Context) ([]H5Link, error)
ListBanners(ctx context.Context, query BannerQuery) ([]Banner, error)
}
type h5LinkDefinition struct {
Key string
Label string
}
var h5LinkDefinitions = []h5LinkDefinition{
{Key: "host-center", Label: "Host Center"},
{Key: "bd-center", Label: "BD Center"},
{Key: "bd-leader-center", Label: "BD Leader Center"},
{Key: "agency-center", Label: "Agency Center"},
{Key: "invite-user", Label: "Invite User"},
}
// MySQLReader 从 hyapp_admin.admin_app_configs 读取后台 H5 配置。
type MySQLReader struct {
db *sql.DB
}
// OpenMySQLReader 创建后台配置只读连接池。
func OpenMySQLReader(dsn string) (*MySQLReader, error) {
db, err := sql.Open("mysql", strings.TrimSpace(dsn))
if err != nil {
return nil, err
}
return &MySQLReader{db: db}, nil
}
// Close 关闭后台配置连接池。
func (r *MySQLReader) Close() error {
if r == nil || r.db == nil {
return nil
}
return r.db.Close()
}
// ListH5Links 返回固定 H5 入口集合;未配置的入口保留空 URL方便客户端按 key 稳定渲染。
func (r *MySQLReader) ListH5Links(ctx context.Context) ([]H5Link, error) {
if r == nil || r.db == nil {
return nil, errors.New("app config reader is not configured")
}
rows, err := r.db.QueryContext(ctx, listH5LinksSQL, h5LinkGroup)
if err != nil {
return nil, err
}
defer rows.Close()
configs := make(map[string]H5Link, len(h5LinkDefinitions))
for rows.Next() {
var key string
var url string
var updatedAt sql.NullTime
if err := rows.Scan(&key, &url, &updatedAt); err != nil {
return nil, err
}
key = strings.TrimSpace(key)
link := H5Link{
Key: key,
URL: strings.TrimSpace(url),
}
if updatedAt.Valid {
link.UpdatedAtMs = updatedAt.Time.UnixMilli()
}
configs[key] = link
}
if err := rows.Err(); err != nil {
return nil, err
}
items := make([]H5Link, 0, len(h5LinkDefinitions))
for _, definition := range h5LinkDefinitions {
item := H5Link{
Key: definition.Key,
Label: definition.Label,
}
if config, ok := configs[definition.Key]; ok {
item.URL = config.URL
item.UpdatedAtMs = config.UpdatedAtMs
}
items = append(items, item)
}
return items, nil
}
// ListBanners 返回当前 App、平台和地域可见的首页 banner。
func (r *MySQLReader) ListBanners(ctx context.Context, query BannerQuery) ([]Banner, error) {
if r == nil || r.db == nil {
return nil, errors.New("app config reader is not configured")
}
appCode := normalizeAppCode(query.AppCode)
platform := normalizePlatform(query.Platform)
country := normalizeCountry(query.Country)
rows, err := r.db.QueryContext(ctx, listAppBannersSQL, appCode, platform, platform, query.RegionID, country)
if err != nil {
return nil, err
}
defer rows.Close()
items := make([]Banner, 0)
for rows.Next() {
var item Banner
var appCode string
var updatedAt sql.NullTime
if err := rows.Scan(
&item.ID,
&appCode,
&item.CoverURL,
&item.BannerType,
&item.Param,
&item.Platform,
&item.SortOrder,
&item.RegionID,
&item.CountryCode,
&item.Description,
&updatedAt,
); err != nil {
return nil, err
}
if updatedAt.Valid {
item.UpdatedAtMs = updatedAt.Time.UnixMilli()
}
items = append(items, item)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
// StaticReader 给测试和临时环境提供内存版 H5 配置读取。
type StaticReader struct {
Links []H5Link
Banners []Banner
Err error
}
// ListH5Links 返回预置 H5 配置。
func (r StaticReader) ListH5Links(context.Context) ([]H5Link, error) {
if r.Err != nil {
return nil, r.Err
}
out := make([]H5Link, len(r.Links))
copy(out, r.Links)
return out, nil
}
// ListBanners 返回预置 App banner 配置。
func (r StaticReader) ListBanners(context.Context, BannerQuery) ([]Banner, error) {
if r.Err != nil {
return nil, r.Err
}
out := make([]Banner, len(r.Banners))
copy(out, r.Banners)
return out, nil
}
func normalizeAppCode(value string) string {
value = strings.ToLower(strings.TrimSpace(value))
if value == "" {
return "lalu"
}
return value
}
func normalizePlatform(value string) string {
return strings.ToLower(strings.TrimSpace(value))
}
func normalizeCountry(value string) string {
return strings.ToUpper(strings.TrimSpace(value))
}