Files
hfb_sys/backend/internal/modules/distribution/handler.go
T
yml2213 3465475756 feat(caddy): 支持分销站点独立域名 On-Demand TLS 自动签发 SSL 证书
- 后端新增 GET /api/distribution/check-domain 作为 Caddy on_demand_tls ask 白名单端点
- 补充 CheckDomain 相关场景自动化测试
- Caddyfile 启用 on_demand_tls 并配置动态分销前台入口
2026-09-16 14:20:44 +08:00

918 lines
24 KiB
Go

package distribution
import (
"errors"
"net/http"
"strconv"
"strings"
"hfb_sys/backend/internal/middleware"
"hfb_sys/backend/pkg/response"
"github.com/gin-gonic/gin"
)
type Handler struct{ service *Service }
func NewHandler(service *Service) *Handler { return &Handler{service: service} }
func (h *Handler) ListSites(c *gin.Context) {
adminID, _ := currentAdminID(c)
items, err := h.service.ListSitesForAdmin(c.Request.Context(), adminID)
if err != nil {
writeError(c, err)
return
}
response.OK(c, gin.H{"items": items})
}
func (h *Handler) GetSite(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
adminID, _ := currentAdminID(c)
item, err := h.service.FindSite(c.Request.Context(), adminID, id)
if err != nil {
writeError(c, err)
return
}
response.OK(c, item)
}
func (h *Handler) CreateSite(c *gin.Context) {
var req CreateSiteRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "分销网站参数不正确")
return
}
item, err := h.service.CreateSite(c.Request.Context(), req)
if err != nil {
writeError(c, err)
return
}
response.Created(c, item)
}
func (h *Handler) GetSiteDefaults(c *gin.Context) {
holdDays, err := h.service.DefaultSettlementHoldDays(c.Request.Context())
if err != nil {
writeError(c, err)
return
}
response.OK(c, gin.H{"default_settlement_hold_days": holdDays})
}
func (h *Handler) UpdateSite(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
var req UpdateSiteRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "分销网站参数不正确")
return
}
adminID, _ := currentAdminID(c)
item, err := h.service.UpdateSite(c.Request.Context(), adminID, id, req)
if err != nil {
writeError(c, err)
return
}
response.OK(c, item)
}
func (h *Handler) GetSiteSMSConfig(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
adminID, _ := currentAdminID(c)
item, err := h.service.GetSiteSMSConfig(c.Request.Context(), adminID, id)
if err != nil {
writeError(c, err)
return
}
response.OK(c, item)
}
func (h *Handler) UpdateSiteSMSConfig(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
var req UpdateSiteSMSConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "短信配置参数不正确")
return
}
adminID, _ := currentAdminID(c)
item, err := h.service.UpdateSiteSMSConfig(c.Request.Context(), adminID, id, req)
if err != nil {
writeError(c, err)
return
}
response.OK(c, item)
}
func (h *Handler) TestSiteSMSConfig(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
var req TestSiteSMSConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "测试参数不正确,手机号必填")
return
}
adminID, _ := currentAdminID(c)
if err := h.service.TestSiteSMSConfig(c.Request.Context(), adminID, id, req); err != nil {
writeError(c, err)
return
}
response.OK(c, gin.H{"message": "测试短信发送成功"})
}
func (h *Handler) ListRules(c *gin.Context) {
var siteID *uint64
if raw := strings.TrimSpace(c.Query("site_id")); raw != "" {
id, err := strconv.ParseUint(raw, 10, 64)
if err != nil || id == 0 {
response.BadRequest(c, "site_id 不正确")
return
}
siteID = &id
}
adminID, _ := currentAdminID(c)
items, err := h.service.ListRules(c.Request.Context(), adminID, siteID, c.Query("mode"))
if err != nil {
writeError(c, err)
return
}
response.OK(c, gin.H{"items": items})
}
func (h *Handler) CreateRule(c *gin.Context) {
adminID, ok := currentAdminID(c)
if !ok {
response.Unauthorized(c, "缺少管理员上下文")
return
}
var req CreateRuleRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "分销规则参数不正确")
return
}
item, err := h.service.CreateRule(c.Request.Context(), adminID, req)
if err != nil {
writeError(c, err)
return
}
response.Created(c, item)
}
func (h *Handler) UpdateRuleStatus(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
var req UpdateRuleStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "规则状态不正确")
return
}
adminID, _ := currentAdminID(c)
item, err := h.service.UpdateRuleStatus(c.Request.Context(), adminID, id, req.Status)
if err != nil {
writeError(c, err)
return
}
response.OK(c, item)
}
func (h *Handler) ListOrders(c *gin.Context) {
siteID := parseQueryUint(c, "site_id")
if siteID < 0 {
return
}
adminID, _ := currentAdminID(c)
scoped, err := h.service.ScopeSiteID(c.Request.Context(), adminID, uint64(siteID))
if err != nil {
writeError(c, err)
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
result, err := h.service.ListOrders(c.Request.Context(), DistributionOrderListQuery{
SiteID: scoped,
Kind: c.Query("kind"),
Role: c.Query("role"),
Status: c.Query("status"),
Mode: c.Query("mode"),
FinancialStatus: c.Query("financial_status"),
Keyword: c.Query("keyword"),
Page: page,
Size: size,
})
if err != nil {
writeError(c, err)
return
}
response.OK(c, result)
}
func (h *Handler) FindOrder(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
adminID, _ := currentAdminID(c)
item, err := h.service.FindOrder(c.Request.Context(), adminID, id)
if err != nil {
writeError(c, err)
return
}
response.OK(c, item)
}
func (h *Handler) CreateFinancialSnapshot(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
var req CreateFinancialSnapshotRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "分销财务快照参数不正确")
return
}
adminID, _ := currentAdminID(c)
item, err := h.service.CreateFinancialSnapshot(c.Request.Context(), adminID, id, req)
if err != nil {
writeError(c, err)
return
}
response.Created(c, item)
}
func (h *Handler) ListAccountDisplays(c *gin.Context) {
siteID := parseQueryUint(c, "site_id")
if siteID < 0 {
return
}
adminID, _ := currentAdminID(c)
scoped, err := h.service.ScopeSiteID(c.Request.Context(), adminID, uint64(siteID))
if err != nil {
writeError(c, err)
return
}
if scoped == 0 && siteID == 0 {
response.BadRequest(c, "site_id 不正确")
return
}
items, err := h.service.ListAccountDisplays(c.Request.Context(), scoped, c.Query("status"))
if err != nil {
writeError(c, err)
return
}
response.OK(c, gin.H{"items": items})
}
func (h *Handler) UpdateAccountDisplay(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
var req UpdateAccountDisplayRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "账号展示状态不正确")
return
}
adminID, _ := currentAdminID(c)
item, err := h.service.UpdateAccountDisplay(c.Request.Context(), adminID, id, req.DisplayStatus)
if err != nil {
writeError(c, err)
return
}
response.OK(c, item)
}
func (h *Handler) SyncSiteAccounts(c *gin.Context) {
var siteID uint64
id, ok := parseID(c)
if ok && id > 0 {
siteID = id
} else {
qID := parseQueryUint(c, "site_id")
if qID > 0 {
siteID = uint64(qID)
}
}
var req SyncSiteAccountsRequest
if c.Request.ContentLength != 0 {
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "同步参数不正确")
return
}
}
adminID, _ := currentAdminID(c)
scoped, err := h.service.ScopeSiteID(c.Request.Context(), adminID, siteID)
if err != nil {
writeError(c, err)
return
}
if scoped == 0 {
response.BadRequest(c, "site_id 不正确")
return
}
result, err := h.service.SyncSiteAccounts(c.Request.Context(), adminID, scoped, req)
if err != nil {
writeError(c, err)
return
}
response.OK(c, result)
}
func (h *Handler) ListPublicSiteListings(c *gin.Context) {
siteCode := strings.TrimSpace(c.Param("site_code"))
if siteCode == "" {
siteCode = strings.TrimSpace(c.Param("id"))
}
if siteCode == "" {
siteCode = strings.TrimSpace(c.Query("site_code"))
}
if siteCode == "" {
response.BadRequest(c, "site_code 不能为空")
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
result, err := h.service.ListPublicSiteListings(c.Request.Context(), siteCode, page, pageSize)
if err != nil {
writeError(c, err)
return
}
response.OK(c, result)
}
func (h *Handler) GetPublicSiteInfo(c *gin.Context) {
code := strings.TrimSpace(c.Query("code"))
if code == "" {
code = strings.TrimSpace(c.Query("site_code"))
}
if code == "" {
code = strings.TrimSpace(c.GetHeader("X-Distribution-Site-Code"))
}
domain := strings.TrimSpace(c.Query("domain"))
if code == "" && domain == "" {
host := c.Request.Host
if colon := strings.Index(host, ":"); colon != -1 {
host = host[:colon]
}
domain = host
}
info, err := h.service.GetPublicSiteInfo(c.Request.Context(), code, domain)
if err != nil {
writeError(c, err)
return
}
response.OK(c, info)
}
// CheckDomain 供网关(如 Caddy on_demand_tls ask)在按需申请 SSL 证书时校验域名白名单。
// 仅当域名属于系统中正常运行(active)的分销站点时返回 200,其余情况返回 404,
// 防止任意外部未授权域名解析到该服务器刷爆 Let's Encrypt 证书申请配额。
func (h *Handler) CheckDomain(c *gin.Context) {
domain := strings.ToLower(strings.TrimSpace(c.Query("domain")))
if domain == "" {
c.String(http.StatusBadRequest, "domain required")
return
}
if colon := strings.Index(domain, ":"); colon != -1 {
domain = domain[:colon]
}
info, err := h.service.GetPublicSiteInfo(c.Request.Context(), "", domain)
if err != nil || info == nil || info.Status != "active" {
c.String(http.StatusNotFound, "domain not allowed")
return
}
c.String(http.StatusOK, "ok")
}
func (h *Handler) ListPaymentChannels(c *gin.Context) {
siteID := parseQueryUint(c, "site_id")
if siteID <= 0 {
response.BadRequest(c, "site_id 不正确")
return
}
adminID, _ := currentAdminID(c)
scoped, err := h.service.ScopeSiteID(c.Request.Context(), adminID, uint64(siteID))
if err != nil {
writeError(c, err)
return
}
items, err := h.service.ListPaymentChannels(c.Request.Context(), scoped)
if err != nil {
writeError(c, err)
return
}
response.OK(c, gin.H{"items": items})
}
func (h *Handler) BindPaymentChannel(c *gin.Context) {
siteID := parseQueryUint(c, "site_id")
if siteID <= 0 {
response.BadRequest(c, "site_id 不正确")
return
}
adminID, _ := currentAdminID(c)
scoped, err := h.service.ScopeSiteID(c.Request.Context(), adminID, uint64(siteID))
if err != nil {
writeError(c, err)
return
}
var req BindPaymentChannelRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "支付通道参数不正确")
return
}
item, err := h.service.BindPaymentChannel(c.Request.Context(), scoped, req)
if err != nil {
writeError(c, err)
return
}
response.Created(c, item)
}
func (h *Handler) UnbindPaymentChannel(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
adminID, _ := currentAdminID(c)
if err := h.service.UnbindPaymentChannel(c.Request.Context(), adminID, id); err != nil {
writeError(c, err)
return
}
response.OK(c, gin.H{"deleted": true})
}
func (h *Handler) ListSettlementBatches(c *gin.Context) {
siteID := parseQueryUint(c, "site_id")
if siteID <= 0 {
response.BadRequest(c, "site_id 不正确")
return
}
adminID, _ := currentAdminID(c)
scoped, err := h.service.ScopeSiteID(c.Request.Context(), adminID, uint64(siteID))
if err != nil {
writeError(c, err)
return
}
items, err := h.service.ListSettlementBatches(c.Request.Context(), scoped)
if err != nil {
writeError(c, err)
return
}
response.OK(c, gin.H{"items": items})
}
func (h *Handler) CreateSettlementBatch(c *gin.Context) {
adminID, ok := currentAdminID(c)
if !ok {
response.Unauthorized(c, "缺少管理员上下文")
return
}
var req CreateSettlementBatchRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "结算周期参数不正确")
return
}
item, err := h.service.CreateSettlementBatch(c.Request.Context(), adminID, req)
if err != nil {
writeError(c, err)
return
}
response.Created(c, item)
}
func (h *Handler) ListSettlementItems(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
adminID, _ := currentAdminID(c)
items, err := h.service.ListSettlementItems(c.Request.Context(), adminID, id)
if err != nil {
writeError(c, err)
return
}
response.OK(c, gin.H{"items": items})
}
func (h *Handler) MarkSettlementBatch(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
adminID, _ := currentAdminID(c)
item, err := h.service.MarkSettlementBatch(c.Request.Context(), adminID, id)
if err != nil {
writeError(c, err)
return
}
response.OK(c, item)
}
func (h *Handler) CancelSettlementBatch(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
adminID, _ := currentAdminID(c)
item, err := h.service.CancelSettlementBatch(c.Request.Context(), adminID, id)
if err != nil {
writeError(c, err)
return
}
response.OK(c, item)
}
// parseQueryUint 返回 -1 表示格式错误,0 表示未传入。
func parseQueryUint(c *gin.Context, key string) int64 {
raw := strings.TrimSpace(c.Query(key))
if raw == "" {
return 0
}
id, err := strconv.ParseUint(raw, 10, 64)
if err != nil || id == 0 {
response.BadRequest(c, key+" 不正确")
return -1
}
return int64(id)
}
func (h *Handler) Login(c *gin.Context) {
var req DistributionLoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "用户名和密码不能为空")
return
}
resp, err := h.service.Login(c.Request.Context(), req, c.ClientIP())
if err != nil {
writeError(c, err)
return
}
secure := isHTTPSRequest(c)
c.SetSameSite(http.SameSiteStrictMode)
c.SetCookie(middleware.AdminAccessCookieName, resp.Token, 2*3600, "/api", "", secure, true)
response.OK(c, resp)
}
func (h *Handler) Me(c *gin.Context) {
adminID, ok := currentAdminID(c)
if !ok {
response.Unauthorized(c, "缺少管理员上下文")
return
}
profile, err := h.service.Me(c.Request.Context(), adminID)
if err != nil {
writeError(c, err)
return
}
response.OK(c, profile)
}
func (h *Handler) Logout(c *gin.Context) {
secure := isHTTPSRequest(c)
c.SetSameSite(http.SameSiteStrictMode)
c.SetCookie(middleware.AdminAccessCookieName, "", -1, "/api", "", secure, true)
response.OK(c, gin.H{"logged_out": true})
}
func (h *Handler) OnboardSite(c *gin.Context) {
var req OnboardSiteRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "开通分销商参数格式不正确")
return
}
resp, err := h.service.OnboardSite(c.Request.Context(), req)
if err != nil {
writeError(c, err)
return
}
response.Created(c, resp)
}
func (h *Handler) resolveSiteID(c *gin.Context) (uint64, error) {
adminID, _ := currentAdminID(c)
var requested uint64
if raw := c.Param("id"); raw != "" {
if id, err := strconv.ParseUint(raw, 10, 64); err == nil && id > 0 {
requested = id
}
}
if requested == 0 {
if q := parseQueryUint(c, "site_id"); q > 0 {
requested = uint64(q)
}
}
return h.service.ScopeSiteID(c.Request.Context(), adminID, requested)
}
func (h *Handler) UpdateSiteBranding(c *gin.Context) {
siteID, err := h.resolveSiteID(c)
if err != nil {
writeError(c, err)
return
}
if siteID == 0 {
response.BadRequest(c, "未指定分销站点")
return
}
var req UpdateSiteBrandingRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "品牌装修参数格式不正确")
return
}
adminID, _ := currentAdminID(c)
dto, err := h.service.UpdateSiteBranding(c.Request.Context(), adminID, siteID, req)
if err != nil {
writeError(c, err)
return
}
response.OK(c, dto)
}
func (h *Handler) UpdateAccountMarkup(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
var req UpdateAccountMarkupRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "加价参数格式不正确")
return
}
adminID, _ := currentAdminID(c)
dto, err := h.service.UpdateAccountMarkup(c.Request.Context(), adminID, id, req.ShelfRatioDelta)
if err != nil {
writeError(c, err)
return
}
response.OK(c, dto)
}
func (h *Handler) BatchUpdateAccountMarkup(c *gin.Context) {
siteID, err := h.resolveSiteID(c)
if err != nil {
writeError(c, err)
return
}
var req BatchUpdateAccountMarkupRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "批量加价参数格式不正确")
return
}
if siteID == 0 {
response.BadRequest(c, "未指定分销站点")
return
}
adminID, _ := currentAdminID(c)
if err := h.service.BatchUpdateAccountMarkup(c.Request.Context(), adminID, siteID, req); err != nil {
writeError(c, err)
return
}
response.OK(c, gin.H{"updated": true})
}
func (h *Handler) BatchUpdateAccountDisplay(c *gin.Context) {
siteID, err := h.resolveSiteID(c)
if err != nil {
writeError(c, err)
return
}
var req BatchUpdateAccountDisplayRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "批量启停参数格式不正确")
return
}
if siteID == 0 {
response.BadRequest(c, "未指定分销站点")
return
}
adminID, _ := currentAdminID(c)
if err := h.service.BatchUpdateAccountDisplay(c.Request.Context(), adminID, siteID, req); err != nil {
writeError(c, err)
return
}
response.OK(c, gin.H{"updated": true})
}
func (h *Handler) ListReceivedListings(c *gin.Context) {
siteID, err := h.resolveSiteID(c)
if err != nil {
writeError(c, err)
return
}
if siteID == 0 {
response.BadRequest(c, "未指定分销站点")
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
q := ReceivedListingQuery{
Page: page,
PageSize: pageSize,
Status: c.Query("status"),
ReviewStatus: c.Query("review_status"),
GameName: c.Query("game_name"),
Keyword: c.Query("keyword"),
}
adminID, _ := currentAdminID(c)
resp, err := h.service.ListReceivedListings(c.Request.Context(), adminID, siteID, q)
if err != nil {
writeError(c, err)
return
}
response.OK(c, resp)
}
func (h *Handler) UpdateReceivedListingStatus(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
var req UpdateReceivedListingStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "状态参数格式不正确")
return
}
adminID, _ := currentAdminID(c)
if err := h.service.UpdateReceivedListingStatus(c.Request.Context(), adminID, id, req.Status); err != nil {
writeError(c, err)
return
}
response.OK(c, gin.H{"updated": true})
}
func (h *Handler) GetSiteWallet(c *gin.Context) {
siteID, err := h.resolveSiteID(c)
if err != nil {
writeError(c, err)
return
}
if siteID == 0 {
response.BadRequest(c, "未指定分销站点")
return
}
adminID, _ := currentAdminID(c)
walletDTO, err := h.service.GetSiteWallet(c.Request.Context(), adminID, siteID)
if err != nil {
writeError(c, err)
return
}
response.OK(c, walletDTO)
}
func (h *Handler) ApplySiteWithdrawal(c *gin.Context) {
siteID, err := h.resolveSiteID(c)
if err != nil {
writeError(c, err)
return
}
if siteID == 0 {
response.BadRequest(c, "未指定分销站点")
return
}
var req ApplySiteWithdrawalRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "提现申请参数格式不正确")
return
}
adminID, _ := currentAdminID(c)
dto, err := h.service.ApplySiteWithdrawal(c.Request.Context(), adminID, siteID, req)
if err != nil {
writeError(c, err)
return
}
response.Created(c, dto)
}
func (h *Handler) ListSiteWithdrawals(c *gin.Context) {
siteID, err := h.resolveSiteID(c)
if err != nil {
writeError(c, err)
return
}
if siteID == 0 {
response.BadRequest(c, "未指定分销站点")
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
adminID, _ := currentAdminID(c)
pageResult, err := h.service.ListSiteWithdrawals(c.Request.Context(), adminID, siteID, page, pageSize)
if err != nil {
writeError(c, err)
return
}
response.OK(c, pageResult)
}
func (h *Handler) GetDashboardAnalytics(c *gin.Context) {
siteID, err := h.resolveSiteID(c)
if err != nil {
writeError(c, err)
return
}
if siteID == 0 {
response.BadRequest(c, "未指定分销站点")
return
}
adminID, _ := currentAdminID(c)
analytics, err := h.service.GetDashboardAnalytics(c.Request.Context(), adminID, siteID)
if err != nil {
writeError(c, err)
return
}
response.OK(c, analytics)
}
func isHTTPSRequest(c *gin.Context) bool {
if c.Request.TLS != nil {
return true
}
return strings.EqualFold(c.GetHeader("X-Forwarded-Proto"), "https")
}
func currentAdminID(c *gin.Context) (uint64, bool) {
value, ok := c.Get(middleware.ContextAdminID)
if !ok {
return 0, false
}
id, ok := value.(uint64)
return id, ok && id > 0
}
func parseID(c *gin.Context) (uint64, bool) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil || id == 0 {
response.BadRequest(c, "ID 不正确")
return 0, false
}
return id, true
}
func writeError(c *gin.Context, err error) {
switch {
case errors.Is(err, ErrSiteNotFound), errors.Is(err, ErrRuleNotFound), errors.Is(err, ErrOrderNotFound), errors.Is(err, ErrAccountDisplayNotFound), errors.Is(err, ErrPaymentChannelNotFound), errors.Is(err, ErrSettlementBatchNotFound):
response.NotFound(c, "分销资源不存在")
case errors.Is(err, ErrPaymentConfigNotFound):
response.BadRequest(c, "支付商户配置不存在")
case errors.Is(err, ErrOrderSnapshotExists):
response.Error(c, http.StatusConflict, "snapshot_exists", "该订单已存在财务快照,不能重复补数")
case errors.Is(err, ErrDuplicateSiteCodeOrDomain):
response.Error(c, http.StatusConflict, "duplicate_site", "分销站点标识或域名已被占用")
case errors.Is(err, ErrDuplicateUsername):
response.Error(c, http.StatusConflict, "duplicate_username", "管理员用户名已被占用")
case errors.Is(err, ErrInvalidCredential):
response.BadRequest(c, "用户名或密码错误")
case errors.Is(err, ErrAdminDisabled):
response.Error(c, http.StatusForbidden, "admin_disabled", "分销管理员账号已被禁用")
case errors.Is(err, ErrInvalidAmount), errors.Is(err, ErrInvalidRate), errors.Is(err, ErrInvalidServiceUnit), errors.Is(err, ErrUnsupportedMode), errors.Is(err, ErrInvalidOrderKind), errors.Is(err, ErrInvalidPeriod), errors.Is(err, ErrBatchAlreadySettled), errors.Is(err, ErrSettlementBatchEmpty), errors.Is(err, ErrSettlementTotalMismatch):
response.BadRequest(c, "分销规则参数不正确")
case errors.Is(err, ErrInsufficientBalance):
response.BadRequest(c, "可提现余额不足")
case errors.Is(err, ErrSettlementBatchStale):
response.BadRequest(c, "批次内订单发生退款变动或维权,该批次已失效,请作废后重新生成")
case errors.Is(err, ErrInvalidSettlementHold):
response.BadRequest(c, "结算冷却天数须在 0 到 365 之间")
case errors.Is(err, ErrBatchNotCancellable):
response.BadRequest(c, "该结算批次无法作废")
case errors.Is(err, ErrTooManySyncListings):
response.BadRequest(c, "一次勾选同步的商品过多")
case errors.Is(err, ErrSyncInProgress):
response.BadRequest(c, "当前分销站点商品同步正在进行中,请勿重复操作")
case errors.Is(err, ErrSyncListingNotFound):
response.BadRequest(c, "勾选的商品不存在")
case errors.Is(err, ErrEmptyListingIDs):
response.BadRequest(c, "请选择要加价的商品")
case errors.Is(err, ErrInvalidListingStatus):
response.BadRequest(c, "商品展示状态不正确")
case errors.Is(err, ErrSMSConfigInvalid):
response.BadRequest(c, "分销短信配置不完整,启用时必须配置 AccessKey、签名与模板")
case errors.Is(err, ErrSMSTestRateLimited):
response.Error(c, http.StatusTooManyRequests, "rate_limited", "测试短信发送过于频繁,请稍后再试")
case errors.Is(err, ErrSMSSendFailed):
response.Error(c, http.StatusBadGateway, "sms_send_failed", "短信测试发送失败,请检查配置与通道额度")
case errors.Is(err, ErrSiteAccessDenied):
response.Error(c, http.StatusForbidden, "forbidden", "无权访问该分销站点")
case errors.Is(err, ErrDependencyUnavailable):
response.ServiceUnavailable(c, "分销服务暂时不可用")
default:
response.RecordError(c, err)
response.Error(c, http.StatusInternalServerError, "distribution_error", "分销服务暂时不可用")
}
}