feat():learning后台管理项目初始化

This commit is contained in:
yuj
2025-12-04 16:23:46 +08:00
parent 39886d50d2
commit 88e048f4d1
154 changed files with 28966 additions and 6 deletions

View File

@@ -0,0 +1,291 @@
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))
}