package parser import ( "fmt" "log/slog" "net/http" "strings" "schedule_converter/client" "schedule_converter/config" "github.com/PuerkitoBio/goquery" ) type ExamType string const ( ExamTypeFinal ExamType = "final" ExamTypeMidterm ExamType = "midterm" ExamTypeMakeup ExamType = "makeup" ) type ExamEntry struct { Course string Date string Time string Week string Weekday string Type ExamType } func examTypeToCode(t ExamType) string { switch t { case ExamTypeMidterm: return "02" case ExamTypeMakeup: return "03" default: return "01" } } func FetchExams(httpClient *http.Client, examType ExamType) ([]ExamEntry, error) { examURL := config.Get().BaseURL + "/kscx/queryKsForXs" log := slog.With("exam_type", string(examType)) log.Info("获取考试时间数据") data := fmt.Sprintf("xnxq=&kssjd=%s", examTypeToCode(examType)) req, err := http.NewRequest("POST", examURL, strings.NewReader(data)) if err != nil { return nil, err } req.Header = client.GetHeadersWithContentType("application/x-www-form-urlencoded") resp, err := httpClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("获取考试数据失败, 状态码: %d", resp.StatusCode) } html, err := decodeResponseBody(resp) if err != nil { return nil, err } exams, err := parseExamTimeHTML(html, examType) if err != nil { return nil, err } log.Info("考试数据获取完成", "exam_count", len(exams)) return exams, nil } func FetchAllExams(httpClient *http.Client) ([]ExamEntry, error) { var allExams []ExamEntry var lastErr error for _, et := range []ExamType{ExamTypeFinal, ExamTypeMidterm, ExamTypeMakeup} { exams, err := FetchExams(httpClient, et) if err != nil { slog.Warn("获取考试数据失败", "exam_type", string(et), "error", err) lastErr = err continue } allExams = append(allExams, exams...) } if len(allExams) == 0 && lastErr != nil { return nil, fmt.Errorf("所有考试类型获取均失败: %w", lastErr) } return allExams, nil } func parseExamTimeHTML(html string, examType ExamType) ([]ExamEntry, error) { doc, err := goquery.NewDocumentFromReader(strings.NewReader(html)) if err != nil { return nil, err } var exams []ExamEntry doc.Find("table.bot_line").Each(func(_ int, table *goquery.Selection) { rows := table.Find("tr") if rows.Length() < 2 { return } rows.Each(func(idx int, row *goquery.Selection) { if idx == 0 { return } cells := row.Find("td") if cells.Length() < 6 { return } texts := getCellTexts(cells) exam := ExamEntry{ Course: safeGet(texts, 1), Date: safeGet(texts, 2), Time: safeGet(texts, 3), Week: safeGet(texts, 4), Weekday: safeGet(texts, 5), Type: examType, } if exam.Course != "" && len(exam.Course) >= 2 { exams = append(exams, exam) } }) }) return exams, nil }