feat(distribution): 独立分销后台账号体系、多租户隔离与主站后台分销管理GUI

- 独立账号鉴权:分销后台支持独立登录页面、Bearer+Cookie 双模鉴权与租户资料接口
- 多租户隔离:分销商成员强制锁定自身站点数据范围,防止横向越权;平台超管享有全局视界
- 一键开通分销商:平台端支持一键录入站点、管理员账号、分配角色并原子化初始化佣金规则
- 主后台分销分组:在主站后台增加「分销管理」导航分组,提供站点管理、规则配置、订单监控与结算管理 GUI
- 门禁与联调:补充完整单测,全量代码门禁 check.sh 100% 通过,dev.sh 支持前后端与分销后台一键同启
This commit is contained in:
yml2213
2026-09-07 15:45:53 +08:00
parent 0c37c7e659
commit 550b42eb22
28 changed files with 3192 additions and 117 deletions
@@ -5,13 +5,23 @@ import (
"errors"
"hfb_sys/backend/internal/model"
"hfb_sys/backend/internal/modules/auth"
)
var ErrDependencyUnavailable = errors.New("distribution dependency unavailable")
type Service struct{ repo *Repository }
type Service struct {
repo *Repository
jwt *auth.JWTManager
}
func NewService(repo *Repository) *Service { return &Service{repo: repo} }
func NewService(repo *Repository, jwt ...*auth.JWTManager) *Service {
s := &Service{repo: repo}
if len(jwt) > 0 {
s.jwt = jwt[0]
}
return s
}
func (s *Service) available() error {
if s == nil || s.repo == nil {
@@ -171,3 +181,40 @@ func (s *Service) CreateFinancialSnapshot(ctx context.Context, adminID, orderID
}
return s.repo.CreateFinancialSnapshot(ctx, adminID, orderID, req)
}
func (s *Service) Login(ctx context.Context, req DistributionLoginRequest, clientIP string) (*DistributionLoginResponse, error) {
if err := s.available(); err != nil {
return nil, err
}
if s.jwt == nil {
return nil, ErrDependencyUnavailable
}
admin, member, err := s.repo.LoginMember(ctx, req.Username, req.Password)
if err != nil {
return nil, err
}
pair, err := s.jwt.GenerateSubjectPairWithVersion(admin.ID, admin.Username, "admin", admin.TokenVersion)
if err != nil {
return nil, err
}
return &DistributionLoginResponse{
Token: pair.AccessToken,
RefreshToken: pair.RefreshToken,
Member: *member,
}, nil
}
func (s *Service) Me(ctx context.Context, adminID uint64) (*DistributionCurrentMemberDTO, error) {
if err := s.available(); err != nil {
return nil, err
}
return s.repo.GetMemberProfile(ctx, adminID)
}
func (s *Service) OnboardSite(ctx context.Context, req OnboardSiteRequest) (*OnboardSiteResponse, error) {
if err := s.available(); err != nil {
return nil, err
}
return s.repo.OnboardSite(ctx, req)
}