Update mengyastore

This commit is contained in:
2026-05-13 12:11:46 +08:00
parent ad54b1eb7e
commit 37983f3b60
156 changed files with 10064 additions and 5681 deletions

View File

@@ -9,7 +9,7 @@ import (
"mengyastore-backend/internal/models"
)
type productPayload struct {
type ProductPayload struct {
Name string `json:"name"`
Price float64 `json:"price"`
DiscountPrice float64 `json:"discountPrice"`
@@ -17,6 +17,7 @@ type productPayload struct {
CoverURL string `json:"coverUrl"`
Codes []string `json:"codes"`
ScreenshotURLs []string `json:"screenshotUrls"`
PaymentQrURLs []string `json:"paymentQrUrls"`
Description string `json:"description"`
Active *bool `json:"active"`
RequireLogin bool `json:"requireLogin"`
@@ -28,7 +29,7 @@ type productPayload struct {
ShowContact bool `json:"showContact"`
}
func normalizeFulfillmentPayload(payload *productPayload) string {
func normalizeFulfillmentPayload(payload *ProductPayload) string {
ft := strings.TrimSpace(strings.ToLower(payload.FulfillmentType))
if ft == "fixed" {
return "fixed"
@@ -36,16 +37,26 @@ func normalizeFulfillmentPayload(payload *productPayload) string {
return "card"
}
type togglePayload struct {
type TogglePayload struct {
Active bool `json:"active"`
}
// VerifyAdminToken checks whether the supplied token is correct.
// Returns {"valid": true/false} without leaking the real token value.
// AdminVerifyTokenRequest 管理端校验令牌Body非 Header
type AdminVerifyTokenRequest struct {
Token string `json:"token"`
}
// VerifyAdminToken 校验请求中的令牌是否正确。
// @Summary 校验管理员令牌
// @Description 响应为 {"valid": true/false},不泄露真实口令。
// @Tags 管理端-认证
// @Accept json
// @Produce json
// @Param body body AdminVerifyTokenRequest true "待校验 token"
// @Success 200 {object} SwaggerValidBody
// @Router /api/admin/verify [post]
func (h *AdminHandler) VerifyAdminToken(c *gin.Context) {
var payload struct {
Token string `json:"token"`
}
var payload AdminVerifyTokenRequest
if err := c.ShouldBindJSON(&payload); err != nil || payload.Token == "" {
c.JSON(http.StatusOK, gin.H{"valid": false})
return
@@ -53,6 +64,15 @@ func (h *AdminHandler) VerifyAdminToken(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"valid": payload.Token == h.cfg.AdminToken})
}
// ListAllProducts 管理端商品全量列表(含下架与卡密等敏感字段)。
// @Summary 全部商品(管理)
// @Tags 管理端-商品
// @Produce json
// @Security AdminToken
// @Success 200 {object} SwaggerProductListBody
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/admin/products [get]
func (h *AdminHandler) ListAllProducts(c *gin.Context) {
if !h.requireAdmin(c) {
return
@@ -65,18 +85,35 @@ func (h *AdminHandler) ListAllProducts(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": items})
}
// CreateProduct 创建商品。
// @Summary 创建商品
// @Tags 管理端-商品
// @Accept json
// @Produce json
// @Security AdminToken
// @Param body body ProductPayload true "商品字段"
// @Success 200 {object} SwaggerProductOneBody
// @Failure 400 {object} SwaggerErrorBody
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/admin/products [post]
func (h *AdminHandler) CreateProduct(c *gin.Context) {
if !h.requireAdmin(c) {
return
}
var payload productPayload
var payload ProductPayload
if err := c.ShouldBindJSON(&payload); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数有误"})
return
}
screenshotURLs, valid := normalizeScreenshotURLs(payload.ScreenshotURLs)
if !valid {
c.JSON(http.StatusBadRequest, gin.H{"error": "截图链接最多 5 条"})
c.JSON(http.StatusBadRequest, gin.H{"error": "截图链接最多 6 条"})
return
}
paymentQrURLs, valid := normalizePaymentQrURLs(payload.PaymentQrURLs)
if !valid {
c.JSON(http.StatusBadRequest, gin.H{"error": "萌芽收款渠道链接最多 6 条"})
return
}
active := true
@@ -100,6 +137,7 @@ func (h *AdminHandler) CreateProduct(c *gin.Context) {
CoverURL: strings.TrimSpace(payload.CoverURL),
Codes: payload.Codes,
ScreenshotURLs: screenshotURLs,
PaymentQrURLs: paymentQrURLs,
Description: payload.Description,
Active: active,
RequireLogin: payload.RequireLogin,
@@ -118,19 +156,38 @@ func (h *AdminHandler) CreateProduct(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": created})
}
// UpdateProduct 更新商品。
// @Summary 更新商品
// @Tags 管理端-商品
// @Accept json
// @Produce json
// @Security AdminToken
// @Param id path string true "商品 ID"
// @Param body body ProductPayload true "商品字段"
// @Success 200 {object} SwaggerProductOneBody
// @Failure 400 {object} SwaggerErrorBody
// @Failure 401 {object} SwaggerErrorBody
// @Failure 404 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/admin/products/{id} [put]
func (h *AdminHandler) UpdateProduct(c *gin.Context) {
if !h.requireAdmin(c) {
return
}
id := c.Param("id")
var payload productPayload
var payload ProductPayload
if err := c.ShouldBindJSON(&payload); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数有误"})
return
}
screenshotURLs, valid := normalizeScreenshotURLs(payload.ScreenshotURLs)
if !valid {
c.JSON(http.StatusBadRequest, gin.H{"error": "截图链接最多 5 条"})
c.JSON(http.StatusBadRequest, gin.H{"error": "截图链接最多 6 条"})
return
}
paymentQrURLs, valid := normalizePaymentQrURLs(payload.PaymentQrURLs)
if !valid {
c.JSON(http.StatusBadRequest, gin.H{"error": "萌芽收款渠道链接最多 6 条"})
return
}
active := false
@@ -154,6 +211,7 @@ func (h *AdminHandler) UpdateProduct(c *gin.Context) {
CoverURL: strings.TrimSpace(payload.CoverURL),
Codes: payload.Codes,
ScreenshotURLs: screenshotURLs,
PaymentQrURLs: paymentQrURLs,
Description: payload.Description,
Active: active,
RequireLogin: payload.RequireLogin,
@@ -172,12 +230,25 @@ func (h *AdminHandler) UpdateProduct(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": updated})
}
// ToggleProduct 上架/下架切换。
// @Summary 切换上架状态
// @Tags 管理端-商品
// @Accept json
// @Produce json
// @Security AdminToken
// @Param id path string true "商品 ID"
// @Param body body TogglePayload true "{active}"
// @Success 200 {object} SwaggerProductOneBody
// @Failure 400 {object} SwaggerErrorBody
// @Failure 401 {object} SwaggerErrorBody
// @Failure 404 {object} SwaggerErrorBody
// @Router /api/admin/products/{id}/status [patch]
func (h *AdminHandler) ToggleProduct(c *gin.Context) {
if !h.requireAdmin(c) {
return
}
id := c.Param("id")
var payload togglePayload
var payload TogglePayload
if err := c.ShouldBindJSON(&payload); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数有误"})
return
@@ -190,6 +261,16 @@ func (h *AdminHandler) ToggleProduct(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": updated})
}
// DeleteProduct 删除商品。
// @Summary 删除商品
// @Tags 管理端-商品
// @Produce json
// @Security AdminToken
// @Param id path string true "商品 ID"
// @Success 200 {object} SwaggerBoolOKWrap
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/admin/products/{id} [delete]
func (h *AdminHandler) DeleteProduct(c *gin.Context) {
if !h.requireAdmin(c) {
return
@@ -202,6 +283,26 @@ func (h *AdminHandler) DeleteProduct(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true}})
}
const maxScreenshotURLsAdmin = 6
const maxPaymentQrURLsAdmin = 6
func normalizePaymentQrURLs(urls []string) ([]string, bool) {
cleaned := make([]string, 0, len(urls))
seen := map[string]bool{}
for _, u := range urls {
trimmed := strings.TrimSpace(u)
if trimmed == "" || seen[trimmed] {
continue
}
seen[trimmed] = true
cleaned = append(cleaned, trimmed)
if len(cleaned) > maxPaymentQrURLsAdmin {
return nil, false
}
}
return cleaned, true
}
func normalizeScreenshotURLs(urls []string) ([]string, bool) {
cleaned := make([]string, 0, len(urls))
for _, url := range urls {
@@ -210,7 +311,7 @@ func normalizeScreenshotURLs(urls []string) ([]string, bool) {
continue
}
cleaned = append(cleaned, trimmed)
if len(cleaned) > 5 {
if len(cleaned) > maxScreenshotURLsAdmin {
return nil, false
}
}