parent
ec3f252a31
commit
976b2fd54b
@ -0,0 +1,9 @@ |
|||||||
|
package cn.linghang.mywust.core.api; |
||||||
|
|
||||||
|
public class Graduate { |
||||||
|
public static final String GRADUATE_CAPTCHA_API = "http://59.68.177.189/pyxx/PageTemplate/NsoftPage/yzm/createyzm.aspx"; |
||||||
|
public static final String GRADUATE_LOGIN_API = "http://59.68.177.189/pyxx/login.aspx"; |
||||||
|
|
||||||
|
public static final String GRADUATE_STUDENT_INFO_API = "http://59.68.177.189/pyxx/grgl/student_info1.aspx"; |
||||||
|
public static final String GRADUATE_COURSE_TABLE_API = "http://59.68.177.189/pyxx/pygl/kbcx_xs.aspx"; |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
package cn.linghang.mywust.core.exception; |
||||||
|
|
||||||
|
public class ApiException extends BasicException { |
||||||
|
private final int code; |
||||||
|
|
||||||
|
public ApiException(int code) { |
||||||
|
this.code = code; |
||||||
|
} |
||||||
|
|
||||||
|
public ApiException(int code, String message) { |
||||||
|
super(message); |
||||||
|
this.code = code; |
||||||
|
} |
||||||
|
|
||||||
|
public int getCode() { |
||||||
|
return code; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return "接口调用异常: " + code; |
||||||
|
} |
||||||
|
|
||||||
|
public static class ApiExceptionCode { |
||||||
|
// 统一认证的异常
|
||||||
|
public static final int UNI_LOGIN_PASSWORD_WRONG = 100101; |
||||||
|
public static final int UNI_LOGIN_USER_NOT_EXISTS = 100102; |
||||||
|
public static final int UNI_LOGIN_USER_BANNED = 100103; |
||||||
|
|
||||||
|
// 共有异常码:cookie无效
|
||||||
|
// 放一起了
|
||||||
|
public static final int BKJX_COOKIE_INVALID = 110101; |
||||||
|
public static final int LIBRARY_COOKIE_INVALID = 12101; |
||||||
|
public static final int PHYSICS_COOKIE_INVALID = 130101; |
||||||
|
public static final int GRADE_COOKIE_INVALID = 140101; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,121 @@ |
|||||||
|
package cn.linghang.mywust.core.parser.postgraduate; |
||||||
|
|
||||||
|
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.Document; |
||||||
|
import org.jsoup.nodes.Element; |
||||||
|
import org.jsoup.select.Elements; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.regex.Matcher; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
public class GraduateCourseTablePageParser implements Parser<List<Course>> { |
||||||
|
|
||||||
|
private static final Pattern COURSE_TABLE_REGEX = Pattern.compile("课程:(?<name>.*?)<br>班级:(?<class>.*?)<br>\\((?<classRoom>.*?)\\)<br>(?<week>.*?)<br>(?<section>.*?)<br>主讲教师:(?<teacher>.*?)<br>"); |
||||||
|
|
||||||
|
private static final Pattern WEEK_REGEX = Pattern.compile("(?<startWeek>\\d+)-(?<endWeek>\\d+)周.*?星期(?<weekDay>[一二三四五六七日天])"); |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<Course> parse(String html) throws ParseException { |
||||||
|
Document document = Jsoup.parse(html); |
||||||
|
Element table = document.getElementById("DataGrid1"); |
||||||
|
if (table == null) { |
||||||
|
throw new ParseException("解析研究生课表失败:关键元素不存在...", html); |
||||||
|
} |
||||||
|
|
||||||
|
// 初步拿到所有的课程格子
|
||||||
|
Elements girds = table.getElementsByAttribute("rowspan"); |
||||||
|
String girdsHtml = girds.outerHtml(); |
||||||
|
|
||||||
|
List<Course> courses = new ArrayList<>(girds.size()); |
||||||
|
Course.CourseBuilder courseBuilder = Course.builder(); |
||||||
|
|
||||||
|
// 正则提取每一段课程文本
|
||||||
|
Matcher matcher = COURSE_TABLE_REGEX.matcher(girdsHtml); |
||||||
|
while (matcher.find()) { |
||||||
|
String name = matcher.group("name"); |
||||||
|
courseBuilder.name(name); |
||||||
|
|
||||||
|
String teachClass = matcher.group("class"); |
||||||
|
courseBuilder.teachClass(teachClass); |
||||||
|
|
||||||
|
String classroom = matcher.group("classRoom"); |
||||||
|
courseBuilder.classroom(new ClassRoom("", "", "", classroom)); |
||||||
|
|
||||||
|
String teacher = matcher.group("teacher"); |
||||||
|
courseBuilder.teacher(teacher); |
||||||
|
|
||||||
|
String weekStr = matcher.group("week"); |
||||||
|
this.parseWeek(weekStr, courseBuilder); |
||||||
|
|
||||||
|
String section = matcher.group("section"); |
||||||
|
this.fillCourseList(section, courseBuilder, courses); |
||||||
|
} |
||||||
|
|
||||||
|
return courses; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 解析周次信息文本 |
||||||
|
* |
||||||
|
* @param weekText 周次文本,如“3-14周:连续周 星期三” |
||||||
|
* @param builder Lesson的builder |
||||||
|
*/ |
||||||
|
private void parseWeek(String weekText, Course.CourseBuilder builder) { |
||||||
|
Matcher matcher = WEEK_REGEX.matcher(weekText); |
||||||
|
if (matcher.find()) { |
||||||
|
String startWeek = matcher.group("startWeek"); |
||||||
|
String endWeek = matcher.group("endWeek"); |
||||||
|
String weekDay = matcher.group("weekDay"); |
||||||
|
|
||||||
|
builder.startWeek(Integer.parseInt(startWeek)); |
||||||
|
builder.endWeek(Integer.parseInt(endWeek)); |
||||||
|
builder.weekDay(Course.getWeekDayNumber(weekDay)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 解析节次,并将解析出来的完整的节次放入List中 |
||||||
|
* |
||||||
|
* @param sectionText 提取出来的节次文本,如“上1,上2,上3,上4,下1,下2” |
||||||
|
* @param builder LessonImpl中的builder |
||||||
|
* @param courses 存放Lesson的List |
||||||
|
*/ |
||||||
|
private void fillCourseList(String sectionText, Course.CourseBuilder builder, List<Course> courses) { |
||||||
|
String[] sections = sectionText.split(","); |
||||||
|
for (int i = 0; i < sections.length / 2; i += 2) { |
||||||
|
int startSection = this.getSection(sections[i]); |
||||||
|
int endSection = this.getSection(sections[i + 1]); |
||||||
|
|
||||||
|
builder.startSection(startSection); |
||||||
|
builder.endSection(endSection); |
||||||
|
|
||||||
|
courses.add(builder.build()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 将上1,下2,晚1这种相对的节次格式转换为相应的绝对节次 |
||||||
|
* |
||||||
|
* @param time 类似于上1,下2,晚1这种相对的节次格式文本 |
||||||
|
* @return 相应的绝对节次 |
||||||
|
*/ |
||||||
|
private int getSection(String time) { |
||||||
|
int i = time.charAt(1) - 48; |
||||||
|
switch (time.charAt(0)) { |
||||||
|
case '上': |
||||||
|
return i; |
||||||
|
case '下': |
||||||
|
return i + 4; |
||||||
|
case '晚': |
||||||
|
return i + 8; |
||||||
|
default: |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,97 @@ |
|||||||
|
package cn.linghang.mywust.core.parser.postgraduate; |
||||||
|
|
||||||
|
import cn.linghang.mywust.core.exception.ParseException; |
||||||
|
import cn.linghang.mywust.core.parser.Parser; |
||||||
|
import cn.linghang.mywust.model.global.StudentInfo; |
||||||
|
import org.jsoup.Jsoup; |
||||||
|
import org.jsoup.nodes.Element; |
||||||
|
|
||||||
|
public class GraduateStudentInfoPageParser implements Parser<StudentInfo> { |
||||||
|
|
||||||
|
private static final String INFO_TABLE_XPATH = "//*[@id=\"Form1\"]/table/tbody/tr[2]/td/table/tbody/tr[2]/td/table"; |
||||||
|
|
||||||
|
@Override |
||||||
|
public StudentInfo parse(String html) throws ParseException { |
||||||
|
Element table = Jsoup.parse(html).selectXpath(INFO_TABLE_XPATH).get(0); |
||||||
|
|
||||||
|
StudentInfo student = new StudentInfo(); |
||||||
|
|
||||||
|
Element studentNumberElement = table.getElementById("txtxh"); |
||||||
|
student.setStudentNumber(this.getAttr(studentNumberElement, "value")); |
||||||
|
|
||||||
|
// Element gradeElement = table.getElementById("txtnj");
|
||||||
|
// int grade = Integer.parseInt(this.getAttr(gradeElement, "value"));
|
||||||
|
|
||||||
|
Element idNumberElement = table.getElementById("txtsfzh"); |
||||||
|
student.setIdNumber(this.getAttr(idNumberElement, "value")); |
||||||
|
|
||||||
|
Element nationalityElement = table.getElementById("drpmz"); |
||||||
|
student.setNationality(this.getSelectContent(nationalityElement)); |
||||||
|
|
||||||
|
Element hometownElement = table.getElementById("txtjg"); |
||||||
|
student.setHometown(this.getAttr(hometownElement, "value")); |
||||||
|
|
||||||
|
Element nameElement = table.getElementById("txtxm"); |
||||||
|
student.setName(this.getAttr(nameElement, "value")); |
||||||
|
|
||||||
|
Element birthdayElement = table.getElementById("lblcsrq"); |
||||||
|
student.setBirthday(this.getText(birthdayElement)); |
||||||
|
|
||||||
|
Element collegeElement = table.getElementById("droyx"); |
||||||
|
student.setCollege(this.getSelectContent(collegeElement)); |
||||||
|
|
||||||
|
Element majorElement = table.getElementById("drozy"); |
||||||
|
student.setMajor(this.getSelectContent(majorElement)); |
||||||
|
|
||||||
|
Element genderElement = table.getElementById("droxb"); |
||||||
|
|
||||||
|
student.setSex(this.getSelectContent(genderElement)); |
||||||
|
|
||||||
|
// 研究生没有班级
|
||||||
|
student.setClazz(""); |
||||||
|
|
||||||
|
return student; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 从Element中拿到指定的标签值 |
||||||
|
* @param element 元素对象 |
||||||
|
* @param key 标签值的key |
||||||
|
* @return 相应的值,若element为空则返回空字符串 |
||||||
|
*/ |
||||||
|
private String getAttr(Element element, String key) { |
||||||
|
if (element == null) { |
||||||
|
return ""; |
||||||
|
} else { |
||||||
|
return element.attr(key); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 从Element中拿到指定的文本内容 |
||||||
|
* @param element 元素对象 |
||||||
|
* @return 相应的值,若element为空则返回空字符串 |
||||||
|
*/ |
||||||
|
public String getText(Element element) { |
||||||
|
if (element == null) { |
||||||
|
return ""; |
||||||
|
} else { |
||||||
|
return element.ownText(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 从select类型的Element中拿取到已选中的选项值 |
||||||
|
* @param element 元素对象 |
||||||
|
* @return 相应的值,若element为空则返回空字符串 |
||||||
|
*/ |
||||||
|
public String getSelectContent(Element element) { |
||||||
|
if (element == null) { |
||||||
|
return ""; |
||||||
|
} else { |
||||||
|
return element.getElementsByAttributeValue("selected", "selected") |
||||||
|
.get(0) |
||||||
|
.ownText(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
package cn.linghang.mywust.core.request.graduate; |
||||||
|
|
||||||
|
import cn.linghang.mywust.captcha.SolvedImageCaptcha; |
||||||
|
import cn.linghang.mywust.core.api.Graduate; |
||||||
|
import cn.linghang.mywust.core.request.RequestFactory; |
||||||
|
import cn.linghang.mywust.network.entitys.HttpRequest; |
||||||
|
import cn.linghang.mywust.util.StringUtil; |
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
public class GraduateRequestFactory extends RequestFactory { |
||||||
|
public static HttpRequest captchaRequest() { |
||||||
|
return makeHttpRequest(Graduate.GRADUATE_CAPTCHA_API); |
||||||
|
} |
||||||
|
|
||||||
|
private static final Map<String,String> CONST_PARAMS = new HashMap<>(5); |
||||||
|
static { |
||||||
|
CONST_PARAMS.put("__VIEWSTATE", "/wEPDwUENTM4MWQYAQUeX19Db250cm9sc1JlcXVpcmVQb3N0QmFja0tleV9fFgIFEl9jdGwwOkltYWdlQnV0dG9uMQUSX2N0bDA6SW1hZ2VCdXR0b24yXnZLY54iSWFQ6B2yKH0EisNqU3/eKWEJPibQUElowzU="); |
||||||
|
CONST_PARAMS.put("__EVENTVALIDATION", "/wEdAAYVkBquZFuFxLpraDgB64v+UDagjadrq+xukJizXKfuf485DjYUnSc4B1y8D5WGXeCaN+cQ7B52HzGj0ueO5HRlbdfASR9MjKgO1uRUmJC5kWf476Bpzok4CsBoBh+4Dc7vLkoP0tXUghu7H8qg++pYHeGok+i2xPFtG5oj0qB2dw=="); |
||||||
|
CONST_PARAMS.put("__VIEWSTATEGENERATOR", "496CE0B8"); |
||||||
|
CONST_PARAMS.put("_ctl0:ImageButton1.x", "39"); |
||||||
|
CONST_PARAMS.put("_ctl0:ImageButton1.y", "10"); |
||||||
|
} |
||||||
|
|
||||||
|
public static HttpRequest loginRequest(String username, String password, SolvedImageCaptcha captcha) { |
||||||
|
Map<String, String> params = new HashMap<>(7); |
||||||
|
params.putAll(CONST_PARAMS); |
||||||
|
params.put("_ctl0:txtusername", username); |
||||||
|
params.put("_ctl0:txtpassword", password); |
||||||
|
params.put("_ctl0:txtyzm", captcha.getResult()); |
||||||
|
|
||||||
|
byte[] requestData = StringUtil.generateQueryString(params).getBytes(StandardCharsets.UTF_8); |
||||||
|
|
||||||
|
return makeHttpRequest(Graduate.GRADUATE_LOGIN_API, requestData); |
||||||
|
} |
||||||
|
|
||||||
|
public static HttpRequest studentInfoRequest(String cookie) { |
||||||
|
return makeHttpRequest(Graduate.GRADUATE_STUDENT_INFO_API, null, cookie); |
||||||
|
} |
||||||
|
|
||||||
|
public static HttpRequest courseTableRequest(String cookie) { |
||||||
|
return makeHttpRequest(Graduate.GRADUATE_COURSE_TABLE_API, null, cookie); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
package cn.linghang.mywust.captcha; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
/** |
||||||
|
* 已处理的图片验证码 |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class SolvedImageCaptcha { |
||||||
|
/** |
||||||
|
* 和验证码绑定的信息,如cookie,某种id等 |
||||||
|
*/ |
||||||
|
private String bindInfo; |
||||||
|
|
||||||
|
/** |
||||||
|
* 验证码验证数据 |
||||||
|
*/ |
||||||
|
private String result; |
||||||
|
|
||||||
|
public SolvedImageCaptcha(UnsolvedImageCaptcha unsolvedImageCaptcha) { |
||||||
|
this.bindInfo = unsolvedImageCaptcha.getBindInfo(); |
||||||
|
} |
||||||
|
|
||||||
|
private void setBindInfo(String bindInfo) {} |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
package cn.linghang.mywust.captcha; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
/** |
||||||
|
* 待处理的图片验证码 |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class UnsolvedImageCaptcha { |
||||||
|
/** |
||||||
|
* 和验证码绑定的信息,如cookie,某种id等 |
||||||
|
*/ |
||||||
|
private String bindInfo; |
||||||
|
|
||||||
|
/** |
||||||
|
* 验证码图片,使用byte数组存储 |
||||||
|
*/ |
||||||
|
private byte[] image; |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
package cn.linghang.mywust.captcha.solver; |
||||||
|
|
||||||
|
import cn.linghang.mywust.captcha.SolvedImageCaptcha; |
||||||
|
import cn.linghang.mywust.captcha.UnsolvedImageCaptcha; |
||||||
|
|
||||||
|
public interface CaptchaSolver { |
||||||
|
SolvedImageCaptcha solve(UnsolvedImageCaptcha unsolvedImageCaptcha); |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
package cn.linghang.mywust.captcha.solver; |
||||||
|
|
||||||
|
import cn.linghang.mywust.captcha.SolvedImageCaptcha; |
||||||
|
import cn.linghang.mywust.captcha.UnsolvedImageCaptcha; |
||||||
|
|
||||||
|
public class LinghangOcrServiceCaptchaSolver implements CaptchaSolver { |
||||||
|
@Override |
||||||
|
public SolvedImageCaptcha solve(UnsolvedImageCaptcha unsolvedImageCaptcha) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
package cn.linghang.mywust.captcha.solver; |
||||||
|
|
||||||
|
import cn.linghang.mywust.captcha.SolvedImageCaptcha; |
||||||
|
import cn.linghang.mywust.captcha.UnsolvedImageCaptcha; |
||||||
|
|
||||||
|
public class LocalPaddleOcrCaptchaSolver implements CaptchaSolver { |
||||||
|
@Override |
||||||
|
public SolvedImageCaptcha solve(UnsolvedImageCaptcha unsolvedImageCaptcha) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
@ -1,4 +1,4 @@ |
|||||||
package cn.linghang.mywust.model.undergrade; |
package cn.linghang.mywust.model.global; |
||||||
|
|
||||||
import lombok.AllArgsConstructor; |
import lombok.AllArgsConstructor; |
||||||
import lombok.Builder; |
import lombok.Builder; |
@ -1,4 +1,4 @@ |
|||||||
package cn.linghang.mywust.model.undergrade; |
package cn.linghang.mywust.model.global; |
||||||
|
|
||||||
import lombok.AllArgsConstructor; |
import lombok.AllArgsConstructor; |
||||||
import lombok.Builder; |
import lombok.Builder; |
Loading…
Reference in new issue