292 lines
7.4 KiB
Go
292 lines
7.4 KiB
Go
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))
|
||
}
|