2026-05-12 01:28:18 +08:00
|
|
|
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)
|
2026-06-07 00:11:47 +08:00
|
|
|
if err := s.saveGrades(ctx, userID, grades); err != nil {
|
2026-05-12 01:28:18 +08:00
|
|
|
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 {
|
2026-06-07 00:11:47 +08:00
|
|
|
if err := s.gradeRepo.DeleteGpaSummaryByUser(ctx, userID); err != nil {
|
|
|
|
|
s.logger.Warn("failed to delete old gpa summary",
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
zap.Error(err))
|
|
|
|
|
}
|
|
|
|
|
summary := s.convertGpaSummary(userID, resultData.GpaSummary)
|
2026-05-12 01:28:18 +08:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-07 00:11:47 +08:00
|
|
|
func (s *gradeSyncService) convertGpaSummary(userID string, gpa *pb.GpaSummary) *model.GpaSummary {
|
2026-05-12 01:28:18 +08:00
|
|
|
return &model.GpaSummary{
|
|
|
|
|
ID: uuid.New().String(),
|
|
|
|
|
UserID: userID,
|
2026-06-07 00:11:47 +08:00
|
|
|
Semester: gpa.Semesters,
|
2026-05-12 01:28:18 +08:00
|
|
|
AverageGpa: gpa.AverageGpa,
|
|
|
|
|
Rank: gpa.Rank,
|
|
|
|
|
ExamTotalGpa: gpa.ExamTotalGpa,
|
|
|
|
|
ExamTotalCredits: gpa.ExamTotalCredits,
|
|
|
|
|
FailCredits: gpa.FailCredits,
|
|
|
|
|
Semesters: gpa.Semesters,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-07 00:11:47 +08:00
|
|
|
func (s *gradeSyncService) saveGrades(ctx context.Context, userID string, grades []*model.Grade) error {
|
|
|
|
|
if err := s.gradeRepo.DeleteByUser(ctx, userID); err != nil {
|
2026-05-12 01:28:18 +08:00
|
|
|
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
|
|
|
|
|
}
|