Update mengyastore
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "", "", "", ""
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
162
mengyastore-backend-go/internal/handlers/swagger_models.go
Normal file
162
mengyastore-backend-go/internal/handlers/swagger_models.go
Normal 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"`
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user