Files
backend/internal/service/grade_sync_service.go
lan 2f2bbc646e
All checks were successful
Build Backend / build (push) Successful in 2m18s
Build Backend / build-docker (push) Successful in 1m20s
feat(api): implement exam and grade synchronization modules
Add new functionality for managing and synchronizing academic grades and exam schedules. This includes new models, repositories, services, and HTTP handlers, along with updated gRPC definitions and dependency injection configuration.

Additionally, improve the reliability of unread message count tracking by implementing an atomic Lua script for Redis operations in the conversation cache.
2026-05-12 01:28:18 +08:00

156 lines
4.3 KiB
Go

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
}