- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion. - Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects. - Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management. - Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering. - Improve database initialization by moving it from `internal/model` to `internal/database`. - Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool. - Enhance error handling and security by implementing fail-fast checks for encryption key length during startup. - Clean up unused code, including the `avatar` package and several unused DTOs.
138 lines
3.7 KiB
Go
138 lines
3.7 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 SyncExamsRequest struct {
|
|
Username string `json:"username" binding:"required"`
|
|
Password string `json:"password" binding:"required"`
|
|
Semester string `json:"semester"`
|
|
}
|
|
|
|
type SyncExamsResult struct {
|
|
Success bool `json:"success"`
|
|
Message string `json:"message"`
|
|
SyncedCount int `json:"synced_count"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
}
|
|
|
|
type ExamSyncService interface {
|
|
SyncUserExams(ctx context.Context, userID string, req *SyncExamsRequest) (*SyncExamsResult, error)
|
|
ListExams(userID, semester string) ([]*model.Exam, error)
|
|
}
|
|
|
|
type examSyncService struct {
|
|
taskManager *runner.TaskManager
|
|
examRepo repository.ExamRepository
|
|
logger *zap.Logger
|
|
}
|
|
|
|
func NewExamSyncService(
|
|
taskManager *runner.TaskManager,
|
|
examRepo repository.ExamRepository,
|
|
logger *zap.Logger,
|
|
) ExamSyncService {
|
|
return &examSyncService{
|
|
taskManager: taskManager,
|
|
examRepo: examRepo,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
func (s *examSyncService) SyncUserExams(ctx context.Context, userID string, req *SyncExamsRequest) (*SyncExamsResult, error) {
|
|
payload := &pb.GetExamsPayload{
|
|
Username: req.Username,
|
|
Password: req.Password,
|
|
Semester: req.Semester,
|
|
}
|
|
|
|
taskResult, err := s.taskManager.SubmitTaskSync(ctx, pb.TaskType_TASK_TYPE_GET_EXAMS, payload)
|
|
if err != nil {
|
|
s.logger.Error("failed to submit exams 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 &SyncExamsResult{
|
|
Success: false,
|
|
Message: "考试安排获取失败",
|
|
ErrorMessage: taskResult.ErrorMessage,
|
|
}, nil
|
|
}
|
|
|
|
var resultData pb.ExamsResultData
|
|
if err := json.Unmarshal(taskResult.Data, &resultData); err != nil {
|
|
s.logger.Error("failed to parse exams result",
|
|
zap.String("user_id", userID),
|
|
zap.Error(err))
|
|
return nil, fmt.Errorf("failed to parse result: %w", err)
|
|
}
|
|
|
|
exams := s.convertExams(userID, &resultData)
|
|
if err := s.saveExams(ctx, userID, resultData.Semester, exams); err != nil {
|
|
s.logger.Error("failed to save exams",
|
|
zap.String("user_id", userID),
|
|
zap.Error(err))
|
|
return nil, fmt.Errorf("failed to save exams: %w", err)
|
|
}
|
|
|
|
s.logger.Info("exams sync completed",
|
|
zap.String("user_id", userID),
|
|
zap.Int("exam_count", len(exams)))
|
|
|
|
return &SyncExamsResult{
|
|
Success: true,
|
|
Message: "考试安排同步成功",
|
|
SyncedCount: len(exams),
|
|
}, nil
|
|
}
|
|
|
|
func (s *examSyncService) convertExams(userID string, data *pb.ExamsResultData) []*model.Exam {
|
|
exams := make([]*model.Exam, 0, len(data.Exams))
|
|
for _, e := range data.Exams {
|
|
exams = append(exams, &model.Exam{
|
|
ID: uuid.New().String(),
|
|
UserID: userID,
|
|
Semester: data.Semester,
|
|
CourseName: e.CourseName,
|
|
ExamType: e.ExamType,
|
|
Date: e.Date,
|
|
Time: e.Time,
|
|
Week: e.Week,
|
|
Weekday: e.Weekday,
|
|
})
|
|
}
|
|
return exams
|
|
}
|
|
|
|
func (s *examSyncService) saveExams(ctx context.Context, userID, semester string, exams []*model.Exam) error {
|
|
if err := s.examRepo.DeleteByUserAndSemester(ctx, userID, semester); err != nil {
|
|
return fmt.Errorf("failed to delete old exams: %w", err)
|
|
}
|
|
if len(exams) == 0 {
|
|
return nil
|
|
}
|
|
if err := s.examRepo.BatchCreate(ctx, exams); err != nil {
|
|
return fmt.Errorf("failed to create exams: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListExams 查询用户某学期的考试安排。
|
|
func (s *examSyncService) ListExams(userID, semester string) ([]*model.Exam, error) {
|
|
return s.examRepo.ListByUserAndSemester(userID, semester)
|
|
}
|