feat(wallpaper): 平台配置脱敏回显 + 图库元字段补齐
Some checks failed
Build and Deploy (service.xpcool.com) / build-and-deploy (push) Failing after 28s

后台做完界面才发现的两个接口缺口:

1) 平台配置不回显 → 用户只改一个开关,defaultQuery / purity / baseUrl
   会被一起清空(SaveSource 只对 apiKey/apiSecret 做了"留空即保留")。
   现在 Sources() 返回脱敏后的 config(凭据一律置空,是否已设置由
   HasApiKey 表达),前台接口不下发该字段 —— toAPISource 加 withConfig
   开关,公开接口没必要泄露自建反代地址等内部信息。

2) Item 缺 enabled / sort / remark → 后台列表无法显示启用状态,
   编辑抽屉也拿不到当前值。补齐字段并在 library.toItem 中填充,
   开源平台条目保持默认值(这三个字段对前台无意义)。

另抽出 sanitizeSourceConfig 统一处理凭据抹除,避免以后再加配置项时漏抹。
This commit is contained in:
夏犀麟 2026-09-14 01:48:23 +08:00
parent 582485ba2b
commit d0a084b80e
7 changed files with 87 additions and 19 deletions

View File

@ -36,7 +36,12 @@ type Item struct {
Tags string `json:"tags"` Tags string `json:"tags"`
Category string `json:"category"` Category string `json:"category"`
Filesize int64 `json:"filesize"` Filesize int64 `json:"filesize"`
CreatedAt string `json:"createdAt"` // Enabled / Sort / Remark 仅自建图库有值,供后台列表展示与就地编辑;
// 开源平台条目恒为默认值,前台不关心。
Enabled int `json:"enabled"`
Sort int `json:"sort"`
Remark string `json:"remark"`
CreatedAt string `json:"createdAt"`
} }
// SourceInfo 开源平台的可用性描述。 // SourceInfo 开源平台的可用性描述。
@ -50,6 +55,22 @@ type SourceInfo struct {
Hint string `json:"hint"` // 不可用原因 Hint string `json:"hint"` // 不可用原因
Remark string `json:"remark"` Remark string `json:"remark"`
HasApiKey bool `json:"hasApiKey"` // 只回传「是否已设置」,不回传明文 HasApiKey bool `json:"hasApiKey"` // 只回传「是否已设置」,不回传明文
// Config 是脱敏后的平台配置,供后台编辑界面回显(前台接口不下发)。
// apiKey / apiSecret 恒为空串 —— 提交时留空即表示「不修改已保存的 Key」。
Config *SourceConfig `json:"config"`
}
// SourceConfig 是平台可配置项,与 dto.WallpaperSourceConfig 一一对应。
type SourceConfig struct {
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
DefaultQuery string `json:"defaultQuery"`
// Purity 分级过滤Wallhavensfw / sketchy / nsfw默认只放 sfw。
Purity string `json:"purity"`
// Categories 分类Wallhavengeneral/anime/people 组合。
Categories string `json:"categories"`
// BaseUrl 覆盖平台接口地址(自建反代或镜像)。
BaseUrl string `json:"baseUrl"`
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -167,7 +167,9 @@ func (c *Controller) SourceList(ctx context.Context, req *wallpaperv1.AdminSourc
} }
out := make([]*wallpaperv1.SourceInfo, 0, len(list)) out := make([]*wallpaperv1.SourceInfo, 0, len(list))
for i := range list { for i := range list {
out = append(out, toAPISource(&list[i])) // 后台带配置:编辑界面需要回显 defaultQuery / purity / baseUrl 等,
// 否则用户只改开关也会把这些字段一并清空。
out = append(out, toAPISource(&list[i], true))
} }
return &wallpaperv1.AdminSourceListRes{List: out}, nil return &wallpaperv1.AdminSourceListRes{List: out}, nil
} }

View File

@ -37,6 +37,9 @@ func toAPIItem(v *dto.WallpaperItem) *wallpaperv1.Item {
Tags: v.Tags, Tags: v.Tags,
Category: v.Category, Category: v.Category,
Filesize: v.Filesize, Filesize: v.Filesize,
Enabled: v.Enabled,
Sort: v.Sort,
Remark: v.Remark,
CreatedAt: v.CreatedAt, CreatedAt: v.CreatedAt,
} }
} }
@ -51,8 +54,11 @@ func toAPIItems(list []dto.WallpaperItem) []*wallpaperv1.Item {
} }
// toAPISource 把平台信息转成接口输出结构。 // toAPISource 把平台信息转成接口输出结构。
func toAPISource(v *dto.WallpaperSourceInfo) *wallpaperv1.SourceInfo { //
return &wallpaperv1.SourceInfo{ // withConfig 控制是否下发脱敏后的配置:后台需要它回显,前台不需要
// (自建反代地址之类属于内部信息,没必要给公网浏览器)。
func toAPISource(v *dto.WallpaperSourceInfo, withConfig bool) *wallpaperv1.SourceInfo {
out := &wallpaperv1.SourceInfo{
Code: v.Code, Code: v.Code,
Name: v.Name, Name: v.Name,
Enabled: v.Enabled, Enabled: v.Enabled,
@ -63,4 +69,15 @@ func toAPISource(v *dto.WallpaperSourceInfo) *wallpaperv1.SourceInfo {
Remark: v.Remark, Remark: v.Remark,
HasApiKey: v.HasApiKey, HasApiKey: v.HasApiKey,
} }
if withConfig && v.Config != nil {
out.Config = &wallpaperv1.SourceConfig{
ApiKey: v.Config.ApiKey,
ApiSecret: v.Config.ApiSecret,
DefaultQuery: v.Config.DefaultQuery,
Purity: v.Config.Purity,
Categories: v.Config.Categories,
BaseUrl: v.Config.BaseUrl,
}
}
return out
} }

View File

@ -37,7 +37,8 @@ func (c *OpenController) Sources(ctx context.Context, req *wallpaperv1.OpenSourc
out := make([]*wallpaperv1.SourceInfo, 0, len(list)) out := make([]*wallpaperv1.SourceInfo, 0, len(list))
for i := range list { for i := range list {
out = append(out, toAPISource(&list[i])) // 前台不带平台配置:公开接口没必要下发 baseUrl 等内部信息。
out = append(out, toAPISource(&list[i], false))
} }
return &wallpaperv1.OpenSourceListRes{List: out, MineCount: mineCount}, nil return &wallpaperv1.OpenSourceListRes{List: out, MineCount: mineCount}, nil
} }

View File

@ -25,18 +25,23 @@ type WallpaperItem struct {
PreviewUrl string `json:"previewUrl"` PreviewUrl string `json:"previewUrl"`
FullUrl string `json:"fullUrl"` FullUrl string `json:"fullUrl"`
// ---- 归属 ---- // ---- 归属 ----
Source string `json:"source"` // 平台编码,自建图库固定为 "mine" Source string `json:"source"` // 平台编码,自建图库固定为 "mine"
SourceName string `json:"sourceName"` // 平台显示名,用于界面标注 SourceName string `json:"sourceName"` // 平台显示名,用于界面标注
FromOpen bool `json:"fromOpen"` // true=开源平台 false=自建图库 FromOpen bool `json:"fromOpen"` // true=开源平台 false=自建图库
// ---- 版权信息(自建图库留空)---- // ---- 版权信息(自建图库留空)----
Author string `json:"author"` Author string `json:"author"`
AuthorUrl string `json:"authorUrl"` AuthorUrl string `json:"authorUrl"`
PageUrl string `json:"pageUrl"` // 平台详情页,用于「查看原页面」 PageUrl string `json:"pageUrl"` // 平台详情页,用于「查看原页面」
License string `json:"license"` // 许可说明,如 "Unsplash License" License string `json:"license"` // 许可说明,如 "Unsplash License"
// ---- 自建图库专有 ---- // ---- 自建图库专有 ----
Tags string `json:"tags"` Tags string `json:"tags"`
Category string `json:"category"` Category string `json:"category"`
Filesize int64 `json:"filesize"` Filesize int64 `json:"filesize"`
// Enabled / Sort / Remark 只有自建图库有值(开源平台条目恒为默认值),
// 后台列表要显示启用状态并支持就地编辑,前台不关心这三个字段。
Enabled int `json:"enabled"`
Sort int `json:"sort"`
Remark string `json:"remark"`
CreatedAt string `json:"createdAt"` CreatedAt string `json:"createdAt"`
} }
@ -54,6 +59,11 @@ type WallpaperSourceInfo struct {
Available bool `json:"available"` Available bool `json:"available"`
Hint string `json:"hint"` // 不可用原因,可用时为空 Hint string `json:"hint"` // 不可用原因,可用时为空
Remark string `json:"remark"` Remark string `json:"remark"`
// Config 是脱敏后的平台配置,供后台编辑界面回显。
// apiKey / apiSecret 一律置空不回传明文,是否已设置由 HasApiKey 表达;
// 界面上留空提交即表示「不修改已保存的 Key」。
// 前台接口不下发本字段(避免暴露自建反代地址等内部信息)。
Config *WallpaperSourceConfig `json:"config"`
// Config 是脱敏后的配置apiKey 只回传是否已设置,不回传明文)。 // Config 是脱敏后的配置apiKey 只回传是否已设置,不回传明文)。
HasApiKey bool `json:"hasApiKey"` HasApiKey bool `json:"hasApiKey"`
} }
@ -101,13 +111,13 @@ type WallpaperQuery struct {
// WallpaperSaveInput 是后台编辑壁纸元数据的入参(不含文件本身)。 // WallpaperSaveInput 是后台编辑壁纸元数据的入参(不含文件本身)。
type WallpaperSaveInput struct { type WallpaperSaveInput struct {
Id uint64 `json:"id"` Id uint64 `json:"id"`
Title string `json:"title"` Title string `json:"title"`
Tags string `json:"tags"` Tags string `json:"tags"`
Category string `json:"category"` Category string `json:"category"`
Enabled int `json:"enabled"` Enabled int `json:"enabled"`
Sort int `json:"sort"` Sort int `json:"sort"`
Remark string `json:"remark"` Remark string `json:"remark"`
} }
// WallpaperStat 是图库概览统计。 // WallpaperStat 是图库概览统计。

View File

@ -161,6 +161,9 @@ func (s *service) toItem(e *entity.Wallpaper) dto.WallpaperItem {
Tags: e.Tags, Tags: e.Tags,
Category: e.Category, Category: e.Category,
Filesize: e.Filesize, Filesize: e.Filesize,
Enabled: e.Enabled,
Sort: e.Sort,
Remark: e.Remark,
CreatedAt: e.CreatedAt, CreatedAt: e.CreatedAt,
} }
} }

View File

@ -48,6 +48,7 @@ func (s *service) Sources(ctx context.Context) ([]dto.WallpaperSourceInfo, error
Configured: !p.RequiresKey(), Configured: !p.RequiresKey(),
Available: false, Available: false,
Hint: "尚未在后台启用", Hint: "尚未在后台启用",
Config: sanitizeSourceConfig(dto.WallpaperSourceConfig{}),
}) })
continue continue
} }
@ -61,6 +62,7 @@ func (s *service) Sources(ctx context.Context) ([]dto.WallpaperSourceInfo, error
Configured: configured, Configured: configured,
Available: e.Enabled == 1 && configured, Available: e.Enabled == 1 && configured,
Remark: e.Remark, Remark: e.Remark,
Config: sanitizeSourceConfig(cfg),
HasApiKey: cfg.ApiKey != "", HasApiKey: cfg.ApiKey != "",
} }
switch { switch {
@ -216,3 +218,15 @@ func ParseSourceConfig(raw string) dto.WallpaperSourceConfig {
} }
return cfg return cfg
} }
// sanitizeSourceConfig 生成可安全下发给前端的配置副本:抹掉全部凭据。
//
// 后台需要它来「回显」用户此前填过的 defaultQuery / purity / baseUrl
// 否则用户只想改个开关也会把这些字段一起清空;
// 但凭据明文不能出后端,故这里只清 apiKey / apiSecret
// 是否已设置改由 HasApiKey 布尔表达。
func sanitizeSourceConfig(cfg dto.WallpaperSourceConfig) *dto.WallpaperSourceConfig {
cfg.ApiKey = ""
cfg.ApiSecret = ""
return &cfg
}