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.Builder; |
@ -1,4 +1,4 @@ |
||||
package cn.linghang.mywust.model.undergrade; |
||||
package cn.linghang.mywust.model.global; |
||||
|
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Builder; |
Loading…
Reference in new issue