refactor(grpc): restructure task handling and migrate to slog
All checks were successful
Build / build (push) Successful in 5m28s

Refactor the gRPC client and task handling logic to improve modularity,
error handling, and observability.

- Split `grpc/handler.go` into specialized handler files (login,
  classroom, exams, grades, schedule) to reduce complexity.
- Replace standard `log` with `log/slog` throughout the project for
  structured logging.
- Refactor `grpc/client.go` to use a thread-safe connection management
  pattern with `sync.RWMutex`.
- Remove the legacy `service` package and HTTP server implementation,
  transitioning the application to a pure gRPC runner mode.
- Clean up `config` and `pkg/errors` to remove unused utilities and
  simplify the API.
- Improve error reporting in parsers and HTTP clients by checking
  response status codes.
This commit is contained in:
2026-06-09 23:08:03 +08:00
parent ee5c36f3ac
commit accc6567f2
26 changed files with 720 additions and 1442 deletions

View File

@@ -2,6 +2,7 @@ package parser
import (
"fmt"
"log/slog"
"net/http"
"strings"
@@ -19,19 +20,20 @@ type EmptyClassroomEntry struct {
Weeks string `json:"weeks"`
}
type BuildingInfo struct {
type buildingInfo struct {
DM string `json:"dm"`
MC string `json:"mc"`
}
type VenueInfo struct {
type venueInfo struct {
DM string `json:"dm"`
MC string `json:"mc"`
}
func FetchEmptyClassrooms(httpClient *http.Client, semester, weekStart, weekEnd, campusCode string) ([]EmptyClassroomEntry, error) {
url := config.BaseURL + "/kjscx/queryKjs"
fmt.Printf("[空教室] 正在获取空教室数据 (学期=%s, 周次=%s-%s)...\n", semester, weekStart, weekEnd)
url := config.Get().BaseURL + "/kjscx/queryKjs"
log := slog.With("semester", semester, "week_start", weekStart, "week_end", weekEnd)
log.Info("获取空教室数据")
// First GET to establish session
resp, err := httpClient.Get(url)
@@ -57,21 +59,25 @@ func FetchEmptyClassrooms(httpClient *http.Client, semester, weekStart, weekEnd,
}
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
}
entries, err := ParseEmptyClassroomHTML(html, semester, weekStart, weekEnd)
entries, err := parseEmptyClassroomHTML(html, semester, weekStart, weekEnd)
if err != nil {
return nil, err
}
fmt.Printf("[空教室] 共获取 %d 条空教室记录\n", len(entries))
log.Info("空教室数据获取完成", "count", len(entries))
return entries, nil
}
func ParseEmptyClassroomHTML(html, semester, weekStart, weekEnd string) ([]EmptyClassroomEntry, error) {
func parseEmptyClassroomHTML(html, semester, weekStart, weekEnd string) ([]EmptyClassroomEntry, error) {
if html == "" {
return nil, nil
}
@@ -193,9 +199,9 @@ func ParseEmptyClassroomHTML(html, semester, weekStart, weekEnd string) ([]Empty
return classrooms, nil
}
func FetchBuildings(httpClient *http.Client, campusCode string) ([]BuildingInfo, error) {
url := config.BaseURL + "/kjscx/queryJxlListBySjid"
fmt.Printf("[空教室] 正在获取楼号列表 (校区=%s)...\n", campusCode)
func fetchBuildings(httpClient *http.Client, campusCode string) ([]buildingInfo, error) {
url := config.Get().BaseURL + "/kjscx/queryJxlListBySjid"
slog.Info("获取楼号列表", "campus", campusCode)
data := fmt.Sprintf("id=%s", campusCode)
req, err := http.NewRequest("POST", url, strings.NewReader(data))
@@ -204,7 +210,7 @@ func FetchBuildings(httpClient *http.Client, campusCode string) ([]BuildingInfo,
}
req.Header = client.GetHeadersWithContentType("application/x-www-form-urlencoded")
req.Header.Set("Referer", config.BaseURL+"/kjscx/queryKjs")
req.Header.Set("Referer", config.Get().BaseURL+"/kjscx/queryKjs")
resp, err := httpClient.Do(req)
if err != nil {
@@ -212,11 +218,11 @@ func FetchBuildings(httpClient *http.Client, campusCode string) ([]BuildingInfo,
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("获取楼号列表失败, 状态码: %d", resp.StatusCode)
}
var buildings []BuildingInfo
var buildings []buildingInfo
if err := decodeJSONResponse(resp, &buildings); err != nil {
return nil, fmt.Errorf("解析楼号列表失败: %w", err)
}
@@ -224,9 +230,9 @@ func FetchBuildings(httpClient *http.Client, campusCode string) ([]BuildingInfo,
return buildings, nil
}
func FetchVenues(httpClient *http.Client, buildingCode string) ([]VenueInfo, error) {
url := config.BaseURL + "/kjscx/queryJxcdListBySjid"
fmt.Printf("[空教室] 正在获取场地列表 (楼号=%s)...\n", buildingCode)
func fetchVenues(httpClient *http.Client, buildingCode string) ([]venueInfo, error) {
url := config.Get().BaseURL + "/kjscx/queryJxcdListBySjid"
slog.Info("获取场地列表", "building", buildingCode)
data := fmt.Sprintf("id=%s", buildingCode)
req, err := http.NewRequest("POST", url, strings.NewReader(data))
@@ -235,7 +241,7 @@ func FetchVenues(httpClient *http.Client, buildingCode string) ([]VenueInfo, err
}
req.Header = client.GetHeadersWithContentType("application/x-www-form-urlencoded")
req.Header.Set("Referer", config.BaseURL+"/kjscx/queryKjs")
req.Header.Set("Referer", config.Get().BaseURL+"/kjscx/queryKjs")
resp, err := httpClient.Do(req)
if err != nil {
@@ -243,11 +249,11 @@ func FetchVenues(httpClient *http.Client, buildingCode string) ([]VenueInfo, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("获取场地列表失败, 状态码: %d", resp.StatusCode)
}
var venues []VenueInfo
var venues []venueInfo
if err := decodeJSONResponse(resp, &venues); err != nil {
return nil, fmt.Errorf("解析场地列表失败: %w", err)
}