// 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` = ?" // 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"` } // Reader 是 HTTP 层读取 H5 配置的最小依赖。 type Reader interface { ListH5Links(ctx context.Context) ([]H5Link, 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 } // StaticReader 给测试和临时环境提供内存版 H5 配置读取。 type StaticReader struct { Links []H5Link 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 }