311 lines
8.9 KiB
Go
311 lines
8.9 KiB
Go
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
|
||
}
|