Files
backend/internal/service/schedule_sync_service.go
lafay 2f584c1f2c
All checks were successful
Build Backend / build (push) Successful in 5m10s
Build Backend / build-docker (push) Successful in 2m22s
This is a breaking change that renames the entire project from "Carrot BBS" to "WithYou".
The Go module name has been changed from `carrot_bbs` to `with_you`, which requires
all import paths to be updated throughout the codebase and by external consumers.

BREAKING CHANGE: Module name changed from carrot_bbs to with_you. All imports
and dependencies must be updated from carrot_bbs/* to with_you/*.
2026-04-22 16:01:59 +08:00

193 lines
5.1 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"
)
// 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
fmt.Sscanf(st.Day, "%d", &dayOfWeek)
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]
}