parent
efab489d17
commit
0e85b3e58c
@ -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(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -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.request.RequestFactory; |
||||
import cn.linghang.mywust.network.entitys.HttpRequest; |
||||
|
||||
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.request.RequestFactory; |
||||
import cn.linghang.mywust.network.entitys.HttpRequest; |
||||
import cn.linghang.mywust.util.StringUtil; |
||||
|
@ -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; |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
||||
} |
@ -1,4 +1,4 @@ |
||||
package cn.linghang.mywust.model; |
||||
package cn.linghang.mywust.model.physics; |
||||
|
||||
import cn.linghang.mywust.model.global.Course; |
||||
import lombok.Getter; |
@ -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); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
Loading…
Reference in new issue