Files
backend/internal/service/empty_classroom_sync_service.go
lan d9aa4b46c3
All checks were successful
Build Backend / build (push) Successful in 4m55s
Build Backend / build-docker (push) Successful in 10m34s
refactor(server): decouple services and improve architecture
- 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.
2026-06-15 03:41:59 +08:00

142 lines
4.4 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 SyncEmptyClassroomsRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
Semester string `json:"semester"`
WeekStart string `json:"week_start"`
WeekEnd string `json:"week_end"`
CampusCode string `json:"campus_code"`
}
type SyncEmptyClassroomsResult struct {
Success bool `json:"success"`
Message string `json:"message"`
SyncedCount int `json:"synced_count"`
ErrorMessage string `json:"error_message,omitempty"`
}
type EmptyClassroomSyncService interface {
SyncUserEmptyClassrooms(ctx context.Context, userID string, req *SyncEmptyClassroomsRequest) (*SyncEmptyClassroomsResult, error)
ListClassrooms(userID, semester string) ([]*model.EmptyClassroom, error)
}
type emptyClassroomSyncService struct {
taskManager *runner.TaskManager
classroomRepo repository.EmptyClassroomRepository
logger *zap.Logger
}
func NewEmptyClassroomSyncService(
taskManager *runner.TaskManager,
classroomRepo repository.EmptyClassroomRepository,
logger *zap.Logger,
) EmptyClassroomSyncService {
return &emptyClassroomSyncService{
taskManager: taskManager,
classroomRepo: classroomRepo,
logger: logger,
}
}
func (s *emptyClassroomSyncService) SyncUserEmptyClassrooms(ctx context.Context, userID string, req *SyncEmptyClassroomsRequest) (*SyncEmptyClassroomsResult, error) {
payload := &pb.GetEmptyClassroomPayload{
Username: req.Username,
Password: req.Password,
Semester: req.Semester,
WeekStart: req.WeekStart,
WeekEnd: req.WeekEnd,
CampusCode: req.CampusCode,
}
taskResult, err := s.taskManager.SubmitTaskSync(ctx, pb.TaskType_TASK_TYPE_GET_EMPTY_CLASSROOM, payload)
if err != nil {
s.logger.Error("failed to submit empty classroom 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 &SyncEmptyClassroomsResult{
Success: false,
Message: "空教室查询失败",
ErrorMessage: taskResult.ErrorMessage,
}, nil
}
var resultData pb.EmptyClassroomResultData
if err := json.Unmarshal(taskResult.Data, &resultData); err != nil {
s.logger.Error("failed to parse empty classroom result",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("failed to parse result: %w", err)
}
classrooms := s.convertClassrooms(userID, &resultData)
if err := s.saveClassrooms(ctx, userID, resultData.Semester, classrooms); err != nil {
s.logger.Error("failed to save empty classrooms",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("failed to save classrooms: %w", err)
}
s.logger.Info("empty classrooms sync completed",
zap.String("user_id", userID),
zap.Int("classroom_count", len(classrooms)))
return &SyncEmptyClassroomsResult{
Success: true,
Message: "空教室查询成功",
SyncedCount: len(classrooms),
}, nil
}
func (s *emptyClassroomSyncService) convertClassrooms(userID string, data *pb.EmptyClassroomResultData) []*model.EmptyClassroom {
classrooms := make([]*model.EmptyClassroom, 0, len(data.Classrooms))
for _, c := range data.Classrooms {
classrooms = append(classrooms, &model.EmptyClassroom{
ID: uuid.New().String(),
UserID: userID,
Semester: data.Semester,
Classroom: c.Classroom,
Weekday: c.Weekday,
Periods: c.Periods,
Weeks: c.Weeks,
})
}
return classrooms
}
func (s *emptyClassroomSyncService) saveClassrooms(ctx context.Context, userID, semester string, classrooms []*model.EmptyClassroom) error {
if err := s.classroomRepo.DeleteByUserAndSemester(ctx, userID, semester); err != nil {
return fmt.Errorf("failed to delete old classrooms: %w", err)
}
if len(classrooms) == 0 {
return nil
}
if err := s.classroomRepo.BatchCreate(ctx, classrooms); err != nil {
return fmt.Errorf("failed to create classrooms: %w", err)
}
return nil
}
// ListClassrooms 查询用户某学期的空教室。
func (s *emptyClassroomSyncService) ListClassrooms(userID, semester string) ([]*model.EmptyClassroom, error) {
return s.classroomRepo.ListByUserAndSemester(userID, semester)
}