refactor: introduce service layer and migrate gRPC handlers to use it
All checks were successful
Build / build (push) Successful in 2m17s
All checks were successful
Build / build (push) Successful in 2m17s
Extract business logic from gRPC handlers into a dedicated service package. Add context support throughout for cancellation and timeouts. Move models to their own package, remove hardcoded credentials from config, and simplify parsers to only handle HTML parsing.
This commit is contained in:
@@ -1,107 +1,38 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"schedule_converter/client"
|
||||
"schedule_converter/config"
|
||||
"schedule_converter/models"
|
||||
|
||||
"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 {
|
||||
// examTypeToCode 将考试类型映射为教务系统的请求码。
|
||||
func examTypeToCode(t models.ExamType) string {
|
||||
switch t {
|
||||
case ExamTypeMidterm:
|
||||
case models.ExamTypeMidterm:
|
||||
return "02"
|
||||
case ExamTypeMakeup:
|
||||
case models.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
|
||||
// ExamTypeCode 返回考试类型对应的请求码(供 client 层构造请求使用)。
|
||||
func ExamTypeCode(t models.ExamType) string {
|
||||
return examTypeToCode(t)
|
||||
}
|
||||
|
||||
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) {
|
||||
// ParseExamHTML 解析考试安排页面 HTML,返回考试条目列表。
|
||||
func ParseExamHTML(html string, examType models.ExamType) ([]models.ExamEntry, error) {
|
||||
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var exams []ExamEntry
|
||||
var exams []models.ExamEntry
|
||||
|
||||
doc.Find("table.bot_line").Each(func(_ int, table *goquery.Selection) {
|
||||
rows := table.Find("tr")
|
||||
@@ -120,7 +51,7 @@ func parseExamTimeHTML(html string, examType ExamType) ([]ExamEntry, error) {
|
||||
|
||||
texts := getCellTexts(cells)
|
||||
|
||||
exam := ExamEntry{
|
||||
exam := models.ExamEntry{
|
||||
Course: safeGet(texts, 1),
|
||||
Date: safeGet(texts, 2),
|
||||
Time: safeGet(texts, 3),
|
||||
@@ -136,4 +67,4 @@ func parseExamTimeHTML(html string, examType ExamType) ([]ExamEntry, error) {
|
||||
})
|
||||
|
||||
return exams, nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user