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" ) // SyncScheduleRequest 同步课表请求 type SyncScheduleRequest struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` Semester string `json:"semester"` // 可选,默认当前学期 } // SyncScheduleResult 同步课表结果 type SyncScheduleResult struct { Success bool `json:"success"` Message string `json:"message"` SyncedCount int `json:"synced_count"` ErrorMessage string `json:"error_message,omitempty"` } // ScheduleSyncService 课表同步服务接口 type ScheduleSyncService interface { SyncUserSchedule(ctx context.Context, userID string, req *SyncScheduleRequest) (*SyncScheduleResult, error) } type scheduleSyncService struct { taskManager *runner.TaskManager scheduleRepo repository.ScheduleRepository logger *zap.Logger } // NewScheduleSyncService 创建课表同步服务 func NewScheduleSyncService( taskManager *runner.TaskManager, scheduleRepo repository.ScheduleRepository, logger *zap.Logger, ) ScheduleSyncService { return &scheduleSyncService{ taskManager: taskManager, scheduleRepo: scheduleRepo, logger: logger, } } // SyncUserSchedule 同步用户课表 func (s *scheduleSyncService) SyncUserSchedule(ctx context.Context, userID string, req *SyncScheduleRequest) (*SyncScheduleResult, error) { // 构建任务载荷 payload := &pb.GetSchedulePayload{ Username: req.Username, Password: req.Password, Semester: req.Semester, } // 同步提交任务并等待结果 taskResult, err := s.taskManager.SubmitTaskSync(ctx, pb.TaskType_TASK_TYPE_GET_SCHEDULE, payload) if err != nil { s.logger.Error("failed to submit 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 &SyncScheduleResult{ Success: false, Message: "课表获取失败", ErrorMessage: taskResult.ErrorMessage, }, nil } // 解析结果数据 var resultData pb.ScheduleResultData if err := json.Unmarshal(taskResult.Data, &resultData); err != nil { s.logger.Error("failed to parse schedule result", zap.String("user_id", userID), zap.Error(err)) return nil, fmt.Errorf("failed to parse result: %w", err) } // 转换并保存课表 courses := s.convertCourses(&resultData) if err := s.saveCourses(ctx, userID, courses); err != nil { s.logger.Error("failed to save courses", zap.String("user_id", userID), zap.Error(err)) return nil, fmt.Errorf("failed to save courses: %w", err) } s.logger.Info("schedule sync completed", zap.String("user_id", userID), zap.Int("course_count", len(courses))) return &SyncScheduleResult{ Success: true, Message: "课表同步成功", SyncedCount: len(courses), }, nil } // convertCourses 转换 converter 课表数据为 backend 模型 func (s *scheduleSyncService) convertCourses(data *pb.ScheduleResultData) []*model.ScheduleCourse { var courses []*model.ScheduleCourse for _, c := range data.Courses { for _, st := range c.Schedule { dayOfWeek := 0 if _, err := fmt.Sscanf(st.Day, "%d", &dayOfWeek); err != nil { zap.L().Warn("invalid day format in schedule sync", zap.String("day", st.Day), zap.Error(err)) dayOfWeek = 0 } startSection := 1 endSection := 1 if len(st.Sections) > 0 { startSection = int(st.Sections[0]) endSection = int(st.Sections[len(st.Sections)-1]) } // 转换周次数组 var weeks []int for _, w := range st.Weeks { weeks = append(weeks, int(w)) } weeksJSON, _ := json.Marshal(weeks) course := &model.ScheduleCourse{ ID: uuid.New().String(), Name: c.Name, Teacher: c.Teacher, Location: c.Room, DayOfWeek: dayOfWeek, StartSection: startSection, EndSection: endSection, Weeks: string(weeksJSON), Color: generateCourseColor(c.Name), } courses = append(courses, course) } } return courses } // saveCourses 保存课表到数据库 func (s *scheduleSyncService) saveCourses(ctx context.Context, userID string, courses []*model.ScheduleCourse) error { // 先删除用户旧课表 if err := s.scheduleRepo.DeleteByUserID(ctx, userID); err != nil { return fmt.Errorf("failed to delete old courses: %w", err) } // 设置用户 ID 并保存 for _, c := range courses { c.UserID = userID if err := s.scheduleRepo.Create(c); err != nil { return fmt.Errorf("failed to create course: %w", err) } } return nil } // generateCourseColor 根据课程名生成颜色 func generateCourseColor(name string) string { // 预定义的课程颜色列表 colors := []string{ "#3498DB", // 蓝色 "#2ECC71", // 绿色 "#E74C3C", // 红色 "#F39C12", // 橙色 "#9B59B6", // 紫色 "#1ABC9C", // 青色 "#E67E22", // 深橙色 "#34495E", // 深灰色 "#16A085", // 深青色 "#27AE60", // 深绿色 } // 根据课程名哈希选择颜色 hash := 0 for _, c := range name { hash = (hash + int(c)) % len(colors) } return colors[hash] }