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.
85 lines
2.4 KiB
Go
85 lines
2.4 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"log/slog"
|
||
|
||
"schedule_converter/client"
|
||
"schedule_converter/models"
|
||
"schedule_converter/parser"
|
||
apperr "schedule_converter/pkg/errors"
|
||
|
||
pb "schedule_converter/proto/runner"
|
||
)
|
||
|
||
// GetGrades 获取成绩:登录 → 优先 GPA 页面 → 回退到成绩页面 → 解析 → 转换为 pb 结果。
|
||
func (s *Service) GetGrades(ctx context.Context, req *pb.GetGradesPayload) (*pb.GradesResultData, 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
|
||
}
|
||
|
||
var gpaSummary *pb.GpaSummary
|
||
|
||
gpaHTML, err := client.FetchHTML(ctx, httpClient, "GET", "/xfj/queryListXfj", nil, "", "")
|
||
if err != nil {
|
||
log.Warn("获取GPA页面失败,尝试成绩页面", "error", err)
|
||
gradesHTML, ferr := client.FetchHTML(ctx, httpClient, "GET", "/cjcx/queryQmcj", nil, "", "")
|
||
if ferr != nil {
|
||
return nil, apperr.New("fetch_grades", ferr, "获取成绩失败")
|
||
}
|
||
grades, perr := parser.ParseGradesHTML(gradesHTML)
|
||
if perr != nil {
|
||
return nil, apperr.New("parse_grades", perr, "解析成绩失败")
|
||
}
|
||
return &pb.GradesResultData{
|
||
StudentId: req.Username,
|
||
Semester: req.Semester,
|
||
Grades: convertGrades(grades),
|
||
}, nil
|
||
}
|
||
|
||
summary, grades, err := parser.ParseGPAHTML(gpaHTML)
|
||
if err != nil {
|
||
return nil, apperr.New("parse_gpa", err, "解析学分绩失败")
|
||
}
|
||
if summary != nil {
|
||
gpaSummary = &pb.GpaSummary{
|
||
AverageGpa: summary.AverageGPA,
|
||
Rank: summary.Rank,
|
||
ExamTotalGpa: summary.ExamTotalGPA,
|
||
ExamTotalCredits: summary.ExamTotalCredits,
|
||
FailCredits: summary.FailCredits,
|
||
Semesters: summary.Semesters,
|
||
}
|
||
}
|
||
|
||
return &pb.GradesResultData{
|
||
StudentId: req.Username,
|
||
Semester: req.Semester,
|
||
Grades: convertGrades(grades),
|
||
GpaSummary: gpaSummary,
|
||
}, nil
|
||
}
|
||
|
||
// convertGrades 将内部成绩模型转换为 pb.Grade 列表。
|
||
func convertGrades(grades []models.GradeEntry) []*pb.Grade {
|
||
result := make([]*pb.Grade, 0, len(grades))
|
||
for _, g := range grades {
|
||
result = append(result, &pb.Grade{
|
||
Term: g.Term,
|
||
CourseCode: g.Code,
|
||
CourseName: g.Name,
|
||
Nature: g.Nature,
|
||
Category: g.Category,
|
||
Credit: g.Credit,
|
||
Score: g.Score,
|
||
GradePoint: g.GradePoint,
|
||
IsExam: g.IsExam,
|
||
})
|
||
}
|
||
return result
|
||
}
|