Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 50s
更新了前端构建产物包括JavaScript、CSS和HTML文件,主要涉及以下变更: 1. 新增了多个组件和工具函数,包括异常页面组件、iframe组件等 2. 更新了活动管理、产品管理、优惠券管理等业务模块 3. 优化了构建配置和依赖管理 4. 修复了一些样式和功能问题 5. 更新了测试相关文件 同时更新了部分后端服务接口和测试用例。这些变更主要是为了支持新功能和改进现有功能的用户体验。
190 lines
6.6 KiB
Go
190 lines
6.6 KiB
Go
package app
|
||
|
||
import (
|
||
"net/http"
|
||
"strconv"
|
||
|
||
"bindbox-game/internal/code"
|
||
"bindbox-game/internal/pkg/core"
|
||
"bindbox-game/internal/pkg/validation"
|
||
"bindbox-game/internal/pkg/logger"
|
||
"bindbox-game/internal/repository/mysql"
|
||
"bindbox-game/internal/repository/mysql/dao"
|
||
prodsvc "bindbox-game/internal/service/product"
|
||
)
|
||
|
||
type productHandler struct {
|
||
logger logger.CustomLogger
|
||
readDB *dao.Query
|
||
product prodsvc.Service
|
||
}
|
||
|
||
func NewProduct(logger logger.CustomLogger, db mysql.Repo) *productHandler {
|
||
return &productHandler{logger: logger, readDB: dao.Use(db.GetDbR()), product: prodsvc.New(logger, db)}
|
||
}
|
||
|
||
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"`
|
||
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 {
|
||
rsp.List[i] = listAppProductsItem{ID: it.ID, Name: it.Name, MainImage: it.MainImage, Price: it.Price, 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"`
|
||
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
|
||
}
|
||
rsp := &getAppProductDetailResponse{ID: d.ID, Name: d.Name, Album: d.Album, Price: d.Price, Sales: d.Sales, Stock: d.Stock, Description: d.Description, Service: d.Service, Recommendations: make([]listAppProductsItem, len(d.Recommendations))}
|
||
for i, it := range d.Recommendations {
|
||
rsp.Recommendations[i] = listAppProductsItem{ID: it.ID, Name: it.Name, MainImage: it.MainImage, Price: it.Price, 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 ""
|
||
} |