package service import ( "context" "encoding/json" "fmt" "with_you/internal/grpc/runner" "with_you/internal/model" "with_you/internal/repository" pb "with_you/proto/runner" "github.com/google/uuid" "go.uber.org/zap" ) type SyncGradesRequest struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` Semester string `json:"semester"` } type SyncGradesResult struct { Success bool `json:"success"` Message string `json:"message"` SyncedCount int `json:"synced_count"` ErrorMessage string `json:"error_message,omitempty"` } type GradeSyncService interface { SyncUserGrades(ctx context.Context, userID string, req *SyncGradesRequest) (*SyncGradesResult, error) } type gradeSyncService struct { taskManager *runner.TaskManager gradeRepo repository.GradeRepository logger *zap.Logger } func NewGradeSyncService( taskManager *runner.TaskManager, gradeRepo repository.GradeRepository, logger *zap.Logger, ) GradeSyncService { return &gradeSyncService{ taskManager: taskManager, gradeRepo: gradeRepo, logger: logger, } } func (s *gradeSyncService) SyncUserGrades(ctx context.Context, userID string, req *SyncGradesRequest) (*SyncGradesResult, error) { payload := &pb.GetGradesPayload{ Username: req.Username, Password: req.Password, Semester: req.Semester, } taskResult, err := s.taskManager.SubmitTaskSync(ctx, pb.TaskType_TASK_TYPE_GET_GRADES, payload) if err != nil { s.logger.Error("failed to submit grades sync task", zap.String("user_id", userID), zap.Error(err)) return nil, fmt.Errorf("failed to submit task: %w", err) } if taskResult.Status != pb.TaskStatus_TASK_STATUS_SUCCESS { return &SyncGradesResult{ Success: false, Message: "成绩获取失败", ErrorMessage: taskResult.ErrorMessage, }, nil } var resultData pb.GradesResultData if err := json.Unmarshal(taskResult.Data, &resultData); err != nil { s.logger.Error("failed to parse grades result", zap.String("user_id", userID), zap.Error(err)) return nil, fmt.Errorf("failed to parse result: %w", err) } grades := s.convertGrades(userID, &resultData) if err := s.saveGrades(ctx, userID, resultData.Semester, grades); err != nil { s.logger.Error("failed to save grades", zap.String("user_id", userID), zap.Error(err)) return nil, fmt.Errorf("failed to save grades: %w", err) } if resultData.GpaSummary != nil { summary := s.convertGpaSummary(userID, resultData.Semester, resultData.GpaSummary) if err := s.gradeRepo.SaveGpaSummary(ctx, summary); err != nil { s.logger.Warn("failed to save gpa summary", zap.String("user_id", userID), zap.Error(err)) } } s.logger.Info("grades sync completed", zap.String("user_id", userID), zap.Int("grade_count", len(grades))) return &SyncGradesResult{ Success: true, Message: "成绩同步成功", SyncedCount: len(grades), }, nil } func (s *gradeSyncService) convertGrades(userID string, data *pb.GradesResultData) []*model.Grade { grades := make([]*model.Grade, 0, len(data.Grades)) for _, g := range data.Grades { grades = append(grades, &model.Grade{ ID: uuid.New().String(), UserID: userID, Term: g.Term, CourseCode: g.CourseCode, CourseName: g.CourseName, Nature: g.Nature, Category: g.Category, Credit: g.Credit, Score: g.Score, GradePoint: g.GradePoint, IsExam: g.IsExam, }) } return grades } func (s *gradeSyncService) convertGpaSummary(userID, semester string, gpa *pb.GpaSummary) *model.GpaSummary { return &model.GpaSummary{ ID: uuid.New().String(), UserID: userID, Semester: semester, AverageGpa: gpa.AverageGpa, Rank: gpa.Rank, ExamTotalGpa: gpa.ExamTotalGpa, ExamTotalCredits: gpa.ExamTotalCredits, FailCredits: gpa.FailCredits, Semesters: gpa.Semesters, } } func (s *gradeSyncService) saveGrades(ctx context.Context, userID, semester string, grades []*model.Grade) error { if err := s.gradeRepo.DeleteByUserAndSemester(ctx, userID, semester); err != nil { return fmt.Errorf("failed to delete old grades: %w", err) } if len(grades) == 0 { return nil } if err := s.gradeRepo.BatchCreate(ctx, grades); err != nil { return fmt.Errorf("failed to create grades: %w", err) } return nil }