feat(grpc): add support for fetching grades and exams
All checks were successful
Build / build (push) Successful in 2m14s
All checks were successful
Build / build (push) Successful in 2m14s
Implement new gRPC task handlers for retrieving student grades and exam schedules. This includes adding new parser logic for processing score and exam data, and updating the client capabilities to advertise support for these new task types. - Add `getGrades` and `getExams` handlers in `grpc/handler.go` - Add `parser/encoding.go`, `parser/exam.go`, and `parser/score.go` for data parsing - Update `grpc/client.go` to include `TASK_TYPE_GET_GRADES` and `TASK_TYPE_GET_EXAMS` in capabilities
This commit is contained in:
@@ -78,6 +78,8 @@ func (c *Client) register() error {
|
||||
Version: c.version,
|
||||
Capabilities: map[string]string{
|
||||
"TASK_TYPE_GET_SCHEDULE": "true",
|
||||
"TASK_TYPE_GET_GRADES": "true",
|
||||
"TASK_TYPE_GET_EXAMS": "true",
|
||||
"TASK_TYPE_LOGIN": "true",
|
||||
},
|
||||
},
|
||||
|
||||
142
grpc/handler.go
142
grpc/handler.go
@@ -8,6 +8,8 @@ import (
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -52,6 +54,10 @@ func (h *TaskHandler) Execute(task *pb.Task) *pb.TaskResult {
|
||||
switch task.Type {
|
||||
case pb.TaskType_TASK_TYPE_GET_SCHEDULE:
|
||||
data, err = h.getSchedule(context.Background(), task.Payload)
|
||||
case pb.TaskType_TASK_TYPE_GET_GRADES:
|
||||
data, err = h.getGrades(context.Background(), task.Payload)
|
||||
case pb.TaskType_TASK_TYPE_GET_EXAMS:
|
||||
data, err = h.getExams(context.Background(), task.Payload)
|
||||
case pb.TaskType_TASK_TYPE_LOGIN:
|
||||
data, err = h.login(context.Background(), task.Payload)
|
||||
default:
|
||||
@@ -220,6 +226,86 @@ func executeLoginAndFetchSchedule(ctx context.Context, httpClient *http.Client,
|
||||
return parser.FetchScheduleWithClient(httpClient, semester)
|
||||
}
|
||||
|
||||
func (h *TaskHandler) getGrades(ctx context.Context, payload []byte) ([]byte, error) {
|
||||
var req pb.GetGradesPayload
|
||||
if err := json.Unmarshal(payload, &req); err != nil {
|
||||
return nil, apperr.New("parse_payload", err, "解析任务载荷失败")
|
||||
}
|
||||
|
||||
log := slog.With("username", req.Username, "semester", req.Semester)
|
||||
log.Info("获取成绩任务")
|
||||
|
||||
httpClient, err := createHTTPClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := config.Get()
|
||||
setTWFIDCookie(httpClient, cfg.BaseURL, "")
|
||||
|
||||
if err := executeLogin(ctx, httpClient, req.Username, req.Password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, grades, err := parser.FetchGPA(httpClient)
|
||||
if err != nil {
|
||||
log.Warn("获取GPA页面失败,尝试成绩页面", "error", err)
|
||||
grades, err = parser.FetchGrades(httpClient, parser.ScoreTypeFinal)
|
||||
if err != nil {
|
||||
return nil, apperr.New("fetch_grades", err, "获取成绩失败")
|
||||
}
|
||||
}
|
||||
|
||||
pbGrades := convertGrades(grades)
|
||||
|
||||
result := &pb.GradesResultData{
|
||||
StudentId: req.Username,
|
||||
Semester: req.Semester,
|
||||
Grades: pbGrades,
|
||||
}
|
||||
|
||||
log.Info("成绩获取成功", "grade_count", len(pbGrades))
|
||||
return json.Marshal(result)
|
||||
}
|
||||
|
||||
func (h *TaskHandler) getExams(ctx context.Context, payload []byte) ([]byte, error) {
|
||||
var req pb.GetExamsPayload
|
||||
if err := json.Unmarshal(payload, &req); err != nil {
|
||||
return nil, apperr.New("parse_payload", err, "解析任务载荷失败")
|
||||
}
|
||||
|
||||
log := slog.With("username", req.Username, "semester", req.Semester)
|
||||
log.Info("获取考试安排任务")
|
||||
|
||||
httpClient, err := createHTTPClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := config.Get()
|
||||
setTWFIDCookie(httpClient, cfg.BaseURL, "")
|
||||
|
||||
if err := executeLogin(ctx, httpClient, req.Username, req.Password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exams, err := parser.FetchAllExams(httpClient)
|
||||
if err != nil {
|
||||
return nil, apperr.New("fetch_exams", err, "获取考试安排失败")
|
||||
}
|
||||
|
||||
pbExams := convertExams(exams)
|
||||
|
||||
result := &pb.ExamsResultData{
|
||||
StudentId: req.Username,
|
||||
Semester: req.Semester,
|
||||
Exams: pbExams,
|
||||
}
|
||||
|
||||
log.Info("考试安排获取成功", "exam_count", len(pbExams))
|
||||
return json.Marshal(result)
|
||||
}
|
||||
|
||||
func convertCourses(courses []models.Course) []*pb.Course {
|
||||
result := make([]*pb.Course, len(courses))
|
||||
for i, c := range courses {
|
||||
@@ -248,3 +334,59 @@ func convertToInt32(arr []int) []int32 {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func convertGrades(grades []parser.GradeEntry) []*pb.Grade {
|
||||
result := make([]*pb.Grade, 0, len(grades))
|
||||
for _, g := range grades {
|
||||
var credit float32
|
||||
if v, err := parseFloat(g.Credit); err == nil {
|
||||
credit = v
|
||||
}
|
||||
var gradePoint float32
|
||||
if v, err := parseFloat(g.GradePoint); err == nil {
|
||||
gradePoint = v
|
||||
}
|
||||
result = append(result, &pb.Grade{
|
||||
CourseCode: g.Code,
|
||||
CourseName: g.Name,
|
||||
Credit: credit,
|
||||
Grade: g.Score,
|
||||
GradePoint: gradePoint,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func convertExams(exams []parser.ExamEntry) []*pb.Exam {
|
||||
result := make([]*pb.Exam, 0, len(exams))
|
||||
for _, e := range exams {
|
||||
examTypeStr := "期末"
|
||||
switch e.Type {
|
||||
case parser.ExamTypeMidterm:
|
||||
examTypeStr = "期中"
|
||||
case parser.ExamTypeMakeup:
|
||||
examTypeStr = "补考"
|
||||
}
|
||||
result = append(result, &pb.Exam{
|
||||
CourseName: e.Course,
|
||||
ExamType: examTypeStr,
|
||||
Date: e.Date,
|
||||
Time: e.Time,
|
||||
Room: e.Week,
|
||||
Seat: e.Weekday,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func parseFloat(s string) (float32, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" || s == "-" {
|
||||
return 0, fmt.Errorf("empty")
|
||||
}
|
||||
v, err := strconv.ParseFloat(s, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return float32(v), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user