Compare commits

...

6 Commits

  1. 9
      mywust-common/pom.xml
  2. 20
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/PagingResult.java
  3. 61
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/origin/BaseLoanBook.java
  4. 67
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/origin/BookSearchRequest.java
  5. 80
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/origin/BookSearchResult.java
  6. 12
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/origin/CurrentLoanBook.java
  7. 12
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/origin/HistoryLoanBook.java
  8. 51
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/parsed/BookDetail.java
  9. 56
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/parsed/BookHolding.java
  10. 69
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/parsed/BookSearchResult.java
  11. 14
      mywust-common/src/main/java/cn/wustlinghang/mywust/exception/ApiException.java
  12. 5
      mywust-core/pom.xml
  13. 12
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/api/LibraryUrls.java
  14. 5
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/parser/graduate/GraduateTrainingPlanPageParser.java
  15. 6
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/factory/RequestFactory.java
  16. 38
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/factory/library/LibraryRequestFactory.java
  17. 40
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/factory/library/request/SearchRequest.java
  18. 4
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/factory/physics/PhysicsSystemRequestFactory.java
  19. 27
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/auth/GraduateLogin.java
  20. 11
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/auth/LibraryLogin.java
  21. 18
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/auth/PhysicsLogin.java
  22. 35
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/auth/UndergraduateLogin.java
  23. 82
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/BaseLibraryApiService.java
  24. 55
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/BookCoverImageUrlApiService.java
  25. 69
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/BookDetailApiService.java
  26. 39
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/BookHoldingApiService.java
  27. 52
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/CurrentLoanApiService.java
  28. 66
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/LibraryApiService.java
  29. 20
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/LibraryApiServiceBase.java
  30. 53
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/LoanHistoryApiService.java
  31. 56
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/OverdueSoonApiService.java
  32. 68
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/SearchApiService.java
  33. 6
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/undergraduate/UndergradApiServiceBase.java
  34. 40
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/util/BkjxUtil.java
  35. 25
      mywust-util/src/main/java/cn/wustlinghang/mywust/util/StringUtil.java

@ -15,6 +15,15 @@
<maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jackson-annotations.version>2.15.2</jackson-annotations.version>
</properties> </properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson-annotations.version}</version>
</dependency>
</dependencies>
</project> </project>

@ -0,0 +1,20 @@
package cn.wustlinghang.mywust.data.library;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PagingResult<T> {
private int currentPage;
private int pageSize;
private int totalPage;
private int totalResult;
private T data;
}

@ -0,0 +1,61 @@
package cn.wustlinghang.mywust.data.library.origin;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* 响应回来的书籍信息借阅历史当前借阅即将逾期等的书目字段
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class BaseLoanBook {
protected String bibId;
protected String title;
protected String author;
/**
* 借书时间
*/
protected String loanDate;
protected String location;
protected String barCode;
/**
* 图书属性信息
*/
@JsonProperty("bibAttrs")
protected BookAttribute bookAttribute;
/**
* 图书属性
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class BookAttribute {
protected String isbn;
protected String publisher;
@JsonProperty("pub_year")
protected String publishYear;
protected String price;
/**
* 索书号
*/
@JsonProperty("callno")
protected String callNumber;
/**
* 书类编号
*/
@JsonProperty("classno")
protected String classNumber;
}
}

@ -0,0 +1,67 @@
package cn.wustlinghang.mywust.data.library.origin;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class BookSearchRequest {
private List<Object> filterFieldList = new ArrayList<>();
private String collapseField = "groupId";
private String sortType = "desc";
private String indexName = "idx.opac";
private String sortField = "relevance";
private List<QueryFieldListItem> queryFieldList = new ArrayList<>(1);
private int pageSize;
private int page;
public BookSearchRequest(String keyWord, int pageSize, int page) {
this.pageSize = pageSize;
this.page = page;
this.queryFieldList.add(new QueryFieldListItem(keyWord));
}
@Data
public static class QueryFieldListItem {
private String field = "all";
private String operator = "*";
private int logic = 0;
private List<String> values = new ArrayList<>(1);
public QueryFieldListItem(String keyWord) {
values.add(keyWord);
}
}
public static class Builder {
private int pageSize;
private int page;
private String keyword;
private Builder() {
}
public Builder pageSize(int pageSize) {
this.pageSize = pageSize;
return this;
}
public Builder page(int page) {
this.page = page;
return this;
}
public Builder keyword(String keyword) {
this.keyword = keyword;
return this;
}
public BookSearchRequest build() {
return new BookSearchRequest(keyword, pageSize, page);
}
}
}

@ -0,0 +1,80 @@
package cn.wustlinghang.mywust.data.library.origin;
import cn.wustlinghang.mywust.data.library.parsed.BookHolding;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class BookSearchResult {
private String bibId;
private String title;
private String author;
@JsonProperty("abstract")
private String summary;
private String publisher;
private List<String> holdingTypes;
@JsonProperty("callno")
private List<String> callNumber;
/**
* 文献类型
*/
private String docType;
private String groupId;
private String isbn;
/**
* 图书馆系统中的图书编号
*/
private String bibNo;
/**
* 图书总数
*/
private int itemCount;
/**
* 在借数量
*/
private int circCount;
@JsonProperty("pub_year")
private String pubYear;
@JsonProperty("classno")
private String classNumber;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private List<BookHolding> holdings;
}
/*
bibId: 图书馆系统中的图书ID用于唯一标识一本图书
holdingTypes: 图书的持有类型此处为print表示该图书以纸质形式持有
author: 作者此处为Walter Savitch著表示该图书的作者是Walter Savitch
callno: 索书号此处为["TP312JA/E26=4/1","TP312JA/E26=4/2"]表示该图书的索书号是TP312JA/E26=4/1和TP312JA/E26=4/2
docType: 文献类型此处为doc.02表示该图书的文献类型为doc.02
groupId: 分组ID此处为1e24652f1d98f0586dc6468136f8eba6可能是用于图书分类的一个标识
isbn: ISBN号此处为7115152888 (v. 1)表示该图书的ISBN号是7115152888其中(v. 1)可能表示版本号
bibNo: 图书馆系统中的图书编号此处为1800067256
title: 书名此处为Java : an introduction to problem solving & programming = Java : 程序设计与问题解决/ Walter Savitch著.表示该图书的书名是Java : 程序设计与问题解决作者是Walter Savitch
itemCount: 图书总数此处为8表示该图书馆系统中该图书的总数
circCount: 图书当前在借数量此处也为8表示该图书当前在借数量为8
pub_year: 出版年份此处为2006.表示该图书的出版年份是2006年
classno: 分类号此处为TP312JA可能是用于图书分类的一个标识
publisher: 出版商此处为Posts & Telecom Press表示该图书的出版商是Posts & Telecom Press
holdings: 持有情况此处为空字符串可能是用于记录该图书的其他持有情况
_id: 记录ID此处为544237可能是用于记录该图书的唯一标识
*/

@ -0,0 +1,12 @@
package cn.wustlinghang.mywust.data.library.origin;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@JsonIgnoreProperties(ignoreUnknown = true)
public class CurrentLoanBook extends BaseLoanBook {
private String dueDate;
}

@ -0,0 +1,12 @@
package cn.wustlinghang.mywust.data.library.origin;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@JsonIgnoreProperties(ignoreUnknown = true)
public class HistoryLoanBook extends BaseLoanBook {
private String returnDate;
}

@ -0,0 +1,51 @@
package cn.wustlinghang.mywust.data.library.parsed;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* 书籍详情
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BookDetail {
private String title;
private String author;
private String isbn;
/**
* 作者介绍
*/
private String authorDescribe;
/**
* 目录
*/
private String catalog;
/**
* 封面url
*/
private String coverUrl;
/**
* 图书简要
*/
private String summary;
/**
* 图书介绍
*/
private String introduction;
/**
* 书籍详细信息因为图书馆系统奇葩的中文不定key因此将这些信息放入key-value对中需要者自取
*/
Map<String, String> extraInfoMap;
}

@ -0,0 +1,56 @@
package cn.wustlinghang.mywust.data.library.parsed;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* 馆藏信息单个
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class BookHolding {
@JsonProperty("callNo")
private String callNumber;
/**
* 馆藏总数
*/
private int itemsCount;
/**
* 可借数
*/
private int itemsAvailable;
/**
* 条形码
*/
private String barCode;
/**
* 所属馆藏地
*/
private String location;
/**
* 临时馆藏地
*/
private String tempLocation;
/**
* 卷年期
*/
@JsonProperty("vol")
private String volume;
/**
* 馆名总馆等等
*/
private String library;
/**
* 状态文本
*/
private String status;
}

@ -0,0 +1,69 @@
package cn.wustlinghang.mywust.data.library.parsed;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class BookSearchResult {
private String bibId;
private List<String> holdingTypes;
private String author;
private List<String> callNumber;
/**
* 文献类型
*/
private String docType;
private String groupId;
private String isbn;
/**
* 图书馆系统中的图书编号
*/
private String bibNo;
private String title;
/**
* 图书总数
*/
private int itemCount;
/**
* 在借数量
*/
private int circCount;
private String pubYear;
private String classNumber;
private String publisher;
private List<BookHolding> holdings;
}
/*
bibId: 图书馆系统中的图书ID用于唯一标识一本图书
holdingTypes: 图书的持有类型此处为print表示该图书以纸质形式持有
author: 作者此处为Walter Savitch著表示该图书的作者是Walter Savitch
callno: 索书号此处为["TP312JA/E26=4/1","TP312JA/E26=4/2"]表示该图书的索书号是TP312JA/E26=4/1和TP312JA/E26=4/2
docType: 文献类型此处为doc.02表示该图书的文献类型为doc.02
groupId: 分组ID此处为1e24652f1d98f0586dc6468136f8eba6可能是用于图书分类的一个标识
isbn: ISBN号此处为7115152888 (v. 1)表示该图书的ISBN号是7115152888其中(v. 1)可能表示版本号
bibNo: 图书馆系统中的图书编号此处为1800067256
title: 书名此处为Java : an introduction to problem solving & programming = Java : 程序设计与问题解决/ Walter Savitch著.表示该图书的书名是Java : 程序设计与问题解决作者是Walter Savitch
itemCount: 图书总数此处为8表示该图书馆系统中该图书的总数
circCount: 图书当前在借数量此处也为8表示该图书当前在借数量为8
pub_year: 出版年份此处为2006.表示该图书的出版年份是2006年
classno: 分类号此处为TP312JA可能是用于图书分类的一个标识
publisher: 出版商此处为Posts & Telecom Press表示该图书的出版商是Posts & Telecom Press
holdings: 持有情况此处为空字符串可能是用于记录该图书的其他持有情况
_id: 记录ID此处为544237可能是用于记录该图书的唯一标识
*/

@ -99,6 +99,11 @@ public class ApiException extends BasicException {
*/ */
UNI_LOGIN_NEED_TFA(100107, "统一认证登录: 用户账号需要TFA二步验证"), UNI_LOGIN_NEED_TFA(100107, "统一认证登录: 用户账号需要TFA二步验证"),
/**
* 专属选课时间段账号被禁用
*/
UNDERGRAD_BANNED_IN_EXCLUSIVE_TIME(100108, "本科生登录:专属选课时间段账号被禁用"),
// -------------------------------- // --------------------------------
// 共有异常码:cookie无效 // 共有异常码:cookie无效
@ -128,6 +133,11 @@ public class ApiException extends BasicException {
*/ */
PHYSICS_PASSWORD_WRONG(130100, "物理实验系统登录: 密码错误"), PHYSICS_PASSWORD_WRONG(130100, "物理实验系统登录: 密码错误"),
/**
* 物理实验系统用户不存在于当前学期
*/
PHYSICS_NOT_CURRENT_TERM(130101, "物理实验系统登录: 当前用户不存在于当前学期"),
// -------------------------------- // --------------------------------
// 研究生API异常代码 // 研究生API异常代码
@ -136,6 +146,10 @@ public class ApiException extends BasicException {
*/ */
GRADUATE_PASSWORD_WRONG(140100, "研究生登录: 密码错误"), GRADUATE_PASSWORD_WRONG(140100, "研究生登录: 密码错误"),
/**
* 研究生验证码错误
*/
GRADUATE_CAPTCHA_WRONG(140101, "研究生登录: 验证码错误"),
// -------------------------------- // --------------------------------
// 图书馆API异常代码 // 图书馆API异常代码

@ -45,13 +45,13 @@
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId> <artifactId>jackson-annotations</artifactId>
<version>2.14.0-rc1</version> <version>${jackson.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<version>2.14.0-rc1</version> <version>${jackson.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@ -70,5 +70,6 @@
<slf4j.version>2.0.3</slf4j.version> <slf4j.version>2.0.3</slf4j.version>
<jsoup.version>1.15.3</jsoup.version> <jsoup.version>1.15.3</jsoup.version>
<hutool.version>5.8.12</hutool.version> <hutool.version>5.8.12</hutool.version>
<jackson.version>2.15.2</jackson.version>
</properties> </properties>
</project> </project>

@ -5,15 +5,19 @@ public class LibraryUrls {
public static final String LIBRARY_INDEX_URL = "https://libsys.wust.edu.cn/meta-local/opac/cas/rosetta"; public static final String LIBRARY_INDEX_URL = "https://libsys.wust.edu.cn/meta-local/opac/cas/rosetta";
public static final String LIBRARY_COOKIE_TEST_URL = "https://libsys.wust.edu.cn/meta-local/opac/users/info"; // 用违章记录信息api地址作为测试url(大部分人应该没有违章吧 :)
public static final String LIBRARY_COOKIE_TEST_URL = "https://libsys.wust.edu.cn/meta-local/opac/users/volts";
public static final String LIBRARY_ACCOUNT_STATUS_API = "https://libsys.wust.edu.cn/meta-local/opac/users/stats"; public static final String LIBRARY_ACCOUNT_STATUS_API = "https://libsys.wust.edu.cn/meta-local/opac/users/stats";
public static final String LIBRARY_CURRENT_LOAN_API = "https://libsys.wust.edu.cn/meta-local/opac/users/loans?page=1&pageSize=100"; public static final String LIBRARY_CURRENT_LOAN_API = "https://libsys.wust.edu.cn/meta-local/opac/users/loans?page=%d&pageSize=%d";
public static final String LIBRARY_LOAN_HISTORY_API = "https://libsys.wust.edu.cn/meta-local/opac/users/loan_hists?page=1&pageSize=100"; public static final String LIBRARY_LOAN_HISTORY_API = "https://libsys.wust.edu.cn/meta-local/opac/users/loan_hists?page=%d&pageSize=%d";
public static final String LIBRARY_OVERDUE_SOON_API = "https://libsys.wust.edu.cn/meta-local/opac/users/overdue_soon"; public static final String LIBRARY_OVERDUE_SOON_API = "https://libsys.wust.edu.cn/meta-local/opac/users/overdue_soon?page=%d&pageSize=%d";
public static final String LIBRARY_BOOK_INFO_API = "https://libsys.wust.edu.cn/meta-local/opac/bibs/%s/infos"; public static final String LIBRARY_BOOK_INFO_API = "https://libsys.wust.edu.cn/meta-local/opac/bibs/%s/infos";
public static final String LIBRARY_BOOK_DOUBAN_INFO_API = "https://libsys.wust.edu.cn/meta-local/opac/third_api/douban/%s/info";
public static final String LIBRARY_BOOK_CONTENT_API = "https://libsys.wust.edu.cn/meta-local/opac/bibs/%s/quoteds/quoted.caj-cd/content";
public static final String LIBRARY_BOOK_HOLDING_API = "https://libsys.wust.edu.cn/meta-local/opac/bibs/%s/holdings";
public static final String LIBRARY_BOOK_COVER_IMAGE_API = "https://libsys.wust.edu.cn/meta-local/opac/search/extend/"; public static final String LIBRARY_BOOK_COVER_IMAGE_API = "https://libsys.wust.edu.cn/meta-local/opac/search/extend/";

@ -9,9 +9,6 @@ import org.jsoup.nodes.Element;
public class GraduateTrainingPlanPageParser implements Parser<String> { public class GraduateTrainingPlanPageParser implements Parser<String> {
private static final String HTML_PREFIX = "<!DOCTYPE html><html lang=\"zh\"><head><meta charset=\"UTF-8\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>培养计划</title></head><body>";
private static final String HTML_SUFFIX = "</body></html>";
@Override @Override
public String parse(String html) throws ParseException { public String parse(String html) throws ParseException {
try { try {
@ -25,7 +22,7 @@ public class GraduateTrainingPlanPageParser implements Parser<String> {
String table = JsoupUtil.getOuterHtml(fullTable.getElementById("_ctl0_MainWork_dgData")); String table = JsoupUtil.getOuterHtml(fullTable.getElementById("_ctl0_MainWork_dgData"));
String foot = JsoupUtil.getOuterHtml(fullTable.getElementById("Table4")); String foot = JsoupUtil.getOuterHtml(fullTable.getElementById("Table4"));
return HTML_PREFIX + head + table + foot + HTML_SUFFIX; return head + table + foot;
}catch (Exception e) { }catch (Exception e) {
// 解析失败就直接返回原网页展示 // 解析失败就直接返回原网页展示
return html; return html;

@ -1,11 +1,14 @@
package cn.wustlinghang.mywust.core.request.factory; package cn.wustlinghang.mywust.core.request.factory;
import cn.wustlinghang.mywust.network.entitys.HttpRequest; import cn.wustlinghang.mywust.network.entitys.HttpRequest;
import lombok.extern.slf4j.Slf4j;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Map; import java.util.Map;
@Slf4j
public class RequestFactory { public class RequestFactory {
protected static final HttpRequest DEFAULT_HTTP_REQUEST = new HttpRequest(); protected static final HttpRequest DEFAULT_HTTP_REQUEST = new HttpRequest();
@ -52,7 +55,8 @@ public class RequestFactory {
public static URL makeUrl(String url) { public static URL makeUrl(String url) {
try { try {
return new URL(url); return new URL(url);
} catch (Exception e) { } catch (MalformedURLException e) {
log.error("[mywust]: Invalid url: {} , {}", url, e.getMessage());
return null; return null;
} }
} }

@ -2,14 +2,16 @@ package cn.wustlinghang.mywust.core.request.factory.library;
import cn.wustlinghang.mywust.core.api.LibraryUrls; import cn.wustlinghang.mywust.core.api.LibraryUrls;
import cn.wustlinghang.mywust.core.request.factory.RequestFactory; import cn.wustlinghang.mywust.core.request.factory.RequestFactory;
import cn.wustlinghang.mywust.core.request.factory.library.request.SearchRequest; import cn.wustlinghang.mywust.data.library.origin.BookSearchRequest;
import cn.wustlinghang.mywust.network.entitys.HttpRequest; import cn.wustlinghang.mywust.network.entitys.HttpRequest;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import java.util.List; import java.util.List;
import java.util.StringJoiner; import java.util.StringJoiner;
@Slf4j
public class LibraryRequestFactory extends RequestFactory { public class LibraryRequestFactory extends RequestFactory {
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
@ -24,10 +26,10 @@ public class LibraryRequestFactory extends RequestFactory {
} }
public static HttpRequest bookSearchRequest(String keyword, int page, int pageSize) { public static HttpRequest bookSearchRequest(String keyword, int page, int pageSize) {
return bookSearchRequest(new SearchRequest(keyword, pageSize, page)); return bookSearchRequest(new BookSearchRequest(keyword, pageSize, page));
} }
public static HttpRequest bookSearchRequest(SearchRequest request) { public static HttpRequest bookSearchRequest(BookSearchRequest request) {
return makeHttpRequest(LibraryUrls.LIBRARY_SEARCH_API, json(request)) return makeHttpRequest(LibraryUrls.LIBRARY_SEARCH_API, json(request))
.addHeaders("Content-Type", "application/json"); .addHeaders("Content-Type", "application/json");
} }
@ -36,16 +38,16 @@ public class LibraryRequestFactory extends RequestFactory {
return makeHttpRequest(LibraryUrls.LIBRARY_ACCOUNT_STATUS_API, null, cookie); return makeHttpRequest(LibraryUrls.LIBRARY_ACCOUNT_STATUS_API, null, cookie);
} }
public static HttpRequest currentLoanRequest(String cookie) { public static HttpRequest currentLoanRequest(String cookie, int page, int pageSize) {
return makeHttpRequest(LibraryUrls.LIBRARY_CURRENT_LOAN_API, null, cookie); return makeHttpRequest(String.format(LibraryUrls.LIBRARY_CURRENT_LOAN_API, page, pageSize), null, cookie);
} }
public static HttpRequest loanHistoryRequest(String cookie) { public static HttpRequest loanHistoryRequest(String cookie, int page, int pageSize) {
return makeHttpRequest(LibraryUrls.LIBRARY_LOAN_HISTORY_API, null, cookie); return makeHttpRequest(String.format(LibraryUrls.LIBRARY_LOAN_HISTORY_API, page, pageSize), null, cookie);
} }
public static HttpRequest overdueSoonRequest(String cookie) { public static HttpRequest overdueSoonRequest(String cookie, int page, int pageSize) {
return makeHttpRequest(LibraryUrls.LIBRARY_OVERDUE_SOON_API, null, cookie); return makeHttpRequest(String.format(LibraryUrls.LIBRARY_OVERDUE_SOON_API, page, pageSize), null, cookie);
} }
public static HttpRequest bookInfoRequest(String bookId) { public static HttpRequest bookInfoRequest(String bookId) {
@ -53,6 +55,21 @@ public class LibraryRequestFactory extends RequestFactory {
return makeHttpRequest(url, (byte[]) null); return makeHttpRequest(url, (byte[]) null);
} }
public static HttpRequest bookDoubanInfoRequest(String isbn) {
String url = String.format(LibraryUrls.LIBRARY_BOOK_DOUBAN_INFO_API, isbn);
return makeHttpRequest(url, (byte[]) null);
}
public static HttpRequest bookContentRequest(String id) {
String url = String.format(LibraryUrls.LIBRARY_BOOK_CONTENT_API, id);
return makeHttpRequest(url, (byte[]) null);
}
public static HttpRequest bookHoldingRequest(String bookId) {
String url = String.format(LibraryUrls.LIBRARY_BOOK_HOLDING_API, bookId);
return makeHttpRequest(url, (byte[]) null);
}
public static HttpRequest bookCoverImageUrlRequest(List<String> isbn) { public static HttpRequest bookCoverImageUrlRequest(List<String> isbn) {
StringJoiner joiner = new StringJoiner("%2C", LibraryUrls.LIBRARY_BOOK_COVER_IMAGE_API, ""); StringJoiner joiner = new StringJoiner("%2C", LibraryUrls.LIBRARY_BOOK_COVER_IMAGE_API, "");
isbn.forEach(joiner::add); isbn.forEach(joiner::add);
@ -67,7 +84,8 @@ public class LibraryRequestFactory extends RequestFactory {
private static String json(Object object) { private static String json(Object object) {
try { try {
return objectMapper.writeValueAsString(object); return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException ignored) { } catch (JsonProcessingException e) {
log.error("生成json时出现不应出现的异常:", e);
return ""; return "";
} }
} }

@ -1,40 +0,0 @@
package cn.wustlinghang.mywust.core.request.factory.library.request;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
@Data
public class SearchRequest {
private final List<Object> filterFieldList = new ArrayList<>();
private final String collapseField = "groupId";
private final String sortType = "desc";
private final String indexName = "idx.opac";
private final String sortField = "relevance";
private final List<QueryFieldListItem> queryFieldList = new ArrayList<>(1);
private int pageSize;
private int page;
public SearchRequest(String keyWord, int pageSize, int page) {
this.pageSize = pageSize;
this.page = page;
this.queryFieldList.add(new QueryFieldListItem(keyWord));
}
@Data
public static class QueryFieldListItem {
private final String field = "all";
private final String operator = "*";
private final int logic = 0;
private final List<String> values = new ArrayList<>(1);
public QueryFieldListItem(String keyWord) {
values.add(keyWord);
}
}
}

@ -31,6 +31,10 @@ public class PhysicsSystemRequestFactory extends RequestFactory {
return makeHttpRequest(redirect, null, cookies); return makeHttpRequest(redirect, null, cookies);
} }
public static HttpRequest physicsSystemIndexRequest(String cookies) {
return makeHttpRequest(PhysicsSystemUrls.PHYSICS_SYSTEM_INDEX_URL, null, cookies);
}
public static HttpRequest physicsCourseRequest(String cookies) { public static HttpRequest physicsCourseRequest(String cookies) {
return makeHttpRequest(PhysicsSystemUrls.PHYSICS_COURSE_API, null, cookies); return makeHttpRequest(PhysicsSystemUrls.PHYSICS_COURSE_API, null, cookies);
} }

@ -11,6 +11,7 @@ import cn.wustlinghang.mywust.network.RequestClientOption;
import cn.wustlinghang.mywust.network.Requester; import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpRequest; import cn.wustlinghang.mywust.network.entitys.HttpRequest;
import cn.wustlinghang.mywust.network.entitys.HttpResponse; import cn.wustlinghang.mywust.network.entitys.HttpResponse;
import lombok.extern.slf4j.Slf4j;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.*; import java.awt.*;
@ -18,7 +19,9 @@ import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Slf4j
public class GraduateLogin { public class GraduateLogin {
private final Requester requester; private final Requester requester;
@ -55,13 +58,37 @@ public class GraduateLogin {
// 登陆成功,应该会是302跳转,不是的话多半是认证错误 // 登陆成功,应该会是302跳转,不是的话多半是认证错误
if (loginResponse.getStatusCode() != HttpResponse.HTTP_REDIRECT_302) { if (loginResponse.getStatusCode() != HttpResponse.HTTP_REDIRECT_302) {
String responseHtml = loginResponse.getStringBody();
if (responseHtml.contains("验证码错误")) {
throw new ApiException(ApiException.Code.GRADUATE_CAPTCHA_WRONG);
} else if (responseHtml.contains("密码错误") || responseHtml.contains("用户名不存在")) {
throw new ApiException(ApiException.Code.GRADUATE_PASSWORD_WRONG); throw new ApiException(ApiException.Code.GRADUATE_PASSWORD_WRONG);
} else {
throw new ApiException(ApiException.Code.UNKNOWN_EXCEPTION);
}
} }
// 使用首页第一次访问得到的cookie来作为登录cookie // 使用首页第一次访问得到的cookie来作为登录cookie
return loginCookie; return loginCookie;
} }
public String getLoginCookie(String username, String password, int maxRetryTimes, RequestClientOption option)
throws IOException, ApiException {
for (int i = 0; i < maxRetryTimes; i++) {
try {
return getLoginCookie(username, password, option);
} catch (ApiException e) {
if (e.getCode() != ApiException.Code.GRADUATE_CAPTCHA_WRONG) {
throw e;
} else {
log.info("[mywust]: Retrying login for {} time(s)", i);
}
}
}
return "";
}
public void checkCookies(String cookie, RequestClientOption option) throws ApiException, IOException { public void checkCookies(String cookie, RequestClientOption option) throws ApiException, IOException {
HttpRequest request = RequestFactory.makeHttpRequest(GraduateUrls.GRADUATE_INDEX_TEST_API, null, cookie); HttpRequest request = RequestFactory.makeHttpRequest(GraduateUrls.GRADUATE_INDEX_TEST_API, null, cookie);
HttpResponse response = requester.get(request, option); HttpResponse response = requester.get(request, option);

@ -2,8 +2,8 @@ package cn.wustlinghang.mywust.core.request.service.auth;
import cn.wustlinghang.mywust.core.api.LibraryUrls; import cn.wustlinghang.mywust.core.api.LibraryUrls;
import cn.wustlinghang.mywust.core.api.UnionAuthUrls; import cn.wustlinghang.mywust.core.api.UnionAuthUrls;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory; import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.network.RequestClientOption; import cn.wustlinghang.mywust.network.RequestClientOption;
import cn.wustlinghang.mywust.network.Requester; import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpRequest; import cn.wustlinghang.mywust.network.entitys.HttpRequest;
@ -30,10 +30,15 @@ public class LibraryLogin {
HttpResponse sessionResponse = requester.get(sessionRequest, requestOption); HttpResponse sessionResponse = requester.get(sessionRequest, requestOption);
String cookies = sessionResponse.getCookies(); String cookies = sessionResponse.getCookies();
// 请求一次首页 // 请求一次首页,这次获得的cookie才是真正的cookie
HttpRequest indexRequest = LibraryRequestFactory.indexRequest(); HttpRequest indexRequest = LibraryRequestFactory.indexRequest();
indexRequest.setCookies(cookies); indexRequest.setCookies(cookies);
requester.get(indexRequest, requestOption); HttpResponse indexResponse = requester.get(indexRequest, requestOption);
cookies = indexResponse.getCookies() != null ? indexResponse.getCookies() : cookies;
if (!checkCookie(cookies)) {
throw new ApiException(ApiException.Code.COOKIE_INVALID);
}
return cookies; return cookies;
} }

@ -1,9 +1,9 @@
package cn.wustlinghang.mywust.core.request.service.auth; package cn.wustlinghang.mywust.core.request.service.auth;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.exception.ParseException;
import cn.wustlinghang.mywust.core.parser.physics.PhysicsIndexPageParser; import cn.wustlinghang.mywust.core.parser.physics.PhysicsIndexPageParser;
import cn.wustlinghang.mywust.core.request.factory.physics.PhysicsSystemRequestFactory; import cn.wustlinghang.mywust.core.request.factory.physics.PhysicsSystemRequestFactory;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.exception.ParseException;
import cn.wustlinghang.mywust.network.RequestClientOption; import cn.wustlinghang.mywust.network.RequestClientOption;
import cn.wustlinghang.mywust.network.Requester; import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpRequest; import cn.wustlinghang.mywust.network.entitys.HttpRequest;
@ -34,7 +34,14 @@ public class PhysicsLogin {
HttpRequest loginCookieRequest = PhysicsSystemRequestFactory.loginCookiesRequest(username, password, loginIndex); HttpRequest loginCookieRequest = PhysicsSystemRequestFactory.loginCookiesRequest(username, password, loginIndex);
HttpResponse loginCookieResponse = requester.post(loginCookieRequest, requestClientOption); HttpResponse loginCookieResponse = requester.post(loginCookieRequest, requestClientOption);
if (loginCookieResponse.getStatusCode() != HttpResponse.HTTP_REDIRECT_302) { if (loginCookieResponse.getStatusCode() != HttpResponse.HTTP_REDIRECT_302) {
String responseHtml = loginCookieResponse.getStringBody();
if (responseHtml.contains("该用户不存在于当前学期")){
throw new ApiException(ApiException.Code.PHYSICS_NOT_CURRENT_TERM);
} else if (responseHtml.contains("用户名或者密码有误")) {
throw new ApiException(ApiException.Code.PHYSICS_PASSWORD_WRONG); throw new ApiException(ApiException.Code.PHYSICS_PASSWORD_WRONG);
} else {
throw new ApiException(ApiException.Code.UNKNOWN_EXCEPTION);
}
} }
String loginCookies = loginCookieResponse.getCookies(); String loginCookies = loginCookieResponse.getCookies();
@ -63,4 +70,11 @@ public class PhysicsLogin {
return loginCookies; return loginCookies;
} }
public boolean checkCookie(String cookie, RequestClientOption option) throws IOException {
HttpRequest testRequest = PhysicsSystemRequestFactory.physicsSystemIndexRequest(cookie);
HttpResponse testResponse = requester.get(testRequest, option);
return testResponse.getStatusCode() != HttpResponse.HTTP_REDIRECT_302;
}
} }

@ -84,36 +84,43 @@ public class UndergraduateLogin {
} }
private void checkCookie(String cookies, RequestClientOption requestOption) throws ApiException, IOException { private void checkCookie(String cookies, RequestClientOption requestOption) throws ApiException, IOException {
if (roughCheckCookieFail(cookies)) { if (!roughCheckCookie(cookies)) {
log.error("[mywust]: Cookie粗查不通过:{}", cookies); log.error("[mywust]: Cookie粗查不通过:{}", cookies);
throw new ApiException(ApiException.Code.UNKNOWN_EXCEPTION, "登录获取的Cookie无效"); throw new ApiException(ApiException.Code.UNKNOWN_EXCEPTION, "登录获取的Cookie无效");
} }
// 检查Cookie是否真正可用,同时请求一次任意接口使后续接口能够正确响应 // 检查Cookie是否真正可用,同时请求一次任意接口使后续接口能够正确响应
// 拿到Cookie后调用的第一个接口有时候会产生302/301跳转到主页,需要再次调用才能正确响应 // 拿到Cookie后调用的第一个接口有时候会产生302/301跳转到主页,需要再次调用才能正确响应
if (checkCookiesFail(cookies, requestOption)) { if (!testCookie(cookies, requestOption)) {
log.warn("[mywust]: Cookie检查不通过:{}", cookies); log.warn("[mywust]: Cookie检查不通过:{}", cookies);
} }
} }
private boolean roughCheckCookieFail(String cookies) { private boolean roughCheckCookie(String cookies) {
return cookies == null || !cookies.contains("JSESSIONID") || !cookies.contains("SERVERID"); return cookies != null && cookies.contains("JSESSIONID") && cookies.contains("SERVERID");
} }
private static final int COOKIES_ERROR_RESPONSE_LENGTH = public boolean testCookie(String cookies, RequestClientOption option) throws IOException, ApiException {
("<script languge='javascript'>window.location.href='https://auth.wust.edu.cn/lyuapServer/login?service=http%3A%2F%2Fbkjx.wust.edu.cn%2Fjsxsd%2Fframework%2FblankPage.jsp'</script>")
.length();
public boolean checkCookiesFail(String cookies, RequestClientOption option) throws IOException {
HttpRequest testRequest = BkjxRequestFactory.makeHttpRequest(UndergradUrls.BKJX_TEST_API, null, cookies); HttpRequest testRequest = BkjxRequestFactory.makeHttpRequest(UndergradUrls.BKJX_TEST_API, null, cookies);
HttpResponse testResponse = requester.get(testRequest, option); HttpResponse testResponse = requester.get(testRequest, option);
// 判断响应长度是否为这么多个字,登录跳转响应长度 // 空白页返回的是很短的几个换行符,优先判断响应长度,暂时定为8
// 这种办法虽然在极端情况下可能会出错(而且还挺蠢的),但是是最快的办法中比较准确的了 if (testResponse.getBody().length < 8) {
return Math.abs(COOKIES_ERROR_RESPONSE_LENGTH - testResponse.getBody().length) <= 8; return true;
}
String test = testResponse.getStringBody();
if (test.contains("script")) {
return false;
} else if (test.contains("禁用")) {
// 选课时间段
throw new ApiException(ApiException.Code.UNDERGRAD_BANNED_IN_EXCLUSIVE_TIME);
}
return true;
} }
public boolean checkCookiesFail(String cookies) throws IOException { public boolean testCookie(String cookies) throws IOException, ApiException {
return this.checkCookiesFail(cookies, null); return this.testCookie(cookies, null);
} }
} }

@ -0,0 +1,82 @@
package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.data.library.origin.BaseLoanBook;
import cn.wustlinghang.mywust.data.library.origin.BookSearchResult;
import cn.wustlinghang.mywust.data.library.origin.CurrentLoanBook;
import cn.wustlinghang.mywust.data.library.origin.HistoryLoanBook;
import cn.wustlinghang.mywust.data.library.parsed.BookHolding;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpResponse;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.util.List;
public abstract class BaseLibraryApiService {
protected static final ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new SpecialStringDeserializer());
objectMapper.registerModule(module);
}
protected static final JavaType loanBookListType =
objectMapper.getTypeFactory().constructParametricType(List.class, BaseLoanBook.class);
protected static final JavaType historyLoanBookListType =
objectMapper.getTypeFactory().constructParametricType(List.class, HistoryLoanBook.class);
protected static final JavaType currentLoanBookListType =
objectMapper.getTypeFactory().constructParametricType(List.class, CurrentLoanBook.class);
protected static final JavaType bookSearchResultListType =
objectMapper.getTypeFactory().constructParametricType(List.class, BookSearchResult.class);
protected static final JavaType bookHoldingListType =
objectMapper.getTypeFactory().constructParametricType(List.class, BookHolding.class);
protected final Requester requester;
public BaseLibraryApiService(Requester requester) {
this.requester = requester;
}
public void checkResponse(HttpResponse response) throws ApiException {
// 检查响应是否正确
if (response.getStatusCode() != 200) {
throw new ApiException(ApiException.Code.COOKIE_INVALID);
}
}
}
/**
* <p>继承自原版StringDeserializer特殊定制的字符串反序列化器</p>
* <p>测试时偶然发现的图书馆搜索api返回的json中`pub_year``abstract`等一些本应该是字符串值的字段有概率会出现为空数组的奇葩情况...</p>
* <p>想了半天也没想明白那系统究竟是怎么在字符串里蹦出`[]`...</p>
*/
class SpecialStringDeserializer extends StringDeserializer {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// The critical path: ensure we handle the common case first.
if (p.hasToken(JsonToken.VALUE_STRING)) {
return p.getText();
}
// [databind#381]
if (p.hasToken(JsonToken.START_ARRAY)) {
// 仅修改了这行
return p.getValueAsString("");
}
return _parseString(p, ctxt, this);
}
}

@ -0,0 +1,55 @@
package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpRequest;
import cn.wustlinghang.mywust.network.entitys.HttpResponse;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.*;
public class BookCoverImageUrlApiService extends BaseLibraryApiService {
public BookCoverImageUrlApiService(Requester requester) {
super(requester);
}
public List<String> getBookCoverImageUrl(String isbn) throws ApiException, IOException {
HttpRequest request = LibraryRequestFactory.bookCoverImageUrlRequest(isbn);
HttpResponse response = requester.get(request);
checkResponse(response);
// 因为只查了一本书,所以path(isbn)直接一步到位就行了
JsonNode imageResultJson = objectMapper.readTree(response.getBody()).path("data").path(isbn);
List<String> covers = new ArrayList<>(imageResultJson.size());
for (JsonNode resultItemJson : imageResultJson) {
covers.add(resultItemJson.path("imageUrl").asText());
}
return covers;
}
public Map<String, List<String>> getBookCoverImageUrl(List<String> isbnList) throws ApiException, IOException {
HttpRequest request = LibraryRequestFactory.bookCoverImageUrlRequest(isbnList);
HttpResponse response = requester.get(request);
checkResponse(response);
JsonNode data = objectMapper.readTree(response.getBody()).path("data");
Map<String, List<String>> result = new HashMap<>(data.size());
// 遍历data下每本书
Iterator<Map.Entry<String, JsonNode>> it = data.fields();
while (it.hasNext()) {
Map.Entry<String, JsonNode> bookItemJson = it.next();
List<String> covers = new ArrayList<>(bookItemJson.getValue().size());
for (JsonNode resultItemJson : bookItemJson.getValue()) {
covers.add(resultItemJson.path("imageUrl").asText());
}
result.put(bookItemJson.getKey(), covers);
}
return result;
}
}

@ -0,0 +1,69 @@
package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory;
import cn.wustlinghang.mywust.data.library.parsed.BookDetail;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpRequest;
import cn.wustlinghang.mywust.network.entitys.HttpResponse;
import cn.wustlinghang.mywust.util.StringUtil;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class BookDetailApiService extends BaseLibraryApiService {
public BookDetailApiService(Requester requester) {
super(requester);
}
public BookDetail getBookDetail(String bookId) throws ApiException, IOException {
HttpRequest infoRequest = LibraryRequestFactory.bookInfoRequest(bookId);
HttpResponse infoResponse = requester.get(infoRequest);
checkResponse(infoResponse);
JsonNode data = objectMapper.readTree(infoResponse.getBody())
.path("data").path("map");
JsonNode baseInfo = data.path("baseInfo").path("map");
JsonNode detailInfo = data.path("detailInfo").path("map");
Map<String, String> extraInfoMap = new HashMap<>(detailInfo.size());
Iterator<Map.Entry<String, JsonNode>> it = detailInfo.fields();
while (it.hasNext()) {
Map.Entry<String, JsonNode> field = it.next();
extraInfoMap.put(field.getKey(), StringUtil.cleanHtml(field.getValue().asText("无")));
}
String isbn = baseInfo.get("isbn").asText("无");
String author = baseInfo.path("author").asText("无");
String title = baseInfo.path("title").asText("无");
HttpRequest doubanInfoRequest = LibraryRequestFactory.bookDoubanInfoRequest(isbn);
HttpResponse doubanInfoResponse = requester.get(doubanInfoRequest);
checkResponse(doubanInfoResponse);
JsonNode doubanInfo = objectMapper.readTree(doubanInfoResponse.getBody()).path("data");
String authorDescribe = doubanInfo.path("authorInfo").asText("无");
String catalog = doubanInfo.path("catalog").asText("无");
String summary = doubanInfo.path("content").asText("无");
String introduction = doubanInfo.path("intro").asText(summary);
String coverUrl = doubanInfo.path("imageUrl").asText("");
return BookDetail.builder()
.isbn(isbn)
.author(author)
.title(title)
.extraInfoMap(extraInfoMap)
.authorDescribe(StringUtil.cleanHtml(authorDescribe))
.catalog(StringUtil.cleanHtml(catalog))
.summary(StringUtil.cleanHtml(summary))
.introduction(StringUtil.cleanHtml(introduction))
.coverUrl(coverUrl)
.build();
}
}

@ -0,0 +1,39 @@
package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory;
import cn.wustlinghang.mywust.data.library.parsed.BookHolding;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.exception.ParseException;
import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpRequest;
import cn.wustlinghang.mywust.network.entitys.HttpResponse;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.List;
public class BookHoldingApiService extends BaseLibraryApiService {
public BookHoldingApiService(Requester requester) {
super(requester);
}
public List<BookHolding> getHoldingList(String id)
throws ApiException, IOException, ParseException {
HttpRequest loanRequest = LibraryRequestFactory.bookHoldingRequest(id);
HttpResponse loanResponse = requester.get(loanRequest);
checkResponse(loanResponse);
try {
JsonNode data = objectMapper.readTree(loanResponse.getBody()).path("data");
// 这里的holding是个string值,需要独立解析(坑人)
JsonNode holdings = data.path("holdings");
return objectMapper.readValue(holdings.asText("[]"), bookHoldingListType);
} catch (JacksonException e) {
throw new ParseException(e.getMessage(), "");
}
}
}

@ -0,0 +1,52 @@
package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory;
import cn.wustlinghang.mywust.data.library.PagingResult;
import cn.wustlinghang.mywust.data.library.origin.CurrentLoanBook;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.exception.ParseException;
import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpRequest;
import cn.wustlinghang.mywust.network.entitys.HttpResponse;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.List;
public class CurrentLoanApiService extends BaseLibraryApiService {
public CurrentLoanApiService(Requester requester) {
super(requester);
}
public PagingResult<List<CurrentLoanBook>> getCurrentLoan(String cookie, int page, int pageSize)
throws ApiException, IOException, ParseException {
HttpRequest loanRequest = LibraryRequestFactory.currentLoanRequest(cookie, page, pageSize);
HttpResponse loanResponse = requester.get(loanRequest);
checkResponse(loanResponse);
try {
JsonNode data = objectMapper.readTree(loanResponse.getBody()).path("data");
// 获取,计算分页数据
pageSize = data.path("pageSize").asInt(pageSize);
int currentPage = data.path("currentPage").asInt(page);
int totalResult = data.path("total").asInt(0);
int totalPage = data.path("totalPage").asInt(0);
JsonNode itemsJson = data.path("items");
List<CurrentLoanBook> books = objectMapper.treeToValue(itemsJson, currentLoanBookListType);
return PagingResult.<List<CurrentLoanBook>>builder()
.currentPage(currentPage)
.totalResult(totalResult)
.pageSize(pageSize)
.totalPage(totalPage)
.data(books)
.build();
} catch (JacksonException e) {
throw new ParseException(e.getMessage(), "");
}
}
}

@ -1,66 +0,0 @@
package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory;
import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpRequest;
import cn.wustlinghang.mywust.network.entitys.HttpResponse;
import java.io.IOException;
/**
* 图书馆相关接口由于返回的数据都是json特别好解析所以这里的数据获取到了之后可以直接拿来用自己来解析
*/
public class LibraryApiService extends LibraryApiServiceBase {
public LibraryApiService(Requester requester) {
super(requester);
}
public String search(String keyword, int page, int pageSize) throws ApiException, IOException {
HttpRequest request = LibraryRequestFactory.bookSearchRequest(keyword, page, pageSize);
HttpResponse response = requester.post(request);
checkResponse(response);
return response.getStringBody();
}
public String getBookDetail(String bookId) throws ApiException, IOException {
HttpRequest request = LibraryRequestFactory.bookInfoRequest(bookId);
HttpResponse response = requester.get(request);
checkResponse(response);
return response.getStringBody();
}
public String getBookCoverImageUrl(String isbn) throws ApiException, IOException {
HttpRequest request = LibraryRequestFactory.bookCoverImageUrlRequest(isbn);
HttpResponse response = requester.get(request);
checkResponse(response);
return response.getStringBody();
}
public String getOverdueSoon(String cookie) throws ApiException, IOException {
HttpRequest request = LibraryRequestFactory.overdueSoonRequest(cookie);
HttpResponse response = requester.get(request);
checkResponse(response);
return response.getStringBody();
}
public String getCurrentLoan(String cookie) throws ApiException, IOException {
HttpRequest request = LibraryRequestFactory.currentLoanRequest(cookie);
HttpResponse response = requester.get(request);
checkResponse(response);
return response.getStringBody();
}
public String getLoanHistory(String cookie) throws ApiException, IOException {
HttpRequest request = LibraryRequestFactory.loanHistoryRequest(cookie);
HttpResponse response = requester.get(request);
checkResponse(response);
return response.getStringBody();
}
}

@ -1,20 +0,0 @@
package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpResponse;
public abstract class LibraryApiServiceBase {
protected final Requester requester;
public LibraryApiServiceBase(Requester requester) {
this.requester = requester;
}
public void checkResponse(HttpResponse response) throws ApiException {
// 检查响应是否正确
if (response.getStatusCode() != 200) {
throw new ApiException(ApiException.Code.COOKIE_INVALID);
}
}
}

@ -0,0 +1,53 @@
package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory;
import cn.wustlinghang.mywust.data.library.PagingResult;
import cn.wustlinghang.mywust.data.library.origin.HistoryLoanBook;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.exception.ParseException;
import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpRequest;
import cn.wustlinghang.mywust.network.entitys.HttpResponse;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.List;
public class LoanHistoryApiService extends BaseLibraryApiService {
public LoanHistoryApiService(Requester requester) {
super(requester);
}
public PagingResult<List<HistoryLoanBook>> getLoanHistory(String cookie, int page, int pageSize)
throws ApiException, IOException, ParseException {
HttpRequest request = LibraryRequestFactory.loanHistoryRequest(cookie, page, pageSize);
HttpResponse response = requester.get(request);
checkResponse(response);
try {
JsonNode data = objectMapper.readTree(response.getBody()).path("data");
// 获取,计算分页数据
pageSize = data.path("pageSize").asInt(pageSize);
int currentPage = data.path("currentPage").asInt(page);
int totalResult = data.path("total").asInt(0);
int totalPage = data.path("totalPage").asInt(0);
JsonNode itemsJson = data.path("items");
List<HistoryLoanBook> books = objectMapper.treeToValue(itemsJson, historyLoanBookListType);
return PagingResult.<List<HistoryLoanBook>>builder()
.currentPage(currentPage)
.totalResult(totalResult)
.pageSize(pageSize)
.totalPage(totalPage)
.data(books)
.build();
} catch (JacksonException e) {
throw new ParseException(e.getMessage(), "");
}
}
}

@ -0,0 +1,56 @@
package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory;
import cn.wustlinghang.mywust.data.library.PagingResult;
import cn.wustlinghang.mywust.data.library.origin.BaseLoanBook;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.exception.ParseException;
import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpRequest;
import cn.wustlinghang.mywust.network.entitys.HttpResponse;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.List;
@Slf4j
public class OverdueSoonApiService extends BaseLibraryApiService {
public OverdueSoonApiService(Requester requester) {
super(requester);
}
public PagingResult<List<BaseLoanBook>> getOverdueSoon(String cookie, int page, int pageSize)
throws ApiException, IOException, ParseException {
HttpRequest request = LibraryRequestFactory.overdueSoonRequest(cookie, page, pageSize);
HttpResponse response = requester.get(request);
checkResponse(response);
try {
// todo 这里即将到期书籍字段跟当前借阅和借阅历史字段格式貌似有点不太一样(data字段是个数组)
// 但是目前手上没有样本,因此这里还需要再测试测试
JsonNode data = objectMapper.readTree(response.getBody()).path("data");
// 获取,计算分页数据
pageSize = data.path("pageSize").asInt(pageSize);
int currentPage = data.path("currentPage").asInt(page);
int totalResult = data.path("total").asInt(0);
int totalPage = data.path("totalPage").asInt(0);
JsonNode itemsJson = data.path("items");
List<BaseLoanBook> baseLoanBooks = objectMapper.treeToValue(itemsJson, loanBookListType);
return PagingResult.<List<BaseLoanBook>>builder()
.currentPage(currentPage)
.totalResult(totalResult)
.pageSize(pageSize)
.totalPage(totalPage)
.data(baseLoanBooks)
.build();
} catch (JacksonException e) {
throw new ParseException(e.getMessage(), "");
}
}
}

@ -0,0 +1,68 @@
package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory;
import cn.wustlinghang.mywust.data.library.PagingResult;
import cn.wustlinghang.mywust.data.library.origin.BookSearchRequest;
import cn.wustlinghang.mywust.data.library.origin.BookSearchResult;
import cn.wustlinghang.mywust.data.library.parsed.BookHolding;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.exception.ParseException;
import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpRequest;
import cn.wustlinghang.mywust.network.entitys.HttpResponse;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class SearchApiService extends BaseLibraryApiService {
public SearchApiService(Requester requester) {
super(requester);
}
public PagingResult<List<BookSearchResult>> search(String keyword, int page, int pageSize)
throws ApiException, IOException, ParseException {
return search(new BookSearchRequest(keyword, pageSize, page));
}
public PagingResult<List<BookSearchResult>> search(BookSearchRequest request)
throws ApiException, IOException, ParseException {
HttpRequest searchRequest = LibraryRequestFactory.bookSearchRequest(request);
HttpResponse searchResponse = requester.post(searchRequest);
checkResponse(searchResponse);
try {
JsonNode data = objectMapper.readTree(searchResponse.getBody()).path("data");
int pageSize = data.path("limit").asInt(request.getPageSize());
int currentPage = data.path("offset").asInt(request.getPage()) / pageSize + 1;
int totalResult = data.path("actualTotal").asInt(0);
int totalPage = totalResult / pageSize + 1;
JsonNode dataList = data.get("dataList");
List<BookSearchResult> books = new ArrayList<>(dataList.size());
for (JsonNode item : dataList) {
// 反序列化holding字段,原字段为字符串值,无法直接原样转换
List<BookHolding> holdings = objectMapper.readValue(
item.path("holdings").asText("[]"), bookHoldingListType);
BookSearchResult book = objectMapper.treeToValue(item, BookSearchResult.class);
book.setHoldings(holdings);
books.add(book);
}
return PagingResult.<List<BookSearchResult>>builder()
.currentPage(currentPage)
.totalResult(totalResult)
.pageSize(pageSize)
.totalPage(totalPage)
.data(books)
.build();
} catch (JacksonException e) {
e.printStackTrace();
throw new ParseException(e.getMessage(), "");
}
}
}

@ -1,9 +1,9 @@
package cn.wustlinghang.mywust.core.request.service.undergraduate; package cn.wustlinghang.mywust.core.request.service.undergraduate;
import cn.wustlinghang.mywust.core.api.UndergradUrls; import cn.wustlinghang.mywust.core.api.UndergradUrls;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.core.request.factory.RequestFactory; import cn.wustlinghang.mywust.core.request.factory.RequestFactory;
import cn.wustlinghang.mywust.core.util.BkjxUtil; import cn.wustlinghang.mywust.core.util.BkjxUtil;
import cn.wustlinghang.mywust.exception.ApiException;
import cn.wustlinghang.mywust.network.RequestClientOption; import cn.wustlinghang.mywust.network.RequestClientOption;
import cn.wustlinghang.mywust.network.Requester; import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpRequest; import cn.wustlinghang.mywust.network.entitys.HttpRequest;
@ -23,7 +23,8 @@ public abstract class UndergradApiServiceBase {
// 检查响应是否正确 // 检查响应是否正确
if (response.getBody() == null || if (response.getBody() == null ||
response.getStatusCode() != HttpResponse.HTTP_OK || response.getStatusCode() != HttpResponse.HTTP_OK ||
BkjxUtil.hasLoginFinger(response.getBody())) { BkjxUtil.needLogin(response.getBody()) ||
BkjxUtil.isBannedResponse(response)) {
throw new ApiException(ApiException.Code.COOKIE_INVALID); throw new ApiException(ApiException.Code.COOKIE_INVALID);
} }
@ -41,5 +42,6 @@ public abstract class UndergradApiServiceBase {
} }
public abstract String getPage(String cookie, Map<String, String> params, RequestClientOption option) throws ApiException, IOException; public abstract String getPage(String cookie, Map<String, String> params, RequestClientOption option) throws ApiException, IOException;
public abstract String getPage(String cookie, Map<String, String> params) throws ApiException, IOException; public abstract String getPage(String cookie, Map<String, String> params) throws ApiException, IOException;
} }

@ -1,5 +1,7 @@
package cn.wustlinghang.mywust.core.util; package cn.wustlinghang.mywust.core.util;
import cn.wustlinghang.mywust.network.entitys.HttpResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
/** /**
@ -11,12 +13,13 @@ public class BkjxUtil {
/** /**
* <p>通过粗暴地比较响应字节前几个字符是否为登录跳转特征字符判断是否需要重新登录</p> * <p>通过粗暴地比较响应字节前几个字符是否为登录跳转特征字符判断是否需要重新登录</p>
* <p>只比较前若干个字节在数据较大时比较有用</p>
* <p>对于null对象一律认为不需要</p> * <p>对于null对象一律认为不需要</p>
* *
* @param response 响应的字节 * @param response 响应的字节
* @return 是否需要重新登录 * @return 是否需要重新登录
*/ */
public static boolean hasLoginFinger(byte[] response) { public static boolean needLogin(byte[] response) {
if (response == null) { if (response == null) {
return false; return false;
} }
@ -29,4 +32,39 @@ public class BkjxUtil {
return true; return true;
} }
/**
* <p>检查是否为专属选课时间封号</p>
* <p>对于null对象一律认为不是</p>
*
* @param response HttpResponse响应
* @return 是否为封号响应
*/
public static boolean isBannedResponse(HttpResponse response) {
if (response == null || response.getBody() == null) {
return false;
}
// 这里用了个取巧的办法,如果响应体大于400bytes,则直接认为不是封号的响应,不继续比较关键字
if (response.getBody().length > 400) {
return false;
}
return isBannedResponse(response.getStringBody());
}
/**
* <p>检查是否为专属选课时间封号</p>
* <p>对于null对象一律认为不是</p>
*
* @param responseText 响应字符串
* @return 是否为封号响应
*/
public static boolean isBannedResponse(String responseText) {
if (responseText == null) {
return false;
}
return responseText.contains("当前登录帐号已被禁用");
}
} }

@ -110,4 +110,29 @@ public class StringUtil {
return result; return result;
} }
/**
* 清除字符串中的htmlxml标签其中`&lt;br&gt;`被替换为换行`\n`
*
* @param raw 原始字符串
* @return 处理后的字符串
*/
public static String cleanHtml(String raw) {
return cleanHtml(raw, true);
}
/**
* 清除字符串中的htmlxml标签并指定`&lt;br&gt;`是否要替换为换行符`\n`
*
* @param raw 原始字符串
* @param withBreakLine 是否将换行标签`&lt;br&gt;`替换成换行符`\n`
* @return 处理后的字符串
*/
public static String cleanHtml(String raw, boolean withBreakLine) {
if (withBreakLine) {
raw = raw.replaceAll("<br>", "\n");
}
return raw.replaceAll("<.*?>", "");
}
} }

Loading…
Cancel
Save