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.
78 lines
2.5 KiB
Go
78 lines
2.5 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"log/slog"
|
||
"net/http"
|
||
"strings"
|
||
|
||
"schedule_converter/client"
|
||
"schedule_converter/config"
|
||
"schedule_converter/models"
|
||
"schedule_converter/parser"
|
||
apperr "schedule_converter/pkg/errors"
|
||
|
||
pb "schedule_converter/proto/runner"
|
||
)
|
||
|
||
// GetEmptyClassrooms 获取空教室:登录 → GET 建立会话 → POST 查询 → 解析 → 转换为 pb 结果。
|
||
func (s *Service) GetEmptyClassrooms(ctx context.Context, req *pb.GetEmptyClassroomPayload) (*pb.EmptyClassroomResultData, error) {
|
||
log := slog.With("username", req.Username, "semester", req.Semester)
|
||
|
||
httpClient, err := s.newAuthenticatedClient(ctx, req.Username, req.Password, "")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
kjsURL := config.Get().BaseURL + "/kjscx/queryKjs"
|
||
|
||
// 先 GET 建立会话(沿用原有行为:直接用 client.Get,仅取 cookie,丢弃 body)。
|
||
sessionReq, err := http.NewRequestWithContext(ctx, "GET", kjsURL, nil)
|
||
if err != nil {
|
||
return nil, apperr.New("session_classroom", err, "建立空教室会话失败")
|
||
}
|
||
sessionReq.Header = client.GetHeaders()
|
||
sessionResp, err := httpClient.Do(sessionReq)
|
||
if err != nil {
|
||
return nil, apperr.New("session_classroom", err, "访问空教室页面失败")
|
||
}
|
||
sessionResp.Body.Close()
|
||
|
||
// POST 查询参数。
|
||
data := fmt.Sprintf("pageXnxq=%s&pageZc1=%s&pageZc2=%s&pageXiaoqu=%s",
|
||
req.Semester, req.WeekStart, req.WeekEnd, req.CampusCode)
|
||
html, err := client.FetchHTML(ctx, httpClient, "POST", "/kjscx/queryKjs", strings.NewReader(data), "application/x-www-form-urlencoded", "/kjscx/queryKjs")
|
||
if err != nil {
|
||
return nil, apperr.New("fetch_empty_classrooms", err, "查询空教室失败")
|
||
}
|
||
|
||
entries, err := parser.ParseEmptyClassroomHTML(html, req.Semester, req.WeekStart, req.WeekEnd)
|
||
if err != nil {
|
||
return nil, apperr.New("parse_empty_classrooms", err, "解析空教室失败")
|
||
}
|
||
|
||
log.Info("空教室获取成功", "classroom_count", len(entries))
|
||
|
||
return &pb.EmptyClassroomResultData{
|
||
StudentId: req.Username,
|
||
Semester: req.Semester,
|
||
Classrooms: convertEmptyClassrooms(entries),
|
||
}, nil
|
||
}
|
||
|
||
// convertEmptyClassrooms 将内部空教室模型转换为 pb.EmptyClassroomEntry 列表。
|
||
func convertEmptyClassrooms(entries []models.EmptyClassroomEntry) []*pb.EmptyClassroomEntry {
|
||
result := make([]*pb.EmptyClassroomEntry, 0, len(entries))
|
||
for _, e := range entries {
|
||
result = append(result, &pb.EmptyClassroomEntry{
|
||
Classroom: e.Classroom,
|
||
Weekday: e.Weekday,
|
||
Periods: e.Periods,
|
||
Term: e.Term,
|
||
Weeks: e.Weeks,
|
||
})
|
||
}
|
||
return result
|
||
}
|