feat():learning后台管理项目初始化
This commit is contained in:
310
internal/services/invite_code_application_service.go
Normal file
310
internal/services/invite_code_application_service.go
Normal file
@@ -0,0 +1,310 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user