package services import ( "errors" "fmt" "strings" "time" "github.com/google/uuid" "gorm.io/gorm" "goalfymax-admin/internal/models" ) type InviteCodeApplicationService struct { db *gorm.DB emailService *EmailService } // NewInviteCodeApplicationService 创建邀请码申请服务 func NewInviteCodeApplicationService(db *gorm.DB) *InviteCodeApplicationService { return &InviteCodeApplicationService{ db: db, emailService: NewEmailService(), } } // SubmitApplication 提交邀请码申请(官网使用) func (s *InviteCodeApplicationService) SubmitApplication(req *models.InviteCodeApplicationCreateRequest) (*models.InviteCodeApplication, error) { // 检查是否已经有待处理或已通过的申请 var existingApp models.InviteCodeApplication err := s.db.Where("email = ? AND status IN (?, ?)", req.Email, models.ApplicationStatusPending, models.ApplicationStatusApproved, ).First(&existingApp).Error if err == nil { // 如果找到了记录,说明已经有申请 if existingApp.Status == models.ApplicationStatusPending { return nil, errors.New("您已经提交过申请,请等待审核") } if existingApp.Status == models.ApplicationStatusApproved { return nil, errors.New("您的申请已通过,请检查邮箱") } } // 设置默认语言 language := req.Language if language == "" { language = "zh" } // 创建新的申请 application := &models.InviteCodeApplication{ Email: req.Email, Reason: req.Reason, Language: language, Status: models.ApplicationStatusPending, CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := s.db.Create(application).Error; err != nil { return nil, fmt.Errorf("创建申请失败: %w", err) } return application, nil } // GetApplicationList 获取申请列表(后台管理使用) func (s *InviteCodeApplicationService) GetApplicationList(req *models.InviteCodeApplicationListRequest) (*models.InviteCodeApplicationListResponse, error) { var applications []models.InviteCodeApplication var total int64 query := s.db.Model(&models.InviteCodeApplication{}) // 添加过滤条件 if req.Email != "" { query = query.Where("email LIKE ?", "%"+req.Email+"%") } if req.Status != "" { query = query.Where("status = ?", req.Status) } if req.StartTime != "" { query = query.Where("created_at >= ?", req.StartTime) } if req.EndTime != "" { endTime, _ := time.Parse("2006-01-02", req.EndTime) query = query.Where("created_at < ?", endTime.AddDate(0, 0, 1)) } // 获取总数 query.Count(&total) // 分页查询 offset := (req.Page - 1) * req.Size err := query. Preload("InviteCode"). Order("created_at DESC"). Limit(req.Size). Offset(offset). Find(&applications).Error if err != nil { return nil, fmt.Errorf("查询申请列表失败: %w", err) } return &models.InviteCodeApplicationListResponse{ List: applications, Total: total, }, nil } // GetStatistics 获取申请统计 func (s *InviteCodeApplicationService) GetStatistics() (*models.InviteCodeApplicationStatistics, error) { var stats models.InviteCodeApplicationStatistics // 待处理数量 var pendingCount int64 s.db.Model(&models.InviteCodeApplication{}). Where("status = ?", models.ApplicationStatusPending). Count(&pendingCount) stats.TotalPending = int(pendingCount) // 已通过数量 var approvedCount int64 s.db.Model(&models.InviteCodeApplication{}). Where("status = ?", models.ApplicationStatusApproved). Count(&approvedCount) stats.TotalApproved = int(approvedCount) // 已拒绝数量 var rejectedCount int64 s.db.Model(&models.InviteCodeApplication{}). Where("status = ?", models.ApplicationStatusRejected). Count(&rejectedCount) stats.TotalRejected = int(rejectedCount) // 今日申请数量 var todayCount int64 todayStart := time.Now().Truncate(24 * time.Hour) s.db.Model(&models.InviteCodeApplication{}). Where("created_at >= ?", todayStart). Count(&todayCount) stats.TodayApplied = int(todayCount) return &stats, nil } // ApproveApplication 审批通过申请 func (s *InviteCodeApplicationService) ApproveApplication(req *models.InviteCodeApplicationApproveRequest, approvedBy string) error { return s.db.Transaction(func(tx *gorm.DB) error { // 获取申请信息 var application models.InviteCodeApplication if err := tx.First(&application, req.ApplicationID).Error; err != nil { return fmt.Errorf("申请不存在: %w", err) } if application.Status != models.ApplicationStatusPending { return errors.New("申请状态不是待处理,无法审批") } // 设置有效期,默认3天(72小时) validDays := req.ValidDays if validDays <= 0 { validDays = 3 } expiresAt := time.Now().AddDate(0, 0, validDays) // 创建邀请码 inviteCode := &models.InviteCode{ Code: s.generateInviteCode(), IsUsed: false, ClientID: "xRpT9mgNpt2YvoY9z4FToA", // 默认为正式版客户端 ExpiresAt: &expiresAt, CreatedAt: time.Now(), } if err := tx.Create(inviteCode).Error; err != nil { return fmt.Errorf("创建邀请码失败: %w", err) } // 更新申请状态 now := time.Now() updates := map[string]interface{}{ "status": models.ApplicationStatusApproved, "invite_code_id": inviteCode.ID, "approved_at": now, "approved_by": approvedBy, "updated_at": now, } if err := tx.Model(&application).Updates(updates).Error; err != nil { return fmt.Errorf("更新申请状态失败: %w", err) } // 发送邮件(异步,不影响事务) go func() { // 获取语言设置,默认为中文 lang := application.Language if lang == "" { lang = "zh" } if err := s.emailService.SendInviteCodeApprovalEmail(application.Email, inviteCode.Code, lang, &expiresAt); err != nil { // 记录邮件发送失败,但不回滚事务 fmt.Printf("发送审批通过邮件失败: %v\n", err) } else { // 更新邮件发送时间 emailSentAt := time.Now() tx.Model(&application).Update("email_sent_at", emailSentAt) } }() return nil }) } // RejectApplication 审批拒绝申请 func (s *InviteCodeApplicationService) RejectApplication(req *models.InviteCodeApplicationRejectRequest, approvedBy string) error { return s.db.Transaction(func(tx *gorm.DB) error { // 获取申请信息 var application models.InviteCodeApplication if err := tx.First(&application, req.ApplicationID).Error; err != nil { return fmt.Errorf("申请不存在: %w", err) } if application.Status != models.ApplicationStatusPending { return errors.New("申请状态不是待处理,无法审批") } // 更新申请状态 now := time.Now() updates := map[string]interface{}{ "status": models.ApplicationStatusRejected, "reject_reason": req.RejectReason, "approved_at": now, "approved_by": approvedBy, "updated_at": now, } if err := tx.Model(&application).Updates(updates).Error; err != nil { return fmt.Errorf("更新申请状态失败: %w", err) } // 拒绝操作不发送邮件通知 return nil }) } // BatchApproveApplications 批量审批通过 func (s *InviteCodeApplicationService) BatchApproveApplications(req *models.InviteCodeApplicationBatchApproveRequest, approvedBy string) error { for _, appID := range req.ApplicationIDs { approveReq := &models.InviteCodeApplicationApproveRequest{ ApplicationID: appID, ValidDays: req.ValidDays, } if err := s.ApproveApplication(approveReq, approvedBy); err != nil { // 记录错误但继续处理其他申请 fmt.Printf("审批申请 %d 失败: %v\n", appID, err) } } return nil } // BatchRejectApplications 批量审批拒绝 func (s *InviteCodeApplicationService) BatchRejectApplications(req *models.InviteCodeApplicationBatchRejectRequest, approvedBy string) error { for _, appID := range req.ApplicationIDs { rejectReq := &models.InviteCodeApplicationRejectRequest{ ApplicationID: appID, RejectReason: req.RejectReason, } if err := s.RejectApplication(rejectReq, approvedBy); err != nil { // 记录错误但继续处理其他申请 fmt.Printf("拒绝申请 %d 失败: %v\n", appID, err) } } return nil } // generateInviteCode 生成唯一的邀请码,格式:GFY-XXXXXXXX-XXXX func (s *InviteCodeApplicationService) generateInviteCode() string { // 生成UUID并转换为大写 uuidStr := uuid.New().String() // 移除UUID中的连字符并转为大写 cleaned := "" for _, c := range uuidStr { if c != '-' { cleaned += string(c) } } cleaned = strings.ToUpper(cleaned) // 截取需要的部分:GFY-8位-4位 // 格式:GFY-XXXXXXXX-XXXX part1 := cleaned[0:8] // 8位 part2 := cleaned[8:12] // 4位 return fmt.Sprintf("GFY-%s-%s", part1, part2) } // GetPendingApplicationsCount 获取待处理申请数量 func (s *InviteCodeApplicationService) GetPendingApplicationsCount() (int64, error) { var count int64 err := s.db.Model(&models.InviteCodeApplication{}). Where("status = ?", models.ApplicationStatusPending). Count(&count).Error return count, err }