diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/AiLangType.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/AiLangType.java new file mode 100644 index 00000000..54b61b1e --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/AiLangType.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.mp; + +import lombok.Getter; + +/** + *
+ *  AI开放接口里的语言类型,目前只支持两种:中文和英文
+ *  Created by BinaryWang on 2018/6/10.
+ * 
+ * + * @author Binary Wang + */ +@Getter +public enum AiLangType { + /** + * 中文 汉语 + */ + zh_CN("zh_CN"), + /** + * 英文 英语 + */ + en_US("en_US"); + + private String code; + + AiLangType(String code) { + this.code = code; + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpAiOpenService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpAiOpenService.java new file mode 100644 index 00000000..1e9c8e02 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpAiOpenService.java @@ -0,0 +1,97 @@ +package me.chanjar.weixin.mp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.AiLangType; + +import java.io.File; + +/** + *
+ * 微信AI开放接口(语音识别,微信翻译).
+ * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21516712282KzWVE
+ *  Created by BinaryWang on 2018/6/9.
+ * 
+ * + * @author Binary Wang + */ +public interface WxMpAiOpenService { + String VOICE_UPLOAD_URL = "http://api.weixin.qq.com/cgi-bin/media/voice/addvoicetorecofortext?format=%s&voice_id=%s&lang=%s"; + String VOICE_QUERY_RESULT_URL = "http://api.weixin.qq.com/cgi-bin/media/voice/queryrecoresultfortext"; + + /** + *
+   * 提交语音.
+   * 接口调用请求说明
+   *
+   * http请求方式: POST
+   * http://api.weixin.qq.com/cgi-bin/media/voice/addvoicetorecofortext?access_token=ACCESS_TOKEN&format=&voice_id=xxxxxx&lang=zh_CN
+   * 参数说明
+   *
+   * 参数	是否必须	说明
+   * access_token	是	接口调用凭证
+   * format	是	文件格式 (只支持mp3,16k,单声道,最大1M)
+   * voice_id	是	语音唯一标识
+   * lang	否	语言,zh_CN 或 en_US,默认中文
+   * 语音内容放body里或者上传文件的形式
+   * 
+ * + * @param lang 语言,zh_CN 或 en_US,默认中文 + * @param voiceFile 语音文件 + * @param voiceId 语音唯一标识 + */ + void uploadVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException; + + /** + *
+   * 获取语音识别结果.
+   * 接口调用请求说明
+   *
+   * http请求方式: POST
+   * http://api.weixin.qq.com/cgi-bin/media/voice/queryrecoresultfortext?access_token=ACCESS_TOKEN&voice_id=xxxxxx&lang=zh_CN
+   * 请注意,添加完文件之后10s内调用这个接口
+   *
+   * 参数说明
+   *
+   * 参数	是否必须	说明
+   * access_token	是	接口调用凭证
+   * voice_id	是	语音唯一标识
+   * lang	否	语言,zh_CN 或 en_US,默认中文
+   * 
+ * + * @param lang 语言,zh_CN 或 en_US,默认中文 + * @param voiceId 语音唯一标识 + */ + String queryRecognitionResult(String voiceId, AiLangType lang) throws WxErrorException; + + /** + * 识别指定语音文件内容. + * 此方法揉合了前两两个方法:uploadVoice 和 queryRecognitionResult + * + * @param lang 语言,zh_CN 或 en_US,默认中文 + * @param voiceFile 语音文件 + * @param voiceId 语音唯一标识 + */ + String recogniseVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException; + + /** + *
+   * 微信翻译.
+   * 接口调用请求说明
+   *
+   * http请求方式: POST
+   * http://api.weixin.qq.com/cgi-bin/media/voice/translatecontent?access_token=ACCESS_TOKEN&lfrom=xxx<o=xxx
+   * 参数说明
+   *
+   * 参数	是否必须	说明
+   * access_token	是	接口调用凭证
+   * lfrom	是	源语言,zh_CN 或 en_US
+   * lto	是	目标语言,zh_CN 或 en_US
+   * 源内容放body里或者上传文件的形式(utf8格式,最大600Byte)
+   * 
+ * + * @param langFrom 源语言,zh_CN 或 en_US + * @param langTo 目标语言,zh_CN 或 en_US + * @param content 要翻译的文本内容 + */ + String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException; +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java index 1ae411e8..dc45e9a8 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java @@ -408,6 +408,13 @@ public interface WxMpService { */ WxMpMassMessageService getMassMessageService(); + /** + * 返回AI开放接口方法的实现类对象,以方便调用其各个接口 + * + * @return WxMpAiOpenService + */ + WxMpAiOpenService getAiOpenService(); + void setKefuService(WxMpKefuService kefuService); void setMaterialService(WxMpMaterialService materialService); @@ -437,4 +444,6 @@ public interface WxMpService { void setMemberCardService(WxMpMemberCardService memberCardService); void setMassMessageService(WxMpMassMessageService massMessageService); + + void setAiOpenService(WxMpAiOpenService aiOpenService); } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceBaseImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java similarity index 97% rename from weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceBaseImpl.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java index 59a76c56..a874a117 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceBaseImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java @@ -26,11 +26,14 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.concurrent.locks.Lock; -public abstract class WxMpServiceBaseImpl implements WxMpService, RequestHttp { - +/** + * @author someone + */ +public abstract class BaseWxMpServiceImpl implements WxMpService, RequestHttp { private static final JsonParser JSON_PARSER = new JsonParser(); protected final Logger log = LoggerFactory.getLogger(this.getClass()); + protected WxSessionManager sessionManager = new StandardSessionManager(); protected WxMpConfigStorage wxMpConfigStorage; private WxMpKefuService kefuService = new WxMpKefuServiceImpl(this); @@ -49,6 +52,7 @@ public abstract class WxMpServiceBaseImpl implements WxMpService, RequestH private WxMpShakeService shakeService = new WxMpShakeServiceImpl(this); private WxMpMemberCardService memberCardService = new WxMpMemberCardServiceImpl(this); private WxMpMassMessageService massMessageService = new WxMpMassMessageServiceImpl(this); + private WxMpAiOpenService aiOpenService = new WxMpAiOpenServiceImpl(this); private int retrySleepMillis = 1000; private int maxRetryTimes = 5; @@ -487,4 +491,14 @@ public abstract class WxMpServiceBaseImpl implements WxMpService, RequestH public void setMassMessageService(WxMpMassMessageService massMessageService) { this.massMessageService = massMessageService; } + + @Override + public WxMpAiOpenService getAiOpenService() { + return this.aiOpenService; + } + + @Override + public void setAiOpenService(WxMpAiOpenService aiOpenService) { + this.aiOpenService = aiOpenService; + } } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpAiOpenServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpAiOpenServiceImpl.java new file mode 100644 index 00000000..f492e467 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpAiOpenServiceImpl.java @@ -0,0 +1,76 @@ +package me.chanjar.weixin.mp.api.impl; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import me.chanjar.weixin.common.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.AiLangType; +import me.chanjar.weixin.mp.api.WxMpAiOpenService; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.util.requestexecuter.voice.VoiceUploadRequestExecutor; + +import java.io.File; + +/** + *
+ *  Created by BinaryWang on 2018/6/9.
+ * 
+ * + * @author Binary Wang + */ +public class WxMpAiOpenServiceImpl implements WxMpAiOpenService { + + private static final JsonParser JSON_PARSER = new JsonParser(); + public static final String TRANSLATE_URL = "http://api.weixin.qq.com/cgi-bin/media/voice/translatecontent?lfrom=%s<o=%s"; + private WxMpService wxMpService; + + public WxMpAiOpenServiceImpl(WxMpService wxMpService) { + this.wxMpService = wxMpService; + } + + @Override + public void uploadVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException { + if (lang == null) { + lang = AiLangType.zh_CN; + } + + this.wxMpService.execute(VoiceUploadRequestExecutor.create(this.wxMpService.getRequestHttp()), + String.format(VOICE_UPLOAD_URL, "mp3", voiceId, lang.getCode()), + voiceFile); + } + + @Override + public String recogniseVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException { + this.uploadVoice(voiceId, lang, voiceFile); + return this.queryRecognitionResult(voiceId, lang); + } + + @Override + public String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException { + final String responseContent = this.wxMpService.post(String.format(TRANSLATE_URL, langFrom.getCode(), langTo.getCode()), + content); + final JsonObject jsonObject = new JsonParser().parse(responseContent).getAsJsonObject(); + if (jsonObject.get("errcode") == null || jsonObject.get("errcode").getAsInt() == 0) { + return jsonObject.get("to_content").getAsString(); + } + + throw new WxErrorException(WxError.fromJson(responseContent, WxType.MP)); + } + + @Override + public String queryRecognitionResult(String voiceId, AiLangType lang) throws WxErrorException { + if (lang == null) { + lang = AiLangType.zh_CN; + } + + final String responseContent = this.wxMpService.get(VOICE_QUERY_RESULT_URL, + String.format("voice_id=%s&lang=%s", voiceId, lang.getCode())); + final JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject(); + if (jsonObject.get("errcode") == null || jsonObject.get("errcode").getAsInt() == 0) { + return jsonObject.get("result").getAsString(); + } + + throw new WxErrorException(WxError.fromJson(responseContent, WxType.MP)); + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpClientImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpClientImpl.java index ae574092..3db9e9b1 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpClientImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpClientImpl.java @@ -22,7 +22,7 @@ import java.util.concurrent.locks.Lock; /** * apache http client方式实现. */ -public class WxMpServiceHttpClientImpl extends WxMpServiceBaseImpl { +public class WxMpServiceHttpClientImpl extends BaseWxMpServiceImpl { private CloseableHttpClient httpClient; private HttpHost httpProxy; diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceJoddHttpImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceJoddHttpImpl.java index 7f7d5761..ee8411ab 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceJoddHttpImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceJoddHttpImpl.java @@ -15,7 +15,7 @@ import java.util.concurrent.locks.Lock; /** * jodd-http方式实现 */ -public class WxMpServiceJoddHttpImpl extends WxMpServiceBaseImpl { +public class WxMpServiceJoddHttpImpl extends BaseWxMpServiceImpl { private HttpConnectionProvider httpClient; private ProxyInfo httpProxy; diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceOkHttpImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceOkHttpImpl.java index 7b21528d..6d3f5bf2 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceOkHttpImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceOkHttpImpl.java @@ -15,7 +15,7 @@ import java.util.concurrent.locks.Lock; /** * okhttp实现 */ -public class WxMpServiceOkHttpImpl extends WxMpServiceBaseImpl { +public class WxMpServiceOkHttpImpl extends BaseWxMpServiceImpl { private OkHttpClient httpClient; private OkHttpProxyInfo httpProxy; diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadApacheHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadApacheHttpRequestExecutor.java new file mode 100644 index 00000000..c23b3a32 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadApacheHttpRequestExecutor.java @@ -0,0 +1,65 @@ +package me.chanjar.weixin.mp.util.requestexecuter.voice; + +import me.chanjar.weixin.common.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.File; +import java.io.IOException; + +/** + *
+ *  Created by BinaryWang on 2018/6/9.
+ * 
+ * + * @author Binary Wang + */ +public class VoiceUploadApacheHttpRequestExecutor extends VoiceUploadRequestExecutor { + public VoiceUploadApacheHttpRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public Boolean execute(String uri, File data) throws WxErrorException, IOException { + if (data == null) { + throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("文件对象为空").build()); + } + + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", data) + .setMode(HttpMultipartMode.RFC6532) + .build(); + httpPost.setEntity(entity); + httpPost.setHeader("Content-Type", ContentType.MULTIPART_FORM_DATA.toString()); + + try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + WxError error = WxError.fromJson(responseContent, WxType.MP); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + + return true; + } finally { + httpPost.releaseConnection(); + } + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadRequestExecutor.java new file mode 100644 index 00000000..34c7ae21 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadRequestExecutor.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.mp.util.requestexecuter.voice; + +import me.chanjar.weixin.common.util.http.RequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; + +import java.io.File; + +/** + *
+ *  Created by BinaryWang on 2018/6/9.
+ * 
+ * + * @author Binary Wang + */ +public abstract class VoiceUploadRequestExecutor implements RequestExecutor { + protected RequestHttp requestHttp; + + public VoiceUploadRequestExecutor(RequestHttp requestHttp) { + this.requestHttp = requestHttp; + } + + public static RequestExecutor create(RequestHttp requestHttp) { + switch (requestHttp.getRequestType()) { + case APACHE_HTTP: + return new VoiceUploadApacheHttpRequestExecutor(requestHttp); + case JODD_HTTP: + case OK_HTTP: + default: + return null; + } + } + +} diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpAiOpenServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpAiOpenServiceImplTest.java new file mode 100644 index 00000000..f10f9888 --- /dev/null +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpAiOpenServiceImplTest.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.mp.api.impl; + +import com.google.inject.Inject; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.AiLangType; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.test.ApiTestModule; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import java.io.File; + +/** + *
+ *  Created by BinaryWang on 2018/6/10.
+ * 
+ * + * @author Binary Wang + */ +@Test +@Guice(modules = ApiTestModule.class) +public class WxMpAiOpenServiceImplTest { + @Inject + protected WxMpService wxService; + + @Test + public void testUploadVoice() throws WxErrorException { + String voiceId = System.currentTimeMillis() + "a"; + AiLangType lang = AiLangType.zh_CN; + this.wxService.getAiOpenService().uploadVoice(voiceId, lang, new File("d:\\t.mp3")); + } + + @Test + public void testRecogniseVoice() throws WxErrorException { + String voiceId = System.currentTimeMillis() + "a"; + AiLangType lang = AiLangType.zh_CN; + final String result = this.wxService.getAiOpenService().recogniseVoice(voiceId, lang, new File("d:\\t.mp3")); + System.out.println(result); + } + + @Test + public void testTranslate() throws WxErrorException { + final String responseContent = this.wxService.getAiOpenService() + .translate(AiLangType.zh_CN, AiLangType.en_US, "微信文档很坑爹"); + System.out.println(responseContent); + } +} diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/test/ApiTestModule.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/test/ApiTestModule.java index 959e2daa..98173f7d 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/test/ApiTestModule.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/test/ApiTestModule.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.concurrent.locks.ReentrantLock; +import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,7 +29,7 @@ public class ApiTestModule implements Module { TestConfigStorage config = this.fromXml(TestConfigStorage.class, inputStream); config.setAccessTokenLock(new ReentrantLock()); - WxMpService wxService = new WxMpServiceOkHttpImpl(); + WxMpService wxService = new WxMpServiceHttpClientImpl(); wxService.setWxMpConfigStorage(config); binder.bind(WxMpService.class).toInstance(wxService);