f
This commit is contained in:
@@ -21,6 +21,8 @@ import (
|
||||
product_entities "hyapi-server/internal/domains/product/entities"
|
||||
product_services "hyapi-server/internal/domains/product/services"
|
||||
user_repositories "hyapi-server/internal/domains/user/repositories"
|
||||
securityEntities "hyapi-server/internal/domains/security/entities"
|
||||
securityServices "hyapi-server/internal/domains/security/services"
|
||||
task_entities "hyapi-server/internal/infrastructure/task/entities"
|
||||
"hyapi-server/internal/infrastructure/task/interfaces"
|
||||
"hyapi-server/internal/shared/crypto"
|
||||
@@ -83,6 +85,7 @@ type ApiApplicationServiceImpl struct {
|
||||
contractInfoService user_repositories.ContractInfoRepository
|
||||
productManagementService *product_services.ProductManagementService
|
||||
userRepo user_repositories.UserRepository
|
||||
blacklistService *securityServices.BlacklistService
|
||||
txManager *database.TransactionManager
|
||||
config *config.Config
|
||||
logger *zap.Logger
|
||||
@@ -112,6 +115,7 @@ func NewApiApplicationService(
|
||||
subscriptionService *product_services.ProductSubscriptionService,
|
||||
exportManager *export.ExportManager,
|
||||
balanceAlertService finance_services.BalanceAlertService,
|
||||
blacklistService *securityServices.BlacklistService,
|
||||
) ApiApplicationService {
|
||||
service := &ApiApplicationServiceImpl{
|
||||
apiCallService: apiCallService,
|
||||
@@ -121,6 +125,7 @@ func NewApiApplicationService(
|
||||
apiCallRepository: apiCallRepository,
|
||||
productManagementService: productManagementService,
|
||||
userRepo: userRepo,
|
||||
blacklistService: blacklistService,
|
||||
txManager: txManager,
|
||||
config: config,
|
||||
logger: logger,
|
||||
@@ -218,6 +223,24 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co
|
||||
return nil, ErrFrozenAccount
|
||||
}
|
||||
|
||||
if platformUser, err := s.userRepo.GetByID(ctx, apiUser.UserId); err == nil {
|
||||
if platformUser.IsOnBlacklist() {
|
||||
s.logger.Error("账号已列入黑名单", zap.String("userId", apiUser.UserId))
|
||||
if s.blacklistService != nil {
|
||||
s.blacklistService.RecordHit(&securityEntities.BlacklistHitRecord{
|
||||
HitType: securityEntities.HitTypeUserAPI,
|
||||
UserID: apiUser.UserId,
|
||||
Phone: platformUser.Phone,
|
||||
IP: cmd.ClientIP,
|
||||
Path: cmd.ApiName,
|
||||
Method: "POST",
|
||||
Reason: platformUser.BlacklistReason,
|
||||
})
|
||||
}
|
||||
return nil, ErrBlacklistedAccount
|
||||
}
|
||||
}
|
||||
|
||||
// 验证产品是否启用
|
||||
if !product.IsEnabled {
|
||||
s.logger.Error("产品未启用", zap.String("product_code", product.Code))
|
||||
@@ -440,6 +463,9 @@ func (s *ApiApplicationServiceImpl) asyncRecordFailure(ctx context.Context, apiC
|
||||
errorMsg = err.Error()
|
||||
case errors.Is(err, ErrFrozenAccount):
|
||||
errorType = entities.ApiCallErrorFrozenAccount
|
||||
case errors.Is(err, ErrBlacklistedAccount):
|
||||
errorType = entities.ApiCallErrorFrozenAccount
|
||||
errorMsg = err.Error()
|
||||
case errors.Is(err, ErrInvalidIP):
|
||||
errorType = entities.ApiCallErrorInvalidIP
|
||||
case errors.Is(err, ErrArrears):
|
||||
|
||||
@@ -12,8 +12,9 @@ var (
|
||||
ErrInvalidIP = errors.New("未经授权的IP")
|
||||
ErrMissingAccessId = errors.New("缺少Access-Id")
|
||||
ErrInvalidAccessId = errors.New("未经授权的AccessId")
|
||||
ErrFrozenAccount = errors.New("账户已冻结")
|
||||
ErrArrears = errors.New("账户余额不足,无法请求")
|
||||
ErrFrozenAccount = errors.New("账户已冻结")
|
||||
ErrBlacklistedAccount = errors.New("账号已被列入黑名单")
|
||||
ErrArrears = errors.New("账户余额不足,无法请求")
|
||||
ErrInsufficientBalance = errors.New("钱包余额不足")
|
||||
ErrProductNotFound = errors.New("产品不存在")
|
||||
ErrProductDisabled = errors.New("产品已停用")
|
||||
@@ -34,8 +35,9 @@ var ErrorCodeMap = map[error]int{
|
||||
ErrInvalidIP: 1004,
|
||||
ErrMissingAccessId: 1005,
|
||||
ErrInvalidAccessId: 1006,
|
||||
ErrFrozenAccount: 1007,
|
||||
ErrArrears: 1007,
|
||||
ErrFrozenAccount: 1007,
|
||||
ErrBlacklistedAccount: 1007,
|
||||
ErrArrears: 1007,
|
||||
ErrInsufficientBalance: 1007,
|
||||
ErrProductNotFound: 1008,
|
||||
ErrProductDisabled: 1008,
|
||||
|
||||
15
internal/application/user/dto/commands/blacklist_commands.go
Normal file
15
internal/application/user/dto/commands/blacklist_commands.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package commands
|
||||
|
||||
// BlacklistUserCommand 管理员将用户列入黑名单
|
||||
type BlacklistUserCommand struct {
|
||||
UserID string `json:"-"`
|
||||
OperatorUserID string `json:"-"`
|
||||
Reason string `json:"reason" binding:"required"`
|
||||
Source string `json:"source"` // self / police / test,默认 self
|
||||
}
|
||||
|
||||
// UnblacklistUserCommand 管理员将用户移出黑名单
|
||||
type UnblacklistUserCommand struct {
|
||||
UserID string `json:"-"`
|
||||
OperatorUserID string `json:"-"`
|
||||
}
|
||||
@@ -8,24 +8,26 @@ type ListUsersQuery struct {
|
||||
PageSize int `json:"page_size" validate:"min=1,max=100"`
|
||||
Phone string `json:"phone"`
|
||||
UserType string `json:"user_type"` // 用户类型: user/admin
|
||||
IsActive *bool `json:"is_active"` // 是否激活
|
||||
IsCertified *bool `json:"is_certified"` // 是否已认证
|
||||
CompanyName string `json:"company_name"` // 企业名称
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
IsActive *bool `json:"is_active"` // 是否激活
|
||||
IsCertified *bool `json:"is_certified"` // 是否已认证
|
||||
IsBlacklisted *bool `json:"is_blacklisted"` // 是否黑名单
|
||||
CompanyName string `json:"company_name"` // 企业名称
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
|
||||
// ToDomainQuery 转换为领域查询对象
|
||||
func (q *ListUsersQuery) ToDomainQuery() *queries.ListUsersQuery {
|
||||
return &queries.ListUsersQuery{
|
||||
Page: q.Page,
|
||||
PageSize: q.PageSize,
|
||||
Phone: q.Phone,
|
||||
UserType: q.UserType,
|
||||
IsActive: q.IsActive,
|
||||
IsCertified: q.IsCertified,
|
||||
CompanyName: q.CompanyName,
|
||||
StartDate: q.StartDate,
|
||||
EndDate: q.EndDate,
|
||||
Page: q.Page,
|
||||
PageSize: q.PageSize,
|
||||
Phone: q.Phone,
|
||||
UserType: q.UserType,
|
||||
IsActive: q.IsActive,
|
||||
IsCertified: q.IsCertified,
|
||||
IsBlacklisted: q.IsBlacklisted,
|
||||
CompanyName: q.CompanyName,
|
||||
StartDate: q.StartDate,
|
||||
EndDate: q.EndDate,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,22 +4,23 @@ import "time"
|
||||
|
||||
// UserListItem 用户列表项
|
||||
type UserListItem struct {
|
||||
ID string `json:"id"`
|
||||
Phone string `json:"phone"`
|
||||
UserType string `json:"user_type"`
|
||||
Username string `json:"username"`
|
||||
IsActive bool `json:"is_active"`
|
||||
IsCertified bool `json:"is_certified"`
|
||||
LoginCount int `json:"login_count"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// 企业信息
|
||||
ID string `json:"id"`
|
||||
Phone string `json:"phone"`
|
||||
UserType string `json:"user_type"`
|
||||
Username string `json:"username"`
|
||||
IsActive bool `json:"is_active"`
|
||||
IsCertified bool `json:"is_certified"`
|
||||
IsBlacklisted bool `json:"is_blacklisted"`
|
||||
BlacklistReason string `json:"blacklist_reason,omitempty"`
|
||||
BlacklistSource string `json:"blacklist_source,omitempty"`
|
||||
BlacklistedAt *time.Time `json:"blacklisted_at,omitempty"`
|
||||
LoginCount int `json:"login_count"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
EnterpriseInfo *EnterpriseInfoItem `json:"enterprise_info,omitempty"`
|
||||
|
||||
// 钱包信息
|
||||
WalletBalance string `json:"wallet_balance,omitempty"`
|
||||
WalletBalance string `json:"wallet_balance,omitempty"`
|
||||
}
|
||||
|
||||
// EnterpriseInfoItem 企业信息项
|
||||
|
||||
@@ -43,8 +43,12 @@ type UserProfileResponse struct {
|
||||
Phone string `json:"phone" example:"13800138000"`
|
||||
Username string `json:"username,omitempty" example:"admin"`
|
||||
UserType string `json:"user_type" example:"user"`
|
||||
IsActive bool `json:"is_active" example:"true"`
|
||||
LastLoginAt *time.Time `json:"last_login_at,omitempty" example:"2024-01-01T00:00:00Z"`
|
||||
IsActive bool `json:"is_active" example:"true"`
|
||||
IsBlacklisted bool `json:"is_blacklisted" example:"false"`
|
||||
BlacklistReason string `json:"blacklist_reason,omitempty"`
|
||||
BlacklistSource string `json:"blacklist_source,omitempty"`
|
||||
BlacklistedAt *time.Time `json:"blacklisted_at,omitempty"`
|
||||
LastLoginAt *time.Time `json:"last_login_at,omitempty" example:"2024-01-01T00:00:00Z"`
|
||||
LoginCount int `json:"login_count" example:"10"`
|
||||
Permissions []string `json:"permissions,omitempty" example:"['user:read','user:write']"`
|
||||
EnterpriseInfo *EnterpriseInfoResponse `json:"enterprise_info,omitempty"`
|
||||
|
||||
@@ -22,4 +22,6 @@ type UserApplicationService interface {
|
||||
ListUsers(ctx context.Context, query *queries.ListUsersQuery) (*responses.UserListResponse, error)
|
||||
GetUserDetail(ctx context.Context, userID string) (*responses.UserDetailResponse, error)
|
||||
GetUserStats(ctx context.Context) (*responses.UserStatsResponse, error)
|
||||
BlacklistUser(ctx context.Context, cmd *commands.BlacklistUserCommand) error
|
||||
UnblacklistUser(ctx context.Context, cmd *commands.UnblacklistUserCommand) error
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"hyapi-server/internal/application/user/dto/commands"
|
||||
"hyapi-server/internal/application/user/dto/queries"
|
||||
"hyapi-server/internal/application/user/dto/responses"
|
||||
api_service "hyapi-server/internal/domains/api/services"
|
||||
finance_service "hyapi-server/internal/domains/finance/services"
|
||||
"hyapi-server/internal/domains/user/entities"
|
||||
"hyapi-server/internal/domains/user/events"
|
||||
@@ -25,6 +26,7 @@ type UserApplicationServiceImpl struct {
|
||||
smsCodeService *user_service.SMSCodeService
|
||||
walletService finance_service.WalletAggregateService
|
||||
contractService user_service.ContractAggregateService
|
||||
apiUserService api_service.ApiUserAggregateService
|
||||
eventBus interfaces.EventBus
|
||||
jwtAuth *middleware.JWTAuthMiddleware
|
||||
logger *zap.Logger
|
||||
@@ -37,6 +39,7 @@ func NewUserApplicationService(
|
||||
smsCodeService *user_service.SMSCodeService,
|
||||
walletService finance_service.WalletAggregateService,
|
||||
contractService user_service.ContractAggregateService,
|
||||
apiUserService api_service.ApiUserAggregateService,
|
||||
eventBus interfaces.EventBus,
|
||||
jwtAuth *middleware.JWTAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
@@ -47,6 +50,7 @@ func NewUserApplicationService(
|
||||
smsCodeService: smsCodeService,
|
||||
walletService: walletService,
|
||||
contractService: contractService,
|
||||
apiUserService: apiUserService,
|
||||
eventBus: eventBus,
|
||||
jwtAuth: jwtAuth,
|
||||
logger: logger,
|
||||
@@ -316,16 +320,20 @@ func (s *UserApplicationServiceImpl) ListUsers(ctx context.Context, query *queri
|
||||
items := make([]*responses.UserListItem, 0, len(users))
|
||||
for _, user := range users {
|
||||
item := &responses.UserListItem{
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
UserType: user.UserType,
|
||||
Username: user.Username,
|
||||
IsActive: user.Active,
|
||||
IsCertified: user.IsCertified,
|
||||
LoginCount: user.LoginCount,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
UserType: user.UserType,
|
||||
Username: user.Username,
|
||||
IsActive: user.Active,
|
||||
IsCertified: user.IsCertified,
|
||||
IsBlacklisted: user.IsBlacklisted,
|
||||
BlacklistReason: user.BlacklistReason,
|
||||
BlacklistSource: user.BlacklistSource,
|
||||
BlacklistedAt: user.BlacklistedAt,
|
||||
LoginCount: user.LoginCount,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
|
||||
// 添加企业信息
|
||||
@@ -388,16 +396,20 @@ func (s *UserApplicationServiceImpl) GetUserDetail(ctx context.Context, userID s
|
||||
|
||||
// 2. 构建响应数据
|
||||
item := &responses.UserListItem{
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
UserType: user.UserType,
|
||||
Username: user.Username,
|
||||
IsActive: user.Active,
|
||||
IsCertified: user.IsCertified,
|
||||
LoginCount: user.LoginCount,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
UserType: user.UserType,
|
||||
Username: user.Username,
|
||||
IsActive: user.Active,
|
||||
IsCertified: user.IsCertified,
|
||||
IsBlacklisted: user.IsBlacklisted,
|
||||
BlacklistReason: user.BlacklistReason,
|
||||
BlacklistSource: user.BlacklistSource,
|
||||
BlacklistedAt: user.BlacklistedAt,
|
||||
LoginCount: user.LoginCount,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
|
||||
// 添加企业信息
|
||||
@@ -459,3 +471,64 @@ func (s *UserApplicationServiceImpl) GetUserStats(ctx context.Context) (*respons
|
||||
CertifiedUsers: stats.CertifiedUsers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BlacklistUser 将用户列入黑名单,并同步冻结 API 账号
|
||||
func (s *UserApplicationServiceImpl) BlacklistUser(ctx context.Context, cmd *commands.BlacklistUserCommand) error {
|
||||
user, err := s.userAggregateService.GetUserByID(ctx, cmd.UserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在")
|
||||
}
|
||||
|
||||
source := cmd.Source
|
||||
if source == "" {
|
||||
source = "self"
|
||||
}
|
||||
if err := user.AddToBlacklist(cmd.Reason, source, cmd.OperatorUserID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.userAggregateService.SaveUser(ctx, user); err != nil {
|
||||
return fmt.Errorf("保存黑名单状态失败: %w", err)
|
||||
}
|
||||
|
||||
// 同步冻结 API 访问(无 API 账号时忽略)
|
||||
if err := s.apiUserService.FreezeApiUser(ctx, cmd.UserID); err != nil {
|
||||
s.logger.Warn("冻结 API 账号失败(可能尚未开通)",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.Error(err))
|
||||
}
|
||||
|
||||
s.logger.Info("用户已列入黑名单",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("operator", cmd.OperatorUserID),
|
||||
zap.String("reason", cmd.Reason),
|
||||
zap.Time("blacklisted_at", *user.BlacklistedAt),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnblacklistUser 移出黑名单并解冻 API
|
||||
func (s *UserApplicationServiceImpl) UnblacklistUser(ctx context.Context, cmd *commands.UnblacklistUserCommand) error {
|
||||
user, err := s.userAggregateService.GetUserByID(ctx, cmd.UserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在")
|
||||
}
|
||||
|
||||
if err := user.RemoveFromBlacklist(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.userAggregateService.SaveUser(ctx, user); err != nil {
|
||||
return fmt.Errorf("移出黑名单失败: %w", err)
|
||||
}
|
||||
|
||||
if err := s.apiUserService.UnfreezeApiUser(ctx, cmd.UserID); err != nil {
|
||||
s.logger.Warn("解冻 API 账号失败(可能尚未开通)",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.Error(err))
|
||||
}
|
||||
|
||||
s.logger.Info("用户已移出黑名单",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("operator", cmd.OperatorUserID),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user