package cn.seecoder.bok.client; import cn.seecoder.bok.common.dto.CollectDTO; import cn.seecoder.bok.common.dto.RateDTO; import cn.seecoder.bok.common.vo.*; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import okhttp3.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.*; import org.springframework.data.util.Streamable; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.io.IOException; import java.text.MessageFormat; import java.util.*; import java.util.function.Function; /** * @author kunduin */ public class SeecoderBokClient implements SeecoderBokQuestionApi, SeecoderBokQuestionQueryApi, SeecoderBokRecordApi, SeecoderBokUserInteractiveApi, SeecoderBokKnowledgeApi { private final Logger logger = LoggerFactory.getLogger(getClass()); private final OkHttpClient client; private final ObjectMapper mapper; private final int CONFIG_PAGE_START; private final String CONFIG_HOST; /** * 创建client * * @param host bok地址,示例:{@code "http://bok.seecoder.cn"} */ public SeecoderBokClient(String host) { this(host, 0); } public SeecoderBokClient(String host, int pageStart) { this.CONFIG_HOST = host; this.CONFIG_PAGE_START = pageStart; client = new OkHttpClient(); mapper = JsonMapper.builder() .addModule(new ParameterNamesModule()) .addModule(new Jdk8Module()) .addModule(new JavaTimeModule()) .build(); } @Override public List getKnowledgeNode(String name) { HttpUrl httpUrl = createUrlBuilder("/api/knowledge") .addQueryParameter("name", name).build(); Response response = get(httpUrl); return getResponseBody(response, new TypeReference>() {}) .orElse(Collections.emptyList()); } @Override public Page getQuestionList( String stem, String tag, String knowledgeId, Pageable pageable) { HttpUrl.Builder urlBuilder = createUrlBuilder("/api/question") .addQueryParameter("stem", stem) .addQueryParameter("tag", tag) .addQueryParameter("knowledgeId", knowledgeId); return getQuestionVOS(pageable, urlBuilder); } @Override public QuestionVO createQuestion(@NotNull QuestionVO question) { HttpUrl httpUrl = createUrlBuilder("/api/question").build(); Response response = post(httpUrl, writeRequestBody(question)); return getResponseBody(response, QuestionVO.class).orElseThrow(() -> SeecoderBokException.UNWRAP_EXCEPTION); } @Override public Optional getQuestionById(@NotBlank String questionId) { HttpUrl httpUrl = createUrlBuilder(MessageFormat.format("/api/question/{0}", questionId)).build(); Response response = get(httpUrl); return getResponseBody(response, QuestionVO.class); } @Override public QuestionVO updateQuestion(@NotNull QuestionVO question) { HttpUrl httpUrl = createUrlBuilder(MessageFormat.format("/api/question/{0}", question.getId())).build(); Response response = put(httpUrl, writeRequestBody(question)); return getResponseBody(response, QuestionVO.class).orElseThrow(() -> SeecoderBokException.UNWRAP_EXCEPTION); } @Override public void deleteQuestionById(@NotBlank String questionId) { HttpUrl httpUrl = createUrlBuilder(MessageFormat.format("/api/question/{0}", questionId)).build(); delete(httpUrl); } @Override public List getQuestionListByIds(@NotNull List ids) { HttpUrl httpUrl = createUrlBuilder("/api/question-query/id") .addQueryParameter("id", joinComma(ids)) .build(); Response response = get(httpUrl); return getResponseBody(response, new TypeReference>() {}) .orElse(Collections.emptyList()); } @Override public List createUserRecord(@NotNull List records) { HttpUrl httpUrl = createUrlBuilder("/api/record").build(); Response response = post(httpUrl, writeRequestBody(records)); return getResponseBody(response, new TypeReference>() {}) .orElse(Collections.emptyList()); } @Override public Page getUserRecord( List userIdList, List questionIdList, List recordIdList, Pageable pageable) { HttpUrl.Builder urlBuilder = createUrlBuilder("/api/record") .addQueryParameter("userId", joinComma(userIdList)) .addQueryParameter("questionId", joinComma(questionIdList)) .addQueryParameter("recordId", joinComma(recordIdList)); HttpUrl httpUrl = addPageableToParams(urlBuilder, pageable).build(); Response response = get(httpUrl); return getResponseBody(response, new TypeReference>() {}) .map(recordVOS -> new PageImpl<>( recordVOS.getContent(), parseBokToPageable(recordVOS.getNumber(), recordVOS.getSize(), pageable), recordVOS.getTotalElements())) .orElse(new PageImpl<>(Collections.emptyList())); } @Override public PageableRecordQuestionVO getUserRecordQuestions( @NotNull String userId, Pageable pageable) { HttpUrl.Builder urlBuilder = createUrlBuilder(MessageFormat.format("/api/record/{0}/question", userId)); HttpUrl httpUrl = addPageableToParams(urlBuilder, pageable).build(); Response response = get(httpUrl); return getResponseBody(response, PageableRecordQuestionVO.class) .map(pageableRecordQuestionVO -> { PageVO page = pageableRecordQuestionVO.getPage(); page.setNumber(parseBokToPageableNumber(page.getNumber())); pageableRecordQuestionVO.setPage(page); return pageableRecordQuestionVO; }) .orElse(PageableRecordQuestionVO.ofEmpty()); } @Override public void collectQuestion( @NotBlank String userId, @NotBlank String questionId, @NotNull CollectDTO collectDTO) { HttpUrl httpUrl = createUrlBuilder( MessageFormat.format("/api/user/{0}/question/{1}/collection", userId, questionId)).build(); post(httpUrl, writeRequestBody(collectDTO)); } @Override public void rateQuestion( @NotBlank String userId, @NotBlank String questionId, @NotNull RateDTO rateDTO) { HttpUrl httpUrl = createUrlBuilder( MessageFormat.format("/api/user/{0}/question/{1}/rate", userId, questionId)).build(); post(httpUrl, writeRequestBody(rateDTO)); } @Override public Page getUserInteractiveQuestions( @NotBlank String userId, Boolean collect, Boolean rate, Pageable pageable) { HttpUrl.Builder urlBuilder = createUrlBuilder(MessageFormat.format("/api/user/{0}/question", userId)) .addQueryParameter("collection", String.valueOf(collect)) .addQueryParameter("rate", String.valueOf(rate)); return getQuestionVOS(pageable, urlBuilder); } @Override public Optional getUserInteractiveQuestion( @NotBlank String userId, @NotBlank String questionId) { HttpUrl httpUrl = createUrlBuilder( MessageFormat.format("/api/user/{0}/question/{1}", userId, questionId)).build(); Response response = get(httpUrl); return getResponseBody(response, InteractiveVO.class); } @Override public List getUserInteractiveQuestionIn( @NotBlank String userId, List questionIdList) { HttpUrl httpUrl = createUrlBuilder( MessageFormat.format("/api/user/{0}/questionInList", userId)).build(); Response response = get(httpUrl); return getResponseBody(response, new TypeReference>() {}) .orElse(Collections.emptyList()); } private Page getQuestionVOS(Pageable pageable, HttpUrl.Builder urlBuilder) { HttpUrl httpUrl = addPageableToParams(urlBuilder, pageable).build(); Response response = get(httpUrl); return getResponseBody(response, PageableQuestionListVO.class) .map(pageableQuestionListVO -> new PageImpl<>( pageableQuestionListVO.getQuestions(), parseBokToPageable(pageableQuestionListVO.getPage(), pageable), pageableQuestionListVO.getPage().getTotalElements())) .orElse(new PageImpl<>(Collections.emptyList())); } private Response get(HttpUrl httpUrl) { return requestWithMethod("GET", httpUrl, null); } private void delete(HttpUrl httpUrl) { requestWithMethod("DELETE", httpUrl, null); } private Response post(HttpUrl httpUrl, RequestBody requestBody) { return requestWithMethod("POST", httpUrl, requestBody); } private Response put(HttpUrl httpUrl, RequestBody requestBody) { return requestWithMethod("PUT", httpUrl, requestBody); } private Response requestWithMethod(String method, HttpUrl httpUrl, RequestBody requestBody) { Request request = new Request.Builder().url(httpUrl).method(method, requestBody).build(); try { return client.newCall(request).execute(); } catch (IOException e) { logger.error("IOException.", e); throw SeecoderBokException.IO_EXCEPTION; } } private HttpUrl.Builder createUrlBuilder(final String path) { return Objects.requireNonNull(HttpUrl.parse(CONFIG_HOST + path)).newBuilder(); } private Optional getResponseBody(Response response, Class tClass) { return getResponseBody( response, bodyString -> { try { return mapper.readValue(bodyString, tClass); } catch (JsonProcessingException e) { logger.error("JsonProcessingException.", e); throw SeecoderBokException.JSON_EXCEPTION; } }); } private Optional getResponseBody(Response response, TypeReference typeReference) { return getResponseBody( response, bodyString -> { try { return mapper.readValue(bodyString, typeReference); } catch (JsonProcessingException e) { logger.error("JsonProcessingException.", e); throw SeecoderBokException.JSON_EXCEPTION; } }); } private Optional getResponseBody(Response response, Function mapType) { return Optional.ofNullable(response.body()) .map(body -> { try { String bodyString = body.string(); return mapType.apply(bodyString); } catch (IOException e) { logger.error("IOException.", e); throw SeecoderBokException.IO_EXCEPTION; } }); } private RequestBody writeRequestBody(T obj) { if (obj == null) { return null; } try { return RequestBody.create(mapper.writeValueAsString(obj), SeecoderBokConstants.JSON); } catch (JsonProcessingException e) { logger.error("JsonProcessingException.", e); throw SeecoderBokException.JSON_EXCEPTION; } } private int parsePageableNumberToBok(int pageNumber) { return pageNumber - CONFIG_PAGE_START + SeecoderBokConstants.PAGE_START; } private int parseBokToPageableNumber(int bokPageNumber) { return bokPageNumber - SeecoderBokConstants.PAGE_START + CONFIG_PAGE_START; } private Pageable parseBokToPageable(PageVO pageVO, Pageable pageable) { return parseBokToPageable(pageVO.getNumber(), pageVO.getSize(), pageable); } private Pageable parseBokToPageable(int page, int size, Pageable pageable) { if (pageable == null) { return PageRequest.of(parseBokToPageableNumber(page), size); } return pageable; } private HttpUrl.Builder addPageableToParams(HttpUrl.Builder builder, Pageable pageable) { return Optional.ofNullable(pageable) .map(notNullPageable -> // TODO: not accept error builder.addQueryParameter("page", String.valueOf(parsePageableNumberToBok(notNullPageable.getPageNumber()) + 1)) .addQueryParameter("sort", parseSort(notNullPageable.getSort())) .addQueryParameter("size", String.valueOf(notNullPageable.getPageSize())) ) .orElse(builder); } private String joinComma(Iterable list) { return Optional.ofNullable(list) .map(notNullList -> String.join(",", notNullList)) .orElse(""); } private String parseSort(Sort sort) { return String.join("|", sort.map(order -> order.getProperty() + "," + order.getDirection())); } }