impl: 图书馆:图书馆全部功能实现(图书详情获取;书籍搜索;馆藏信息查询;当前/历史/即将过期借阅查询;图书封面地址获取)

main
lensfrex 1 year ago
parent 99e1ea7c8f
commit 965562b69e
Signed by: lensfrex
GPG Key ID: 0F69A0A2FBEE98A0
  1. 2
      README.md
  2. 9
      mywust-common/pom.xml
  3. 20
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/PagingResult.java
  4. 61
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/origin/BaseLoanBook.java
  5. 67
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/origin/BookSearchRequest.java
  6. 80
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/origin/BookSearchResult.java
  7. 12
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/origin/CurrentLoanBook.java
  8. 12
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/origin/HistoryLoanBook.java
  9. 51
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/parsed/BookDetail.java
  10. 56
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/parsed/BookHolding.java
  11. 69
      mywust-common/src/main/java/cn/wustlinghang/mywust/data/library/parsed/BookSearchResult.java
  12. 5
      mywust-core/pom.xml
  13. 9
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/api/LibraryUrls.java
  14. 38
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/factory/library/LibraryRequestFactory.java
  15. 40
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/factory/library/request/SearchRequest.java
  16. 62
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/BaseLibraryApiService.java
  17. 40
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/BookCoverImageUrlApiService.java
  18. 56
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/BookDetailApiService.java
  19. 39
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/BookHoldingApiService.java
  20. 40
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/CurrentLoanApiService.java
  21. 36
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/LoanHistoryApiService.java
  22. 39
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/OverdueSoonApiService.java
  23. 54
      mywust-core/src/main/java/cn/wustlinghang/mywust/core/request/service/library/SearchApiService.java
  24. 25
      mywust-util/src/main/java/cn/wustlinghang/mywust/util/StringUtil.java
  25. 2
      pom.xml

@ -6,7 +6,7 @@
核心代码来自武科大助手后端爬虫模块,在此基础上进行部分修改以适用于各种平台,因此不会使用重量级的框架,尽量保证仅使用原生java或jvm兼容的语言即可使用,是一个比较~~轻量~~的库 核心代码来自武科大助手后端爬虫模块,在此基础上进行部分修改以适用于各种平台,因此不会使用重量级的框架,尽量保证仅使用原生java或jvm兼容的语言即可使用,是一个比较~~轻量~~的库
由于处在早期阶段,因此项目结构随时可能发生巨大变化,在正式版出来前请勿重度依赖 由于处在早期阶段,因此项目结构以及外部接口随时可能发生巨大变化,在正式版出来前请勿重度依赖
后续可能会根据需要新增其他语言的实现以提供给其他语言和平台的使用 后续可能会根据需要新增其他语言的实现以提供给其他语言和平台的使用

@ -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可能是用于记录该图书的唯一标识
*/

@ -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>

@ -10,11 +10,14 @@ public class LibraryUrls {
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/";

@ -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);
}
}
}

@ -1,10 +1,51 @@
package cn.wustlinghang.mywust.core.request.service.library; 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.exception.ApiException;
import cn.wustlinghang.mywust.network.Requester; import cn.wustlinghang.mywust.network.Requester;
import cn.wustlinghang.mywust.network.entitys.HttpResponse; 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 { 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; protected final Requester requester;
public BaseLibraryApiService(Requester requester) { public BaseLibraryApiService(Requester requester) {
@ -18,3 +59,24 @@ public abstract class BaseLibraryApiService {
} }
} }
} }
/**
* <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);
}
}

@ -5,19 +5,55 @@ import cn.wustlinghang.mywust.exception.ApiException;
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 com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException; import java.io.IOException;
import java.util.*;
public class BookCoverImageUrlApiService extends BaseLibraryApiService { public class BookCoverImageUrlApiService extends BaseLibraryApiService {
public BookCoverImageUrlApiService(Requester requester) { public BookCoverImageUrlApiService(Requester requester) {
super(requester); super(requester);
} }
public String getBookCoverImageUrl(String isbn) throws ApiException, IOException { public List<String> getBookCoverImageUrl(String isbn) throws ApiException, IOException {
HttpRequest request = LibraryRequestFactory.bookCoverImageUrlRequest(isbn); HttpRequest request = LibraryRequestFactory.bookCoverImageUrlRequest(isbn);
HttpResponse response = requester.get(request); HttpResponse response = requester.get(request);
checkResponse(response); checkResponse(response);
return response.getStringBody(); // 因为只查了一本书,所以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()
.replaceAll("^//", "http://")
);
}
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()
.replaceAll("^//", "http://")
);
}
result.put(bookItemJson.getKey(), covers);
}
return result;
} }
} }

@ -1,24 +1,70 @@
package cn.wustlinghang.mywust.core.request.service.library; package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory; 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.exception.ApiException;
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 cn.wustlinghang.mywust.util.StringUtil;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class BookDetailApiService extends BaseLibraryApiService { public class BookDetailApiService extends BaseLibraryApiService {
public BookDetailApiService(Requester requester) { public BookDetailApiService(Requester requester) {
super(requester); super(requester);
} }
public String getBookDetail(String bookId) throws ApiException, IOException { public BookDetail getBookDetail(String bookId) throws ApiException, IOException {
HttpRequest request = LibraryRequestFactory.bookInfoRequest(bookId); HttpRequest infoRequest = LibraryRequestFactory.bookInfoRequest(bookId);
HttpResponse response = requester.get(request); HttpResponse infoResponse = requester.get(infoRequest);
checkResponse(response); checkResponse(infoResponse);
return response.getStringBody(); 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("")
.replaceAll("^//", "http://");
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(), "");
}
}
}

@ -1,24 +1,52 @@
package cn.wustlinghang.mywust.core.request.service.library; package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory; 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.ApiException;
import cn.wustlinghang.mywust.exception.ParseException;
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 com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException; import java.io.IOException;
import java.util.List;
public class CurrentLoanApiService extends BaseLibraryApiService { public class CurrentLoanApiService extends BaseLibraryApiService {
public CurrentLoanApiService(Requester requester) { public CurrentLoanApiService(Requester requester) {
super(requester); super(requester);
} }
public String getCurrentLoan(String cookie) throws ApiException, IOException { public PagingResult<List<CurrentLoanBook>> getCurrentLoan(String cookie, int page, int pageSize)
HttpRequest request = LibraryRequestFactory.currentLoanRequest(cookie); throws ApiException, IOException, ParseException {
HttpResponse response = requester.get(request);
checkResponse(response);
return response.getStringBody(); 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,23 +1,53 @@
package cn.wustlinghang.mywust.core.request.service.library; package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory; 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.ApiException;
import cn.wustlinghang.mywust.exception.ParseException;
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 com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException; import java.io.IOException;
import java.util.List;
public class LoanHistoryApiService extends BaseLibraryApiService { public class LoanHistoryApiService extends BaseLibraryApiService {
public LoanHistoryApiService(Requester requester) { public LoanHistoryApiService(Requester requester) {
super(requester); super(requester);
} }
public String getLoanHistory(String cookie) throws ApiException, IOException { public PagingResult<List<HistoryLoanBook>> getLoanHistory(String cookie, int page, int pageSize)
HttpRequest request = LibraryRequestFactory.loanHistoryRequest(cookie); throws ApiException, IOException, ParseException {
HttpRequest request = LibraryRequestFactory.loanHistoryRequest(cookie, page, pageSize);
HttpResponse response = requester.get(request); HttpResponse response = requester.get(request);
checkResponse(response); 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);
return response.getStringBody(); 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(), "");
}
} }
} }

@ -1,23 +1,56 @@
package cn.wustlinghang.mywust.core.request.service.library; package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory; 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.ApiException;
import cn.wustlinghang.mywust.exception.ParseException;
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 com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException; import java.io.IOException;
import java.util.List;
@Slf4j
public class OverdueSoonApiService extends BaseLibraryApiService { public class OverdueSoonApiService extends BaseLibraryApiService {
public OverdueSoonApiService(Requester requester) { public OverdueSoonApiService(Requester requester) {
super(requester); super(requester);
} }
public String getOverdueSoon(String cookie) throws ApiException, IOException { public PagingResult<List<BaseLoanBook>> getOverdueSoon(String cookie, int page, int pageSize)
HttpRequest request = LibraryRequestFactory.overdueSoonRequest(cookie); throws ApiException, IOException, ParseException {
HttpRequest request = LibraryRequestFactory.overdueSoonRequest(cookie, page, pageSize);
HttpResponse response = requester.get(request); HttpResponse response = requester.get(request);
checkResponse(response); 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 response.getStringBody(); return PagingResult.<List<BaseLoanBook>>builder()
.currentPage(currentPage)
.totalResult(totalResult)
.pageSize(pageSize)
.totalPage(totalPage)
.data(baseLoanBooks)
.build();
} catch (JacksonException e) {
throw new ParseException(e.getMessage(), "");
}
} }
} }

@ -1,24 +1,68 @@
package cn.wustlinghang.mywust.core.request.service.library; package cn.wustlinghang.mywust.core.request.service.library;
import cn.wustlinghang.mywust.core.request.factory.library.LibraryRequestFactory; 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.ApiException;
import cn.wustlinghang.mywust.exception.ParseException;
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 com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class SearchApiService extends BaseLibraryApiService { public class SearchApiService extends BaseLibraryApiService {
public SearchApiService(Requester requester) { public SearchApiService(Requester requester) {
super(requester); super(requester);
} }
public String search(String keyword, int page, int pageSize) throws ApiException, IOException { public PagingResult<List<BookSearchResult>> search(String keyword, int page, int pageSize)
HttpRequest request = LibraryRequestFactory.bookSearchRequest(keyword, page, pageSize); throws ApiException, IOException, ParseException {
HttpResponse response = requester.post(request); return search(new BookSearchRequest(keyword, pageSize, page));
checkResponse(response); }
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;
return response.getStringBody(); 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(), "");
}
}
} }

@ -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("<.*?>", "");
}
} }

@ -29,7 +29,7 @@
</dependencies> </dependencies>
<properties> <properties>
<revision>0.0.2-SNAPSHOT</revision> <revision>0.0.2-beta</revision>
<lombok.version>1.18.22</lombok.version> <lombok.version>1.18.22</lombok.version>

Loading…
Cancel
Save