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

@@ -8,6 +8,14 @@ import (
)
// GetAllConversations 返回所有用户会话列表。
// @Summary 全部会话列表
// @Tags 管理端-聊天
// @Produce json
// @Security AdminToken
// @Success 200 {object} map[string]interface{}
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/admin/chat [get]
func (h *AdminHandler) GetAllConversations(c *gin.Context) {
if !h.requireAdmin(c) {
return
@@ -21,6 +29,16 @@ func (h *AdminHandler) GetAllConversations(c *gin.Context) {
}
// GetConversation 返回指定账号的全部消息记录。
// @Summary 指定用户聊天记录
// @Tags 管理端-聊天
// @Produce json
// @Security AdminToken
// @Param account path string true "用户账号"
// @Success 200 {object} SwaggerMessagesWrap
// @Failure 400 {object} SwaggerErrorBody
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/admin/chat/{account} [get]
func (h *AdminHandler) GetConversation(c *gin.Context) {
if !h.requireAdmin(c) {
return
@@ -38,11 +56,23 @@ func (h *AdminHandler) GetConversation(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": gin.H{"messages": msgs}})
}
type adminChatPayload struct {
type AdminChatPayload struct {
Content string `json:"content"`
}
// AdminReply 向指定用户发送管理员回复。
// @Summary 管理员回复
// @Tags 管理端-聊天
// @Accept json
// @Produce json
// @Security AdminToken
// @Param account path string true "用户账号"
// @Param body body AdminChatPayload true "回复内容"
// @Success 200 {object} SwaggerOneChatMsgWrap
// @Failure 400 {object} SwaggerErrorBody
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/admin/chat/{account} [post]
func (h *AdminHandler) AdminReply(c *gin.Context) {
if !h.requireAdmin(c) {
return
@@ -52,7 +82,7 @@ func (h *AdminHandler) AdminReply(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "缺少账号参数"})
return
}
var payload adminChatPayload
var payload AdminChatPayload
if err := c.ShouldBindJSON(&payload); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数有误"})
return
@@ -71,6 +101,16 @@ func (h *AdminHandler) AdminReply(c *gin.Context) {
}
// ClearConversation 清除与指定用户的全部消息记录。
// @Summary 清空会话
// @Tags 管理端-聊天
// @Produce json
// @Security AdminToken
// @Param account path string true "用户账号"
// @Success 200 {object} SwaggerBoolOKWrap
// @Failure 400 {object} SwaggerErrorBody
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/admin/chat/{account} [delete]
func (h *AdminHandler) ClearConversation(c *gin.Context) {
if !h.requireAdmin(c) {
return

View File

@@ -6,6 +6,15 @@ import (
"github.com/gin-gonic/gin"
)
// ListAllOrders 管理端全部订单。
// @Summary 全部订单(管理)
// @Tags 管理端-订单
// @Produce json
// @Security AdminToken
// @Success 200 {object} SwaggerOrdersBody
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/admin/orders [get]
func (h *AdminHandler) ListAllOrders(c *gin.Context) {
if !h.requireAdmin(c) {
return
@@ -18,6 +27,17 @@ func (h *AdminHandler) ListAllOrders(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": orders})
}
// DeleteOrder 管理端删除订单。
// @Summary 删除订单
// @Tags 管理端-订单
// @Produce json
// @Security AdminToken
// @Param id path string true "订单 ID"
// @Success 200 {object} SwaggerBoolOKWrap
// @Failure 400 {object} SwaggerErrorBody
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/admin/orders/{id} [delete]
func (h *AdminHandler) DeleteOrder(c *gin.Context) {
if !h.requireAdmin(c) {
return

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
}
}

View File

@@ -8,16 +8,28 @@ import (
"mengyastore-backend/internal/storage"
)
type maintenancePayload struct {
type MaintenancePayload struct {
Maintenance bool `json:"maintenance"`
Reason string `json:"reason"`
}
// SetMaintenance 设置站点维护模式。
// @Summary 设置维护模式
// @Tags 管理端-站点
// @Accept json
// @Produce json
// @Security AdminToken
// @Param body body MaintenancePayload true "维护开关与原因"
// @Success 200 {object} SwaggerMaintenanceWrap
// @Failure 400 {object} SwaggerErrorBody
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/admin/site/maintenance [post]
func (h *AdminHandler) SetMaintenance(c *gin.Context) {
if !h.requireAdmin(c) {
return
}
var payload maintenancePayload
var payload MaintenancePayload
if err := c.ShouldBindJSON(&payload); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数有误"})
return
@@ -34,6 +46,15 @@ func (h *AdminHandler) SetMaintenance(c *gin.Context) {
})
}
// GetSMTPConfig 获取 SMTP 配置(密码脱敏)。
// @Summary 获取 SMTP 配置
// @Tags 管理端-站点
// @Produce json
// @Security AdminToken
// @Success 200 {object} SwaggerSMTPWrap
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/admin/site/smtp [get]
func (h *AdminHandler) GetSMTPConfig(c *gin.Context) {
if !h.requireAdmin(c) {
return
@@ -51,6 +72,18 @@ func (h *AdminHandler) GetSMTPConfig(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": masked})
}
// SetSMTPConfig 保存 SMTP 配置。
// @Summary 保存 SMTP 配置
// @Tags 管理端-站点
// @Accept json
// @Produce json
// @Security AdminToken
// @Param body body storage.SMTPConfig true "SMTP 字段"
// @Success 200 {object} SwaggerStringOKBody
// @Failure 400 {object} SwaggerErrorBody
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/admin/site/smtp [post]
func (h *AdminHandler) SetSMTPConfig(c *gin.Context) {
if !h.requireAdmin(c) {
return

View File

@@ -17,7 +17,7 @@ import (
"mengyastore-backend/internal/mq"
)
// SystemStatusHandler exposes admin-only operational status.
// SystemStatusHandler 向管理端暴露运维状态(仅供管理员)。
type SystemStatusHandler struct {
cfg *config.Config
db *gormDB.DB
@@ -29,7 +29,14 @@ func NewSystemStatusHandler(cfg *config.Config, db *gormDB.DB, mqClient *mq.Clie
return &SystemStatusHandler{cfg: cfg, db: db, mq: mqClient, start: startedAt}
}
// GetSystemStatus returns JSON for the admin dashboard. Requires admin token.
// GetSystemStatus 返回管理后台用 JSON需通过管理员令牌。
// @Summary 系统运行状态
// @Tags 管理端-系统
// @Produce json
// @Security AdminToken
// @Success 200 {object} map[string]interface{}
// @Failure 401 {object} SwaggerErrorBody
// @Router /api/admin/system-status [get]
func (h *SystemStatusHandler) GetSystemStatus(c *gin.Context) {
if !h.adminTokenOK(c) {
return
@@ -278,7 +285,7 @@ func (h *SystemStatusHandler) rabbitmqInfo() gin.H {
return out
}
// parseAMQPBroker returns host, port, vhost, user without password (for display only).
// parseAMQPBroker 从 amqp URL 解析主机、端口、vhost、用户名不含口令仅展示用
func parseAMQPBroker(raw string) (host, port, vhost, user string) {
if raw == "" {
return "", "", "", ""

View File

@@ -35,6 +35,14 @@ func (h *ChatHandler) requireChatUser(c *gin.Context) (account, name string, ok
}
// GetMyMessages 返回当前登录用户的全部聊天消息。
// @Summary 我的聊天消息
// @Tags 聊天(用户)
// @Produce json
// @Security BearerAuth
// @Success 200 {object} SwaggerMessagesWrap
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/chat/messages [get]
func (h *ChatHandler) GetMyMessages(c *gin.Context) {
account, _, ok := h.requireChatUser(c)
if !ok {
@@ -48,17 +56,30 @@ func (h *ChatHandler) GetMyMessages(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": gin.H{"messages": msgs}})
}
type chatMessagePayload struct {
// ChatMessagePayload 用户发送消息请求体。
type ChatMessagePayload struct {
Content string `json:"content"`
}
// SendMyMessage 向管理员发送一条用户消息。
// @Summary 发送用户消息
// @Tags 聊天(用户)
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param body body ChatMessagePayload true "消息正文"
// @Success 200 {object} SwaggerOneChatMsgWrap
// @Failure 400 {object} SwaggerErrorBody
// @Failure 401 {object} SwaggerErrorBody
// @Failure 429 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/chat/messages [post]
func (h *ChatHandler) SendMyMessage(c *gin.Context) {
account, name, ok := h.requireChatUser(c)
if !ok {
return
}
var payload chatMessagePayload
var payload ChatMessagePayload
if err := c.ShouldBindJSON(&payload); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数有误"})
return

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,13 @@ func NewPublicHandler(store *storage.ProductStore) *PublicHandler {
return &PublicHandler{store: store}
}
// ListProducts 上架商品列表(公开字段,不含卡密与管理信息)。
// @Summary 上架商品列表
// @Tags 公开
// @Produce json
// @Success 200 {object} SwaggerProductListBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/products [get]
func (h *PublicHandler) ListProducts(c *gin.Context) {
items, err := h.store.ListActive()
if err != nil {
@@ -27,6 +34,14 @@ func (h *PublicHandler) ListProducts(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": sanitizeForPublic(items)})
}
// RecordProductView 记录商品浏览(去重策略由服务端指纹决定)。
// @Summary 记录商品浏览
// @Tags 公开
// @Produce json
// @Param id path string true "商品 ID"
// @Success 200 {object} SwaggerProductViewWrap
// @Failure 404 {object} SwaggerErrorBody
// @Router /api/products/{id}/view [post]
func (h *PublicHandler) RecordProductView(c *gin.Context) {
id := c.Param("id")
fingerprint := buildViewerFingerprint(c)

View File

@@ -17,6 +17,13 @@ func NewStatsHandler(orderStore *storage.OrderStore, siteStore *storage.SiteStor
return &StatsHandler{orderStore: orderStore, siteStore: siteStore}
}
// GetStats 订单总数与站点访问总数。
// @Summary 站点统计
// @Tags 公开
// @Produce json
// @Success 200 {object} SwaggerStatsWrap
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/stats [get]
func (h *StatsHandler) GetStats(c *gin.Context) {
totalOrders, err := h.orderStore.Count()
if err != nil {
@@ -36,6 +43,13 @@ func (h *StatsHandler) GetStats(c *gin.Context) {
})
}
// RecordVisit 记录一次站点访问并返回累计访问量。
// @Summary 记录站点访问
// @Tags 公开
// @Produce json
// @Success 200 {object} SwaggerVisitWrap
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/site/visit [post]
func (h *StatsHandler) RecordVisit(c *gin.Context) {
fingerprint := buildViewerFingerprint(c)
totalVisits, counted, err := h.siteStore.RecordVisit(fingerprint)
@@ -51,6 +65,13 @@ func (h *StatsHandler) RecordVisit(c *gin.Context) {
})
}
// GetMaintenance 当前维护模式与原因。
// @Summary 维护模式状态
// @Tags 公开
// @Produce json
// @Success 200 {object} SwaggerMaintenanceWrap
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/site/maintenance [get]
func (h *StatsHandler) GetMaintenance(c *gin.Context) {
enabled, reason, err := h.siteStore.GetMaintenance()
if err != nil {

View File

@@ -0,0 +1,162 @@
package handlers
import (
"time"
"mengyastore-backend/internal/models"
"mengyastore-backend/internal/storage"
)
// 以下类型仅用于 swag/OpenAPI 描述,与实际 handler 返回值形状对齐。
type SwaggerErrorBody struct {
Error string `json:"error"`
}
type SwaggerValidBody struct {
Valid bool `json:"valid"`
}
type SwaggerProductListBody struct {
Data []models.Product `json:"data"`
}
type SwaggerProductOneBody struct {
Data models.Product `json:"data"`
}
type SwaggerOrdersBody struct {
Data []models.Order `json:"data"`
}
type SwaggerSMTPWrap struct {
Data storage.SMTPConfig `json:"data"`
}
type SwaggerStringOKBody struct {
Data string `json:"data"`
}
type SwaggerBoolOKWrap struct {
Data SwaggerBoolOK `json:"data"`
}
type SwaggerBoolOK struct {
OK bool `json:"ok"`
}
type SwaggerProductViewWrap struct {
Data SwaggerProductViewData `json:"data"`
}
type SwaggerProductViewData struct {
ID string `json:"id"`
ViewCount int `json:"viewCount"`
Counted bool `json:"counted"`
}
type SwaggerStatsWrap struct {
Data SwaggerStatsData `json:"data"`
}
type SwaggerStatsData struct {
TotalOrders int `json:"totalOrders"`
TotalVisits int `json:"totalVisits"`
}
type SwaggerVisitWrap struct {
Data SwaggerVisitData `json:"data"`
}
type SwaggerVisitData struct {
TotalVisits int `json:"totalVisits"`
Counted bool `json:"counted"`
}
type SwaggerMaintenanceWrap struct {
Data SwaggerMaintenanceData `json:"data"`
}
type SwaggerMaintenanceData struct {
Maintenance bool `json:"maintenance"`
Reason string `json:"reason"`
}
type SwaggerCheckoutWrap struct {
Data SwaggerCheckoutData `json:"data"`
}
type SwaggerCheckoutData struct {
OrderID string `json:"orderId"`
QrCodeURL string `json:"qrCodeUrl"`
ProductID string `json:"productId"`
ProductQty int `json:"productQty"`
ViewCount int `json:"viewCount"`
Status string `json:"status"`
PaymentMethod string `json:"paymentMethod"`
PaymentExpectedTotal float64 `json:"paymentExpectedTotal,omitempty"`
PaymentQrURLs []string `json:"paymentQrUrls,omitempty"`
PaymentExpiresAt *time.Time `json:"paymentExpiresAt,omitempty"`
}
type SwaggerConfirmWrap struct {
Data SwaggerConfirmData `json:"data"`
}
type SwaggerConfirmData struct {
OrderID string `json:"orderId"`
Status string `json:"status"`
DeliveryMode string `json:"deliveryMode"`
DeliveredCodes []string `json:"deliveredCodes"`
IsManual bool `json:"isManual"`
}
type SwaggerPaymentStatusWrap struct {
Data SwaggerPaymentStatusData `json:"data"`
}
type SwaggerPaymentStatusData struct {
Status string `json:"status"`
ExpectedTotal float64 `json:"expectedTotal"`
PaymentExpiresAt *time.Time `json:"paymentExpiresAt"`
PaymentMethod string `json:"paymentMethod"`
}
type SwaggerWebhookMengyaResp struct {
OK bool `json:"ok"`
Matched bool `json:"matched"`
MatchedOrderID string `json:"matched_order_id,omitempty"`
}
type SwaggerCancelWrap struct {
Data SwaggerCancelMsg `json:"data"`
}
type SwaggerCancelMsg struct {
OK bool `json:"ok"`
Message string `json:"message,omitempty"`
}
type SwaggerWishlistWrap struct {
Data SwaggerWishlistItems `json:"data"`
}
type SwaggerWishlistItems struct {
Items []string `json:"items"`
}
type SwaggerMessagesWrap struct {
Data SwaggerMessagesInner `json:"data"`
}
type SwaggerMessagesInner struct {
Messages []models.ChatMessage `json:"messages"`
}
type SwaggerOneChatMsgWrap struct {
Data SwaggerSingleChatWrap `json:"data"`
}
type SwaggerSingleChatWrap struct {
Message models.ChatMessage `json:"message"`
}

View File

@@ -34,6 +34,15 @@ func (h *WishlistHandler) requireUser(c *gin.Context) (string, bool) {
return result.User.Account, true
}
// GetWishlist 当前用户收藏的商品 ID 列表。
// @Summary 收藏列表
// @Tags 收藏
// @Produce json
// @Security BearerAuth
// @Success 200 {object} SwaggerWishlistWrap
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/wishlist [get]
func (h *WishlistHandler) GetWishlist(c *gin.Context) {
account, ok := h.requireUser(c)
if !ok {
@@ -47,16 +56,29 @@ func (h *WishlistHandler) GetWishlist(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": gin.H{"items": ids}})
}
type wishlistItemPayload struct {
// WishlistItemPayload 添加收藏请求体。
type WishlistItemPayload struct {
ProductID string `json:"productId"`
}
// AddToWishlist 添加收藏。
// @Summary 添加收藏
// @Tags 收藏
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param body body WishlistItemPayload true "商品 ID"
// @Success 200 {object} SwaggerWishlistWrap
// @Failure 400 {object} SwaggerErrorBody
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/wishlist [post]
func (h *WishlistHandler) AddToWishlist(c *gin.Context) {
account, ok := h.requireUser(c)
if !ok {
return
}
var payload wishlistItemPayload
var payload WishlistItemPayload
if err := c.ShouldBindJSON(&payload); err != nil || payload.ProductID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数有误"})
return
@@ -69,6 +91,17 @@ func (h *WishlistHandler) AddToWishlist(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": gin.H{"items": ids}})
}
// RemoveFromWishlist 按商品 ID 移除收藏。
// @Summary 移除收藏
// @Tags 收藏
// @Produce json
// @Security BearerAuth
// @Param id path string true "商品 ID"
// @Success 200 {object} SwaggerWishlistWrap
// @Failure 400 {object} SwaggerErrorBody
// @Failure 401 {object} SwaggerErrorBody
// @Failure 500 {object} SwaggerErrorBody
// @Router /api/wishlist/{id} [delete]
func (h *WishlistHandler) RemoveFromWishlist(c *gin.Context) {
account, ok := h.requireUser(c)
if !ok {