Add app logo metadata
This commit is contained in:
parent
14604861ea
commit
d6c2948ab0
43
scripts/mysql/066_app_logo_url.sql
Normal file
43
scripts/mysql/066_app_logo_url.sql
Normal file
@ -0,0 +1,43 @@
|
||||
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE hyapp_user;
|
||||
|
||||
-- 只新增无索引的展示列,并强制 INSTANT 算法;不支持元数据即时变更时直接失败,避免意外触发全表重建。
|
||||
SET @apps_logo_column_exists = (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'apps'
|
||||
AND COLUMN_NAME = 'logo_url'
|
||||
);
|
||||
SET @apps_logo_ddl = IF(
|
||||
@apps_logo_column_exists = 0,
|
||||
'ALTER TABLE apps ADD COLUMN logo_url VARCHAR(1024) NOT NULL DEFAULT '''' COMMENT ''应用 Logo 的公网访问地址'', ALGORITHM=INSTANT',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE apps_logo_stmt FROM @apps_logo_ddl;
|
||||
EXECUTE apps_logo_stmt;
|
||||
DEALLOCATE PREPARE apps_logo_stmt;
|
||||
|
||||
SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED);
|
||||
|
||||
-- app_code 有唯一索引,五个常量键只会做定点查找;不在 apps 表的 legacy App 由 admin-server 财务源配置提供 Logo。
|
||||
UPDATE apps
|
||||
SET logo_url = CASE app_code
|
||||
WHEN 'lalu' THEN 'https://media.haiyihy.com/admin/apps/logos/lalu.png'
|
||||
WHEN 'huwaa' THEN 'https://media.haiyihy.com/admin/apps/logos/huwaa.png'
|
||||
WHEN 'fami' THEN 'https://media.haiyihy.com/admin/apps/logos/fami.png'
|
||||
WHEN 'aslan' THEN 'https://media.haiyihy.com/admin/apps/logos/aslan.png'
|
||||
WHEN 'yumi' THEN 'https://media.haiyihy.com/admin/apps/logos/yumi.png'
|
||||
ELSE logo_url
|
||||
END,
|
||||
updated_at_ms = @now_ms
|
||||
WHERE app_code IN ('lalu', 'huwaa', 'fami', 'aslan', 'yumi')
|
||||
AND logo_url <> CASE app_code
|
||||
WHEN 'lalu' THEN 'https://media.haiyihy.com/admin/apps/logos/lalu.png'
|
||||
WHEN 'huwaa' THEN 'https://media.haiyihy.com/admin/apps/logos/huwaa.png'
|
||||
WHEN 'fami' THEN 'https://media.haiyihy.com/admin/apps/logos/fami.png'
|
||||
WHEN 'aslan' THEN 'https://media.haiyihy.com/admin/apps/logos/aslan.png'
|
||||
WHEN 'yumi' THEN 'https://media.haiyihy.com/admin/apps/logos/yumi.png'
|
||||
ELSE logo_url
|
||||
END;
|
||||
@ -18,6 +18,7 @@ money_region_sources:
|
||||
- enabled: true
|
||||
app_code: "yumi"
|
||||
app_name: "Yumi"
|
||||
logo_url: "https://media.haiyihy.com/admin/apps/logos/yumi.png"
|
||||
sys_origin: "LIKEI"
|
||||
mongo_uri: "mongodb://root:123456@10.2.21.9:27017/tarab_all?authSource=admin&readPreference=secondaryPreferred&retryWrites=false&maxPoolSize=50&minPoolSize=5&maxIdleTimeMS=6000"
|
||||
mongo_database: "tarab_all"
|
||||
@ -26,6 +27,7 @@ money_region_sources:
|
||||
- enabled: true
|
||||
app_code: "aslan"
|
||||
app_name: "Aslan"
|
||||
logo_url: "https://media.haiyihy.com/admin/apps/logos/aslan.png"
|
||||
sys_origin: "ATYOU"
|
||||
mongo_uri: "mongodb://aslan_data:aslan_data@lb-le274w4o-wjgmcdkhye3v0ors.clb.sg-tencentclb.com:27017,lb-le274w4o-wjgmcdkhye3v0ors.clb.sg-tencentclb.com:27018/test?replicaSet=cmgo-6cztxjgr_0&authSource=admin&readPreference=secondaryPreferred"
|
||||
mongo_database: "atyou"
|
||||
@ -36,6 +38,7 @@ finance_bill_sources:
|
||||
- enabled: true
|
||||
app_code: "yumi"
|
||||
app_name: "Yumi"
|
||||
logo_url: "https://media.haiyihy.com/admin/apps/logos/yumi.png"
|
||||
sys_origin: "LIKEI"
|
||||
mongo_uri: "mongodb://root:123456@10.2.21.9:27017/tarab_all?authSource=admin&readPreference=secondaryPreferred&retryWrites=false&maxPoolSize=50&minPoolSize=5&maxIdleTimeMS=6000"
|
||||
mongo_database: "tarab_all"
|
||||
@ -47,6 +50,7 @@ finance_bill_sources:
|
||||
- enabled: true
|
||||
app_code: "aslan"
|
||||
app_name: "Aslan"
|
||||
logo_url: "https://media.haiyihy.com/admin/apps/logos/aslan.png"
|
||||
sys_origin: "ATYOU"
|
||||
mongo_uri: "mongodb://aslan_data:aslan_data@lb-le274w4o-wjgmcdkhye3v0ors.clb.sg-tencentclb.com:27017,lb-le274w4o-wjgmcdkhye3v0ors.clb.sg-tencentclb.com:27018/test?replicaSet=cmgo-6cztxjgr_0&authSource=admin&readPreference=secondaryPreferred"
|
||||
mongo_database: "atyou"
|
||||
|
||||
@ -18,6 +18,7 @@ money_region_sources:
|
||||
- enabled: false
|
||||
app_code: "yumi"
|
||||
app_name: "Yumi"
|
||||
logo_url: "https://media.haiyihy.com/admin/apps/logos/yumi.png"
|
||||
sys_origin: "LIKEI"
|
||||
mongo_uri: ""
|
||||
mongo_database: "tarab_all"
|
||||
@ -26,6 +27,7 @@ money_region_sources:
|
||||
- enabled: false
|
||||
app_code: "aslan"
|
||||
app_name: "Aslan"
|
||||
logo_url: "https://media.haiyihy.com/admin/apps/logos/aslan.png"
|
||||
sys_origin: "ATYOU"
|
||||
mongo_uri: ""
|
||||
mongo_database: "atyou"
|
||||
@ -36,6 +38,7 @@ finance_bill_sources:
|
||||
- enabled: false
|
||||
app_code: "yumi"
|
||||
app_name: "Yumi"
|
||||
logo_url: "https://media.haiyihy.com/admin/apps/logos/yumi.png"
|
||||
sys_origin: "LIKEI"
|
||||
mongo_uri: ""
|
||||
mongo_database: "tarab_all"
|
||||
@ -47,6 +50,7 @@ finance_bill_sources:
|
||||
- enabled: false
|
||||
app_code: "aslan"
|
||||
app_name: "Aslan"
|
||||
logo_url: "https://media.haiyihy.com/admin/apps/logos/aslan.png"
|
||||
sys_origin: "ATYOU"
|
||||
mongo_uri: ""
|
||||
mongo_database: "atyou"
|
||||
|
||||
@ -149,6 +149,7 @@ type MoneyRegionSourceConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
AppCode string `yaml:"app_code"`
|
||||
AppName string `yaml:"app_name"`
|
||||
LogoURL string `yaml:"logo_url"`
|
||||
SysOrigin string `yaml:"sys_origin"`
|
||||
MongoURI string `yaml:"mongo_uri"`
|
||||
MongoDatabase string `yaml:"mongo_database"`
|
||||
@ -162,6 +163,7 @@ type FinanceBillSourceConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
AppCode string `yaml:"app_code"`
|
||||
AppName string `yaml:"app_name"`
|
||||
LogoURL string `yaml:"logo_url"`
|
||||
SysOrigin string `yaml:"sys_origin"`
|
||||
MongoURI string `yaml:"mongo_uri"`
|
||||
MongoDatabase string `yaml:"mongo_database"`
|
||||
@ -237,6 +239,7 @@ func Default() Config {
|
||||
Enabled: false,
|
||||
AppCode: "yumi",
|
||||
AppName: "Yumi",
|
||||
LogoURL: "https://media.haiyihy.com/admin/apps/logos/yumi.png",
|
||||
SysOrigin: "LIKEI",
|
||||
MongoDatabase: "tarab_all",
|
||||
MongoCollection: "sys_region_config",
|
||||
@ -246,6 +249,7 @@ func Default() Config {
|
||||
Enabled: false,
|
||||
AppCode: "aslan",
|
||||
AppName: "Aslan",
|
||||
LogoURL: "https://media.haiyihy.com/admin/apps/logos/aslan.png",
|
||||
SysOrigin: "ATYOU",
|
||||
MongoDatabase: "tarab_all",
|
||||
MongoCollection: "sys_region_config",
|
||||
@ -257,6 +261,7 @@ func Default() Config {
|
||||
Enabled: false,
|
||||
AppCode: "yumi",
|
||||
AppName: "Yumi",
|
||||
LogoURL: "https://media.haiyihy.com/admin/apps/logos/yumi.png",
|
||||
SysOrigin: "LIKEI",
|
||||
MongoDatabase: "tarab_all",
|
||||
MongoCollection: "in_app_purchase_details",
|
||||
@ -266,6 +271,7 @@ func Default() Config {
|
||||
Enabled: false,
|
||||
AppCode: "aslan",
|
||||
AppName: "Aslan",
|
||||
LogoURL: "https://media.haiyihy.com/admin/apps/logos/aslan.png",
|
||||
// Aslan(likei-services 项目)与 Yumi 同一套平台代码,但独立 Mongo 集群;业务库是 atyou。
|
||||
SysOrigin: "ATYOU",
|
||||
MongoDatabase: "atyou",
|
||||
@ -585,6 +591,7 @@ func (cfg *Config) Normalize() {
|
||||
// legacy 区域来源只服务财务范围目录;这里先规整配置,handler 层只处理业务过滤,不重复处理大小写和默认集合名。
|
||||
source.AppCode = strings.ToLower(strings.TrimSpace(source.AppCode))
|
||||
source.AppName = strings.TrimSpace(source.AppName)
|
||||
source.LogoURL = strings.TrimSpace(source.LogoURL)
|
||||
source.SysOrigin = strings.ToUpper(strings.TrimSpace(source.SysOrigin))
|
||||
source.MongoURI = strings.TrimSpace(source.MongoURI)
|
||||
source.MongoDatabase = strings.Trim(strings.TrimSpace(source.MongoDatabase), "/")
|
||||
@ -605,6 +612,7 @@ func (cfg *Config) Normalize() {
|
||||
// legacy 账单源只服务财务充值明细;配置层统一大小写和默认集合,handler 只按 app_code 命中。
|
||||
source.AppCode = strings.ToLower(strings.TrimSpace(source.AppCode))
|
||||
source.AppName = strings.TrimSpace(source.AppName)
|
||||
source.LogoURL = strings.TrimSpace(source.LogoURL)
|
||||
source.SysOrigin = strings.ToUpper(strings.TrimSpace(source.SysOrigin))
|
||||
source.MongoURI = strings.TrimSpace(source.MongoURI)
|
||||
source.MongoDatabase = strings.Trim(strings.TrimSpace(source.MongoDatabase), "/")
|
||||
|
||||
@ -14,6 +14,7 @@ type App struct {
|
||||
AppID int64 `json:"appId"`
|
||||
AppCode string `json:"appCode"`
|
||||
AppName string `json:"appName"`
|
||||
LogoURL string `json:"logoUrl"`
|
||||
PackageName string `json:"packageName"`
|
||||
Platform string `json:"platform"`
|
||||
Status string `json:"status"`
|
||||
@ -30,7 +31,7 @@ func (s *Service) ListApps(ctx context.Context) ([]App, error) {
|
||||
return nil, fmt.Errorf("user mysql is not configured")
|
||||
}
|
||||
rows, err := s.userDB.QueryContext(ctx, `
|
||||
SELECT app_id, app_code, app_name, package_name, platform, status, created_at_ms, updated_at_ms
|
||||
SELECT app_id, app_code, app_name, logo_url, package_name, platform, status, created_at_ms, updated_at_ms
|
||||
FROM apps
|
||||
WHERE status = 'active'
|
||||
ORDER BY app_name ASC, app_code ASC
|
||||
@ -43,7 +44,7 @@ func (s *Service) ListApps(ctx context.Context) ([]App, error) {
|
||||
items := []App{}
|
||||
for rows.Next() {
|
||||
var item App
|
||||
if err := rows.Scan(&item.AppID, &item.AppCode, &item.AppName, &item.PackageName, &item.Platform, &item.Status, &item.CreatedAtMs, &item.UpdatedAtMs); err != nil {
|
||||
if err := rows.Scan(&item.AppID, &item.AppCode, &item.AppName, &item.LogoURL, &item.PackageName, &item.Platform, &item.Status, &item.CreatedAtMs, &item.UpdatedAtMs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, item)
|
||||
|
||||
31
server/admin/internal/modules/appregistry/service_test.go
Normal file
31
server/admin/internal/modules/appregistry/service_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
package appregistry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
)
|
||||
|
||||
func TestListAppsReturnsLogoURL(t *testing.T) {
|
||||
db, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("create sqlmock: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
mock.ExpectQuery(`SELECT app_id, app_code, app_name, logo_url, package_name, platform, status, created_at_ms, updated_at_ms`).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"app_id", "app_code", "app_name", "logo_url", "package_name", "platform", "status", "created_at_ms", "updated_at_ms"}).
|
||||
AddRow(1, "lalu", "Lalu", "https://media.haiyihy.com/admin/apps/logos/lalu.png", "com.org.laluparty", "", "active", 10, 20))
|
||||
|
||||
items, err := NewService(db).ListApps(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("list apps: %v", err)
|
||||
}
|
||||
if len(items) != 1 || items[0].LogoURL != "https://media.haiyihy.com/admin/apps/logos/lalu.png" {
|
||||
t.Fatalf("unexpected app items: %+v", items)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("sql expectations: %v", err)
|
||||
}
|
||||
}
|
||||
@ -240,6 +240,11 @@ func (s *MongoRechargeBillSource) AppName() string {
|
||||
return s.config.AppCode
|
||||
}
|
||||
|
||||
// LogoURL 与 legacy 账单源同配置生命周期;这类 App 不是 HyApp gateway 租户,不为了展示 Logo 向 apps 注册表塞入虚假 active 记录。
|
||||
func (s *MongoRechargeBillSource) LogoURL() string {
|
||||
return strings.TrimSpace(s.config.LogoURL)
|
||||
}
|
||||
|
||||
type legacyCoinSellerRechargeStats struct {
|
||||
TotalUSDMinor int64
|
||||
DailyUSDMinor map[string]int64
|
||||
|
||||
@ -16,6 +16,7 @@ func yumiBillSourceConfig() config.FinanceBillSourceConfig {
|
||||
Enabled: true,
|
||||
AppCode: "yumi",
|
||||
AppName: "Yumi",
|
||||
LogoURL: "https://media.haiyihy.com/admin/apps/logos/yumi.png",
|
||||
SysOrigin: "LIKEI",
|
||||
MongoDatabase: "test",
|
||||
MongoCollection: "in_app_purchase_details",
|
||||
@ -23,6 +24,13 @@ func yumiBillSourceConfig() config.FinanceBillSourceConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegacyRechargeBillSourceExposesConfiguredLogoURL(t *testing.T) {
|
||||
source := &MongoRechargeBillSource{config: yumiBillSourceConfig()}
|
||||
if source.LogoURL() != "https://media.haiyihy.com/admin/apps/logos/yumi.png" {
|
||||
t.Fatalf("logo url=%q", source.LogoURL())
|
||||
}
|
||||
}
|
||||
|
||||
func aslanBillSourceConfig() config.FinanceBillSourceConfig {
|
||||
cfg := yumiBillSourceConfig()
|
||||
cfg.AppCode = "aslan"
|
||||
|
||||
@ -46,6 +46,7 @@ type moneyAppDTO struct {
|
||||
AppID int64 `json:"appId"`
|
||||
AppCode string `json:"appCode"`
|
||||
AppName string `json:"appName"`
|
||||
LogoURL string `json:"logoUrl"`
|
||||
PackageName string `json:"packageName"`
|
||||
Platform string `json:"platform"`
|
||||
Status string `json:"status"`
|
||||
@ -456,7 +457,7 @@ func (h *Handler) loadMoneyMasterData(ctx context.Context, access repository.Mon
|
||||
func (h *Handler) listMoneyApps(ctx context.Context, access repository.MoneyAccess) ([]moneyAppDTO, error) {
|
||||
where, args := scopedAppWhere(access)
|
||||
rows, err := h.userDB.QueryContext(ctx, `
|
||||
SELECT app_id, app_code, app_name, package_name, platform, status
|
||||
SELECT app_id, app_code, app_name, logo_url, package_name, platform, status
|
||||
FROM apps
|
||||
WHERE status = 'active'`+where+`
|
||||
ORDER BY app_name ASC, app_code ASC`, args...)
|
||||
@ -467,7 +468,7 @@ func (h *Handler) listMoneyApps(ctx context.Context, access repository.MoneyAcce
|
||||
items := []moneyAppDTO{}
|
||||
for rows.Next() {
|
||||
var item moneyAppDTO
|
||||
if err := rows.Scan(&item.AppID, &item.AppCode, &item.AppName, &item.PackageName, &item.Platform, &item.Status); err != nil {
|
||||
if err := rows.Scan(&item.AppID, &item.AppCode, &item.AppName, &item.LogoURL, &item.PackageName, &item.Platform, &item.Status); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, item)
|
||||
|
||||
@ -253,8 +253,10 @@ func legacyRegionDocumentsToMoneyData(sourceConfig config.MoneyRegionSourceConfi
|
||||
}
|
||||
}
|
||||
return []moneyAppDTO{{
|
||||
AppCode: appCode,
|
||||
AppName: appName,
|
||||
AppCode: appCode,
|
||||
AppName: appName,
|
||||
// Legacy App 没有 apps 行;其 Logo 与区域源一起配置,避免为了 UI 展示伪造活跃租户。
|
||||
LogoURL: strings.TrimSpace(sourceConfig.LogoURL),
|
||||
Platform: "legacy",
|
||||
Status: "active",
|
||||
}}, regions, countries, nil
|
||||
|
||||
@ -24,6 +24,7 @@ func TestLegacyRegionDocumentsToMoneyDataFiltersYumiRegions(t *testing.T) {
|
||||
cfg := config.MoneyRegionSourceConfig{
|
||||
AppCode: "yumi",
|
||||
AppName: "Yumi",
|
||||
LogoURL: "https://media.example.com/yumi.png",
|
||||
SysOrigin: "LIKEI",
|
||||
}
|
||||
apps, regions, countries, err := legacyRegionDocumentsToMoneyData(cfg, []legacyRegionDocument{
|
||||
@ -35,7 +36,7 @@ func TestLegacyRegionDocumentsToMoneyDataFiltersYumiRegions(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("legacy money data failed: %v", err)
|
||||
}
|
||||
if len(apps) != 1 || apps[0].AppCode != "yumi" || apps[0].AppName != "Yumi" || apps[0].Platform != "legacy" {
|
||||
if len(apps) != 1 || apps[0].AppCode != "yumi" || apps[0].AppName != "Yumi" || apps[0].LogoURL != cfg.LogoURL || apps[0].Platform != "legacy" {
|
||||
t.Fatalf("apps mismatch: %+v", apps)
|
||||
}
|
||||
if len(regions) != 2 || regions[0].RegionID < legacySyntheticRegionIDBase || regions[0].RegionCode != "OTHER" || regions[1].RegionID != 1001 || regions[1].RegionCode != "MIDDLE_EAST" {
|
||||
@ -159,7 +160,7 @@ func newMoneyRegionSourceUserSQLMock(t *testing.T) (*sql.DB, sqlmock.Sqlmock, fu
|
||||
}
|
||||
|
||||
func expectEmptyMoneyApps(mock sqlmock.Sqlmock, appCodes ...string) {
|
||||
query := mock.ExpectQuery(`(?s)SELECT app_id, app_code, app_name, package_name, platform, status\s+FROM apps\s+WHERE status = 'active'`)
|
||||
query := mock.ExpectQuery(`(?s)SELECT app_id, app_code, app_name, logo_url, package_name, platform, status\s+FROM apps\s+WHERE status = 'active'`)
|
||||
if len(appCodes) > 0 {
|
||||
args := make([]driver.Value, 0, len(appCodes))
|
||||
for _, appCode := range appCodes {
|
||||
@ -167,12 +168,12 @@ func expectEmptyMoneyApps(mock sqlmock.Sqlmock, appCodes ...string) {
|
||||
}
|
||||
query.WithArgs(args...)
|
||||
}
|
||||
query.WillReturnRows(sqlmock.NewRows([]string{"app_id", "app_code", "app_name", "package_name", "platform", "status"}))
|
||||
query.WillReturnRows(sqlmock.NewRows([]string{"app_id", "app_code", "app_name", "logo_url", "package_name", "platform", "status"}))
|
||||
}
|
||||
|
||||
func TestMoneyRegionSourceConfigDefaults(t *testing.T) {
|
||||
cfg := config.Default()
|
||||
if len(cfg.MoneyRegionSources) != 2 || cfg.MoneyRegionSources[0].RequestTimeout != 5*time.Second || cfg.MoneyRegionSources[1].AppCode != "aslan" {
|
||||
if len(cfg.MoneyRegionSources) != 2 || cfg.MoneyRegionSources[0].RequestTimeout != 5*time.Second || cfg.MoneyRegionSources[0].LogoURL == "" || cfg.MoneyRegionSources[1].AppCode != "aslan" {
|
||||
t.Fatalf("default money region source mismatch: %+v", cfg.MoneyRegionSources)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestListRechargeBillAppsReturnsStoredLogoURL(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("create sqlmock: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
mock.ExpectQuery(`SELECT app_code, app_name, logo_url`).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"app_code", "app_name", "logo_url"}).
|
||||
AddRow("lalu", "Lalu", "https://media.haiyihy.com/admin/apps/logos/lalu.png"))
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
// 直接使用 recorder 承载 Gin 响应,测试只关注 App 目录映射,不绕过真实 handler 序列化。
|
||||
context, _ := gin.CreateTestContext(recorder)
|
||||
context.Request = httptest.NewRequest("GET", "/api/v1/admin/payment/recharge-apps", nil)
|
||||
(&Handler{userDB: db, billSources: map[string]RechargeBillSource{}}).ListRechargeBillApps(context)
|
||||
|
||||
if recorder.Code != 200 {
|
||||
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
var payload struct {
|
||||
Data struct {
|
||||
Items []rechargeBillAppDTO `json:"items"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if len(payload.Data.Items) != 1 || payload.Data.Items[0].LogoURL != "https://media.haiyihy.com/admin/apps/logos/lalu.png" {
|
||||
t.Fatalf("unexpected apps: %+v", payload.Data.Items)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("sql expectations: %v", err)
|
||||
}
|
||||
}
|
||||
@ -279,6 +279,12 @@ func (h *Handler) RefreshGoogleRechargePaidDetails(c *gin.Context) {
|
||||
type rechargeBillAppDTO struct {
|
||||
AppCode string `json:"appCode"`
|
||||
AppName string `json:"appName"`
|
||||
LogoURL string `json:"logoUrl"`
|
||||
}
|
||||
|
||||
// rechargeBillSourceLogo 是兼容扩展:历史或测试账单源不实现时仍可列出,新的 Mongo 源则补充 Logo。
|
||||
type rechargeBillSourceLogo interface {
|
||||
LogoURL() string
|
||||
}
|
||||
|
||||
// ListRechargeBillApps 返回充值详情可选的 App 目录:hyapp 在册应用 + 配置的 legacy 账单源(Yumi 等)。
|
||||
@ -287,7 +293,7 @@ func (h *Handler) ListRechargeBillApps(c *gin.Context) {
|
||||
seen := map[string]struct{}{}
|
||||
if h.userDB != nil {
|
||||
rows, err := h.userDB.QueryContext(c.Request.Context(), `
|
||||
SELECT app_code, app_name
|
||||
SELECT app_code, app_name, logo_url
|
||||
FROM apps
|
||||
WHERE status = 'active'
|
||||
ORDER BY app_name ASC, app_code ASC`)
|
||||
@ -298,7 +304,7 @@ func (h *Handler) ListRechargeBillApps(c *gin.Context) {
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var item rechargeBillAppDTO
|
||||
if err := rows.Scan(&item.AppCode, &item.AppName); err != nil {
|
||||
if err := rows.Scan(&item.AppCode, &item.AppName, &item.LogoURL); err != nil {
|
||||
response.ServerError(c, "获取 App 列表失败")
|
||||
return
|
||||
}
|
||||
@ -317,7 +323,11 @@ func (h *Handler) ListRechargeBillApps(c *gin.Context) {
|
||||
if _, ok := seen[appCode]; ok {
|
||||
continue
|
||||
}
|
||||
legacyApps = append(legacyApps, rechargeBillAppDTO{AppCode: appCode, AppName: source.AppName()})
|
||||
logoURL := ""
|
||||
if logoSource, ok := source.(rechargeBillSourceLogo); ok {
|
||||
logoURL = strings.TrimSpace(logoSource.LogoURL())
|
||||
}
|
||||
legacyApps = append(legacyApps, rechargeBillAppDTO{AppCode: appCode, AppName: source.AppName(), LogoURL: logoURL})
|
||||
}
|
||||
sort.Slice(legacyApps, func(i, j int) bool { return legacyApps[i].AppName < legacyApps[j].AppName })
|
||||
items = append(items, legacyApps...)
|
||||
|
||||
@ -10,6 +10,7 @@ CREATE TABLE IF NOT EXISTS apps (
|
||||
app_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '应用 ID',
|
||||
app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离',
|
||||
app_name VARCHAR(128) NOT NULL COMMENT '应用名称',
|
||||
logo_url VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '应用 Logo 的公网访问地址',
|
||||
package_name VARCHAR(191) NOT NULL COMMENT '包名称',
|
||||
platform VARCHAR(32) NOT NULL DEFAULT '' COMMENT '平台',
|
||||
status VARCHAR(32) NOT NULL COMMENT '业务状态',
|
||||
@ -20,14 +21,15 @@ CREATE TABLE IF NOT EXISTS apps (
|
||||
KEY idx_apps_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='应用注册表';
|
||||
|
||||
INSERT INTO apps (app_code, app_name, package_name, platform, status, created_at_ms, updated_at_ms)
|
||||
INSERT INTO apps (app_code, app_name, logo_url, package_name, platform, status, created_at_ms, updated_at_ms)
|
||||
VALUES
|
||||
('lalu', 'Lalu', 'com.org.laluparty', '', 'active', 0, 0),
|
||||
('huwaa', 'Huwaa', 'com.app.huwaa', '', 'active', 0, 0),
|
||||
('fami', 'Fami', 'com.chat.fami', '', 'active', 0, 0),
|
||||
('yumi', 'Yumi', 'com.org.yumi', '', 'active', 0, 0)
|
||||
('lalu', 'Lalu', 'https://media.haiyihy.com/admin/apps/logos/lalu.png', 'com.org.laluparty', '', 'active', 0, 0),
|
||||
('huwaa', 'Huwaa', 'https://media.haiyihy.com/admin/apps/logos/huwaa.png', 'com.app.huwaa', '', 'active', 0, 0),
|
||||
('fami', 'Fami', 'https://media.haiyihy.com/admin/apps/logos/fami.png', 'com.chat.fami', '', 'active', 0, 0),
|
||||
('yumi', 'Yumi', 'https://media.haiyihy.com/admin/apps/logos/yumi.png', 'com.org.yumi', '', 'active', 0, 0)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
app_name = VALUES(app_name),
|
||||
logo_url = VALUES(logo_url),
|
||||
package_name = VALUES(package_name),
|
||||
platform = VALUES(platform),
|
||||
status = VALUES(status),
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE hyapp_user;
|
||||
|
||||
-- apps 是 App 目录于后台与 gateway 共用的事实源;Logo 只是稳定展示元数据,不参与租户解析或鉴权。
|
||||
SET @apps_logo_column_exists = (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'apps'
|
||||
AND COLUMN_NAME = 'logo_url'
|
||||
);
|
||||
SET @apps_logo_ddl = IF(
|
||||
@apps_logo_column_exists = 0,
|
||||
'ALTER TABLE apps ADD COLUMN logo_url VARCHAR(1024) NOT NULL DEFAULT '''' COMMENT ''应用 Logo 的公网访问地址'', ALGORITHM=INSTANT',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE apps_logo_stmt FROM @apps_logo_ddl;
|
||||
EXECUTE apps_logo_stmt;
|
||||
DEALLOCATE PREPARE apps_logo_stmt;
|
||||
|
||||
SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED);
|
||||
|
||||
-- 用 app_code 唯一索引定点更新;Aslan/Yumi 若仅作为 legacy 财务源不在表内,本语句不会为其创建可被 gateway 解析的租户记录。
|
||||
UPDATE apps
|
||||
SET logo_url = CASE app_code
|
||||
WHEN 'lalu' THEN 'https://media.haiyihy.com/admin/apps/logos/lalu.png'
|
||||
WHEN 'huwaa' THEN 'https://media.haiyihy.com/admin/apps/logos/huwaa.png'
|
||||
WHEN 'fami' THEN 'https://media.haiyihy.com/admin/apps/logos/fami.png'
|
||||
WHEN 'aslan' THEN 'https://media.haiyihy.com/admin/apps/logos/aslan.png'
|
||||
WHEN 'yumi' THEN 'https://media.haiyihy.com/admin/apps/logos/yumi.png'
|
||||
ELSE logo_url
|
||||
END,
|
||||
updated_at_ms = @now_ms
|
||||
WHERE app_code IN ('lalu', 'huwaa', 'fami', 'aslan', 'yumi')
|
||||
AND logo_url <> CASE app_code
|
||||
WHEN 'lalu' THEN 'https://media.haiyihy.com/admin/apps/logos/lalu.png'
|
||||
WHEN 'huwaa' THEN 'https://media.haiyihy.com/admin/apps/logos/huwaa.png'
|
||||
WHEN 'fami' THEN 'https://media.haiyihy.com/admin/apps/logos/fami.png'
|
||||
WHEN 'aslan' THEN 'https://media.haiyihy.com/admin/apps/logos/aslan.png'
|
||||
WHEN 'yumi' THEN 'https://media.haiyihy.com/admin/apps/logos/yumi.png'
|
||||
ELSE logo_url
|
||||
END;
|
||||
Loading…
x
Reference in New Issue
Block a user