package realname import ( "context" "errors" "regexp" "strings" "hfb_sys/backend/internal/logging" "hfb_sys/backend/internal/model" "go.uber.org/zap" ) const ( StatusPending = "pending" StatusVerified = "verified" StatusRejected = "rejected" ) var ( ErrDependencyUnavailable = errors.New("dependency unavailable") ErrInvalidRealnameInput = errors.New("invalid realname input") ErrProviderConfigInvalid = errors.New("realname provider config invalid") ErrProviderRateLimited = errors.New("realname provider rate limited") ErrProviderUnavailable = errors.New("realname provider unavailable") ) type Service struct { repo *Repository provider Provider log *zap.Logger } type PublicStatus struct { Status string `json:"status"` Provider string `json:"provider,omitempty"` ProviderOrderNo string `json:"provider_order_no,omitempty"` MaskedName string `json:"masked_name,omitempty"` MaskedIDNo string `json:"masked_id_no,omitempty"` VerifiedAt *string `json:"verified_at,omitempty"` FailReason string `json:"fail_reason,omitempty"` } func NewService(repo *Repository, provider Provider, log *zap.Logger) *Service { return &Service{repo: repo, provider: provider, log: log} } func (s *Service) Start(ctx context.Context, userID uint64, name string, idNo string) (*model.UserRealname, error) { if s.repo == nil || s.provider == nil { return nil, ErrDependencyUnavailable } name = strings.TrimSpace(name) idNo = strings.TrimSpace(idNo) if !validName(name) || !validIDNo(idNo) { return nil, ErrInvalidRealnameInput } result, err := s.provider.Start(ctx, StartRequest{ UserID: userID, Name: name, IDNo: idNo, }) if err != nil { if s.log != nil { fields := []zap.Field{ zap.String("module", "realname"), zap.String("provider", s.provider.Name()), zap.Uint64("user_id", userID), zap.String("masked_id_no", maskIDNo(idNo)), zap.Error(err), } if requestID := logging.RequestIDFromContext(ctx); requestID != "" { fields = append(fields, zap.String("request_id", requestID)) } s.log.Warn("实名认证服务调用失败", fields...) } return nil, err } return s.repo.SaveResult(ctx, userID, s.provider.Name(), result) } func (s *Service) Status(ctx context.Context, userID uint64) (PublicStatus, error) { if s.repo == nil { return PublicStatus{}, ErrDependencyUnavailable } record, err := s.repo.FindByUserID(ctx, userID) if err != nil { return PublicStatus{Status: "unverified"}, nil } return ToPublicStatus(record), nil } func ToPublicStatus(record *model.UserRealname) PublicStatus { var verifiedAt *string if record.VerifiedAt != nil { value := record.VerifiedAt.Format("2006-01-02 15:04:05") verifiedAt = &value } return PublicStatus{ Status: record.Status, Provider: record.Provider, ProviderOrderNo: record.ProviderOrderNo, MaskedName: record.MaskedName, MaskedIDNo: record.MaskedIDNo, VerifiedAt: verifiedAt, FailReason: record.FailReason, } } func validName(name string) bool { return len([]rune(name)) >= 2 && len([]rune(name)) <= 30 } func validIDNo(idNo string) bool { return regexp.MustCompile(`^\d{17}[\dXx]$`).MatchString(idNo) }