2025-12-26 12:22:32 +08:00

198 lines
6.6 KiB
Go
Raw Permalink 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 app
import (
"net/http"
"strconv"
"bindbox-game/internal/code"
"bindbox-game/internal/pkg/core"
"bindbox-game/internal/pkg/logger"
"bindbox-game/internal/pkg/validation"
"bindbox-game/internal/repository/mysql"
"bindbox-game/internal/repository/mysql/dao"
prodsvc "bindbox-game/internal/service/product"
usersvc "bindbox-game/internal/service/user"
)
type productHandler struct {
logger logger.CustomLogger
readDB *dao.Query
product prodsvc.Service
user usersvc.Service
}
func NewProduct(logger logger.CustomLogger, db mysql.Repo, user usersvc.Service) *productHandler {
return &productHandler{logger: logger, readDB: dao.Use(db.GetDbR()), product: prodsvc.New(logger, db), user: user}
}
type listAppProductsRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
CategoryID *int64 `form:"category_id"`
PriceMin *int64 `form:"price_min"`
PriceMax *int64 `form:"price_max"`
SalesMin *int64 `form:"sales_min"`
InStock *bool `form:"in_stock"`
SortBy string `form:"sort_by"`
Order string `form:"order"`
}
type listAppProductsItem struct {
ID int64 `json:"id"`
Name string `json:"name"`
MainImage string `json:"main_image"`
Price int64 `json:"price"`
PointsRequired int64 `json:"points_required"`
Sales int64 `json:"sales"`
InStock bool `json:"in_stock"`
}
type listAppProductsResponse struct {
Total int64 `json:"total"`
CurrentPage int `json:"currentPage"`
PageSize int `json:"pageSize"`
List []listAppProductsItem `json:"list"`
}
// ListProductsForApp 商品列表
// @Summary 商品列表
// @Description 分页查询商品列表,支持分类筛选,返回分页信息与商品数组
// @Tags APP端.商品
// @Accept json
// @Produce json
// @Security LoginVerifyToken
// @Param page query int false "页码默认1"
// @Param page_size query int false "每页数量默认20"
// @Param category_id query int false "分类ID"
// @Success 200 {object} listAppProductsResponse
// @Failure 400 {object} code.Failure
// @Router /api/app/products [get]
func (h *productHandler) ListProductsForApp() core.HandlerFunc {
return func(ctx core.Context) {
req := new(listAppProductsRequest)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
if req.Page <= 0 {
req.Page = 1
}
if req.PageSize <= 0 {
req.PageSize = 20
}
items, total, err := h.product.ListForApp(ctx.RequestContext(), prodsvc.AppListInput{CategoryID: req.CategoryID, PriceMin: req.PriceMin, PriceMax: req.PriceMax, SalesMin: req.SalesMin, InStock: req.InStock, SortBy: req.SortBy, Order: req.Order, Page: req.Page, PageSize: req.PageSize})
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, validation.Error(err)))
return
}
rsp := &listAppProductsResponse{Total: total, CurrentPage: req.Page, PageSize: req.PageSize, List: make([]listAppProductsItem, len(items))}
for i, it := range items {
pts, _ := h.user.CentsToPoints(ctx.RequestContext(), it.Price)
rsp.List[i] = listAppProductsItem{ID: it.ID, Name: it.Name, MainImage: it.MainImage, Price: it.Price, PointsRequired: pts, Sales: it.Sales, InStock: it.InStock}
}
ctx.Payload(rsp)
}
}
type getAppProductDetailResponse struct {
ID int64 `json:"id"`
Name string `json:"name"`
Album []string `json:"album"`
Price int64 `json:"price"`
PointsRequired int64 `json:"points_required"`
Sales int64 `json:"sales"`
Stock int64 `json:"stock"`
Description string `json:"description"`
Service []string `json:"service"`
Recommendations []listAppProductsItem `json:"recommendations"`
}
// GetProductDetailForApp 商品详情
// @Summary 商品详情
// @Description 根据商品ID返回完整商品信息含相册与同类推荐校验下架/缺货状态
// @Tags APP端.商品
// @Accept json
// @Produce json
// @Security LoginVerifyToken
// @Param id path int true "商品ID"
// @Success 200 {object} getAppProductDetailResponse
// @Failure 400 {object} code.Failure
// @Router /api/app/products/{id} [get]
func (h *productHandler) GetProductDetailForApp() core.HandlerFunc {
return func(ctx core.Context) {
idStr := ctx.Param("id")
id, _ := strconv.ParseInt(idStr, 10, 64)
d, err := h.product.GetDetailForApp(ctx.RequestContext(), id)
if err != nil {
if err.Error() == "PRODUCT_OFFSHELF" {
ctx.AbortWithError(core.Error(http.StatusOK, 20001, "商品已下架"))
return
}
if err.Error() == "PRODUCT_OUT_OF_STOCK" {
ctx.AbortWithError(core.Error(http.StatusOK, 20002, "商品缺货"))
return
}
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, validation.Error(err)))
return
}
ptsDetail, _ := h.user.CentsToPoints(ctx.RequestContext(), d.Price)
rsp := &getAppProductDetailResponse{ID: d.ID, Name: d.Name, Album: d.Album, Price: d.Price, PointsRequired: ptsDetail, Sales: d.Sales, Stock: d.Stock, Description: d.Description, Service: d.Service, Recommendations: make([]listAppProductsItem, len(d.Recommendations))}
for i, it := range d.Recommendations {
ptsRec, _ := h.user.CentsToPoints(ctx.RequestContext(), it.Price)
rsp.Recommendations[i] = listAppProductsItem{ID: it.ID, Name: it.Name, MainImage: it.MainImage, Price: it.Price, PointsRequired: ptsRec, Sales: it.Sales, InStock: it.InStock}
}
ctx.Payload(rsp)
}
}
func parseImages(s string) []string {
var arr []string
// lightweight JSON array parser without comments
if s == "" {
return arr
}
// naive parse: trim brackets and split by comma, remove quotes
b := []byte(s)
if len(b) < 2 {
return arr
}
start, end := 0, len(b)
for start < end && (b[start] == '[' || b[start] == ' ' || b[start] == '\n' || b[start] == '\t') {
start++
}
for end > start && (b[end-1] == ']' || b[end-1] == ' ' || b[end-1] == '\n' || b[end-1] == '\t') {
end--
}
if start >= end {
return arr
}
content := string(b[start:end])
parts := []rune(content)
cur := ""
inStr := false
for _, r := range parts {
if r == '"' {
if inStr {
arr = append(arr, cur)
cur = ""
inStr = false
} else {
inStr = true
}
continue
}
if inStr {
cur += string(r)
}
}
return arr
}
func parseFirstImage(s string) string {
imgs := parseImages(s)
if len(imgs) > 0 {
return imgs[0]
}
return ""
}