feat(invitation): 支持分销站点自主配置返利比例与权限隔离

- 后端新增分站专属配置及审计接口(/api/distribution/invitation-config 等),基于分站管理员严格鉴权隔离
- 分销独立后台新增「邀请返利设置」页面(Invitations.tsx),支持分站管理员自主开启/调整返利比例并查看本站流水
- 主站后台邀请接口路径补齐 /admin 前缀,解决 404/401 鉴权作用域问题
- 验证方式:backend 单元测试全数通过,distribution-admin 类型与构建检查通过
This commit is contained in:
yml2213
2026-10-10 13:47:53 +08:00
parent 984433f866
commit 7cddbe9463
8 changed files with 799 additions and 7 deletions
+139 -1
View File
@@ -1,6 +1,7 @@
package invitation
import (
"context"
"errors"
"net/http"
"strconv"
@@ -13,14 +14,23 @@ import (
"github.com/gin-gonic/gin"
)
type SiteScoper interface {
ScopeSiteID(ctx context.Context, adminID, requested uint64) (uint64, error)
}
type Handler struct {
service *Service
service *Service
siteScoper SiteScoper
}
func NewHandler(service *Service) *Handler {
return &Handler{service: service}
}
func (h *Handler) SetSiteScoper(scoper SiteScoper) {
h.siteScoper = scoper
}
// GetInviteInfo 公开接口:根据邀请码查询邀请人简要信息(用于落地注册页)
func (h *Handler) GetInviteInfo(c *gin.Context) {
code := strings.TrimSpace(c.Param("code"))
@@ -225,3 +235,131 @@ func writeInvitationError(c *gin.Context, err error) {
response.Error(c, http.StatusInternalServerError, "invitation_error", err.Error())
}
}
// DistributionGetConfig 分销端获取本站邀请返利比例配置
func (h *Handler) DistributionGetConfig(c *gin.Context) {
siteID, err := h.resolveDistributionSiteID(c)
if err != nil {
response.Forbidden(c, "无权访问该分销站点")
return
}
cfg, err := h.service.GetRebateConfig(c.Request.Context(), siteID)
if err != nil {
writeInvitationError(c, err)
return
}
response.OK(c, cfg)
}
// DistributionUpdateConfig 分销端更新本站邀请返利比例配置
func (h *Handler) DistributionUpdateConfig(c *gin.Context) {
siteID, err := h.resolveDistributionSiteID(c)
if err != nil {
response.Forbidden(c, "无权操作该分销站点")
return
}
if siteID == 0 {
response.BadRequest(c, "未指定有效分销站点")
return
}
var req UpdateConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "比例参数格式不正确,必须在0到1之间")
return
}
cfg, err := h.service.UpdateRebateConfig(c.Request.Context(), siteID, req)
if err != nil {
writeInvitationError(c, err)
return
}
response.OK(c, cfg)
}
// DistributionListInvitations 分销端查看本站邀请关系
func (h *Handler) DistributionListInvitations(c *gin.Context) {
siteID, err := h.resolveDistributionSiteID(c)
if err != nil {
response.Forbidden(c, "无权访问该分销站点")
return
}
var query AdminInvitationQuery
if err := c.ShouldBindQuery(&query); err != nil {
response.BadRequest(c, "查询参数不正确")
return
}
query.DistributionSiteID = &siteID
items, total, err := h.service.ListAdminInvitations(c.Request.Context(), query)
if err != nil {
writeInvitationError(c, err)
return
}
response.OK(c, gin.H{
"items": items,
"total": total,
"page": query.Page,
"page_size": query.PageSize,
})
}
// DistributionListRebates 分销端查看本站返利支出流水
func (h *Handler) DistributionListRebates(c *gin.Context) {
siteID, err := h.resolveDistributionSiteID(c)
if err != nil {
response.Forbidden(c, "无权访问该分销站点")
return
}
var query AdminRebateLedgerQuery
if err := c.ShouldBindQuery(&query); err != nil {
response.BadRequest(c, "查询参数不正确")
return
}
query.DistributionSiteID = &siteID
items, total, err := h.service.ListAdminRebateLedgers(c.Request.Context(), query)
if err != nil {
writeInvitationError(c, err)
return
}
response.OK(c, gin.H{
"items": items,
"total": total,
"page": query.Page,
"page_size": query.PageSize,
})
}
func (h *Handler) resolveDistributionSiteID(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 raw := c.Query("site_id"); raw != "" {
if id, err := strconv.ParseUint(raw, 10, 64); err == nil && id > 0 {
requested = id
}
}
}
if requested == 0 {
if raw := c.Query("distribution_site_id"); raw != "" {
if id, err := strconv.ParseUint(raw, 10, 64); err == nil && id > 0 {
requested = id
}
}
}
if h.siteScoper != nil {
return h.siteScoper.ScopeSiteID(c.Request.Context(), adminID, requested)
}
return requested, nil
}
func currentAdminID(c *gin.Context) (uint64, bool) {
value, ok := c.Get(middleware.ContextAdminID)
if !ok {
return 0, false
}
adminID, ok := value.(uint64)
return adminID, ok
}