// Package personal_customer 提供个人客户资料管理的业务逻辑服务 package personal_customer import ( "context" stderrors "errors" accessauditapp "github.com/break/junhong_cmp_fiber/internal/application/accessaudit" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/store/postgres" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "go.uber.org/zap" "gorm.io/gorm" "gorm.io/gorm/clause" ) // Service 个人客户服务 type Service struct { db *gorm.DB store *postgres.PersonalCustomerStore phoneStore *postgres.PersonalCustomerPhoneStore logger *zap.Logger accessAudit accessauditapp.Writer } // NewService 创建个人客户服务实例 func NewService( db *gorm.DB, store *postgres.PersonalCustomerStore, phoneStore *postgres.PersonalCustomerPhoneStore, logger *zap.Logger, accessAudit accessauditapp.Writer, ) *Service { return &Service{ db: db, store: store, phoneStore: phoneStore, logger: logger, accessAudit: accessAudit, } } // UpdateProfile 更新个人资料 func (s *Service) UpdateProfile(ctx context.Context, customerID uint, nickname, avatarURL string) error { if s.db == nil || s.accessAudit == nil { return errors.New(errors.CodeInvalidStatus, "个人客户审计接缝未配置") } customer := &model.PersonalCustomer{Model: gorm.Model{ID: customerID}} failureCustomer := customer var beforeData map[string]any loaded := false err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(customer, customerID).Error; err != nil { return errors.Wrap(errors.CodeInternalError, err, "查询个人客户失败") } loaded = true beforeData = personalProfileAuditData(customer) current := *customer failureCustomer = ¤t if nickname != "" { customer.Nickname = nickname } if avatarURL != "" { customer.AvatarURL = avatarURL } if err := tx.Save(customer).Error; err != nil { return errors.Wrap(errors.CodeInternalError, err, "更新个人资料失败") } return s.accessAudit.WriteAccessChange(ctx, tx, personalProfileAudit(customer, beforeData, constants.AuditResultSuccess)) }) if err != nil { if !loaded { s.logger.Error("查询个人客户失败", zap.Uint("customer_id", customerID), zap.Error(err)) return err } s.logger.Error("更新个人资料失败", zap.Uint("customer_id", customerID), zap.Error(err), ) failure := personalProfileAudit(failureCustomer, beforeData, personalAuditFailureResult(err)) failure.Summary = "更新个人资料失败" failure.SubjectSummary = "个人资料更新失败" failure.SubjectData = nil accessauditapp.RecordFailure(ctx, s.db, s.accessAudit, failure, err) return err } s.logger.Info("更新个人资料成功", zap.Uint("customer_id", customerID), ) return nil } func personalProfileAudit(customer *model.PersonalCustomer, beforeData map[string]any, result string) accessauditapp.ChangeAudit { return accessauditapp.ChangeAudit{ ActionCode: constants.AuditActionPersonalCustomerProfileUpdated, Summary: "更新个人资料", Result: result, OperatorID: customer.ID, ActorKind: constants.AuditActorPersonalCustomer, ActorName: customer.Nickname, Source: constants.AuditSourcePersonalAPI, ScopeType: constants.AuditScopePersonalCustomer, PersonalCustomer: customer, BeforeData: beforeData, AfterData: personalProfileAuditData(customer), SubjectVisibility: constants.AuditSubjectDetail, SubjectSummary: "个人资料已更新", SubjectData: map[string]any{"nickname": customer.Nickname, "avatar_url": customer.AvatarURL}, } } func personalProfileAuditData(customer *model.PersonalCustomer) map[string]any { return map[string]any{"nickname": customer.Nickname, "avatar_url": customer.AvatarURL} } func personalAuditFailureResult(err error) string { var appErr *errors.AppError if stderrors.As(err, &appErr) { switch appErr.Code { case errors.CodeForbidden, errors.CodeInvalidParam, errors.CodeNotFound, errors.CodeCustomerNotFound: return constants.AuditResultDenied } } return constants.AuditResultFailed } // GetProfileWithPhone 获取个人资料(包含主手机号) func (s *Service) GetProfileWithPhone(ctx context.Context, customerID uint) (*model.PersonalCustomer, string, error) { // 获取客户信息 customer, err := s.store.GetByID(ctx, customerID) if err != nil { s.logger.Error("查询个人客户失败", zap.Uint("customer_id", customerID), zap.Error(err), ) return nil, "", errors.Wrap(errors.CodeInternalError, err, "查询个人客户失败") } // 获取主手机号 phone := "" primaryPhone, err := s.phoneStore.GetPrimaryPhone(ctx, customerID) if err != nil { if err != gorm.ErrRecordNotFound { s.logger.Error("查询主手机号失败", zap.Uint("customer_id", customerID), zap.Error(err), ) // 不返回错误,继续返回客户信息(手机号为空) } } else { phone = primaryPhone.Phone } return customer, phone, nil }