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.
156 lines
4.3 KiB
Go
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
|
|
} |