refactor: introduce service layer and migrate gRPC handlers to use it
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:
lafay
2026-06-16 18:33:13 +08:00
parent ae9297c11c
commit ab37aeb2fc
26 changed files with 603 additions and 683 deletions

View File

@@ -1,128 +1,21 @@
package parser
import (
"fmt"
"log/slog"
"net/http"
"strings"
"schedule_converter/client"
"schedule_converter/config"
"schedule_converter/models"
"github.com/PuerkitoBio/goquery"
)
type ScoreType string
const (
ScoreTypeFinal ScoreType = "final"
ScoreTypeMidterm ScoreType = "midterm"
)
type GradeEntry struct {
Term string
Code string
Name string
Nature string
Category string
Credit string
Score string
GradePoint string
IsExam string
}
type GPASummary struct {
AverageGPA string
Rank string
ExamTotalGPA string
ExamTotalCredits string
FailCredits string
Semesters string
}
func FetchGrades(httpClient *http.Client, examType ScoreType) ([]GradeEntry, error) {
var gradeURL string
switch examType {
case ScoreTypeMidterm:
gradeURL = config.Get().BaseURL + "/cjcx/queryQzcj"
default:
gradeURL = config.Get().BaseURL + "/cjcx/queryQmcj"
}
log := slog.With("exam_type", string(examType))
log.Info("获取成绩数据")
req, err := http.NewRequest("GET", gradeURL, nil)
if err != nil {
return nil, err
}
req.Header = client.GetHeaders()
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
}
grades, err := parseGradesHTML(html)
if err != nil {
return nil, err
}
log.Info("成绩获取完成", "grade_count", len(grades))
return grades, nil
}
func FetchGPA(httpClient *http.Client) (*GPASummary, []GradeEntry, error) {
gpaURL := config.Get().BaseURL + "/xfj/queryListXfj"
slog.Info("获取学分绩数据")
req, err := http.NewRequest("GET", gpaURL, nil)
if err != nil {
return nil, nil, err
}
req.Header = client.GetHeaders()
resp, err := httpClient.Do(req)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, nil, fmt.Errorf("获取学分绩失败, 状态码: %d", resp.StatusCode)
}
html, err := decodeResponseBody(resp)
if err != nil {
return nil, nil, err
}
summary, grades, err := parseGPAHTML(html)
if err != nil {
return nil, nil, err
}
slog.Info("学分绩获取完成", "grade_count", len(grades))
return summary, grades, nil
}
func parseGradesHTML(html string) ([]GradeEntry, error) {
// ParseGradesHTML 解析成绩页面 HTML返回成绩条目列表。
func ParseGradesHTML(html string) ([]models.GradeEntry, error) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
return nil, err
}
var grades []GradeEntry
var grades []models.GradeEntry
doc.Find("table.bot_line").Each(func(_ int, table *goquery.Selection) {
rows := table.Find("tr")
@@ -146,7 +39,7 @@ func parseGradesHTML(html string) ([]GradeEntry, error) {
isMidterm := len(texts) == 10
entry := GradeEntry{
entry := models.GradeEntry{
Term: safeGet(texts, 1),
Code: safeGet(texts, 3),
Name: safeGet(texts, 4),
@@ -183,13 +76,14 @@ func parseGradesHTML(html string) ([]GradeEntry, error) {
return grades, nil
}
func parseGPAHTML(html string) (*GPASummary, []GradeEntry, error) {
// ParseGPAHTML 解析学分绩页面 HTML返回 GPA 概要与成绩条目列表。
func ParseGPAHTML(html string) (*models.GPASummary, []models.GradeEntry, error) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
return nil, nil, err
}
summary := &GPASummary{}
summary := &models.GPASummary{}
doc.Find("span").Each(func(_ int, span *goquery.Selection) {
id, _ := span.Attr("id")
@@ -210,7 +104,7 @@ func parseGPAHTML(html string) (*GPASummary, []GradeEntry, error) {
}
})
var grades []GradeEntry
var grades []models.GradeEntry
doc.Find("table.bot_line").Each(func(_ int, table *goquery.Selection) {
rows := table.Find("tr")
@@ -229,7 +123,7 @@ func parseGPAHTML(html string) (*GPASummary, []GradeEntry, error) {
texts := getCellTexts(cells)
entry := GradeEntry{
entry := models.GradeEntry{
Term: safeGet(texts, 0),
Code: safeGet(texts, 1),
Name: safeGet(texts, 2),