2026-06-16 18:33:13 +08:00
|
|
|
|
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,
|
2026-06-17 14:48:27 +08:00
|
|
|
|
IsRetake: g.IsRetake,
|
2026-06-16 18:33:13 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|