From 733974d00236d87a8d99702ecb5137cd312f8ec6 Mon Sep 17 00:00:00 2001 From: jiuxia211 <2064166368@qq.com> Date: Sat, 19 Oct 2024 01:21:58 +0800 Subject: [PATCH 1/3] feat: add GetCredit && GetGPA Signed-off-by: jiuxia211 <2064166368@qq.com> --- constants/constants.go | 2 + credit.go | 116 +++++++++++++++++++++++++++++++++++++++++ jwch_test.go | 31 +++++++++++ model.go | 19 ++++++- 4 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 credit.go diff --git a/constants/constants.go b/constants/constants.go index d328422..5c5004d 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -8,6 +8,8 @@ const ( UserInfoURL = "https://jwcjwxt2.fzu.edu.cn:81/jcxx/xsxx/StudentInformation.aspx" SSOLoginURL = "https://jwcjwxt2.fzu.edu.cn/Sfrz/SSOLogin" SchoolCalendarURL = "https://jwcjwxt2.fzu.edu.cn:82/xl.asp" + CreditQueryURL = "https://jwcjwxt2.fzu.edu.cn:81/student/xyzk/xftj/CreditStatistics.aspx" + GPAQueryURL = "https://jwcjwxt2.fzu.edu.cn:81/student/xyzk/jdpm/GPA_sheet.aspx" ) var BuildingArray = []string{"公共教学楼东1", "公共教学楼东2", "公共教学楼东3", "公共教学楼文科楼", "公共教学楼西1", "公共教学楼西2", "公共教学楼西3", "公共教学楼中楼"} diff --git a/credit.go b/credit.go new file mode 100644 index 0000000..cfefe1b --- /dev/null +++ b/credit.go @@ -0,0 +1,116 @@ +package jwch + +import ( + "fmt" + "strings" + + "github.com/antchfx/htmlquery" + "github.com/west2-online/jwch/constants" +) + +func (s *Student) GetCredit() (creditStatistics []CreditStatistics, err error) { + + resp, err := s.GetWithIdentifier(constants.CreditQueryURL) + if err != nil { + return nil, err + } + + spanNode := htmlquery.FindOne(resp, `//*[@id="ContentPlaceHolder1_LB_kb"]`) + if spanNode == nil { + return nil, fmt.Errorf("failed to find the statistics span element") + } + + tables := htmlquery.Find(spanNode, "//table") + if len(tables) == 0 { + return nil, fmt.Errorf("failed to find tables within the span element") + } + tables = tables[:len(tables)-1] // 去掉最后一个表格 + + creditStatistics = []CreditStatistics{} + + for _, table := range tables { + rows := htmlquery.Find(table, "//tr") + + // 临时存储三列数据 + temp := [3][]string{ + make([]string, 0), + make([]string, 0), + make([]string, 0), + } + // 遍历每行,提取单元格数据 + for index, row := range rows { + cells := htmlquery.Find(row, "./td") + // 提取单元格数据 + for _, cell := range cells { + text := htmlquery.InnerText(cell) + if text != "查" { // 因为表格的第三行多了一个单元格,去掉一个无用的格子它使得表格规整 + temp[index] = append(temp[index], text) + } + } + } + // 构建 CreditStatistics + for i := 0; i < len(temp[0]); i++ { + // 去掉个人信息的列(这列第一个单元格式空的)和“修习情况”这个无效的列 + if strings.TrimSpace(temp[0][i]) != "" && !strings.Contains(temp[0][i], "情况") { + bean := CreditStatistics{ + Type: temp[0][i], + Gain: temp[2][i], + Total: temp[1][i], + } + creditStatistics = append(creditStatistics, bean) + } + } + } + + return creditStatistics, nil +} +func (s *Student) GetGPA() (gpa GPABean, err error) { + resp, err := s.GetWithIdentifier(constants.GPAQueryURL) + if err != nil { + return gpa, err + } + + document := htmlquery.FindOne(resp, `//*[@id="ContentPlaceHolder1_Label1"]`) + if document == nil { + return gpa, fmt.Errorf("failed to find the time element") + } + + timeText := htmlquery.InnerText(document) + gpa.Time = strings.TrimSpace(timeText) + + table := htmlquery.FindOne(resp, `//*[@id="ContentPlaceHolder1_DataList_xxk"]`) + if table == nil { + return gpa, fmt.Errorf("failed to find the GPA table") + } + + // 获取表头标题 + titleRow := htmlquery.FindOne(table, `//tr[@style="height:30px; background:#efefef; border-bottom:1px solid gray; border-left:1px solid gray; vertical-align:middle;"]`) + if titleRow == nil { + return gpa, fmt.Errorf("failed to find the title row in GPA table") + } + + // 获取每个表头标题的单元格 + tdsTitle := htmlquery.Find(titleRow, `./td[@align="center"]`) + width := len(tdsTitle) + + // 获取表格中的所有数据 + tdsFull := htmlquery.Find(table, `.//td[@align="center"]`) + if len(tdsFull) == 0 { + return gpa, fmt.Errorf("failed to find GPA data cells") + } + + height := len(tdsFull)/width - 1 + + var data []GPAData + for h := 1; h <= height; h++ { + for w := 0; w < width; w++ { + data = append(data, GPAData{ + Type: htmlquery.InnerText(tdsTitle[w]), + Value: htmlquery.InnerText(tdsFull[width*h+w]), + }) + } + } + gpa.Data = data + + return gpa, nil +} diff --git a/jwch_test.go b/jwch_test.go index c2bce09..b92b8e8 100644 --- a/jwch_test.go +++ b/jwch_test.go @@ -277,3 +277,34 @@ func Test_GetTermEvents(t *testing.T) { fmt.Println(utils.PrintStruct(events)) } + +func Test_GetCredit(t *testing.T) { + if !islogin { + err := login() + if err != nil { + t.Error(err) + } + } + + credit, err := stu.GetCredit() + if err != nil { + t.Error(err) + } + + fmt.Println(utils.PrintStruct(credit)) +} + +func Test_GetGPA(t *testing.T) { + if !islogin { + err := login() + if err != nil { + t.Error(err) + } + } + gpa, err := stu.GetGPA() + if err != nil { + t.Error(err) + } + + fmt.Println(utils.PrintStruct(gpa)) +} diff --git a/model.go b/model.go index cf04a59..497805b 100644 --- a/model.go +++ b/model.go @@ -1,8 +1,9 @@ package jwch import ( - "github.com/go-resty/resty/v2" "net/http" + + "github.com/go-resty/resty/v2" ) // 学生对象 @@ -129,3 +130,19 @@ type CalTermEvent struct { StartDate string `json:"startDate"` // 开始日期 格式:2024-08-26 EndDate string `json:"endDate"` // 结束日期 格式:2025-01-17 } + +type CreditStatistics struct { + Type string // 学分类型 + Gain string // 已获得 + Total string // 应获学分 +} + +type GPAData struct { + Type string + Value string +} + +type GPABean struct { + Time string // 绩点计算时间 + Data []GPAData +} From 95de9af3346a9fea6376489b834c839b5bd16cf3 Mon Sep 17 00:00:00 2001 From: jiuxia211 <2064166368@qq.com> Date: Sat, 19 Oct 2024 18:39:37 +0800 Subject: [PATCH 2/3] update the return value of credit && gpa to pointer slice Signed-off-by: jiuxia211 <2064166368@qq.com> --- credit.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/credit.go b/credit.go index cfefe1b..62cd48a 100644 --- a/credit.go +++ b/credit.go @@ -8,7 +8,7 @@ import ( "github.com/west2-online/jwch/constants" ) -func (s *Student) GetCredit() (creditStatistics []CreditStatistics, err error) { +func (s *Student) GetCredit() (creditStatistics []*CreditStatistics, err error) { resp, err := s.GetWithIdentifier(constants.CreditQueryURL) if err != nil { @@ -26,7 +26,7 @@ func (s *Student) GetCredit() (creditStatistics []CreditStatistics, err error) { } tables = tables[:len(tables)-1] // 去掉最后一个表格 - creditStatistics = []CreditStatistics{} + creditStatistics = make([]*CreditStatistics, 0) for _, table := range tables { rows := htmlquery.Find(table, "//tr") @@ -52,7 +52,7 @@ func (s *Student) GetCredit() (creditStatistics []CreditStatistics, err error) { for i := 0; i < len(temp[0]); i++ { // 去掉个人信息的列(这列第一个单元格式空的)和“修习情况”这个无效的列 if strings.TrimSpace(temp[0][i]) != "" && !strings.Contains(temp[0][i], "情况") { - bean := CreditStatistics{ + bean := &CreditStatistics{ Type: temp[0][i], Gain: temp[2][i], Total: temp[1][i], @@ -64,7 +64,8 @@ func (s *Student) GetCredit() (creditStatistics []CreditStatistics, err error) { return creditStatistics, nil } -func (s *Student) GetGPA() (gpa GPABean, err error) { +func (s *Student) GetGPA() (gpa *GPABean, err error) { + gpa = &GPABean{} resp, err := s.GetWithIdentifier(constants.GPAQueryURL) if err != nil { return gpa, err From 298ca8c5d67dc3d01bd1a09f404a6bf6a43d449a Mon Sep 17 00:00:00 2001 From: jiuxia211 <2064166368@qq.com> Date: Sat, 19 Oct 2024 19:30:15 +0800 Subject: [PATCH 3/3] feat: add get unified exam Signed-off-by: jiuxia211 <2064166368@qq.com> --- constants/constants.go | 1 + jwch_test.go | 22 ++++++++++++++++++ mark.go | 53 ++++++++++++++++++++++++++++++++++++++---- model.go | 6 +++++ 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/constants/constants.go b/constants/constants.go index 5c5004d..2ef0a32 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -5,6 +5,7 @@ const ( CourseURL = "https://jwcjwxt2.fzu.edu.cn:81/student/xkjg/wdxk/xkjg_list.aspx" MarksQueryURL = "https://jwcjwxt2.fzu.edu.cn:81/student/xyzk/cjyl/score_sheet.aspx" CETQueryURL = "https://jwcjwxt2.fzu.edu.cn:81/student/glbm/cet/cet_cszt.aspx" + JSQueryURL = "https://jwcjwxt2.fzu.edu.cn:81/student/glbm/computer/jsj_cszt.aspx" UserInfoURL = "https://jwcjwxt2.fzu.edu.cn:81/jcxx/xsxx/StudentInformation.aspx" SSOLoginURL = "https://jwcjwxt2.fzu.edu.cn/Sfrz/SSOLogin" SchoolCalendarURL = "https://jwcjwxt2.fzu.edu.cn:82/xl.asp" diff --git a/jwch_test.go b/jwch_test.go index b92b8e8..f9a555c 100644 --- a/jwch_test.go +++ b/jwch_test.go @@ -308,3 +308,25 @@ func Test_GetGPA(t *testing.T) { fmt.Println(utils.PrintStruct(gpa)) } + +func TestGetUnifiedExam(t *testing.T) { + if !islogin { + err := login() + if err != nil { + t.Error(err) + } + } + cet, err := stu.GetCET() + if err != nil { + t.Error(err) + } + + fmt.Println(utils.PrintStruct(cet)) + + js, err := stu.GetJS() + if err != nil { + t.Error(err) + } + + fmt.Println(utils.PrintStruct(js)) +} diff --git a/mark.go b/mark.go index 16cdb50..f1c4e25 100644 --- a/mark.go +++ b/mark.go @@ -7,6 +7,7 @@ import ( "github.com/west2-online/jwch/constants" "github.com/west2-online/jwch/errno" "github.com/west2-online/jwch/utils" + "golang.org/x/net/html" "github.com/antchfx/htmlquery" ) @@ -61,13 +62,57 @@ func (s *Student) GetMarks() (resp []*Mark, err error) { } // 获取CET成绩 -func (s *Student) GetCET() error { +func (s *Student) GetCET() ([]*UnifiedExam, error) { resp, err := s.GetWithIdentifier(constants.CETQueryURL) + if err != nil { + return nil, err + } + + return s.parseUnifiedExam(resp) +} +// 获取省计算机成绩 +func (s *Student) GetJS() ([]*UnifiedExam, error) { + resp, err := s.GetWithIdentifier(constants.JSQueryURL) if err != nil { - return err + return nil, err + } + + return s.parseUnifiedExam(resp) +} + +// 解析统一考试成绩 +func (s *Student) parseUnifiedExam(resp *html.Node) ([]*UnifiedExam, error) { + var exams []*UnifiedExam + + // 查找包含成绩的表格 + table := htmlquery.FindOne(resp, `//*[@id="ContentPlaceHolder1_DataList_xxk"]`) + if table == nil { + return nil, fmt.Errorf("failed to find the exam table") + } + + // 查找所有考试成绩行 + rows := htmlquery.Find(table, `.//tr[@onmouseover]`) + if len(rows) == 0 { + return nil, nil // 这里不返回错误,因为有可能没有考试成绩 + } + + // 遍历每一行,提取成绩信息 + for _, row := range rows { + tds := htmlquery.Find(row, `.//td`) + if len(tds) < 3 { + continue // 如果某行的列数不满足要求则跳过 + } + + // 创建一个新的 UnifiedExam 对象 + exam := &UnifiedExam{ + Name: htmlquery.InnerText(tds[0]), + Score: htmlquery.InnerText(tds[2]), + Term: htmlquery.InnerText(tds[1]), + } + + exams = append(exams, exam) } - fmt.Println(resp) - return nil + return exams, nil } diff --git a/model.go b/model.go index 497805b..4bf1bed 100644 --- a/model.go +++ b/model.go @@ -146,3 +146,9 @@ type GPABean struct { Time string // 绩点计算时间 Data []GPAData } + +type UnifiedExam struct { + Name string + Score string + Term string +}