保存更多图片元数据并同步更新前端

This commit is contained in:
2026-01-27 13:52:40 +08:00
parent 6dfffe1236
commit 9c2a5d5cd8
11 changed files with 331 additions and 72 deletions

View File

@@ -461,8 +461,7 @@ const docTemplate = `{
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
"$ref": "#/definitions/handlers.ImageMetaResp"
}
}
}
@@ -518,8 +517,7 @@ const docTemplate = `{
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
"$ref": "#/definitions/handlers.ImageMetaResp"
}
}
}
@@ -575,8 +573,7 @@ const docTemplate = `{
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
"$ref": "#/definitions/handlers.ImageMetaResp"
}
}
}
@@ -607,8 +604,7 @@ const docTemplate = `{
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
"$ref": "#/definitions/handlers.ImageMetaResp"
}
}
}
@@ -885,6 +881,61 @@ const docTemplate = `{
}
}
},
"handlers.ImageMetaResp": {
"type": "object",
"properties": {
"copyright": {
"type": "string"
},
"copyrightlink": {
"type": "string"
},
"date": {
"type": "string"
},
"fullstartdate": {
"type": "string"
},
"hsh": {
"type": "string"
},
"quiz": {
"type": "string"
},
"startdate": {
"type": "string"
},
"title": {
"type": "string"
},
"variants": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ImageVariantResp"
}
}
}
},
"handlers.ImageVariantResp": {
"type": "object",
"properties": {
"format": {
"type": "string"
},
"size": {
"type": "integer"
},
"storage_key": {
"type": "string"
},
"url": {
"type": "string"
},
"variant": {
"type": "string"
}
}
},
"handlers.LoginRequest": {
"type": "object",
"required": [

View File

@@ -455,8 +455,7 @@
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
"$ref": "#/definitions/handlers.ImageMetaResp"
}
}
}
@@ -512,8 +511,7 @@
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
"$ref": "#/definitions/handlers.ImageMetaResp"
}
}
}
@@ -569,8 +567,7 @@
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
"$ref": "#/definitions/handlers.ImageMetaResp"
}
}
}
@@ -601,8 +598,7 @@
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
"$ref": "#/definitions/handlers.ImageMetaResp"
}
}
}
@@ -879,6 +875,61 @@
}
}
},
"handlers.ImageMetaResp": {
"type": "object",
"properties": {
"copyright": {
"type": "string"
},
"copyrightlink": {
"type": "string"
},
"date": {
"type": "string"
},
"fullstartdate": {
"type": "string"
},
"hsh": {
"type": "string"
},
"quiz": {
"type": "string"
},
"startdate": {
"type": "string"
},
"title": {
"type": "string"
},
"variants": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ImageVariantResp"
}
}
}
},
"handlers.ImageVariantResp": {
"type": "object",
"properties": {
"format": {
"type": "string"
},
"size": {
"type": "integer"
},
"storage_key": {
"type": "string"
},
"url": {
"type": "string"
},
"variant": {
"type": "string"
}
}
},
"handlers.LoginRequest": {
"type": "object",
"required": [

View File

@@ -178,6 +178,42 @@ definitions:
required:
- name
type: object
handlers.ImageMetaResp:
properties:
copyright:
type: string
copyrightlink:
type: string
date:
type: string
fullstartdate:
type: string
hsh:
type: string
quiz:
type: string
startdate:
type: string
title:
type: string
variants:
items:
$ref: '#/definitions/handlers.ImageVariantResp'
type: array
type: object
handlers.ImageVariantResp:
properties:
format:
type: string
size:
type: integer
storage_key:
type: string
url:
type: string
variant:
type: string
type: object
handlers.LoginRequest:
properties:
password:
@@ -500,8 +536,7 @@ paths:
"200":
description: OK
schema:
additionalProperties: true
type: object
$ref: '#/definitions/handlers.ImageMetaResp'
summary: 获取指定日期图片元数据
tags:
- image
@@ -538,8 +573,7 @@ paths:
"200":
description: OK
schema:
additionalProperties: true
type: object
$ref: '#/definitions/handlers.ImageMetaResp'
summary: 获取随机图片元数据
tags:
- image
@@ -576,8 +610,7 @@ paths:
"200":
description: OK
schema:
additionalProperties: true
type: object
$ref: '#/definitions/handlers.ImageMetaResp'
summary: 获取今日图片元数据
tags:
- image
@@ -597,8 +630,7 @@ paths:
description: OK
schema:
items:
additionalProperties: true
type: object
$ref: '#/definitions/handlers.ImageMetaResp'
type: array
summary: 获取图片列表
tags:

View File

@@ -16,6 +16,26 @@ import (
"go.uber.org/zap"
)
type ImageVariantResp struct {
Variant string `json:"variant"`
Format string `json:"format"`
Size int64 `json:"size"`
URL string `json:"url"`
StorageKey string `json:"storage_key"`
}
type ImageMetaResp struct {
Date string `json:"date"`
Title string `json:"title"`
Copyright string `json:"copyright"`
CopyrightLink string `json:"copyrightlink"`
Quiz string `json:"quiz"`
StartDate string `json:"startdate"`
FullStartDate string `json:"fullstartdate"`
HSH string `json:"hsh"`
Variants []ImageVariantResp `json:"variants"`
}
// GetToday 获取今日图片
// @Summary 获取今日图片
// @Description 根据参数返回今日必应图片流或重定向
@@ -39,7 +59,7 @@ func GetToday(c *gin.Context) {
// @Description 获取今日必应图片的标题、版权等元数据
// @Tags image
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Success 200 {object} ImageMetaResp
// @Router /image/today/meta [get]
func GetTodayMeta(c *gin.Context) {
img, err := image.GetTodayImage()
@@ -73,7 +93,7 @@ func GetRandom(c *gin.Context) {
// @Description 随机获取一张已抓取图片的元数据
// @Tags image
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Success 200 {object} ImageMetaResp
// @Router /image/random/meta [get]
func GetRandomMeta(c *gin.Context) {
img, err := image.GetRandomImage()
@@ -110,7 +130,7 @@ func GetByDate(c *gin.Context) {
// @Tags image
// @Param date path string true "日期 (yyyy-mm-dd)"
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Success 200 {object} ImageMetaResp
// @Router /image/date/{date}/meta [get]
func GetByDateMeta(c *gin.Context) {
date := c.Param("date")
@@ -128,7 +148,7 @@ func GetByDateMeta(c *gin.Context) {
// @Tags image
// @Param limit query int false "限制数量" default(30)
// @Produce json
// @Success 200 {array} map[string]interface{}
// @Success 200 {array} ImageMetaResp
// @Router /images [get]
func ListImages(c *gin.Context) {
limitStr := c.DefaultQuery("limit", "30")
@@ -232,10 +252,14 @@ func formatMeta(img *model.Image) gin.H {
}
return gin.H{
"date": img.Date,
"title": img.Title,
"copyright": img.Copyright,
"quiz": img.Quiz,
"variants": variants,
"date": img.Date,
"title": img.Title,
"copyright": img.Copyright,
"copyrightlink": img.CopyrightLink,
"quiz": img.Quiz,
"startdate": img.StartDate,
"fullstartdate": img.FullStartDate,
"hsh": img.HSH,
"variants": variants,
}
}

View File

@@ -7,16 +7,20 @@ import (
)
type Image struct {
ID uint `gorm:"primaryKey" json:"id"`
Date string `gorm:"uniqueIndex;type:varchar(10)" json:"date"` // YYYY-MM-DD
Title string `json:"title"`
Copyright string `json:"copyright"`
URLBase string `json:"urlbase"`
Quiz string `json:"quiz"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
Variants []ImageVariant `gorm:"foreignKey:ImageID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"variants"`
ID uint `gorm:"primaryKey" json:"id"`
Date string `gorm:"uniqueIndex;type:varchar(10)" json:"date"` // YYYY-MM-DD
Title string `json:"title"`
Copyright string `json:"copyright"`
CopyrightLink string `json:"copyrightlink"`
URLBase string `json:"urlbase"`
Quiz string `json:"quiz"`
StartDate string `json:"startdate"`
FullStartDate string `json:"fullstartdate"`
HSH string `json:"hsh"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
Variants []ImageVariant `gorm:"foreignKey:ImageID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"variants"`
}
type ImageVariant struct {

View File

@@ -38,6 +38,7 @@ type BingImage struct {
CopyrightLink string `json:"copyrightlink"`
Title string `json:"title"`
Quiz string `json:"quiz"`
HSH string `json:"hsh"`
}
type Fetcher struct {
@@ -111,11 +112,15 @@ func (f *Fetcher) processImage(ctx context.Context, bingImg BingImage) error {
// 创建 DB 记录
dbImg := model.Image{
Date: dateStr,
Title: bingImg.Title,
Copyright: bingImg.Copyright,
URLBase: bingImg.URLBase,
Quiz: bingImg.Quiz,
Date: dateStr,
Title: bingImg.Title,
Copyright: bingImg.Copyright,
CopyrightLink: bingImg.CopyrightLink,
URLBase: bingImg.URLBase,
Quiz: bingImg.Quiz,
StartDate: bingImg.Startdate,
FullStartDate: bingImg.Fullstartdate,
HSH: bingImg.HSH,
}
if err := repo.DB.Clauses(clause.OnConflict{

View File

@@ -455,8 +455,7 @@
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
"$ref": "#/definitions/handlers.ImageMetaResp"
}
}
}
@@ -512,8 +511,7 @@
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
"$ref": "#/definitions/handlers.ImageMetaResp"
}
}
}
@@ -569,8 +567,7 @@
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
"$ref": "#/definitions/handlers.ImageMetaResp"
}
}
}
@@ -601,8 +598,7 @@
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
"$ref": "#/definitions/handlers.ImageMetaResp"
}
}
}
@@ -879,6 +875,61 @@
}
}
},
"handlers.ImageMetaResp": {
"type": "object",
"properties": {
"copyright": {
"type": "string"
},
"copyrightlink": {
"type": "string"
},
"date": {
"type": "string"
},
"fullstartdate": {
"type": "string"
},
"hsh": {
"type": "string"
},
"quiz": {
"type": "string"
},
"startdate": {
"type": "string"
},
"title": {
"type": "string"
},
"variants": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ImageVariantResp"
}
}
}
},
"handlers.ImageVariantResp": {
"type": "object",
"properties": {
"format": {
"type": "string"
},
"size": {
"type": "integer"
},
"storage_key": {
"type": "string"
},
"url": {
"type": "string"
},
"variant": {
"type": "string"
}
}
},
"handlers.LoginRequest": {
"type": "object",
"required": [

View File

@@ -149,6 +149,11 @@ export interface ImageMeta {
date?: string
title?: string
copyright?: string
copyrightlink?: string // 图片的详细版权链接(指向 Bing 搜索页面)
quiz?: string // 旧字段,保留向后兼容
startdate?: string // 图片的发布开始日期格式YYYYMMDD
fullstartdate?: string // 图片的完整发布时间格式YYYYMMDDHHMM
hsh?: string // 图片的唯一哈希值
url?: string
variant?: string
format?: string

View File

@@ -294,7 +294,7 @@
<div class="bg-white/5 backdrop-blur-sm rounded-xl p-6 border border-white/10">
<p class="text-white/70 mb-4">获取图片的元数据信息(标题、版权、日期等),只返回 JSON 数据不返回图片</p>
<div class="space-y-3">
<div class="space-y-3 mb-6">
<div class="flex items-center gap-3 text-sm">
<code class="text-blue-400 font-mono">/image/today/meta</code>
<span class="text-white/50">-</span>
@@ -316,6 +316,45 @@
<span class="text-white/60">图片列表(支持分页)</span>
</div>
</div>
<!-- 元数据字段说明 -->
<div class="mt-6 pt-6 border-t border-white/10">
<h4 class="text-white/80 font-semibold mb-4">响应字段说明</h4>
<div class="space-y-3 text-sm">
<div class="flex gap-4">
<code class="text-yellow-400 min-w-32">date</code>
<span class="text-white/60">图片日期格式YYYY-MM-DD</span>
</div>
<div class="flex gap-4">
<code class="text-yellow-400 min-w-32">title</code>
<span class="text-white/60">图片标题</span>
</div>
<div class="flex gap-4">
<code class="text-yellow-400 min-w-32">copyright</code>
<span class="text-white/60">版权信息</span>
</div>
<div class="flex gap-4">
<code class="text-yellow-400 min-w-32">copyrightlink</code>
<span class="text-white/60">版权详情链接(指向 Bing 搜索页面)⭐ 新增</span>
</div>
<div class="flex gap-4">
<code class="text-yellow-400 min-w-32">startdate</code>
<span class="text-white/60">发布开始日期格式YYYYMMDD⭐ 新增</span>
</div>
<div class="flex gap-4">
<code class="text-yellow-400 min-w-32">fullstartdate</code>
<span class="text-white/60">完整发布时间格式YYYYMMDDHHMM⭐ 新增</span>
</div>
<div class="flex gap-4">
<code class="text-yellow-400 min-w-32">hsh</code>
<span class="text-white/60">图片唯一哈希值 ⭐ 新增</span>
</div>
<div class="flex gap-4">
<code class="text-yellow-400 min-w-32">quiz</code>
<span class="text-white/60">必应 quiz 链接(已废弃,建议使用 copyrightlink</span>
</div>
</div>
</div>
</div>
</section>

View File

@@ -40,8 +40,8 @@
查看大图
</button>
<button
v-if="todayImage.quiz"
@click="openQuiz(todayImage.quiz)"
v-if="todayImage.copyrightlink"
@click="openCopyrightLink(todayImage.copyrightlink)"
class="px-6 py-3 bg-white/10 backdrop-blur-md text-white rounded-lg font-semibold hover:bg-white/20 transition-all border border-white/30"
>
了解更多
@@ -145,7 +145,7 @@
</router-link>
<a
href="https://github.com"
href="https://github.com/hanxuanyu/BingPaper"
target="_blank"
class="text-white/60 hover:text-white transition-colors text-sm flex items-center gap-2"
>
@@ -200,11 +200,10 @@ const viewImage = (date: string) => {
router.push(`/image/${date}`)
}
// 打开必应 quiz 链接
const openQuiz = (quiz: string) => {
// 拼接完整的必应地址
const bingUrl = `https://www.bing.com${quiz}`
window.open(bingUrl, '_blank')
// 打开版权详情链接
const openCopyrightLink = (link: string) => {
// copyrightlink 是完整的 URL直接打开
window.open(link, '_blank')
}
</script>
@@ -213,6 +212,7 @@ const openQuiz = (quiz: string) => {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
line-clamp: 2;
overflow: hidden;
}
</style>

View File

@@ -49,10 +49,10 @@
{{ image.copyright }}
</p>
<!-- Quiz 链接 -->
<!-- 版权详情链接 -->
<a
v-if="image.quiz"
:href="getBingQuizUrl(image.quiz)"
v-if="image.copyrightlink"
:href="image.copyrightlink"
target="_blank"
class="inline-flex items-center gap-2 px-4 py-2 bg-white/20 hover:bg-white/30 text-white rounded-lg text-sm font-medium transition-all group"
>
@@ -175,10 +175,7 @@ const getFullImageUrl = () => {
return bingPaperApi.getImageUrlByDate(currentDate.value, 'UHD', 'jpg')
}
// 获取必应 quiz URL
const getBingQuizUrl = (quiz: string) => {
return `https://www.bing.com${quiz}`
}
// copyrightlink 现在是完整的 URL无需额外处理
// 返回首页
const goBack = () => {