新增课表获取和解析;form表单字符串的key可重复;重命名培养方案;考虑到移除旧版登录方案的支持;

old-package
lensfrex 2 years ago
parent efab489d17
commit 0e85b3e58c
Signed by: lensfrex
GPG Key ID: 0F69A0A2FBEE98A0
  1. 2
      mywust-core/src/main/java/cn/linghang/mywust/core/api/Bkjx.java
  2. 6
      mywust-core/src/main/java/cn/linghang/mywust/core/parser/physics/PhysicsCoursePageParser.java
  3. 107
      mywust-core/src/main/java/cn/linghang/mywust/core/parser/undergraduate/CourseTableParser.java
  4. 5
      mywust-core/src/main/java/cn/linghang/mywust/core/parser/undergraduate/ExamInfoParser.java
  5. 8
      mywust-core/src/main/java/cn/linghang/mywust/core/parser/undergraduate/TrainingPlanPageParser.java
  6. 5
      mywust-core/src/main/java/cn/linghang/mywust/core/request/auth/UnionAuthRequestFactory.java
  7. 3
      mywust-core/src/main/java/cn/linghang/mywust/core/request/library/LibraryRequestFactory.java
  8. 3
      mywust-core/src/main/java/cn/linghang/mywust/core/request/physics/PhysicsSystemRequestFactory.java
  9. 33
      mywust-core/src/main/java/cn/linghang/mywust/core/request/undergrade/BkjxRequestFactory.java
  10. 56
      mywust-core/src/main/java/cn/linghang/mywust/core/request/undergrade/CourseTableRequestParamFactory.java
  11. 49
      mywust-core/src/main/java/cn/linghang/mywust/core/service/auth/JwcLogin.java
  12. 2
      mywust-core/src/main/java/cn/linghang/mywust/core/service/auth/LibraryLogin.java
  13. 2
      mywust-core/src/main/java/cn/linghang/mywust/core/service/auth/PhysicsLogin.java
  14. 6
      mywust-core/src/main/java/cn/linghang/mywust/core/service/auth/UnionLogin.java
  15. 43
      mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/CourseTableApi.java
  16. 12
      mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/ExamInfoApi.java
  17. 43
      mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/SchemeApi.java
  18. 12
      mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/StudentInfoApi.java
  19. 36
      mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/TrainingPlanApi.java
  20. 17
      mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/UndergraduateApi.java
  21. 4
      mywust-model/src/main/java/cn/linghang/mywust/model/global/Course.java
  22. 2
      mywust-model/src/main/java/cn/linghang/mywust/model/physics/PhysicsCourse.java
  23. 17
      mywust-network/src/main/java/cn/linghang/mywust/network/entitys/FormBodyBuilder.java
  24. 50
      mywust-test/src/test/java/CourseTableTest.java
  25. 42
      mywust-test/src/test/java/JwcLegacyLoginTest.java
  26. 7
      mywust-test/src/test/java/SchemeTest.java
  27. 10
      mywust-util/src/main/java/cn/linghang/mywust/util/RepeatableComparator.java
  28. 11
      mywust-util/src/main/java/cn/linghang/mywust/util/StringUtil.java

@ -18,6 +18,8 @@ public class Bkjx {
public static final String BKJX_SCHEME_API = "http://bkjx.wust.edu.cn/jsxsd/pyfa/topyfamx"; public static final String BKJX_SCHEME_API = "http://bkjx.wust.edu.cn/jsxsd/pyfa/topyfamx";
public static final String BKJX_COURSE_TABLE_API = "http://bkjx.wust.edu.cn/jsxsd/xskb/xskb_list.do";
public static class Legacy { public static class Legacy {
public static final String BKJX_INDEX = "http://bkjx.wust.edu.cn"; public static final String BKJX_INDEX = "http://bkjx.wust.edu.cn";
public static final String BKJX_DATA_STRING_API = "http://bkjx.wust.edu.cn/Logon.do?method=logon&flag=sess"; public static final String BKJX_DATA_STRING_API = "http://bkjx.wust.edu.cn/Logon.do?method=logon&flag=sess";

@ -4,7 +4,7 @@ import cn.linghang.mywust.core.exception.ParseException;
import cn.linghang.mywust.core.parser.HuangjiahuClassroomNameParser; import cn.linghang.mywust.core.parser.HuangjiahuClassroomNameParser;
import cn.linghang.mywust.core.parser.Parser; import cn.linghang.mywust.core.parser.Parser;
import cn.linghang.mywust.core.parser.physics.xpath.PhysicsCourseXpath; import cn.linghang.mywust.core.parser.physics.xpath.PhysicsCourseXpath;
import cn.linghang.mywust.model.PhysicsCourse; import cn.linghang.mywust.model.physics.PhysicsCourse;
import cn.linghang.mywust.model.global.ClassRoom; import cn.linghang.mywust.model.global.ClassRoom;
import cn.linghang.mywust.model.global.Course; import cn.linghang.mywust.model.global.Course;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
@ -68,8 +68,8 @@ public class PhysicsCoursePageParser implements Parser<List<PhysicsCourse>> {
Matcher startEndMatcher = PHYSICS_COURSE_START_END_PATTERN.matcher(time); Matcher startEndMatcher = PHYSICS_COURSE_START_END_PATTERN.matcher(time);
if (startEndMatcher.find()) { if (startEndMatcher.find()) {
course.setStart(Integer.parseInt(startEndMatcher.group("start"))); course.setStartSection(Integer.parseInt(startEndMatcher.group("start")));
course.setEnd(Integer.parseInt(startEndMatcher.group("end"))); course.setEndSection(Integer.parseInt(startEndMatcher.group("end")));
} }
Matcher dateMatcher = PHYSICS_COURSE_DATE_PATTERN.matcher(time); Matcher dateMatcher = PHYSICS_COURSE_DATE_PATTERN.matcher(time);

@ -0,0 +1,107 @@
package cn.linghang.mywust.core.parser.undergraduate;
import cn.linghang.mywust.core.exception.ParseException;
import cn.linghang.mywust.core.parser.Parser;
import cn.linghang.mywust.model.global.ClassRoom;
import cn.linghang.mywust.model.global.Course;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CourseTableParser implements Parser<List<Course>> {
private static final Logger log = LoggerFactory.getLogger(CourseTableParser.class);
private static final String COURSE_SPLIT_STR = " --------------------- ";
private static final Pattern WEEK_RANGE_REGEX = Pattern.compile("(?<startWeek>\\d+)-(?<endWeek>\\d+)\\(周\\)");
private static final Pattern SINGLE_WEEK_REGEX = Pattern.compile("(?<week>\\d+)\\(周\\)");
@Override
public List<Course> parse(String html) throws ParseException {
try {
// 用css选择器获取所有格子(不包括标题格和左侧格,也就是说拿到的所有元素都是课程信息的格子)
// 不过需要注意的是,通过.kbcontent1:not(.sykb1)获取到的是没有老师的,是精简过的
// 效果和使用xpath是一样的
// 实际上可以解析name=jx0415zbdiv_1的input表单value获得对应的table id
// 通过这个id直接拿到对应的内容,相对于css选择器能更好的防止脑抽改class
// 但是class都改了估计整个页面逻辑也会改了(ui换了),用什么办法多少都得要去改
// 按照这系统的尿性,并且为简单起见,直接用css选择器得了
Elements girds = Jsoup.parse(html).select(".kbcontent:not(.sykb2)");
List<Course> courses = new ArrayList<>(girds.size());
// 解析每个格子
int girdCount = 0;
for (Element gird : girds) {
girdCount++;
// 直接获取格子里所有课程的关键字段,每个下表对应格子里相应的课程
Elements classElements = gird.getElementsByAttributeValue("title", "课堂名称");
Elements teacherElements = gird.getElementsByAttributeValue("title", "老师");
Elements timeElements = gird.getElementsByAttributeValue("title", "周次(节次)");
Elements classroomElements = gird.getElementsByAttributeValue("title", "教室");
// 如果一个格子有好几节课,用" --------------------- "切开,提取字段的时候用i指定对应字段的下表
String[] courseNames = gird.ownText().split(COURSE_SPLIT_STR);
// 解析格子里的所有课
for (int i = 0; i < courseNames.length; i++) {
// 格子文本为空,说明这个格子没课,直接跳过这个格子就行了
if ("".equals(courseNames[i])) {
continue;
}
Course course = new Course();
course.setName(courseNames[i]);
course.setTeachClass(classElements.isEmpty() ? "" : classElements.get(i).text());
course.setTeacher(teacherElements.isEmpty() ? "" : teacherElements.get(i).text());
ClassRoom classRoom = new ClassRoom();
classRoom.setRoom(classroomElements.isEmpty() ? "" : classroomElements.get(i).text());
course.setClassroom(classRoom);
// 靠行位置来确定节次,而不是靠time字段的节次数据确定(因为太不好处理了)
// 具体算法就是行索引x2 + 1就是开始的节次(索引从0开始)
// 对于只有一个小节的课程,这类课程多数是在线课程,实际选课的时候照样会和其他课冲突,因此这里一律按照两小节大课处理
int lineIndex = girdCount / 7;
course.setStartSection(lineIndex * 2 + 1);
course.setEndSection(lineIndex * 2 + 2);
course.setWeekDay(girdCount % 7);
// 提取周次信息,根据老项目,可能会有用","分成两段周次信息,但根据实际测试没有发现类似的课程
String time = timeElements.isEmpty() ? "" : timeElements.get(i).text();
Matcher matcher = WEEK_RANGE_REGEX.matcher(time);
if (matcher.find()) {
course.setStartWeek(Integer.parseInt(matcher.group("startWeek")));
course.setEndWeek(Integer.parseInt(matcher.group("endWeek")));
} else {
// 普通匹配不到的话多半就是只有一周的课程
matcher = SINGLE_WEEK_REGEX.matcher(time);
if (matcher.find()) {
course.setStartWeek(Integer.parseInt(matcher.group("week")));
course.setEndWeek(Integer.parseInt(matcher.group("week")));
}
}
courses.add(course);
}
}
return courses;
} catch (Exception e) {
log.warn("解析课表时出现问题:{}", e.getMessage(), e);
throw new ParseException();
}
}
}

@ -5,6 +5,7 @@ import cn.linghang.mywust.core.parser.Parser;
import cn.linghang.mywust.core.parser.undergraduate.xpath.ExamInfoXpath; import cn.linghang.mywust.core.parser.undergraduate.xpath.ExamInfoXpath;
import cn.linghang.mywust.model.undergrade.ExamInfo; import cn.linghang.mywust.model.undergrade.ExamInfo;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -25,8 +26,8 @@ public class ExamInfoParser implements Parser<List<ExamInfo>> {
List<ExamInfo> examInfos = new ArrayList<>(rows.size()); List<ExamInfo> examInfos = new ArrayList<>(rows.size());
try { try {
for (int i = 1; i < rows.size(); i++) { for (Element row : rows) {
Elements columns = rows.get(i).getElementsByTag("td"); Elements columns = row.getElementsByTag("td");
ExamInfo examInfo = new ExamInfo(); ExamInfo examInfo = new ExamInfo();

@ -5,15 +5,15 @@ import cn.linghang.mywust.core.parser.Parser;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
public class SchemePageParser implements Parser<String> { public class TrainingPlanPageParser implements Parser<String> {
@Override @Override
public String parse(String html) throws ParseException { public String parse(String html) throws ParseException {
Element schemeElement = Jsoup.parse(html).getElementById("dataList"); Element trainingPlanElement = Jsoup.parse(html).getElementById("dataList");
if (schemeElement == null) { if (trainingPlanElement == null) {
throw new ParseException("教学方案html解析提取失败,id为dataList的元素不存在"); throw new ParseException("教学方案html解析提取失败,id为dataList的元素不存在");
} }
return schemeElement.outerHtml(); return trainingPlanElement.outerHtml();
} }
} }

@ -1,6 +1,7 @@
package cn.linghang.mywust.core.request; package cn.linghang.mywust.core.request.auth;
import cn.linghang.mywust.core.api.UnionAuth; import cn.linghang.mywust.core.api.UnionAuth;
import cn.linghang.mywust.core.request.RequestFactory;
import cn.linghang.mywust.network.entitys.HttpRequest; import cn.linghang.mywust.network.entitys.HttpRequest;
import cn.linghang.mywust.util.StringUtil; import cn.linghang.mywust.util.StringUtil;
@ -8,7 +9,7 @@ import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class AuthRequestFactory extends RequestFactory { public class UnionAuthRequestFactory extends RequestFactory {
public static HttpRequest unionLoginTGTRequest(String username, String password, String service) { public static HttpRequest unionLoginTGTRequest(String username, String password, String service) {
Map<String, String> requestForm = new HashMap<>(4); Map<String, String> requestForm = new HashMap<>(4);
requestForm.put("username", username); requestForm.put("username", username);

@ -1,6 +1,7 @@
package cn.linghang.mywust.core.request; package cn.linghang.mywust.core.request.library;
import cn.linghang.mywust.core.api.Library; import cn.linghang.mywust.core.api.Library;
import cn.linghang.mywust.core.request.RequestFactory;
import cn.linghang.mywust.network.entitys.HttpRequest; import cn.linghang.mywust.network.entitys.HttpRequest;
public class LibraryRequestFactory extends RequestFactory { public class LibraryRequestFactory extends RequestFactory {

@ -1,6 +1,7 @@
package cn.linghang.mywust.core.request; package cn.linghang.mywust.core.request.physics;
import cn.linghang.mywust.core.api.PhysicsSystem; import cn.linghang.mywust.core.api.PhysicsSystem;
import cn.linghang.mywust.core.request.RequestFactory;
import cn.linghang.mywust.network.entitys.HttpRequest; import cn.linghang.mywust.network.entitys.HttpRequest;
import cn.linghang.mywust.util.StringUtil; import cn.linghang.mywust.util.StringUtil;

@ -1,13 +1,14 @@
package cn.linghang.mywust.core.request; package cn.linghang.mywust.core.request.undergrade;
import cn.linghang.mywust.core.api.Bkjx; import cn.linghang.mywust.core.api.Bkjx;
import cn.linghang.mywust.core.request.RequestFactory;
import cn.linghang.mywust.network.entitys.FormBodyBuilder; import cn.linghang.mywust.network.entitys.FormBodyBuilder;
import cn.linghang.mywust.network.entitys.HttpRequest; import cn.linghang.mywust.network.entitys.HttpRequest;
import cn.linghang.mywust.util.StringUtil; import cn.linghang.mywust.util.StringUtil;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.TreeMap;
public class BkjxRequestFactory extends RequestFactory { public class BkjxRequestFactory extends RequestFactory {
public static HttpRequest sessionCookieRequest(String serviceTicket) { public static HttpRequest sessionCookieRequest(String serviceTicket) {
@ -20,7 +21,7 @@ public class BkjxRequestFactory extends RequestFactory {
public static HttpRequest examScoreInfoRequest(String cookies, String time, String courseKind, String courseName) { public static HttpRequest examScoreInfoRequest(String cookies, String time, String courseKind, String courseName) {
FormBodyBuilder formBodyBuilder = new FormBodyBuilder(); FormBodyBuilder formBodyBuilder = new FormBodyBuilder();
// 开课时间 // 开课时间(学期)
formBodyBuilder.addQueryParam("kksj", time); formBodyBuilder.addQueryParam("kksj", time);
// 课程性质 // 课程性质
@ -36,29 +37,17 @@ public class BkjxRequestFactory extends RequestFactory {
return makeHttpRequest(Bkjx.BKJX_EXAM_INFO_API, postData, cookies); return makeHttpRequest(Bkjx.BKJX_EXAM_INFO_API, postData, cookies);
} }
public static HttpRequest schemePageRequest(String cookies) { public static HttpRequest trainingPlanPageRequest(String cookies) {
return makeHttpRequest(Bkjx.BKJX_SCHEME_API, null, cookies); return makeHttpRequest(Bkjx.BKJX_SCHEME_API, null, cookies);
} }
public static class Legacy { public static HttpRequest courseTablePageRequest(String term, String cookies) {
public static HttpRequest dataStringRequest() { Map<String, String> params = new TreeMap<>(FormBodyBuilder.REPEATABLE_COMPARATOR);
return makeHttpRequest(Bkjx.Legacy.BKJX_DATA_STRING_API); params.putAll(CourseTableRequestParamFactory.COURSE_TABLE_MAGIC_QUERY_PARAMS);
} params.put("xnxq01id", term);
public static HttpRequest ticketRedirectRequest(String encode) {
Map<String, String> queryParams = new HashMap<>();
queryParams.put("userAccount", "");
queryParams.put("userPassword", "");
queryParams.put("encoded", encode);
String queryString = StringUtil.generateQueryString(queryParams); byte[] queryData = StringUtil.generateQueryString(params).getBytes(StandardCharsets.UTF_8);
Map<String, String> extendHeaders = new HashMap<>(); return makeHttpRequest(Bkjx.BKJX_COURSE_TABLE_API, queryData, cookies);
extendHeaders.put("Referer", "http://bkjx.wust.edu.cn/");
extendHeaders.put("Origin", "http://bkjx.wust.edu.cn");
return makeHttpRequest(Bkjx.Legacy.BKJX_SESSION_COOKIE_API, queryString.getBytes(StandardCharsets.UTF_8))
.addHeaders(extendHeaders);
}
} }
} }

@ -0,0 +1,56 @@
package cn.linghang.mywust.core.request.undergrade;
import cn.linghang.mywust.core.request.RequestFactory;
import cn.linghang.mywust.network.entitys.FormBodyBuilder;
import java.util.Map;
import java.util.TreeMap;
/**
* <p>课表请求参数生成</p>
* <p>由于请求参数生成部分过于庞大和丑陋不适合放在总的Factory因此单独拎出来了</p>
*
* @author lensfrex
* @create 2022-10-27 10:17
*/
public class CourseTableRequestParamFactory {
// 课表请求的谜之参数,貌似是固定的
// 其实都可以靠解析html中id为Form1的表单获得(input和select两种所有元素的值),这样可以防止系统作妖改掉这些值
// 但为了快速起见,免去重复解析,直接使用也是没有什么大问题的,估计在很长的一段时间内这部分都是不会改的
protected static final Map<String, String> COURSE_TABLE_MAGIC_QUERY_PARAMS = generateMagicQueryParam();
private static final String MAGIC_PARAM_PREFIX_0 = "7DF471C4FF954CFA9C691580B8214B36-";
private static final String MAGIC_PARAM_PREFIX_1 = "EEBED1036A7B4991BF01CBFD47908031-";
private static final String MAGIC_PARAM_PREFIX_2 = "711BA240F1B0462C9359E3CC28D57EBE-";
private static final String MAGIC_PARAM_PREFIX_3 = "2A9E45FD425B477AB74272EF70478E1A-";
private static final String MAGIC_PARAM_PREFIX_4 = "91826EDF3A594498A9F761D685EEAE96-";
private static final String MAGIC_PARAM_PREFIX_5 = "ACC171586F9245B09C86C589102423B4-";
private static Map<String, String> generateMagicQueryParam() {
Map<String, String> magicParams = new TreeMap<>(FormBodyBuilder.REPEATABLE_COMPARATOR);
for (int i = 0; i < 6; i++) {
magicParams.put("jx0415zbdiv_1", MAGIC_PARAM_PREFIX_0 + i + "-1");
magicParams.put("jx0415zbdiv_1", MAGIC_PARAM_PREFIX_1 + i + "-1");
magicParams.put("jx0415zbdiv_1", MAGIC_PARAM_PREFIX_2 + i + "-1");
magicParams.put("jx0415zbdiv_1", MAGIC_PARAM_PREFIX_3 + i + "-1");
magicParams.put("jx0415zbdiv_1", MAGIC_PARAM_PREFIX_4 + i + "-1");
magicParams.put("jx0415zbdiv_1", MAGIC_PARAM_PREFIX_5 + i + "-1");
magicParams.put("jx0415zbdiv_2", MAGIC_PARAM_PREFIX_0 + i + "-2");
magicParams.put("jx0415zbdiv_2", MAGIC_PARAM_PREFIX_1 + i + "-2");
magicParams.put("jx0415zbdiv_2", MAGIC_PARAM_PREFIX_2 + i + "-2");
magicParams.put("jx0415zbdiv_2", MAGIC_PARAM_PREFIX_3 + i + "-2");
magicParams.put("jx0415zbdiv_2", MAGIC_PARAM_PREFIX_4 + i + "-2");
magicParams.put("jx0415zbdiv_2", MAGIC_PARAM_PREFIX_5 + i + "-2");
}
magicParams.put("jx0404id", "");
magicParams.put("cj0701id", "");
magicParams.put("zc", "");
magicParams.put("demo", "");
magicParams.put("sfFD", "1");
magicParams.put("kbjcmsid", "9486203B90F3E3CBE0532914A8C03BE2");
return magicParams;
}
}

@ -3,13 +3,11 @@ package cn.linghang.mywust.core.service.auth;
import cn.linghang.mywust.core.api.Bkjx; import cn.linghang.mywust.core.api.Bkjx;
import cn.linghang.mywust.core.api.UnionAuth; import cn.linghang.mywust.core.api.UnionAuth;
import cn.linghang.mywust.core.exception.BasicException; import cn.linghang.mywust.core.exception.BasicException;
import cn.linghang.mywust.core.exception.PasswordWornException; import cn.linghang.mywust.core.request.undergrade.BkjxRequestFactory;
import cn.linghang.mywust.core.request.BkjxRequestFactory;
import cn.linghang.mywust.network.entitys.HttpRequest; import cn.linghang.mywust.network.entitys.HttpRequest;
import cn.linghang.mywust.network.entitys.HttpResponse; import cn.linghang.mywust.network.entitys.HttpResponse;
import cn.linghang.mywust.network.RequestClientOption; import cn.linghang.mywust.network.RequestClientOption;
import cn.linghang.mywust.network.Requester; import cn.linghang.mywust.network.Requester;
import cn.linghang.mywust.util.PasswordEncoder;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -32,51 +30,6 @@ public class JwcLogin {
this.unionLogin = unionLogin; this.unionLogin = unionLogin;
} }
/**
* <p>旧版的登录方式既相当于直接登录<a href="http://bkjx.wust.edu.cn">bkjx系统</a>不建议使用</p>
* <p>注意这种登录方式的密码和新版可能是不一样的</p>
* <p>不过不论使用哪种登录方式获取到的cookie都是可用的</p>
*
* @return 获取到的Cookies
*/
@Deprecated
public String getLoginCookieLegacy(String username, String password, RequestClientOption requestOption) throws IOException, BasicException {
// 获取某段神秘的dataStr(反正官网代码是这么叫的)
HttpRequest dataStringRequest = BkjxRequestFactory.Legacy.dataStringRequest();
HttpResponse dataStringResponse = requester.post(dataStringRequest, requestOption);
if (dataStringResponse.getBody() == null) {
throw new BasicException();
}
String dataString = new String(dataStringResponse.getBody());
// 获取登录ticket
String encoded = PasswordEncoder.legacyPassword(username, password, dataString);
HttpRequest ticketRequest = BkjxRequestFactory.Legacy.ticketRedirectRequest(encoded);
ticketRequest.setCookies(dataStringResponse.getCookies());
HttpResponse ticketResponse = requester.post(ticketRequest, requestOption);
if (ticketResponse.getBody() == null) {
throw new BasicException();
}
// 使用跳转得到的链接获取cookies
String sessionRedirect = ticketResponse.getHeaders().get("Location");
if (sessionRedirect == null) {
throw new PasswordWornException();
}
HttpRequest sessionRequest = BkjxRequestFactory.makeHttpRequest(sessionRedirect);
HttpResponse sessionResponse = requester.get(sessionRequest, requestOption);
String cookies = sessionResponse.getCookies();
if (roughCheckCookie(cookies)) {
throw new BasicException();
}
return cookies;
}
public String getLoginCookie(String username, String password, RequestClientOption requestOption) throws IOException, BasicException { public String getLoginCookie(String username, String password, RequestClientOption requestOption) throws IOException, BasicException {
// 获取service ticket以进行进一步的登录 // 获取service ticket以进行进一步的登录
String serviceTicket = unionLogin.getServiceTicket(username, password, UnionAuth.Service.BKJX_SSO_SERVICE, requestOption); String serviceTicket = unionLogin.getServiceTicket(username, password, UnionAuth.Service.BKJX_SSO_SERVICE, requestOption);

@ -3,7 +3,7 @@ package cn.linghang.mywust.core.service.auth;
import cn.linghang.mywust.core.api.Library; import cn.linghang.mywust.core.api.Library;
import cn.linghang.mywust.core.api.UnionAuth; import cn.linghang.mywust.core.api.UnionAuth;
import cn.linghang.mywust.core.exception.BasicException; import cn.linghang.mywust.core.exception.BasicException;
import cn.linghang.mywust.core.request.LibraryRequestFactory; import cn.linghang.mywust.core.request.library.LibraryRequestFactory;
import cn.linghang.mywust.network.entitys.HttpRequest; import cn.linghang.mywust.network.entitys.HttpRequest;
import cn.linghang.mywust.network.entitys.HttpResponse; import cn.linghang.mywust.network.entitys.HttpResponse;
import cn.linghang.mywust.network.RequestClientOption; import cn.linghang.mywust.network.RequestClientOption;

@ -3,7 +3,7 @@ package cn.linghang.mywust.core.service.auth;
import cn.linghang.mywust.core.exception.BasicException; import cn.linghang.mywust.core.exception.BasicException;
import cn.linghang.mywust.core.exception.PasswordWornException; import cn.linghang.mywust.core.exception.PasswordWornException;
import cn.linghang.mywust.core.parser.physics.PhysicsIndexPageParser; import cn.linghang.mywust.core.parser.physics.PhysicsIndexPageParser;
import cn.linghang.mywust.core.request.PhysicsSystemRequestFactory; import cn.linghang.mywust.core.request.physics.PhysicsSystemRequestFactory;
import cn.linghang.mywust.network.entitys.HttpRequest; import cn.linghang.mywust.network.entitys.HttpRequest;
import cn.linghang.mywust.network.entitys.HttpResponse; import cn.linghang.mywust.network.entitys.HttpResponse;
import cn.linghang.mywust.network.RequestClientOption; import cn.linghang.mywust.network.RequestClientOption;

@ -2,7 +2,7 @@ package cn.linghang.mywust.core.service.auth;
import cn.linghang.mywust.core.exception.BasicException; import cn.linghang.mywust.core.exception.BasicException;
import cn.linghang.mywust.core.exception.PasswordWornException; import cn.linghang.mywust.core.exception.PasswordWornException;
import cn.linghang.mywust.core.request.AuthRequestFactory; import cn.linghang.mywust.core.request.auth.UnionAuthRequestFactory;
import cn.linghang.mywust.network.entitys.HttpRequest; import cn.linghang.mywust.network.entitys.HttpRequest;
import cn.linghang.mywust.network.entitys.HttpResponse; import cn.linghang.mywust.network.entitys.HttpResponse;
import cn.linghang.mywust.network.RequestClientOption; import cn.linghang.mywust.network.RequestClientOption;
@ -29,7 +29,7 @@ public class UnionLogin {
String encodedPassword = PasswordEncoder.encodePassword(password); String encodedPassword = PasswordEncoder.encodePassword(password);
// 获取ticket granting ticket(TGT),以获取ServiceTicket // 获取ticket granting ticket(TGT),以获取ServiceTicket
HttpRequest TGTRequest = AuthRequestFactory.unionLoginTGTRequest(username, encodedPassword, serviceUrl); HttpRequest TGTRequest = UnionAuthRequestFactory.unionLoginTGTRequest(username, encodedPassword, serviceUrl);
HttpResponse unionAuthResponse = requester.post(TGTRequest, requestOption); HttpResponse unionAuthResponse = requester.post(TGTRequest, requestOption);
String redirectAddress = unionAuthResponse.getHeaders().get("Location"); String redirectAddress = unionAuthResponse.getHeaders().get("Location");
@ -38,7 +38,7 @@ public class UnionLogin {
} }
// 获取服务ticket(service ticket,ST) // 获取服务ticket(service ticket,ST)
HttpRequest serviceTicketRequest = AuthRequestFactory.loginTicketRequest(redirectAddress.replace("http:", "https:"), serviceUrl); HttpRequest serviceTicketRequest = UnionAuthRequestFactory.loginTicketRequest(redirectAddress.replace("http:", "https:"), serviceUrl);
HttpResponse serviceTicketResponse = requester.post(serviceTicketRequest, requestOption); HttpResponse serviceTicketResponse = requester.post(serviceTicketRequest, requestOption);
byte[] serviceTicketResponseData = serviceTicketResponse.getBody(); byte[] serviceTicketResponseData = serviceTicketResponse.getBody();

@ -0,0 +1,43 @@
package cn.linghang.mywust.core.service.undergraduate;
import cn.linghang.mywust.core.exception.CookieInvalidException;
import cn.linghang.mywust.core.exception.ParseException;
import cn.linghang.mywust.core.parser.undergraduate.CourseTableParser;
import cn.linghang.mywust.core.request.undergrade.BkjxRequestFactory;
import cn.linghang.mywust.core.util.BkjxUtil;
import cn.linghang.mywust.model.global.Course;
import cn.linghang.mywust.network.RequestClientOption;
import cn.linghang.mywust.network.Requester;
import cn.linghang.mywust.network.entitys.HttpRequest;
import cn.linghang.mywust.network.entitys.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
public class CourseTableApi extends UndergraduateApi {
private static final Logger log = LoggerFactory.getLogger(CourseTableApi.class);
private static final CourseTableParser parser = new CourseTableParser();
private final Requester requester;
public CourseTableApi(Requester requester) {
this.requester = requester;
}
public String getCourseTablePage(String term, String cookies, RequestClientOption requestClientOption) throws IOException, CookieInvalidException {
HttpRequest request = BkjxRequestFactory.courseTablePageRequest(term, cookies);
HttpResponse response = requester.post(request, requestClientOption);
checkResponse(response, cookies);
return new String(response.getBody());
}
public List<Course> getCourseTable(String term, String cookies, RequestClientOption requestClientOption) throws IOException, CookieInvalidException, ParseException {
String page = this.getCourseTablePage(term, cookies, requestClientOption);
return parser.parse(page);
}
}

@ -3,7 +3,7 @@ package cn.linghang.mywust.core.service.undergraduate;
import cn.linghang.mywust.core.exception.CookieInvalidException; import cn.linghang.mywust.core.exception.CookieInvalidException;
import cn.linghang.mywust.core.exception.ParseException; import cn.linghang.mywust.core.exception.ParseException;
import cn.linghang.mywust.core.parser.undergraduate.ExamInfoParser; import cn.linghang.mywust.core.parser.undergraduate.ExamInfoParser;
import cn.linghang.mywust.core.request.BkjxRequestFactory; import cn.linghang.mywust.core.request.undergrade.BkjxRequestFactory;
import cn.linghang.mywust.core.util.BkjxUtil; import cn.linghang.mywust.core.util.BkjxUtil;
import cn.linghang.mywust.model.undergrade.ExamInfo; import cn.linghang.mywust.model.undergrade.ExamInfo;
import cn.linghang.mywust.network.RequestClientOption; import cn.linghang.mywust.network.RequestClientOption;
@ -16,7 +16,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
public class ExamInfoApi { public class ExamInfoApi extends UndergraduateApi {
private static final Logger log = LoggerFactory.getLogger(ExamInfoApi.class); private static final Logger log = LoggerFactory.getLogger(ExamInfoApi.class);
private final Requester requester; private final Requester requester;
@ -31,13 +31,7 @@ public class ExamInfoApi {
HttpRequest request = BkjxRequestFactory.examScoreInfoRequest(cookies, "", "", ""); HttpRequest request = BkjxRequestFactory.examScoreInfoRequest(cookies, "", "", "");
HttpResponse response = requester.post(request, requestClientOption); HttpResponse response = requester.post(request, requestClientOption);
// 检查响应是否正确 checkResponse(response, cookies);
if (response.getBody() == null ||
response.getStatusCode() != HttpResponse.HTTP_OK ||
BkjxUtil.checkLoginFinger(response.getBody())) {
throw new CookieInvalidException("[请求获取成绩]:Cookie无效:" + cookies);
}
return new String(response.getBody()); return new String(response.getBody());
} }

@ -1,43 +0,0 @@
package cn.linghang.mywust.core.service.undergraduate;
import cn.linghang.mywust.core.exception.CookieInvalidException;
import cn.linghang.mywust.core.exception.ParseException;
import cn.linghang.mywust.core.parser.undergraduate.SchemePageParser;
import cn.linghang.mywust.core.request.BkjxRequestFactory;
import cn.linghang.mywust.core.util.BkjxUtil;
import cn.linghang.mywust.network.RequestClientOption;
import cn.linghang.mywust.network.Requester;
import cn.linghang.mywust.network.entitys.HttpRequest;
import cn.linghang.mywust.network.entitys.HttpResponse;
import java.io.IOException;
public class SchemeApi {
private final Requester requester;
private static final SchemePageParser parser = new SchemePageParser();
public SchemeApi(Requester requester) {
this.requester = requester;
}
public String getSchemePage(String cookies, RequestClientOption requestClientOption) throws CookieInvalidException, IOException {
HttpRequest request = BkjxRequestFactory.schemePageRequest(cookies);
HttpResponse response = requester.get(request, requestClientOption);
// 检查响应是否正确
if (response.getBody() == null ||
response.getStatusCode() != HttpResponse.HTTP_OK ||
BkjxUtil.checkLoginFinger(response.getBody())) {
throw new CookieInvalidException("[请求获取成绩]:Cookie无效:" + cookies);
}
return new String(response.getBody());
}
public String getPrueSchemePage(String cookies, RequestClientOption requestClientOption) throws IOException, CookieInvalidException, ParseException {
String fullPage = this.getSchemePage(cookies, requestClientOption);
return parser.parse(fullPage);
}
}

@ -3,7 +3,7 @@ package cn.linghang.mywust.core.service.undergraduate;
import cn.linghang.mywust.core.exception.CookieInvalidException; import cn.linghang.mywust.core.exception.CookieInvalidException;
import cn.linghang.mywust.core.exception.ParseException; import cn.linghang.mywust.core.exception.ParseException;
import cn.linghang.mywust.core.parser.undergraduate.StudentInfoPageParser; import cn.linghang.mywust.core.parser.undergraduate.StudentInfoPageParser;
import cn.linghang.mywust.core.request.BkjxRequestFactory; import cn.linghang.mywust.core.request.undergrade.BkjxRequestFactory;
import cn.linghang.mywust.core.util.BkjxUtil; import cn.linghang.mywust.core.util.BkjxUtil;
import cn.linghang.mywust.model.undergrade.StudentInfo; import cn.linghang.mywust.model.undergrade.StudentInfo;
import cn.linghang.mywust.network.RequestClientOption; import cn.linghang.mywust.network.RequestClientOption;
@ -13,7 +13,7 @@ import cn.linghang.mywust.network.entitys.HttpResponse;
import java.io.IOException; import java.io.IOException;
public class StudentInfoApi { public class StudentInfoApi extends UndergraduateApi {
private final Requester requester; private final Requester requester;
private static final StudentInfoPageParser parser = new StudentInfoPageParser(); private static final StudentInfoPageParser parser = new StudentInfoPageParser();
@ -26,13 +26,7 @@ public class StudentInfoApi {
HttpRequest request = BkjxRequestFactory.studentInfoRequest(cookies); HttpRequest request = BkjxRequestFactory.studentInfoRequest(cookies);
HttpResponse response = requester.get(request, requestOption); HttpResponse response = requester.get(request, requestOption);
// 检查响应是否正确 checkResponse(response, cookies);
if (response.getBody() == null ||
response.getStatusCode() != HttpResponse.HTTP_OK ||
BkjxUtil.checkLoginFinger(response.getBody())) {
throw new CookieInvalidException();
}
return new String(response.getBody()); return new String(response.getBody());
} }

@ -0,0 +1,36 @@
package cn.linghang.mywust.core.service.undergraduate;
import cn.linghang.mywust.core.exception.CookieInvalidException;
import cn.linghang.mywust.core.exception.ParseException;
import cn.linghang.mywust.core.parser.undergraduate.TrainingPlanPageParser;
import cn.linghang.mywust.core.request.undergrade.BkjxRequestFactory;
import cn.linghang.mywust.network.RequestClientOption;
import cn.linghang.mywust.network.Requester;
import cn.linghang.mywust.network.entitys.HttpRequest;
import cn.linghang.mywust.network.entitys.HttpResponse;
import java.io.IOException;
public class TrainingPlanApi extends UndergraduateApi {
private final Requester requester;
private static final TrainingPlanPageParser parser = new TrainingPlanPageParser();
public TrainingPlanApi(Requester requester) {
this.requester = requester;
}
public String getTrainingPlanPage(String cookies, RequestClientOption requestClientOption) throws CookieInvalidException, IOException {
HttpRequest request = BkjxRequestFactory.trainingPlanPageRequest(cookies);
HttpResponse response = requester.get(request, requestClientOption);
checkResponse(response, cookies);
return new String(response.getBody());
}
public String getPrueSchemePage(String cookies, RequestClientOption requestClientOption) throws IOException, CookieInvalidException, ParseException {
String fullPage = this.getTrainingPlanPage(cookies, requestClientOption);
return parser.parse(fullPage);
}
}

@ -0,0 +1,17 @@
package cn.linghang.mywust.core.service.undergraduate;
import cn.linghang.mywust.core.exception.CookieInvalidException;
import cn.linghang.mywust.core.util.BkjxUtil;
import cn.linghang.mywust.network.entitys.HttpResponse;
public class UndergraduateApi {
public void checkResponse(HttpResponse response, String cookies) throws CookieInvalidException {
// 检查响应是否正确
if (response.getBody() == null ||
response.getStatusCode() != HttpResponse.HTTP_OK ||
BkjxUtil.checkLoginFinger(response.getBody())) {
throw new CookieInvalidException("响应无效,cookie可能过期:" + cookies);
}
}
}

@ -46,12 +46,12 @@ public class Course {
/** /**
* 开始时间 * 开始时间
*/ */
private int start; private int startSection;
/** /**
* 结束时间 * 结束时间
*/ */
private int end; private int endSection;
private ClassRoom classroom; private ClassRoom classroom;

@ -1,4 +1,4 @@
package cn.linghang.mywust.model; package cn.linghang.mywust.model.physics;
import cn.linghang.mywust.model.global.Course; import cn.linghang.mywust.model.global.Course;
import lombok.Getter; import lombok.Getter;

@ -1,12 +1,16 @@
package cn.linghang.mywust.network.entitys; package cn.linghang.mywust.network.entitys;
import cn.linghang.mywust.util.RepeatableComparator;
import cn.linghang.mywust.util.StringUtil; import cn.linghang.mywust.util.StringUtil;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
public class FormBodyBuilder { public class FormBodyBuilder {
public static final Comparator<String> REPEATABLE_COMPARATOR = new RepeatableComparator();
private final Map<String, String> queryParams; private final Map<String, String> queryParams;
public FormBodyBuilder(Map<String, String> queryParams) { public FormBodyBuilder(Map<String, String> queryParams) {
@ -14,7 +18,15 @@ public class FormBodyBuilder {
} }
public FormBodyBuilder() { public FormBodyBuilder() {
this.queryParams = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); this(false);
}
public FormBodyBuilder(boolean repeatable) {
if (repeatable) {
this.queryParams = new TreeMap<>(REPEATABLE_COMPARATOR);
} else {
this.queryParams = new HashMap<>();
}
} }
public FormBodyBuilder(int initSize) { public FormBodyBuilder(int initSize) {
@ -38,4 +50,7 @@ public class FormBodyBuilder {
public String buildAndToString() { public String buildAndToString() {
return StringUtil.generateQueryString(this.queryParams); return StringUtil.generateQueryString(this.queryParams);
} }
} }

@ -0,0 +1,50 @@
import cn.linghang.mywust.core.exception.BasicException;
import cn.linghang.mywust.core.service.undergraduate.CourseTableApi;
import cn.linghang.mywust.core.service.undergraduate.ExamInfoApi;
import cn.linghang.mywust.model.global.Course;
import cn.linghang.mywust.model.undergrade.ExamInfo;
import cn.linghang.mywust.network.RequestClientOption;
import cn.linghang.mywust.network.Requester;
import cn.linghang.mywust.network.okhttp.SimpleOkhttpRequester;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;
public class CourseTableTest {
public static void main(String[] args) throws BasicException, IOException {
new CourseTableTest().run();
}
private void run() throws BasicException, IOException {
System.out.println("成绩获取");
System.out.println("Cookie:");
Scanner scanner = new Scanner(System.in);
String cookie = scanner.nextLine();
System.out.println("使用Cookie:" + cookie);
System.out.println("学期(如2022-2023-1):");
String term = scanner.nextLine();
System.out.println("使用学期:" + term);
Requester requester = new SimpleOkhttpRequester();
CourseTableApi service = new CourseTableApi(requester);
RequestClientOption option = new RequestClientOption();
option.setTimeout(5);
RequestClientOption.Proxy proxy = new RequestClientOption.Proxy();
proxy.setPort(6060);
proxy.setAddress("127.0.0.1");
option.setProxy(proxy);
option.setFallowUrlRedirect(false);
List<Course> courses = service.getCourseTable("2023-2024-2", cookie, option);
for (Course info : courses) {
System.out.println(info);
}
}
}

@ -1,42 +0,0 @@
import cn.linghang.mywust.core.exception.BasicException;
import cn.linghang.mywust.core.service.auth.UnionLogin;
import cn.linghang.mywust.core.service.auth.JwcLogin;
import cn.linghang.mywust.network.RequestClientOption;
import cn.linghang.mywust.network.Requester;
import cn.linghang.mywust.network.okhttp.SimpleOkhttpRequester;
import java.io.IOException;
import java.util.Scanner;
public class JwcLegacyLoginTest {
public static void main(String[] args) throws BasicException, IOException {
new JwcLegacyLoginTest().run();
}
private void run() throws BasicException, IOException {
System.out.println("bkjx旧版直登测试");
System.out.println("输入账号(学号)和密码,用“ ”(空格)分割");
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
String username = input.split(" ")[0];
String password = input.split(" ")[1];
System.out.println("账号:" + username);
System.out.println("密码:" + password);
Requester requester = new SimpleOkhttpRequester();
JwcLogin jwcLogin = new JwcLogin(requester, new UnionLogin(requester));
RequestClientOption option = new RequestClientOption();
option.setTimeout(5);
option.setProxy(null);
option.setFallowUrlRedirect(false);
String cookies = jwcLogin.getLoginCookieLegacy(username, password, option);
System.out.println(cookies);
}
}

@ -1,13 +1,10 @@
import cn.linghang.mywust.core.exception.BasicException; import cn.linghang.mywust.core.exception.BasicException;
import cn.linghang.mywust.core.service.undergraduate.ExamInfoApi; import cn.linghang.mywust.core.service.undergraduate.TrainingPlanApi;
import cn.linghang.mywust.core.service.undergraduate.SchemeApi;
import cn.linghang.mywust.model.undergrade.ExamInfo;
import cn.linghang.mywust.network.RequestClientOption; import cn.linghang.mywust.network.RequestClientOption;
import cn.linghang.mywust.network.Requester; import cn.linghang.mywust.network.Requester;
import cn.linghang.mywust.network.okhttp.SimpleOkhttpRequester; import cn.linghang.mywust.network.okhttp.SimpleOkhttpRequester;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.Scanner; import java.util.Scanner;
public class SchemeTest { public class SchemeTest {
@ -34,7 +31,7 @@ public class SchemeTest {
option.setFallowUrlRedirect(false); option.setFallowUrlRedirect(false);
Requester requester = new SimpleOkhttpRequester(); Requester requester = new SimpleOkhttpRequester();
SchemeApi jwcService = new SchemeApi(requester); TrainingPlanApi jwcService = new TrainingPlanApi(requester);
String page = jwcService.getPrueSchemePage(cookie, option); String page = jwcService.getPrueSchemePage(cookie, option);

@ -0,0 +1,10 @@
package cn.linghang.mywust.util;
import java.util.Comparator;
public class RepeatableComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
return 1;
}
}

@ -4,11 +4,11 @@ import com.google.common.base.Joiner;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Map;
public class StringUtil { public class StringUtil {
private static final RepeatableComparator REPEATABLE_COMPARATOR = new RepeatableComparator();
/** /**
* 将map转换成url请求表单格式的请求字符串类似于 user=admin&passwd=123456&time=11111 这种 * 将map转换成url请求表单格式的请求字符串类似于 user=admin&passwd=123456&time=11111 这种
* *
@ -17,12 +17,13 @@ public class StringUtil {
*/ */
public static String generateQueryString(Map<String, String> queryParams) { public static String generateQueryString(Map<String, String> queryParams) {
// 自动对value值进行url编码 // 自动对value值进行url编码
queryParams.forEach((k, v) -> queryParams.put(k, URLEncoder.encode(v, StandardCharsets.UTF_8))); Map<String, String> urlEncodedQueryParams = new TreeMap<>(REPEATABLE_COMPARATOR);
queryParams.forEach((k, v) -> urlEncodedQueryParams.put(k, URLEncoder.encode(v, StandardCharsets.UTF_8)));
return Joiner.on('&') return Joiner.on('&')
.useForNull("") .useForNull("")
.withKeyValueSeparator('=') .withKeyValueSeparator('=')
.join(queryParams); .join(urlEncodedQueryParams);
} }
/** /**

Loading…
Cancel
Save