update
This commit is contained in:
@ -144,9 +144,9 @@ func main() {
|
|||||||
engine := router.New(router.Options{
|
engine := router.New(router.Options{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
JWT: jwtManager,
|
JWT: jwtManager,
|
||||||
Article: handler.NewArticleHandler(articleSvc),
|
Article: handler.NewArticleHandler(articleSvc, favSvc),
|
||||||
Brand: handler.NewBrandHandler(brandSvc),
|
Brand: handler.NewBrandHandler(brandSvc),
|
||||||
StreetSnap: handler.NewStreetSnapHandler(snapSvc),
|
StreetSnap: handler.NewStreetSnapHandler(snapSvc, favSvc),
|
||||||
Auth: handler.NewAuthHandler(authSvc),
|
Auth: handler.NewAuthHandler(authSvc),
|
||||||
Favorite: handler.NewFavoriteHandler(favSvc),
|
Favorite: handler.NewFavoriteHandler(favSvc),
|
||||||
History: handler.NewHistoryHandler(histSvc),
|
History: handler.NewHistoryHandler(histSvc),
|
||||||
|
|||||||
@ -80,6 +80,7 @@ type PublicArticleImage struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
IsDetail uint8 `json:"is_detail"` // 0=主图 1=细节图(详情接口 images 仅含主图;细节在 Details 分组)
|
IsDetail uint8 `json:"is_detail"` // 0=主图 1=细节图(详情接口 images 仅含主图;细节在 Details 分组)
|
||||||
LookIndex uint32 `json:"look_index"` // 细节图归属的主图序号(用于前端分组展开)
|
LookIndex uint32 `json:"look_index"` // 细节图归属的主图序号(用于前端分组展开)
|
||||||
|
Favorited bool `json:"favorited,omitempty"` // 已登录时该图片是否被当前用户收藏(image 级)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunwayLookDetails 某个主图(look)对应的细节图集合,供详情页「细节入口」按需展开。
|
// RunwayLookDetails 某个主图(look)对应的细节图集合,供详情页「细节入口」按需展开。
|
||||||
@ -117,6 +118,7 @@ type PublicArticleDetail struct {
|
|||||||
BrandName string `json:"brand_name"`
|
BrandName string `json:"brand_name"`
|
||||||
Images []PublicArticleImage `json:"images"` // 仅主图(look 图),默认展示
|
Images []PublicArticleImage `json:"images"` // 仅主图(look 图),默认展示
|
||||||
Details []RunwayLookDetails `json:"details"` // 按主图分组的细节图(「细节入口」展开用);无细节时为空
|
Details []RunwayLookDetails `json:"details"` // 按主图分组的细节图(「细节入口」展开用);无细节时为空
|
||||||
|
Favorited bool `json:"favorited,omitempty"` // 已登录时该图集是否被当前用户收藏(gallery 级,r= 前缀)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminRunway 后台管理用的走秀列表项:在 PublicArticle 基础上补回管理字段,
|
// AdminRunway 后台管理用的走秀列表项:在 PublicArticle 基础上补回管理字段,
|
||||||
|
|||||||
@ -66,4 +66,5 @@ type PublicStreetSnapDetail struct {
|
|||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Cover string `json:"cover"`
|
Cover string `json:"cover"`
|
||||||
Images []PublicArticleImage `json:"images"`
|
Images []PublicArticleImage `json:"images"`
|
||||||
|
Favorited bool `json:"favorited,omitempty"` // 已登录时该图集是否被当前用户收藏(gallery 级,s= 前缀)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,11 +17,12 @@ import (
|
|||||||
// ArticleHandler 对外公开的文章接口。
|
// ArticleHandler 对外公开的文章接口。
|
||||||
type ArticleHandler struct {
|
type ArticleHandler struct {
|
||||||
articles service.ArticleService
|
articles service.ArticleService
|
||||||
|
favs service.FavoriteService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewArticleHandler 创建文章 handler。
|
// NewArticleHandler 创建文章 handler。
|
||||||
func NewArticleHandler(articles service.ArticleService) *ArticleHandler {
|
func NewArticleHandler(articles service.ArticleService, favs service.FavoriteService) *ArticleHandler {
|
||||||
return &ArticleHandler{articles: articles}
|
return &ArticleHandler{articles: articles, favs: favs}
|
||||||
}
|
}
|
||||||
|
|
||||||
// List 走秀列表(分页 + 单值筛选 + 排序)。
|
// List 走秀列表(分页 + 单值筛选 + 排序)。
|
||||||
@ -109,5 +110,29 @@ func (h *ArticleHandler) Detail(c *gin.Context) {
|
|||||||
detail.Images = detail.Images[:dto.PreviewLimit]
|
detail.Images = detail.Images[:dto.PreviewLimit]
|
||||||
preview = true
|
preview = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 已登录:把当前用户对该图集(gallery 级)与每张图片(image 级)的收藏态随详情返回,
|
||||||
|
// 前端无需再单独调 /me/favorites/checks。gallery uid(r=)与图片 id(i=)前缀不同,
|
||||||
|
// 混传 FilterExisting 不影响查询;匿名请求不查(保持公开接口无用户态、可缓存)。
|
||||||
|
if uid, ok := middleware.UserIDFrom(c); ok {
|
||||||
|
ids := make([]string, 0, len(detail.Images)+1)
|
||||||
|
ids = append(ids, detail.UID)
|
||||||
|
for _, im := range detail.Images {
|
||||||
|
if im.ID != "" {
|
||||||
|
ids = append(ids, im.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hit, err := h.favs.Check(c.Request.Context(), uid, ids); err == nil {
|
||||||
|
set := make(map[string]bool, len(hit))
|
||||||
|
for _, id := range hit {
|
||||||
|
set[id] = true
|
||||||
|
}
|
||||||
|
detail.Favorited = set[detail.UID]
|
||||||
|
for i := range detail.Images {
|
||||||
|
detail.Images[i].Favorited = set[detail.Images[i].ID]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
response.DataPreview(c, http.StatusOK, detail, preview, imageTotal)
|
response.DataPreview(c, http.StatusOK, detail, preview, imageTotal)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"fashionapi/internal/dto"
|
"fashionapi/internal/dto"
|
||||||
|
"fashionapi/internal/middleware"
|
||||||
"fashionapi/internal/pkg/hashid"
|
"fashionapi/internal/pkg/hashid"
|
||||||
"fashionapi/internal/pkg/response"
|
"fashionapi/internal/pkg/response"
|
||||||
"fashionapi/internal/service"
|
"fashionapi/internal/service"
|
||||||
@ -16,11 +17,12 @@ import (
|
|||||||
// StreetSnapHandler 对外公开的街拍接口。
|
// StreetSnapHandler 对外公开的街拍接口。
|
||||||
type StreetSnapHandler struct {
|
type StreetSnapHandler struct {
|
||||||
svc service.StreetSnapService
|
svc service.StreetSnapService
|
||||||
|
favs service.FavoriteService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStreetSnapHandler 创建街拍 handler。
|
// NewStreetSnapHandler 创建街拍 handler。
|
||||||
func NewStreetSnapHandler(svc service.StreetSnapService) *StreetSnapHandler {
|
func NewStreetSnapHandler(svc service.StreetSnapService, favs service.FavoriteService) *StreetSnapHandler {
|
||||||
return &StreetSnapHandler{svc: svc}
|
return &StreetSnapHandler{svc: svc, favs: favs}
|
||||||
}
|
}
|
||||||
|
|
||||||
// List 街拍列表(分页 + 排序 + 年份过滤)。
|
// List 街拍列表(分页 + 排序 + 年份过滤)。
|
||||||
@ -93,5 +95,28 @@ func (h *StreetSnapHandler) Detail(c *gin.Context) {
|
|||||||
detail.Images = detail.Images[:dto.PreviewLimit]
|
detail.Images = detail.Images[:dto.PreviewLimit]
|
||||||
preview = true
|
preview = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 已登录:图集(gallery 级 s=)与每张图片(image 级 j=)收藏态随详情返回,
|
||||||
|
// 前端无需再单独调 /me/favorites/checks。
|
||||||
|
if uid, ok := middleware.UserIDFrom(c); ok {
|
||||||
|
ids := make([]string, 0, len(detail.Images)+1)
|
||||||
|
ids = append(ids, detail.UID)
|
||||||
|
for _, im := range detail.Images {
|
||||||
|
if im.ID != "" {
|
||||||
|
ids = append(ids, im.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hit, err := h.favs.Check(c.Request.Context(), uid, ids); err == nil {
|
||||||
|
set := make(map[string]bool, len(hit))
|
||||||
|
for _, id := range hit {
|
||||||
|
set[id] = true
|
||||||
|
}
|
||||||
|
detail.Favorited = set[detail.UID]
|
||||||
|
for i := range detail.Images {
|
||||||
|
detail.Images[i].Favorited = set[detail.Images[i].ID]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
response.DataPreview(c, http.StatusOK, detail, preview, imageTotal)
|
response.DataPreview(c, http.StatusOK, detail, preview, imageTotal)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -141,7 +141,6 @@ func New(opt Options) *gin.Engine {
|
|||||||
legacy.GET("/favorites", opt.Favorite.List)
|
legacy.GET("/favorites", opt.Favorite.List)
|
||||||
legacy.POST("/favorites", opt.Favorite.Add)
|
legacy.POST("/favorites", opt.Favorite.Add)
|
||||||
legacy.DELETE("/favorites/:uid", opt.Favorite.Remove)
|
legacy.DELETE("/favorites/:uid", opt.Favorite.Remove)
|
||||||
legacy.POST("/favorites/check", opt.Favorite.Check)
|
|
||||||
|
|
||||||
legacy.GET("/history", opt.History.List)
|
legacy.GET("/history", opt.History.List)
|
||||||
legacy.POST("/history", opt.History.Record)
|
legacy.POST("/history", opt.History.Record)
|
||||||
|
|||||||
Reference in New Issue
Block a user