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.
This commit is contained in:
156
internal/service/grade_sync_service.go
Normal file
156
internal/service/grade_sync_service.go
Normal file
@@ -0,0 +1,156 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user