diff --git a/pom.xml b/pom.xml index d13d4bffc..09d30e185 100644 --- a/pom.xml +++ b/pom.xml @@ -124,6 +124,7 @@ weixin-java-miniapp weixin-java-open weixin-java-qidian + weixin-java-aispeech weixin-java-channel spring-boot-starters solon-plugins diff --git a/weixin-java-aispeech/pom.xml b/weixin-java-aispeech/pom.xml new file mode 100644 index 000000000..6ba64a873 --- /dev/null +++ b/weixin-java-aispeech/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + com.github.binarywang + wx-java + 4.8.3.B + + + weixin-java-aispeech + WxJava - Aispeech Java SDK + 微信智能对话 Java SDK + + + + com.github.binarywang + weixin-java-common + ${project.version} + + + + org.apache.httpcomponents.client5 + httpclient5 + + + + org.testng + testng + test + + + org.projectlombok + lombok + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + src/test/resources/testng.xml + + + + + + diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechDialogService.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechDialogService.java new file mode 100644 index 000000000..51d46562c --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechDialogService.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.aispeech.api; + +import java.util.List; +import me.chanjar.weixin.aispeech.bean.dialog.AsyncTaskResult; +import me.chanjar.weixin.aispeech.bean.dialog.BotIntent; +import me.chanjar.weixin.aispeech.bean.dialog.DialogQueryRequest; +import me.chanjar.weixin.aispeech.bean.dialog.DialogResult; +import me.chanjar.weixin.aispeech.bean.dialog.PublishProgress; +import me.chanjar.weixin.common.error.WxErrorException; + +public interface WxAispeechDialogService { + String getAccessToken(String appid, String account) throws WxErrorException; + + String importBotJson(int mode, List data) throws WxErrorException; + + String publishBot() throws WxErrorException; + + PublishProgress getPublishProgress(String env) throws WxErrorException; + + AsyncTaskResult queryAsyncTask(String taskId) throws WxErrorException; + + DialogResult query(DialogQueryRequest request) throws WxErrorException; +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechKnowledgeService.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechKnowledgeService.java new file mode 100644 index 000000000..fa27d4823 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechKnowledgeService.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.aispeech.api; + +import java.io.File; +import java.util.List; +import java.util.Map; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeInfo; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeManualCreateRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeMoveProgress; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeMoveRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeTagRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeUpdateRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeUrlCreateRequest; +import me.chanjar.weixin.common.error.WxErrorException; + +public interface WxAispeechKnowledgeService { + KnowledgeInfo createKnowledgeByFile(String knowledgeBaseId, File file, String title, String description, String metadata) + throws WxErrorException; + + KnowledgeInfo createKnowledgeByUrl(String knowledgeBaseId, KnowledgeUrlCreateRequest request) throws WxErrorException; + + KnowledgeInfo createKnowledgeByManual(String knowledgeBaseId, KnowledgeManualCreateRequest request) throws WxErrorException; + + List listKnowledge(String knowledgeBaseId, Integer page, Integer pageSize) throws WxErrorException; + + List listKnowledgeByIds(List knowledgeIds) throws WxErrorException; + + KnowledgeInfo getKnowledge(String knowledgeId) throws WxErrorException; + + KnowledgeInfo updateKnowledge(String knowledgeId, KnowledgeUpdateRequest request) throws WxErrorException; + + KnowledgeInfo updateManualKnowledge(String knowledgeId, KnowledgeManualCreateRequest request) throws WxErrorException; + + boolean deleteKnowledge(String knowledgeId) throws WxErrorException; + + boolean updateKnowledgeTags(List knowledgeIds, Long tagId) throws WxErrorException; + + List searchKnowledge(String keyword, String knowledgeBaseId, Integer page, Integer pageSize) + throws WxErrorException; + + String moveKnowledge(KnowledgeMoveRequest request) throws WxErrorException; + + KnowledgeMoveProgress getMoveProgress(String taskId) throws WxErrorException; + + boolean createKnowledgeBaseTag(String knowledgeBaseId, KnowledgeTagRequest request) throws WxErrorException; + + boolean updateKnowledgeBaseTag(String knowledgeBaseId, String tagId, KnowledgeTagRequest request) throws WxErrorException; + + String postRaw(String path, Object requestBody) throws WxErrorException; + + String getRaw(String path, Map queryParams) throws WxErrorException; +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechService.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechService.java new file mode 100644 index 000000000..08ccf837e --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechService.java @@ -0,0 +1,13 @@ +package me.chanjar.weixin.aispeech.api; + +import me.chanjar.weixin.aispeech.config.WxAispeechConfigStorage; + +public interface WxAispeechService { + WxAispeechDialogService getDialogService(); + + WxAispeechKnowledgeService getKnowledgeService(); + + WxAispeechConfigStorage getConfigStorage(); + + void setConfigStorage(WxAispeechConfigStorage configStorage); +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechDialogServiceImpl.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechDialogServiceImpl.java new file mode 100644 index 000000000..9bd53b454 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechDialogServiceImpl.java @@ -0,0 +1,129 @@ +package me.chanjar.weixin.aispeech.api.impl; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import me.chanjar.weixin.aispeech.api.WxAispeechDialogService; +import me.chanjar.weixin.aispeech.bean.dialog.AispeechApiResponse; +import me.chanjar.weixin.aispeech.bean.dialog.AsyncTaskResult; +import me.chanjar.weixin.aispeech.bean.dialog.BotIntent; +import me.chanjar.weixin.aispeech.bean.dialog.DialogQueryRequest; +import me.chanjar.weixin.aispeech.bean.dialog.DialogResult; +import me.chanjar.weixin.aispeech.bean.dialog.PublishProgress; +import me.chanjar.weixin.aispeech.util.WxAispeechSignUtil; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import org.apache.commons.lang3.StringUtils; + +public class WxAispeechDialogServiceImpl implements WxAispeechDialogService { + private final WxAispeechServiceImpl service; + + public WxAispeechDialogServiceImpl(WxAispeechServiceImpl service) { + this.service = service; + } + + @Override + public String getAccessToken(String appid, String account) throws WxErrorException { + Map request = new HashMap<>(); + if (StringUtils.isNotBlank(account)) { + request.put("account", account); + } + + String response = service.executeDialogPost("/v2/token", request, false, appid); + Type type = new TypeToken>() { } .getType(); + AispeechApiResponse result = WxGsonBuilder.create().fromJson(response, type); + ensureSuccess(result); + String token = result.getData().get("access_token").getAsString(); + service.getConfigStorage().setOpenAiToken(token); + return token; + } + + @Override + public String importBotJson(int mode, List data) throws WxErrorException { + Map request = new HashMap<>(); + request.put("mode", mode); + request.put("data", data); + + String response = service.executeDialogPost("/v2/bot/import/json", request, true, null); + Type type = new TypeToken>() { } .getType(); + AispeechApiResponse result = WxGsonBuilder.create().fromJson(response, type); + ensureSuccess(result); + return result.getData().get("task_id").getAsString(); + } + + @Override + public String publishBot() throws WxErrorException { + String response = service.executeDialogPost("/v2/bot/publish", "{}", true, null); + Type type = new TypeToken>() { } .getType(); + AispeechApiResponse result = WxGsonBuilder.create().fromJson(response, type); + ensureSuccess(result); + return result.getRequestId(); + } + + @Override + public PublishProgress getPublishProgress(String env) throws WxErrorException { + Map request = new HashMap<>(); + request.put("env", env); + + String response = service.executeDialogPost("/v2/bot/effective_progress", request, true, null); + Type type = new TypeToken>() { } .getType(); + AispeechApiResponse result = WxGsonBuilder.create().fromJson(response, type); + ensureSuccess(result); + return result.getData(); + } + + @Override + public AsyncTaskResult queryAsyncTask(String taskId) throws WxErrorException { + Map request = new HashMap<>(); + request.put("task_id", taskId); + + String response = service.executeDialogPost("/v2/async/fetch", request, true, null); + Type type = new TypeToken>() { } .getType(); + AispeechApiResponse result = WxGsonBuilder.create().fromJson(response, type); + ensureSuccess(result); + return result.getData(); + } + + @Override + public DialogResult query(DialogQueryRequest request) throws WxErrorException { + String json = WxGsonBuilder.create().toJson(request); + String encrypted = WxAispeechSignUtil.encryptAesCbcToBase64(json, service.getConfigStorage().getAesKey()); + String response = service.executeDialogPost("/v2/bot/query", encrypted, true, null); + + String responseJson = response; + if (!looksLikeJson(response)) { + responseJson = WxAispeechSignUtil.decryptAesCbcFromBase64(response, service.getConfigStorage().getAesKey()); + } + + Type type = new TypeToken>() { } .getType(); + AispeechApiResponse result = WxGsonBuilder.create().fromJson(responseJson, type); + ensureSuccess(result); + + DialogResult dialogResult = result.getData(); + if (dialogResult != null && looksLikeJson(dialogResult.getAnswer())) { + dialogResult.setRawAnswer(WxGsonBuilder.create().fromJson(dialogResult.getAnswer(), JsonElement.class)); + } + return dialogResult; + } + + private boolean looksLikeJson(String value) { + return StringUtils.isNotBlank(value) && (value.startsWith("{") || value.startsWith("[")); + } + + private void ensureSuccess(AispeechApiResponse response) throws WxErrorException { + if (response == null) { + throw new WxErrorException("响应为空"); + } + if (response.getCode() == null || response.getCode() != 0) { + throw new WxErrorException(WxError.builder() + .errorCode(response.getCode() == null ? -1 : response.getCode()) + .errorMsg(response.getMsg()) + .build()); + } + } +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechKnowledgeServiceImpl.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechKnowledgeServiceImpl.java new file mode 100644 index 000000000..c52550f6b --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechKnowledgeServiceImpl.java @@ -0,0 +1,185 @@ +package me.chanjar.weixin.aispeech.api.impl; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import java.io.File; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import me.chanjar.weixin.aispeech.api.WxAispeechKnowledgeService; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeInfo; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeListResult; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeManualCreateRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeMoveProgress; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeMoveRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeTagRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeUpdateRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeUrlCreateRequest; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import org.apache.commons.lang3.StringUtils; + +public class WxAispeechKnowledgeServiceImpl implements WxAispeechKnowledgeService { + private final WxAispeechServiceImpl service; + + public WxAispeechKnowledgeServiceImpl(WxAispeechServiceImpl service) { + this.service = service; + } + + @Override + public KnowledgeInfo createKnowledgeByFile(String knowledgeBaseId, File file, String title, String description, String metadata) + throws WxErrorException { + String response = service.executeKnowledgeMultipartPost("/api/v1/knowledge-bases/" + knowledgeBaseId + "/knowledge/file", + file, title, description, metadata); + return WxGsonBuilder.create().fromJson(response, KnowledgeInfo.class); + } + + @Override + public KnowledgeInfo createKnowledgeByUrl(String knowledgeBaseId, KnowledgeUrlCreateRequest request) + throws WxErrorException { + String response = service.executeKnowledgePost("/api/v1/knowledge-bases/" + knowledgeBaseId + "/knowledge/url", request); + return WxGsonBuilder.create().fromJson(response, KnowledgeInfo.class); + } + + @Override + public KnowledgeInfo createKnowledgeByManual(String knowledgeBaseId, KnowledgeManualCreateRequest request) + throws WxErrorException { + String response = service.executeKnowledgePost("/api/v1/knowledge-bases/" + knowledgeBaseId + "/knowledge/manual", request); + return WxGsonBuilder.create().fromJson(response, KnowledgeInfo.class); + } + + @Override + public List listKnowledge(String knowledgeBaseId, Integer page, Integer pageSize) + throws WxErrorException { + Map query = new HashMap<>(); + query.put("page", page == null ? null : String.valueOf(page)); + query.put("page_size", pageSize == null ? null : String.valueOf(pageSize)); + String response = service.executeKnowledgeGet("/api/v1/knowledge-bases/" + knowledgeBaseId + "/knowledge", query); + KnowledgeListResult result = WxGsonBuilder.create().fromJson(response, KnowledgeListResult.class); + return result == null ? null : result.getData(); + } + + @Override + public List listKnowledgeByIds(List knowledgeIds) throws WxErrorException { + if (knowledgeIds == null || knowledgeIds.isEmpty()) { + return null; + } + StringJoiner joiner = new StringJoiner(","); + for (String knowledgeId : knowledgeIds) { + if (StringUtils.isNotBlank(knowledgeId)) { + joiner.add(knowledgeId); + } + } + if (joiner.length() == 0) { + return null; + } + + Map query = new HashMap<>(); + query.put("ids", joiner.toString()); + String response = service.executeKnowledgeGet("/api/v1/knowledge/batch", query); + return parseKnowledgeInfoList(response); + } + + @Override + public KnowledgeInfo getKnowledge(String knowledgeId) throws WxErrorException { + String response = service.executeKnowledgeGet("/api/v1/knowledge/" + knowledgeId, null); + return WxGsonBuilder.create().fromJson(response, KnowledgeInfo.class); + } + + @Override + public KnowledgeInfo updateKnowledge(String knowledgeId, KnowledgeUpdateRequest request) throws WxErrorException { + String response = service.executeKnowledgePut("/api/v1/knowledge/" + knowledgeId, request); + return WxGsonBuilder.create().fromJson(response, KnowledgeInfo.class); + } + + @Override + public KnowledgeInfo updateManualKnowledge(String knowledgeId, KnowledgeManualCreateRequest request) throws WxErrorException { + String response = service.executeKnowledgePut("/api/v1/knowledge/manual/" + knowledgeId, request); + return WxGsonBuilder.create().fromJson(response, KnowledgeInfo.class); + } + + @Override + @Override + public boolean deleteKnowledge(String knowledgeId) throws WxErrorException { + service.executeKnowledgeDelete("/api/v1/knowledge/" + knowledgeId); + return true; + } + + @Override + public boolean updateKnowledgeTags(List knowledgeIds, Long tagId) throws WxErrorException { + if (knowledgeIds == null || knowledgeIds.isEmpty() || tagId == null) { + return false; + } + + Map request = new HashMap<>(); + request.put("knowledge_ids", knowledgeIds); + request.put("tag_id", tagId); + String response = service.executeKnowledgePut("/api/v1/knowledge/tags", request); + return StringUtils.isNotBlank(response); + } + + @Override + public List searchKnowledge(String keyword, String knowledgeBaseId, Integer page, Integer pageSize) + throws WxErrorException { + Map query = new HashMap<>(); + query.put("keyword", keyword); + query.put("knowledge_base_id", knowledgeBaseId); + query.put("page", page == null ? null : String.valueOf(page)); + query.put("page_size", pageSize == null ? null : String.valueOf(pageSize)); + String response = service.executeKnowledgeGet("/api/v1/knowledge/search", query); + return parseKnowledgeInfoList(response); + } + + @Override + public String moveKnowledge(KnowledgeMoveRequest request) throws WxErrorException { + return service.executeKnowledgePost("/api/v1/knowledge/move", request); + } + + @Override + public KnowledgeMoveProgress getMoveProgress(String taskId) throws WxErrorException { + String response = service.executeKnowledgeGet("/api/v1/knowledge/move/progress/" + taskId, null); + return WxGsonBuilder.create().fromJson(response, KnowledgeMoveProgress.class); + } + + @Override + public boolean createKnowledgeBaseTag(String knowledgeBaseId, KnowledgeTagRequest request) throws WxErrorException { + String response = service.executeKnowledgePost("/api/v1/knowledge-bases/" + knowledgeBaseId + "/tags", request); + return StringUtils.isNotBlank(response); + } + + @Override + public boolean updateKnowledgeBaseTag(String knowledgeBaseId, String tagId, KnowledgeTagRequest request) + throws WxErrorException { + String response = service.executeKnowledgePut("/api/v1/knowledge-bases/" + knowledgeBaseId + "/tags/" + tagId, request); + return StringUtils.isNotBlank(response); + } + + @Override + public String postRaw(String path, Object requestBody) throws WxErrorException { + return service.executeKnowledgePost(path, requestBody); + } + + @Override + public String getRaw(String path, Map queryParams) throws WxErrorException { + return service.executeKnowledgeGet(path, queryParams); + } + + private List parseKnowledgeInfoList(String response) { + if (StringUtils.isBlank(response)) { + return null; + } + + JsonElement element = WxGsonBuilder.create().fromJson(response, JsonElement.class); + Type listType = new TypeToken>() { } .getType(); + if (element != null && element.isJsonObject()) { + JsonObject object = element.getAsJsonObject(); + if (object.has("data")) { + return WxGsonBuilder.create().fromJson(object.get("data"), listType); + } + } + return WxGsonBuilder.create().fromJson(element, listType); + } +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceHttpClientImpl.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceHttpClientImpl.java new file mode 100644 index 000000000..e37d60e35 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceHttpClientImpl.java @@ -0,0 +1,4 @@ +package me.chanjar.weixin.aispeech.api.impl; + +public class WxAispeechServiceHttpClientImpl extends WxAispeechServiceHttpComponentsImpl { +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceHttpComponentsImpl.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceHttpComponentsImpl.java new file mode 100644 index 000000000..ac91d9893 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceHttpComponentsImpl.java @@ -0,0 +1,4 @@ +package me.chanjar.weixin.aispeech.api.impl; + +public class WxAispeechServiceHttpComponentsImpl extends WxAispeechServiceImpl { +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceImpl.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceImpl.java new file mode 100644 index 000000000..fa04f3623 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceImpl.java @@ -0,0 +1,237 @@ +package me.chanjar.weixin.aispeech.api.impl; + +import com.google.gson.Gson; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.UUID; +import lombok.Getter; +import me.chanjar.weixin.aispeech.api.WxAispeechDialogService; +import me.chanjar.weixin.aispeech.api.WxAispeechKnowledgeService; +import me.chanjar.weixin.aispeech.api.WxAispeechService; +import me.chanjar.weixin.aispeech.config.WxAispeechConfigStorage; +import me.chanjar.weixin.aispeech.util.WxAispeechSignUtil; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.hc.DefaultHttpComponentsClientBuilder; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsClientBuilder; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.net.URIBuilder; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; + +public class WxAispeechServiceImpl implements WxAispeechService { + private static final Gson GSON = new Gson(); + + @Getter + private final WxAispeechDialogService dialogService = new WxAispeechDialogServiceImpl(this); + @Getter + private final WxAispeechKnowledgeService knowledgeService = new WxAispeechKnowledgeServiceImpl(this); + + @Getter + private WxAispeechConfigStorage configStorage; + private CloseableHttpClient httpClient; + private HttpHost proxy; + + @Override + public void setConfigStorage(WxAispeechConfigStorage configStorage) { + this.configStorage = configStorage; + this.initHttp(); + } + + protected void initHttp() { + HttpComponentsClientBuilder builder = configStorage.getHttpComponentsClientBuilder(); + if (builder == null) { + builder = DefaultHttpComponentsClientBuilder.get(); + } + + builder.httpProxyHost(configStorage.getHttpProxyHost()) + .httpProxyPort(configStorage.getHttpProxyPort()) + .httpProxyUsername(configStorage.getHttpProxyUsername()) + .httpProxyPassword(configStorage.getHttpProxyPassword() == null ? null : + configStorage.getHttpProxyPassword().toCharArray()); + + if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) { + this.proxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort()); + } else { + this.proxy = null; + } + + this.httpClient = builder.build(); + } + + protected String executeDialogPost(String path, Object requestBody, boolean withOpenToken, String appid) + throws WxErrorException { + String body = toBody(requestBody); + String requestId = UUID.randomUUID().toString(); + long timestamp = System.currentTimeMillis() / 1000; + String nonce = randomNonce(); + String sign = WxAispeechSignUtil.calcDialogSign(configStorage.getToken(), timestamp, nonce, body); + String resolvedAppid = StringUtils.defaultIfBlank(appid, configStorage.getAppid()); + + HttpPost request = new HttpPost(configStorage.getDialogApiBaseUrl() + path); + request.setHeader("request_id", requestId); + request.setHeader("timestamp", String.valueOf(timestamp)); + request.setHeader("nonce", nonce); + request.setHeader("sign", sign); + request.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType()); + if (withOpenToken) { + if (StringUtils.isBlank(configStorage.getOpenAiToken())) { + throw new WxErrorException("X-OPENAI-TOKEN不能为空,请先调用getAccessToken或手动设置"); + } + request.setHeader("X-OPENAI-TOKEN", configStorage.getOpenAiToken()); + } else { + if (StringUtils.isBlank(resolvedAppid)) { + throw new WxErrorException("X-APPID不能为空"); + } + request.setHeader("X-APPID", resolvedAppid); + } + request.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); + return executeRequest(request); + } + + protected String executeKnowledgeGet(String path, Map queryParams) throws WxErrorException { + try { + URIBuilder builder = new URIBuilder(configStorage.getKnowledgeApiBaseUrl() + path); + if (queryParams != null) { + for (Map.Entry entry : queryParams.entrySet()) { + if (entry.getValue() != null) { + builder.addParameter(entry.getKey(), entry.getValue()); + } + } + } + HttpGet request = new HttpGet(builder.build()); + enrichKnowledgeHeaders(request, ""); + return executeRequest(request); + } catch (Exception e) { + throw toWxErrorException(e); + } + } + + protected String executeKnowledgePost(String path, Object requestBody) throws WxErrorException { + String body = toBody(requestBody); + HttpPost request = new HttpPost(configStorage.getKnowledgeApiBaseUrl() + path); + request.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); + enrichKnowledgeHeaders(request, body); + return executeRequest(request); + } + + protected String executeKnowledgePut(String path, Object requestBody) throws WxErrorException { + String body = toBody(requestBody); + HttpPut request = new HttpPut(configStorage.getKnowledgeApiBaseUrl() + path); + request.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); + enrichKnowledgeHeaders(request, body); + return executeRequest(request); + } + + protected String executeKnowledgeMultipartPost(String path, File file, String title, String description, String metadata) + throws WxErrorException { + HttpPost request = new HttpPost(configStorage.getKnowledgeApiBaseUrl() + path); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addBinaryBody("file", file, ContentType.DEFAULT_BINARY, file.getName()); + if (StringUtils.isNotBlank(title)) { + builder.addTextBody("title", title, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + } + if (StringUtils.isNotBlank(description)) { + builder.addTextBody("description", description, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + } + if (StringUtils.isNotBlank(metadata)) { + builder.addTextBody("metadata", metadata, ContentType.APPLICATION_JSON); + } + HttpEntity entity = builder.build(); + request.setEntity(entity); + if (entity.getContentType() != null) { + request.setHeader("Content-Type", entity.getContentType()); + } + enrichKnowledgeHeaders(request, ""); + return executeRequest(request); + } + + protected String executeKnowledgeDelete(String path) throws WxErrorException { + HttpUriRequestBase request = new HttpUriRequestBase("DELETE", URI.create(configStorage.getKnowledgeApiBaseUrl() + path)); + enrichKnowledgeHeaders(request, ""); + return executeRequest(request); + } + + private void enrichKnowledgeHeaders(HttpUriRequestBase request, String body) throws WxErrorException { + if (StringUtils.isBlank(configStorage.getAppid())) { + throw new WxErrorException("知识助理请求需要配置appid"); + } + if (StringUtils.isBlank(configStorage.getSecretKey())) { + throw new WxErrorException("知识助理请求需要配置secretKey"); + } + + String requestId = UUID.randomUUID().toString(); + long timestamp = System.currentTimeMillis() / 1000; + String nonce = randomNonce(); + String signature = WxAispeechSignUtil.calcKnowledgeSignature(configStorage.getSecretKey(), timestamp, nonce, + requestId, body); + + request.setHeader("X-APPID", configStorage.getAppid()); + request.setHeader("X-Request-ID", requestId); + request.setHeader("X-Timestamp", String.valueOf(timestamp)); + request.setHeader("X-Nonce", nonce); + request.setHeader("X-Signature", signature); + if (!request.containsHeader("Content-Type")) { + request.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType()); + } + } + + private String executeRequest(HttpUriRequestBase request) throws WxErrorException { + if (this.proxy != null) { + RequestConfig requestConfig = RequestConfig.custom().setProxy(this.proxy).build(); + request.setConfig(requestConfig); + } + + try (CloseableHttpResponse response = httpClient.execute(request)) { + int statusCode = response.getCode(); + HttpEntity entity = response.getEntity(); + String body = entity == null ? "" : new String(entity.getContent().readAllBytes(), StandardCharsets.UTF_8); + if (statusCode >= 200 && statusCode < 300) { + return body; + } + + throw new WxErrorException(WxError.builder().errorCode(statusCode).errorMsg(body).build()); + } catch (IOException e) { + throw toWxErrorException(e); + } + } + + protected T fromJson(String json, Class clazz) { + return GSON.fromJson(json, clazz); + } + + private String toBody(Object requestBody) { + if (requestBody == null) { + return "{}"; + } + if (requestBody instanceof String) { + return (String) requestBody; + } + return GSON.toJson(requestBody); + } + + private WxErrorException toWxErrorException(Exception e) { + if (e instanceof WxErrorException) { + return (WxErrorException) e; + } + return new WxErrorException(e); + } + + private String randomNonce() { + return UUID.randomUUID().toString().replace("-", "").substring(0, 16); + } +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/AispeechApiResponse.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/AispeechApiResponse.java new file mode 100644 index 000000000..ef04ca351 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/AispeechApiResponse.java @@ -0,0 +1,13 @@ +package me.chanjar.weixin.aispeech.bean.dialog; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +@Data +public class AispeechApiResponse { + private Integer code; + private String msg; + @SerializedName("request_id") + private String requestId; + private T data; +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/AsyncTaskResult.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/AsyncTaskResult.java new file mode 100644 index 000000000..a806fb368 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/AsyncTaskResult.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.aispeech.bean.dialog; + +import com.google.gson.JsonElement; +import java.util.List; +import lombok.Data; + +@Data +public class AsyncTaskResult { + private Integer state; + private String msg; + private Integer progress; + private Long start; + private Long end; + private String url; + private Integer totalCount; + private Integer successCount; + private Integer failCount; + private JsonElement successSkillInfo; + private List successSkillInfoList; + + @Data + public static class SkillInfo { + private Long id; + private String name; + private List intents; + } + + @Data + public static class IntentInfo { + private Long id; + private String name; + } +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/BotIntent.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/BotIntent.java new file mode 100644 index 000000000..3927461fc --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/BotIntent.java @@ -0,0 +1,13 @@ +package me.chanjar.weixin.aispeech.bean.dialog; + +import java.util.List; +import lombok.Data; + +@Data +public class BotIntent { + private String skill; + private String intent; + private Boolean disable; + private List questions; + private List answers; +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/DialogQueryRequest.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/DialogQueryRequest.java new file mode 100644 index 000000000..dd748957f --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/DialogQueryRequest.java @@ -0,0 +1,19 @@ +package me.chanjar.weixin.aispeech.bean.dialog; + +import com.google.gson.annotations.SerializedName; +import java.util.List; +import lombok.Data; + +@Data +public class DialogQueryRequest { + private String query; + private String env; + @SerializedName("first_priority_skills") + private List firstPrioritySkills; + @SerializedName("second_priority_skills") + private List secondPrioritySkills; + @SerializedName("user_name") + private String userName; + private String avatar; + private String userid; +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/DialogResult.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/DialogResult.java new file mode 100644 index 000000000..587279068 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/DialogResult.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.aispeech.bean.dialog; + +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; +import java.util.List; +import lombok.Data; + +@Data +public class DialogResult { + private String answer; + @SerializedName("answer_type") + private String answerType; + @SerializedName("skill_name") + private String skillName; + @SerializedName("intent_name") + private String intentName; + @SerializedName("msg_id") + private String msgId; + private List