SeecoderBokClient.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. package cn.seecoder.bok.client;
  2. import cn.seecoder.bok.common.dto.CollectDTO;
  3. import cn.seecoder.bok.common.dto.RateDTO;
  4. import cn.seecoder.bok.common.vo.*;
  5. import com.fasterxml.jackson.core.JsonProcessingException;
  6. import com.fasterxml.jackson.core.type.TypeReference;
  7. import com.fasterxml.jackson.databind.ObjectMapper;
  8. import com.fasterxml.jackson.databind.json.JsonMapper;
  9. import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
  10. import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
  11. import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
  12. import okhttp3.*;
  13. import org.slf4j.Logger;
  14. import org.slf4j.LoggerFactory;
  15. import org.springframework.data.domain.*;
  16. import org.springframework.data.util.Streamable;
  17. import javax.validation.constraints.NotBlank;
  18. import javax.validation.constraints.NotNull;
  19. import java.io.IOException;
  20. import java.text.MessageFormat;
  21. import java.util.*;
  22. import java.util.function.Function;
  23. /**
  24. * @author kunduin
  25. */
  26. public class SeecoderBokClient
  27. implements SeecoderBokQuestionApi, SeecoderBokQuestionQueryApi,
  28. SeecoderBokRecordApi, SeecoderBokUserInteractiveApi,
  29. SeecoderBokKnowledgeApi {
  30. private final Logger logger = LoggerFactory.getLogger(getClass());
  31. private final OkHttpClient client;
  32. private final ObjectMapper mapper;
  33. private final int CONFIG_PAGE_START;
  34. private final String CONFIG_HOST;
  35. /**
  36. * 创建client
  37. *
  38. * @param host bok地址,示例:{@code "http://bok.seecoder.cn"}
  39. */
  40. public SeecoderBokClient(String host) {
  41. this(host, 0);
  42. }
  43. public SeecoderBokClient(String host, int pageStart) {
  44. this.CONFIG_HOST = host;
  45. this.CONFIG_PAGE_START = pageStart;
  46. client = new OkHttpClient();
  47. mapper = JsonMapper.builder()
  48. .addModule(new ParameterNamesModule())
  49. .addModule(new Jdk8Module())
  50. .addModule(new JavaTimeModule())
  51. .build();
  52. }
  53. @Override
  54. public List<KnowledgeNodeVO> getKnowledgeNode(String name) {
  55. HttpUrl httpUrl = createUrlBuilder("/api/knowledge")
  56. .addQueryParameter("name", name).build();
  57. Response response = get(httpUrl);
  58. return getResponseBody(response, new TypeReference<List<KnowledgeNodeVO>>() {})
  59. .orElse(Collections.emptyList());
  60. }
  61. @Override
  62. public Page<QuestionVO> getQuestionList(
  63. String stem, String tag, String knowledgeId, Pageable pageable) {
  64. HttpUrl.Builder urlBuilder = createUrlBuilder("/api/question")
  65. .addQueryParameter("stem", stem)
  66. .addQueryParameter("tag", tag)
  67. .addQueryParameter("knowledgeId", knowledgeId);
  68. return getQuestionVOS(pageable, urlBuilder);
  69. }
  70. @Override
  71. public QuestionVO createQuestion(@NotNull QuestionVO question) {
  72. HttpUrl httpUrl = createUrlBuilder("/api/question").build();
  73. Response response = post(httpUrl, writeRequestBody(question));
  74. return getResponseBody(response, QuestionVO.class).orElseThrow(() -> SeecoderBokException.UNWRAP_EXCEPTION);
  75. }
  76. @Override
  77. public Optional<QuestionVO> getQuestionById(@NotBlank String questionId) {
  78. HttpUrl httpUrl = createUrlBuilder(MessageFormat.format("/api/question/{0}", questionId)).build();
  79. Response response = get(httpUrl);
  80. return getResponseBody(response, QuestionVO.class);
  81. }
  82. @Override
  83. public QuestionVO updateQuestion(@NotNull QuestionVO question) {
  84. HttpUrl httpUrl = createUrlBuilder(MessageFormat.format("/api/question/{0}", question.getId())).build();
  85. Response response = put(httpUrl, writeRequestBody(question));
  86. return getResponseBody(response, QuestionVO.class).orElseThrow(() -> SeecoderBokException.UNWRAP_EXCEPTION);
  87. }
  88. @Override
  89. public void deleteQuestionById(@NotBlank String questionId) {
  90. HttpUrl httpUrl = createUrlBuilder(MessageFormat.format("/api/question/{0}", questionId)).build();
  91. delete(httpUrl);
  92. }
  93. @Override
  94. public List<QuestionVO> getQuestionListByIds(@NotNull List<String> ids) {
  95. HttpUrl httpUrl = createUrlBuilder("/api/question-query/id")
  96. .addQueryParameter("id", joinComma(ids))
  97. .build();
  98. Response response = get(httpUrl);
  99. return getResponseBody(response, new TypeReference<List<QuestionVO>>() {})
  100. .orElse(Collections.emptyList());
  101. }
  102. @Override
  103. public List<RecordVO> createUserRecord(@NotNull List<RecordVO> records) {
  104. HttpUrl httpUrl = createUrlBuilder("/api/record").build();
  105. Response response = post(httpUrl, writeRequestBody(records));
  106. return getResponseBody(response, new TypeReference<List<RecordVO>>() {})
  107. .orElse(Collections.emptyList());
  108. }
  109. @Override
  110. public Page<RecordVO> getUserRecord(
  111. List<String> userIdList,
  112. List<String> questionIdList,
  113. List<String> recordIdList,
  114. Pageable pageable) {
  115. HttpUrl.Builder urlBuilder = createUrlBuilder("/api/record")
  116. .addQueryParameter("userId", joinComma(userIdList))
  117. .addQueryParameter("questionId", joinComma(questionIdList))
  118. .addQueryParameter("recordId", joinComma(recordIdList));
  119. HttpUrl httpUrl = addPageableToParams(urlBuilder, pageable).build();
  120. Response response = get(httpUrl);
  121. return getResponseBody(response, new TypeReference<Page<RecordVO>>() {})
  122. .map(recordVOS -> new PageImpl<>(
  123. recordVOS.getContent(),
  124. parseBokToPageable(recordVOS.getNumber(), recordVOS.getSize(), pageable),
  125. recordVOS.getTotalElements()))
  126. .orElse(new PageImpl<>(Collections.emptyList()));
  127. }
  128. @Override
  129. public PageableRecordQuestionVO getUserRecordQuestions(
  130. @NotNull String userId, Pageable pageable) {
  131. HttpUrl.Builder urlBuilder = createUrlBuilder(MessageFormat.format("/api/record/{0}/question", userId));
  132. HttpUrl httpUrl = addPageableToParams(urlBuilder, pageable).build();
  133. Response response = get(httpUrl);
  134. return getResponseBody(response, PageableRecordQuestionVO.class)
  135. .map(pageableRecordQuestionVO -> {
  136. PageVO page = pageableRecordQuestionVO.getPage();
  137. page.setNumber(parseBokToPageableNumber(page.getNumber()));
  138. pageableRecordQuestionVO.setPage(page);
  139. return pageableRecordQuestionVO;
  140. })
  141. .orElse(PageableRecordQuestionVO.ofEmpty());
  142. }
  143. @Override
  144. public void collectQuestion(
  145. @NotBlank String userId, @NotBlank String questionId, @NotNull CollectDTO collectDTO) {
  146. HttpUrl httpUrl = createUrlBuilder(
  147. MessageFormat.format("/api/user/{0}/question/{1}/collection", userId, questionId)).build();
  148. post(httpUrl, writeRequestBody(collectDTO));
  149. }
  150. @Override
  151. public void rateQuestion(
  152. @NotBlank String userId, @NotBlank String questionId, @NotNull RateDTO rateDTO) {
  153. HttpUrl httpUrl = createUrlBuilder(
  154. MessageFormat.format("/api/user/{0}/question/{1}/rate", userId, questionId)).build();
  155. post(httpUrl, writeRequestBody(rateDTO));
  156. }
  157. @Override
  158. public Page<QuestionVO> getUserInteractiveQuestions(
  159. @NotBlank String userId, Boolean collect, Boolean rate, Pageable pageable) {
  160. HttpUrl.Builder urlBuilder = createUrlBuilder(MessageFormat.format("/api/user/{0}/question", userId))
  161. .addQueryParameter("collection", String.valueOf(collect))
  162. .addQueryParameter("rate", String.valueOf(rate));
  163. return getQuestionVOS(pageable, urlBuilder);
  164. }
  165. @Override
  166. public Optional<InteractiveVO> getUserInteractiveQuestion(
  167. @NotBlank String userId, @NotBlank String questionId) {
  168. HttpUrl httpUrl = createUrlBuilder(
  169. MessageFormat.format("/api/user/{0}/question/{1}", userId, questionId)).build();
  170. Response response = get(httpUrl);
  171. return getResponseBody(response, InteractiveVO.class);
  172. }
  173. @Override
  174. public List<InteractiveVO> getUserInteractiveQuestionIn(
  175. @NotBlank String userId, List<String> questionIdList) {
  176. HttpUrl httpUrl = createUrlBuilder(
  177. MessageFormat.format("/api/user/{0}/questionInList", userId)).build();
  178. Response response = get(httpUrl);
  179. return getResponseBody(response, new TypeReference<List<InteractiveVO>>() {})
  180. .orElse(Collections.emptyList());
  181. }
  182. private Page<QuestionVO> getQuestionVOS(Pageable pageable, HttpUrl.Builder urlBuilder) {
  183. HttpUrl httpUrl = addPageableToParams(urlBuilder, pageable).build();
  184. Response response = get(httpUrl);
  185. return getResponseBody(response, PageableQuestionListVO.class)
  186. .map(pageableQuestionListVO ->
  187. new PageImpl<>(
  188. pageableQuestionListVO.getQuestions(),
  189. parseBokToPageable(pageableQuestionListVO.getPage(), pageable),
  190. pageableQuestionListVO.getPage().getTotalElements()))
  191. .orElse(new PageImpl<>(Collections.emptyList()));
  192. }
  193. private Response get(HttpUrl httpUrl) {
  194. return requestWithMethod("GET", httpUrl, null);
  195. }
  196. private void delete(HttpUrl httpUrl) {
  197. requestWithMethod("DELETE", httpUrl, null);
  198. }
  199. private Response post(HttpUrl httpUrl, RequestBody requestBody) {
  200. return requestWithMethod("POST", httpUrl, requestBody);
  201. }
  202. private Response put(HttpUrl httpUrl, RequestBody requestBody) {
  203. return requestWithMethod("PUT", httpUrl, requestBody);
  204. }
  205. private Response requestWithMethod(String method, HttpUrl httpUrl, RequestBody requestBody) {
  206. Request request = new Request.Builder().url(httpUrl).method(method, requestBody).build();
  207. try {
  208. return client.newCall(request).execute();
  209. } catch (IOException e) {
  210. logger.error("IOException.", e);
  211. throw SeecoderBokException.IO_EXCEPTION;
  212. }
  213. }
  214. private HttpUrl.Builder createUrlBuilder(final String path) {
  215. return Objects.requireNonNull(HttpUrl.parse(CONFIG_HOST + path)).newBuilder();
  216. }
  217. private <T> Optional<T> getResponseBody(Response response, Class<T> tClass) {
  218. return getResponseBody(
  219. response,
  220. bodyString -> {
  221. try {
  222. return mapper.readValue(bodyString, tClass);
  223. } catch (JsonProcessingException e) {
  224. logger.error("JsonProcessingException.", e);
  225. throw SeecoderBokException.JSON_EXCEPTION;
  226. }
  227. });
  228. }
  229. private <T> Optional<T> getResponseBody(Response response, TypeReference<T> typeReference) {
  230. return getResponseBody(
  231. response,
  232. bodyString -> {
  233. try {
  234. return mapper.readValue(bodyString, typeReference);
  235. } catch (JsonProcessingException e) {
  236. logger.error("JsonProcessingException.", e);
  237. throw SeecoderBokException.JSON_EXCEPTION;
  238. }
  239. });
  240. }
  241. private <T> Optional<T> getResponseBody(Response response, Function<String, T> mapType) {
  242. return Optional.ofNullable(response.body())
  243. .map(body -> {
  244. try {
  245. String bodyString = body.string();
  246. return mapType.apply(bodyString);
  247. } catch (IOException e) {
  248. logger.error("IOException.", e);
  249. throw SeecoderBokException.IO_EXCEPTION;
  250. }
  251. });
  252. }
  253. private <T> RequestBody writeRequestBody(T obj) {
  254. if (obj == null) {
  255. return null;
  256. }
  257. try {
  258. return RequestBody.create(mapper.writeValueAsString(obj), SeecoderBokConstants.JSON);
  259. } catch (JsonProcessingException e) {
  260. logger.error("JsonProcessingException.", e);
  261. throw SeecoderBokException.JSON_EXCEPTION;
  262. }
  263. }
  264. private int parsePageableNumberToBok(int pageNumber) {
  265. return pageNumber - CONFIG_PAGE_START + SeecoderBokConstants.PAGE_START;
  266. }
  267. private int parseBokToPageableNumber(int bokPageNumber) {
  268. return bokPageNumber - SeecoderBokConstants.PAGE_START + CONFIG_PAGE_START;
  269. }
  270. private Pageable parseBokToPageable(PageVO pageVO, Pageable pageable) {
  271. return parseBokToPageable(pageVO.getNumber(), pageVO.getSize(), pageable);
  272. }
  273. private Pageable parseBokToPageable(int page, int size, Pageable pageable) {
  274. if (pageable == null) {
  275. return PageRequest.of(parseBokToPageableNumber(page), size);
  276. }
  277. return pageable;
  278. }
  279. private HttpUrl.Builder addPageableToParams(HttpUrl.Builder builder, Pageable pageable) {
  280. return Optional.ofNullable(pageable)
  281. .map(notNullPageable ->
  282. // TODO: not accept error
  283. builder.addQueryParameter("page", String.valueOf(parsePageableNumberToBok(notNullPageable.getPageNumber()) + 1))
  284. .addQueryParameter("sort", parseSort(notNullPageable.getSort()))
  285. .addQueryParameter("size", String.valueOf(notNullPageable.getPageSize()))
  286. )
  287. .orElse(builder);
  288. }
  289. private String joinComma(Iterable<String> list) {
  290. return Optional.ofNullable(list)
  291. .map(notNullList -> String.join(",", notNullList))
  292. .orElse("");
  293. }
  294. private String parseSort(Sort sort) {
  295. return String.join("|",
  296. sort.map(order -> order.getProperty() + "," + order.getDirection()));
  297. }
  298. }