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,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
}