修复发布后图片无法看到bug
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"hfb_sys/backend/internal/middleware"
|
"hfb_sys/backend/internal/middleware"
|
||||||
|
filemodule "hfb_sys/backend/internal/modules/file"
|
||||||
"hfb_sys/backend/pkg/response"
|
"hfb_sys/backend/pkg/response"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -13,10 +14,11 @@ import (
|
|||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
service *Service
|
service *Service
|
||||||
|
storage *filemodule.Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandler(service *Service) *Handler {
|
func NewHandler(service *Service, storage *filemodule.Storage) *Handler {
|
||||||
return &Handler{service: service}
|
return &Handler{service: service, storage: storage}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) Create(c *gin.Context) {
|
func (h *Handler) Create(c *gin.Context) {
|
||||||
@@ -215,6 +217,59 @@ func (h *Handler) FindPublic(c *gin.Context) {
|
|||||||
response.OK(c, item)
|
response.OK(c, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Cover(c *gin.Context) {
|
||||||
|
id, ok := parseID(c)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
key, err := h.service.FindPublicCoverKey(id)
|
||||||
|
if err != nil {
|
||||||
|
writeListingError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.writePublicObject(c, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Screenshot(c *gin.Context) {
|
||||||
|
id, ok := parseID(c)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
index, err := strconv.Atoi(c.Param("index"))
|
||||||
|
if err != nil || index < 0 {
|
||||||
|
response.BadRequest(c, "截图序号不正确")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
key, err := h.service.FindPublicScreenshotKey(id, index)
|
||||||
|
if err != nil {
|
||||||
|
writeListingError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.writePublicObject(c, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) writePublicObject(c *gin.Context, key string) {
|
||||||
|
if h.storage == nil {
|
||||||
|
response.ServiceUnavailable(c, "文件存储未连接")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
object, err := h.storage.Get(c.Request.Context(), key)
|
||||||
|
if err != nil {
|
||||||
|
response.Error(c, http.StatusNotFound, "not_found", "图片不存在或暂不可访问")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = object.Reader.Close()
|
||||||
|
}()
|
||||||
|
contentType := object.ContentType
|
||||||
|
if contentType == "" {
|
||||||
|
contentType = "application/octet-stream"
|
||||||
|
}
|
||||||
|
c.Header("Content-Type", contentType)
|
||||||
|
c.Header("Cache-Control", "public, max-age=300")
|
||||||
|
c.DataFromReader(http.StatusOK, object.Size, contentType, object.Reader, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) ListMine(c *gin.Context) {
|
func (h *Handler) ListMine(c *gin.Context) {
|
||||||
ownerID, ok := currentUserID(c)
|
ownerID, ok := currentUserID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package listing
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -366,7 +368,7 @@ func (r *Repository) ListPublic() ([]ListingDTO, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return rowsToDTO(rows), nil
|
return publicListings(rowsToDTO(rows)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repository) ListMine(ownerID uint64) ([]ListingDTO, error) {
|
func (r *Repository) ListMine(ownerID uint64) ([]ListingDTO, error) {
|
||||||
@@ -382,7 +384,33 @@ func (r *Repository) ListMine(ownerID uint64) ([]ListingDTO, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repository) FindPublic(id uint64) (*ListingDTO, error) {
|
func (r *Repository) FindPublic(id uint64) (*ListingDTO, error) {
|
||||||
return r.findDTO("l.id = ? AND l.status = ? AND l.review_status = ?", id, "published", "approved")
|
dto, err := r.findDTO("l.id = ? AND l.status = ? AND l.review_status = ?", id, "published", "approved")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
applyPublicListingURLs(dto)
|
||||||
|
return dto, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) FindPublicCoverKey(id uint64) (string, error) {
|
||||||
|
return r.FindPublicScreenshotKey(id, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) FindPublicScreenshotKey(id uint64, index int) (string, error) {
|
||||||
|
if index < 0 {
|
||||||
|
return "", gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
dto, err := r.findDTO("l.id = ? AND l.status = ? AND l.review_status = ?", id, "published", "approved")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if index >= len(dto.ScreenshotURLS) {
|
||||||
|
return "", gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
if key := extractListingObjectKey(dto.ScreenshotURLS[index]); key != "" {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
return "", gorm.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repository) FindMine(ownerID uint64, id uint64) (*ListingDTO, error) {
|
func (r *Repository) FindMine(ownerID uint64, id uint64) (*ListingDTO, error) {
|
||||||
@@ -456,6 +484,17 @@ func rowsToDTO(rows []listingRow) []ListingDTO {
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func publicListings(items []ListingDTO) []ListingDTO {
|
||||||
|
for index := range items {
|
||||||
|
applyPublicListingURLs(&items[index])
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyPublicListingURLs(item *ListingDTO) {
|
||||||
|
item.ScreenshotURLS = publicScreenshotURLs(item.ID, item.ScreenshotURLS, item.Status, item.ReviewStatus)
|
||||||
|
}
|
||||||
|
|
||||||
func (row listingRow) toDTO() ListingDTO {
|
func (row listingRow) toDTO() ListingDTO {
|
||||||
assetSummary := decodeAssetSummary(row.AssetSummary)
|
assetSummary := decodeAssetSummary(row.AssetSummary)
|
||||||
screenshotURLS := cleanScreenshotURLs(decodeScreenshots(row.ScreenshotURLS))
|
screenshotURLS := cleanScreenshotURLs(decodeScreenshots(row.ScreenshotURLS))
|
||||||
@@ -474,7 +513,7 @@ func (row listingRow) toDTO() ListingDTO {
|
|||||||
HafCoinAmount: row.HafCoinAmount,
|
HafCoinAmount: row.HafCoinAmount,
|
||||||
AssetSummary: assetSummary,
|
AssetSummary: assetSummary,
|
||||||
ScreenshotURLS: screenshotURLS,
|
ScreenshotURLS: screenshotURLS,
|
||||||
CoverURL: firstScreenshotURL(screenshotURLS),
|
CoverURL: publicCoverURL(row.ID, screenshotURLS, row.Status, row.ReviewStatus),
|
||||||
PriceHourly: row.PriceHourly,
|
PriceHourly: row.PriceHourly,
|
||||||
PriceDaily: row.PriceDaily,
|
PriceDaily: row.PriceDaily,
|
||||||
PriceWeekly: row.PriceWeekly,
|
PriceWeekly: row.PriceWeekly,
|
||||||
@@ -504,7 +543,7 @@ func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO {
|
|||||||
HafCoinAmount: account.HafCoinAmount,
|
HafCoinAmount: account.HafCoinAmount,
|
||||||
AssetSummary: assetSummary,
|
AssetSummary: assetSummary,
|
||||||
ScreenshotURLS: screenshotURLS,
|
ScreenshotURLS: screenshotURLS,
|
||||||
CoverURL: firstScreenshotURL(screenshotURLS),
|
CoverURL: publicCoverURL(listing.ID, screenshotURLS, listing.Status, listing.ReviewStatus),
|
||||||
PriceHourly: listing.PriceHourly,
|
PriceHourly: listing.PriceHourly,
|
||||||
PriceDaily: listing.PriceDaily,
|
PriceDaily: listing.PriceDaily,
|
||||||
PriceWeekly: listing.PriceWeekly,
|
PriceWeekly: listing.PriceWeekly,
|
||||||
@@ -587,6 +626,47 @@ func firstScreenshotURL(urls []string) string {
|
|||||||
return urls[0]
|
return urls[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func publicCoverURL(listingID uint64, urls []string, status string, reviewStatus string) string {
|
||||||
|
fallback := firstScreenshotURL(urls)
|
||||||
|
if status != "published" || reviewStatus != "approved" || extractListingObjectKey(fallback) == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return "/api/listings/" + strconv.FormatUint(listingID, 10) + "/cover"
|
||||||
|
}
|
||||||
|
|
||||||
|
func publicScreenshotURLs(listingID uint64, urls []string, status string, reviewStatus string) []string {
|
||||||
|
if status != "published" || reviewStatus != "approved" {
|
||||||
|
return urls
|
||||||
|
}
|
||||||
|
publicURLs := make([]string, 0, len(urls))
|
||||||
|
for index, fileURL := range urls {
|
||||||
|
if extractListingObjectKey(fileURL) == "" {
|
||||||
|
publicURLs = append(publicURLs, fileURL)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
publicURLs = append(publicURLs, "/api/listings/"+strconv.FormatUint(listingID, 10)+"/screenshots/"+strconv.Itoa(index))
|
||||||
|
}
|
||||||
|
return publicURLs
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractListingObjectKey(fileURL string) string {
|
||||||
|
if fileURL == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
parsed, err := url.Parse(fileURL)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
key := parsed.Query().Get("key")
|
||||||
|
if key == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(key, "listing/") || strings.Contains(key, "..") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
||||||
raw, err := json.Marshal(detail)
|
raw, err := json.Marshal(detail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -179,6 +179,20 @@ func (s *Service) FindPublic(id uint64) (*ListingDTO, error) {
|
|||||||
return s.repo.FindPublic(id)
|
return s.repo.FindPublic(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) FindPublicCoverKey(id uint64) (string, error) {
|
||||||
|
if s.repo == nil {
|
||||||
|
return "", ErrDependencyUnavailable
|
||||||
|
}
|
||||||
|
return s.repo.FindPublicCoverKey(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) FindPublicScreenshotKey(id uint64, index int) (string, error) {
|
||||||
|
if s.repo == nil {
|
||||||
|
return "", ErrDependencyUnavailable
|
||||||
|
}
|
||||||
|
return s.repo.FindPublicScreenshotKey(id, index)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) FindMine(ownerID uint64, id uint64) (*ListingDTO, error) {
|
func (s *Service) FindMine(ownerID uint64, id uint64) (*ListingDTO, error) {
|
||||||
if s.repo == nil {
|
if s.repo == nil {
|
||||||
return nil, ErrDependencyUnavailable
|
return nil, ErrDependencyUnavailable
|
||||||
|
|||||||
@@ -107,8 +107,6 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
|||||||
}
|
}
|
||||||
systemConfigService := systemconfig.NewService(systemConfigRepo)
|
systemConfigService := systemconfig.NewService(systemConfigRepo)
|
||||||
systemConfigHandler := systemconfig.NewHandler(systemConfigService)
|
systemConfigHandler := systemconfig.NewHandler(systemConfigService)
|
||||||
listingService := listing.NewService(listingRepo, systemConfigRepo)
|
|
||||||
listingHandler := listing.NewHandler(listingService)
|
|
||||||
var fileStorage *filemodule.Storage
|
var fileStorage *filemodule.Storage
|
||||||
if cfg.Storage.Endpoint != "" && cfg.Storage.Bucket != "" {
|
if cfg.Storage.Endpoint != "" && cfg.Storage.Bucket != "" {
|
||||||
var err error
|
var err error
|
||||||
@@ -119,6 +117,8 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
|||||||
}
|
}
|
||||||
fileService := filemodule.NewService(fileStorage)
|
fileService := filemodule.NewService(fileStorage)
|
||||||
fileHandler := filemodule.NewHandler(fileService, fileStorage)
|
fileHandler := filemodule.NewHandler(fileService, fileStorage)
|
||||||
|
listingService := listing.NewService(listingRepo, systemConfigRepo)
|
||||||
|
listingHandler := listing.NewHandler(listingService, fileStorage)
|
||||||
requireAuth := middleware.Auth(jwtManager)
|
requireAuth := middleware.Auth(jwtManager)
|
||||||
requireAdmin := middleware.AdminAuth(jwtManager)
|
requireAdmin := middleware.AdminAuth(jwtManager)
|
||||||
requireRealname := middleware.RequireRealname(userRepo)
|
requireRealname := middleware.RequireRealname(userRepo)
|
||||||
@@ -145,6 +145,8 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
|||||||
listingRoutes := api.Group("/listings")
|
listingRoutes := api.Group("/listings")
|
||||||
{
|
{
|
||||||
listingRoutes.GET("", listingHandler.ListPublic)
|
listingRoutes.GET("", listingHandler.ListPublic)
|
||||||
|
listingRoutes.GET("/:id/cover", listingHandler.Cover)
|
||||||
|
listingRoutes.GET("/:id/screenshots/:index", listingHandler.Screenshot)
|
||||||
listingRoutes.GET("/:id", listingHandler.FindPublic)
|
listingRoutes.GET("/:id", listingHandler.FindPublic)
|
||||||
listingRoutes.POST("", requireAuth, requireRealname, listingHandler.Create)
|
listingRoutes.POST("", requireAuth, requireRealname, listingHandler.Create)
|
||||||
listingRoutes.PUT("/:id", requireAuth, requireRealname, listingHandler.Update)
|
listingRoutes.PUT("/:id", requireAuth, requireRealname, listingHandler.Update)
|
||||||
|
|||||||
Reference in New Issue
Block a user