Files
goalfylearning-admin/internal/services/invite_code_service.go

292 lines
7.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package services
import (
"fmt"
"math/rand"
"strings"
"time"
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/storage"
)
type InviteCodeService interface {
Create(req *models.InviteCodeCreateRequest) ([]*models.InviteCode, error)
GetByID(id uint) (*models.InviteCode, error)
GetByCode(code string) (*models.InviteCode, error)
List(req *models.InviteCodeListRequest) (*models.InviteCodeListResponse, error)
Update(id uint, req *models.InviteCodeUpdateRequest) (*models.InviteCode, error)
Delete(id uint) error
GetStatistics() (*models.InviteCodeStatistics, error)
MarkAsUsed(code string) error
ValidateInviteCode(code string) error
}
type inviteCodeService struct {
storage storage.InviteCodeStorage
emailService *EmailService
}
func NewInviteCodeService(storage storage.InviteCodeStorage) InviteCodeService {
return &inviteCodeService{
storage: storage,
emailService: NewEmailService(),
}
}
func (s *inviteCodeService) Create(req *models.InviteCodeCreateRequest) ([]*models.InviteCode, error) {
if req == nil {
return nil, fmt.Errorf("请求参数不能为空")
}
// 设置过期时间
var expiresAt *time.Time
if req.ExpiresAt != nil {
expiresAt = req.ExpiresAt
} else {
// 默认30天后过期
defaultExpiry := time.Now().AddDate(0, 0, 30)
expiresAt = &defaultExpiry
}
// 设置用户等级ID如果未提供则默认为1
var userLevelID *uint
if req.UserLevelID != nil {
userLevelID = req.UserLevelID
} else {
defaultUserLevelID := uint(1)
userLevelID = &defaultUserLevelID
}
// 如果提供了邮箱列表,为每个邮箱创建一个邀请码
if len(req.Emails) > 0 {
// 对邮箱列表进行去重处理
emailMap := make(map[string]bool)
uniqueEmails := make([]string, 0, len(req.Emails))
for _, email := range req.Emails {
// 去除空格并转换为小写进行去重
email = strings.TrimSpace(strings.ToLower(email))
if email == "" {
continue
}
// 如果邮箱已存在,跳过
if emailMap[email] {
continue
}
emailMap[email] = true
uniqueEmails = append(uniqueEmails, email)
}
var inviteCodes []*models.InviteCode
for _, email := range uniqueEmails {
// 生成唯一邀请码
var code string
for {
code = s.generateCode()
existingCode, err := s.storage.GetByCode(code)
if err != nil || existingCode == nil {
break // 邀请码不存在,可以使用
}
}
inviteCode := &models.InviteCode{
Code: code,
ClientID: req.ClientID,
Email: email,
UserLevelID: userLevelID,
ExpiresAt: expiresAt,
CreatedAt: time.Now(),
}
if err := s.storage.Create(inviteCode); err != nil {
return nil, fmt.Errorf("创建邀请码失败: %w", err)
}
// 发送邀请邮件(如果提供了邮箱)
// 使用 goroutine 异步发送,避免阻塞
if email != "" && s.emailService != nil {
go func(emailAddr, code string) {
// 使用默认语言 "zh" 发送邮件
if err := s.emailService.SendInviteCodeApprovalEmail(emailAddr, code, "zh", expiresAt); err != nil {
// 记录错误但不影响创建流程,邮件发送失败不影响邀请码创建
fmt.Printf("[InviteCodeService] 发送邀请邮件失败: %v (邀请码: %s, 邮箱: %s)\n", err, code, emailAddr)
} else {
fmt.Printf("[InviteCodeService] 邀请邮件发送成功 (邀请码: %s, 邮箱: %s)\n", code, emailAddr)
}
}(email, inviteCode.Code)
}
inviteCodes = append(inviteCodes, inviteCode)
}
if len(inviteCodes) == 0 {
return nil, fmt.Errorf("没有有效的邮箱地址")
}
return inviteCodes, nil
}
// 如果没有提供邮箱,只创建一个邀请码(向后兼容)
var code string
for {
code = s.generateCode()
existingCode, err := s.storage.GetByCode(code)
if err != nil || existingCode == nil {
break // 邀请码不存在,可以使用
}
}
inviteCode := &models.InviteCode{
Code: code,
ClientID: req.ClientID,
Email: "",
UserLevelID: userLevelID,
ExpiresAt: expiresAt,
CreatedAt: time.Now(),
}
if err := s.storage.Create(inviteCode); err != nil {
return nil, fmt.Errorf("创建邀请码失败: %w", err)
}
return []*models.InviteCode{inviteCode}, nil
}
func (s *inviteCodeService) GetByID(id uint) (*models.InviteCode, error) {
return s.storage.GetByID(id)
}
func (s *inviteCodeService) GetByCode(code string) (*models.InviteCode, error) {
return s.storage.GetByCode(code)
}
func (s *inviteCodeService) List(req *models.InviteCodeListRequest) (*models.InviteCodeListResponse, error) {
inviteCodes, total, err := s.storage.List(req)
if err != nil {
return nil, fmt.Errorf("获取邀请码列表失败: %w", err)
}
return &models.InviteCodeListResponse{
List: inviteCodes,
Total: total,
}, nil
}
func (s *inviteCodeService) Update(id uint, req *models.InviteCodeUpdateRequest) (*models.InviteCode, error) {
// 获取现有邀请码
inviteCode, err := s.storage.GetByID(id)
if err != nil {
return nil, fmt.Errorf("邀请码不存在")
}
// 检查是否已被使用
if inviteCode.IsUsed {
return nil, fmt.Errorf("已使用的邀请码无法修改")
}
// 更新客户端ID
if req != nil && req.ClientID != "" {
inviteCode.ClientID = req.ClientID
}
// 更新邮箱
if req != nil && req.Email != "" {
inviteCode.Email = req.Email
}
// 更新用户等级ID
if req != nil && req.UserLevelID != nil {
inviteCode.UserLevelID = req.UserLevelID
}
// 更新过期时间
if req != nil && req.ExpiresAt != nil {
inviteCode.ExpiresAt = req.ExpiresAt
}
// 保存更新
if err := s.storage.Update(inviteCode); err != nil {
return nil, fmt.Errorf("更新邀请码失败: %w", err)
}
return inviteCode, nil
}
func (s *inviteCodeService) Delete(id uint) error {
_, err := s.storage.GetByID(id)
if err != nil {
return fmt.Errorf("邀请码不存在")
}
if err := s.storage.Delete(id); err != nil {
return fmt.Errorf("删除邀请码失败: %w", err)
}
return nil
}
func (s *inviteCodeService) GetStatistics() (*models.InviteCodeStatistics, error) {
return s.storage.GetStatistics()
}
func (s *inviteCodeService) MarkAsUsed(code string) error {
inviteCode, err := s.storage.GetByCode(code)
if err != nil {
return fmt.Errorf("邀请码不存在")
}
if inviteCode.IsUsed {
return fmt.Errorf("邀请码已被使用")
}
// 检查是否过期
if s.storage.IsExpired(inviteCode) {
return fmt.Errorf("邀请码已过期")
}
inviteCode.IsUsed = true
if err := s.storage.Update(inviteCode); err != nil {
return fmt.Errorf("标记邀请码为已使用失败: %w", err)
}
return nil
}
// ValidateInviteCode 验证邀请码是否有效(未使用且未过期)
func (s *inviteCodeService) ValidateInviteCode(code string) error {
inviteCode, err := s.storage.GetByCode(code)
if err != nil {
return fmt.Errorf("邀请码不存在")
}
if inviteCode.IsUsed {
return fmt.Errorf("邀请码已被使用")
}
// 检查是否过期
if s.storage.IsExpired(inviteCode) {
return fmt.Errorf("邀请码已过期")
}
return nil
}
func (s *inviteCodeService) generateCode() string {
chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
// 生成8位随机字符
part1 := make([]byte, 8)
for i := range part1 {
part1[i] = chars[rand.Intn(len(chars))]
}
// 生成4位随机字符
part2 := make([]byte, 4)
for i := range part2 {
part2[i] = chars[rand.Intn(len(chars))]
}
return fmt.Sprintf("GFY-%s-%s", string(part1), string(part2))
}