hyapp-server/server/admin/internal/repository/app_config_repository.go
2026-07-10 20:23:43 +08:00

272 lines
8.7 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 repository
import (
"sort"
"strings"
"time"
"hyapp-admin-server/internal/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type AppVersionListOptions struct {
AppCode string
Platform string
Keyword string
}
type AppExploreTabListOptions struct {
AppCode string
Keyword string
Enabled *bool
}
func (s *Store) ListAppConfigs(group string) ([]model.AppConfig, error) {
var items []model.AppConfig
// 旧的通用配置继续使用 app_code='' 基线行H5 配置必须走 scoped API避免覆盖行被其他旧调用方误读。
err := s.db.Where("app_code = '' AND `group` = ?", strings.TrimSpace(group)).Order("`key` ASC").Find(&items).Error
return items, err
}
func (s *Store) GetAppConfig(group string, key string) (model.AppConfig, error) {
var item model.AppConfig
err := s.db.Where("app_code = '' AND `group` = ? AND `key` = ?", strings.TrimSpace(group), strings.TrimSpace(key)).First(&item).Error
return item, err
}
func (s *Store) CreateAppConfig(item *model.AppConfig) error {
return s.db.Create(item).Error
}
func (s *Store) UpdateAppConfig(item *model.AppConfig) error {
return s.db.Save(item).Error
}
func (s *Store) DeleteAppConfig(group string, key string) error {
return s.db.Where("app_code = '' AND `group` = ? AND `key` = ?", strings.TrimSpace(group), strings.TrimSpace(key)).Delete(&model.AppConfig{}).Error
}
func (s *Store) UpsertAppConfigs(items []model.AppConfig) error {
if len(items) == 0 {
return nil
}
nowMS := time.Now().UTC().UnixMilli()
for index := range items {
items[index].AppCode = ""
items[index].Group = strings.TrimSpace(items[index].Group)
items[index].Key = strings.TrimSpace(items[index].Key)
items[index].IsDeleted = false
items[index].UpdatedAtMS = nowMS
if items[index].CreatedAtMS == 0 {
items[index].CreatedAtMS = nowMS
}
}
return s.db.Clauses(clause.OnConflict{
Columns: []clause.Column{
{Name: "app_code"},
{Name: "group"},
{Name: "key"},
},
DoUpdates: clause.AssignmentColumns([]string{"value", "description", "is_deleted", "updated_at_ms"}),
}).Create(&items).Error
}
// ListScopedAppConfigs 返回当前 App 的有效配置视图scoped 行覆盖 legacy 基线,墓碑显式屏蔽基线。
// legacy 基线保持只读,因此迁移后未知的新 App 也能继续获得原配置,同时任意 App 的写操作不会污染其他 App。
func (s *Store) ListScopedAppConfigs(appCode string, group string) ([]model.AppConfig, error) {
appCode = normalizeScopedAppCode(appCode)
var rows []model.AppConfig
err := s.db.
Where("`group` = ? AND app_code IN ?", strings.TrimSpace(group), []string{"", appCode}).
Order("`key` ASC, app_code ASC").
Find(&rows).Error
if err != nil {
return nil, err
}
return mergeScopedAppConfigs(rows, appCode), nil
}
// GetScopedAppConfig 和列表使用同一覆盖规则,防止更新/删除误操作其他 App 或 legacy 基线行。
func (s *Store) GetScopedAppConfig(appCode string, group string, key string) (model.AppConfig, error) {
appCode = normalizeScopedAppCode(appCode)
var rows []model.AppConfig
err := s.db.
Where("`group` = ? AND `key` = ? AND app_code IN ?", strings.TrimSpace(group), strings.TrimSpace(key), []string{"", appCode}).
Order("app_code ASC").
Find(&rows).Error
if err != nil {
return model.AppConfig{}, err
}
items := mergeScopedAppConfigs(rows, appCode)
if len(items) == 0 {
return model.AppConfig{}, gorm.ErrRecordNotFound
}
return items[0], nil
}
// UpsertScopedAppConfigs 只写当前 app_code调用方无法通过请求体覆盖租户作用域。
func (s *Store) UpsertScopedAppConfigs(appCode string, items []model.AppConfig) error {
if len(items) == 0 {
return nil
}
appCode = normalizeScopedAppCode(appCode)
nowMS := time.Now().UTC().UnixMilli()
for index := range items {
items[index].AppCode = appCode
items[index].Group = strings.TrimSpace(items[index].Group)
items[index].Key = strings.TrimSpace(items[index].Key)
items[index].IsDeleted = false
items[index].UpdatedAtMS = nowMS
if items[index].CreatedAtMS == 0 {
items[index].CreatedAtMS = nowMS
}
}
return s.db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "app_code"}, {Name: "group"}, {Name: "key"}},
DoUpdates: clause.AssignmentColumns([]string{"value", "description", "is_deleted", "updated_at_ms"}),
}).Create(&items).Error
}
// TombstoneScopedAppConfig 不物理删除 scoped 行。若配置来自 legacy 基线,墓碑会阻止它在删除后重新出现。
func (s *Store) TombstoneScopedAppConfig(appCode string, group string, key string) error {
appCode = normalizeScopedAppCode(appCode)
nowMS := time.Now().UTC().UnixMilli()
item := model.AppConfig{
AppCode: appCode,
Group: strings.TrimSpace(group),
Key: strings.TrimSpace(key),
IsDeleted: true,
CreatedAtMS: nowMS,
UpdatedAtMS: nowMS,
}
return s.db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "app_code"}, {Name: "group"}, {Name: "key"}},
DoUpdates: clause.Assignments(map[string]any{
"is_deleted": true,
"updated_at_ms": nowMS,
}),
}).Create(&item).Error
}
func normalizeScopedAppCode(value string) string {
return strings.ToLower(strings.TrimSpace(value))
}
func mergeScopedAppConfigs(rows []model.AppConfig, appCode string) []model.AppConfig {
appCode = normalizeScopedAppCode(appCode)
baseline := make(map[string]model.AppConfig)
overrides := make(map[string]model.AppConfig)
for _, item := range rows {
item.AppCode = normalizeScopedAppCode(item.AppCode)
item.Group = strings.TrimSpace(item.Group)
item.Key = strings.TrimSpace(item.Key)
if item.Key == "" {
continue
}
switch item.AppCode {
case "":
baseline[item.Key] = item
case appCode:
overrides[item.Key] = item
}
}
keys := make(map[string]struct{}, len(baseline)+len(overrides))
for key := range baseline {
keys[key] = struct{}{}
}
for key := range overrides {
keys[key] = struct{}{}
}
sortedKeys := make([]string, 0, len(keys))
for key := range keys {
sortedKeys = append(sortedKeys, key)
}
sort.Strings(sortedKeys)
items := make([]model.AppConfig, 0, len(sortedKeys))
for _, key := range sortedKeys {
item, overridden := overrides[key]
if !overridden {
item = baseline[key]
}
if item.IsDeleted {
continue
}
// 对外有效视图始终归属于当前 Appbaseline 的空作用域不应泄漏给响应或后续写操作。
item.AppCode = appCode
items = append(items, item)
}
return items
}
func (s *Store) ListAppVersions(options AppVersionListOptions) ([]model.AppVersion, error) {
db := s.db.Where("app_code = ?", strings.TrimSpace(options.AppCode))
if platform := strings.TrimSpace(options.Platform); platform != "" {
db = db.Where("platform = ?", platform)
}
if keyword := strings.TrimSpace(options.Keyword); keyword != "" {
like := "%" + keyword + "%"
db = db.Where("version LIKE ? OR download_url LIKE ? OR description LIKE ?", like, like, like)
}
var items []model.AppVersion
err := db.Order("platform ASC, build_number DESC, id DESC").Find(&items).Error
return items, err
}
func (s *Store) GetAppVersion(appCode string, id uint) (model.AppVersion, error) {
var item model.AppVersion
err := s.db.Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).First(&item).Error
return item, err
}
func (s *Store) CreateAppVersion(item *model.AppVersion) error {
return s.db.Create(item).Error
}
func (s *Store) UpdateAppVersion(item *model.AppVersion) error {
return s.db.Save(item).Error
}
func (s *Store) DeleteAppVersion(appCode string, id uint) error {
return s.db.Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).Delete(&model.AppVersion{}).Error
}
func (s *Store) ListAppExploreTabs(options AppExploreTabListOptions) ([]model.AppExploreTab, error) {
db := s.db.Where("app_code = ?", strings.TrimSpace(options.AppCode))
if options.Enabled != nil {
db = db.Where("enabled = ?", *options.Enabled)
}
if keyword := strings.TrimSpace(options.Keyword); keyword != "" {
like := "%" + keyword + "%"
db = db.Where("tab LIKE ? OR h5_url LIKE ?", like, like)
}
var items []model.AppExploreTab
err := db.Order("sort_order ASC, id DESC").Find(&items).Error
return items, err
}
func (s *Store) GetAppExploreTab(appCode string, id uint) (model.AppExploreTab, error) {
var item model.AppExploreTab
err := s.db.Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).First(&item).Error
return item, err
}
func (s *Store) CreateAppExploreTab(item *model.AppExploreTab) error {
return s.db.Create(item).Error
}
func (s *Store) UpdateAppExploreTab(item *model.AppExploreTab) error {
return s.db.Save(item).Error
}
func (s *Store) DeleteAppExploreTab(appCode string, id uint) error {
return s.db.Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).Delete(&model.AppExploreTab{}).Error
}