| @@ -0,0 +1,29 @@ | |||||
| package me.chanjar.weixin.mp; | |||||
| import lombok.Getter; | |||||
| /** | |||||
| * <pre> | |||||
| * AI开放接口里的语言类型,目前只支持两种:中文和英文 | |||||
| * Created by BinaryWang on 2018/6/10. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| @Getter | |||||
| public enum AiLangType { | |||||
| /** | |||||
| * 中文 汉语 | |||||
| */ | |||||
| zh_CN("zh_CN"), | |||||
| /** | |||||
| * 英文 英语 | |||||
| */ | |||||
| en_US("en_US"); | |||||
| private String code; | |||||
| AiLangType(String code) { | |||||
| this.code = code; | |||||
| } | |||||
| } | |||||
| @@ -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; | |||||
| /** | |||||
| * <pre> | |||||
| * 微信AI开放接口(语音识别,微信翻译). | |||||
| * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21516712282KzWVE | |||||
| * Created by BinaryWang on 2018/6/9. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| 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"; | |||||
| /** | |||||
| * <pre> | |||||
| * 提交语音. | |||||
| * 接口调用请求说明 | |||||
| * | |||||
| * 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里或者上传文件的形式 | |||||
| * </pre> | |||||
| * | |||||
| * @param lang 语言,zh_CN 或 en_US,默认中文 | |||||
| * @param voiceFile 语音文件 | |||||
| * @param voiceId 语音唯一标识 | |||||
| */ | |||||
| void uploadVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 获取语音识别结果. | |||||
| * 接口调用请求说明 | |||||
| * | |||||
| * 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,默认中文 | |||||
| * </pre> | |||||
| * | |||||
| * @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; | |||||
| /** | |||||
| * <pre> | |||||
| * 微信翻译. | |||||
| * 接口调用请求说明 | |||||
| * | |||||
| * 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) | |||||
| * </pre> | |||||
| * | |||||
| * @param langFrom 源语言,zh_CN 或 en_US | |||||
| * @param langTo 目标语言,zh_CN 或 en_US | |||||
| * @param content 要翻译的文本内容 | |||||
| */ | |||||
| String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException; | |||||
| } | |||||
| @@ -408,6 +408,13 @@ public interface WxMpService { | |||||
| */ | */ | ||||
| WxMpMassMessageService getMassMessageService(); | WxMpMassMessageService getMassMessageService(); | ||||
| /** | |||||
| * 返回AI开放接口方法的实现类对象,以方便调用其各个接口 | |||||
| * | |||||
| * @return WxMpAiOpenService | |||||
| */ | |||||
| WxMpAiOpenService getAiOpenService(); | |||||
| void setKefuService(WxMpKefuService kefuService); | void setKefuService(WxMpKefuService kefuService); | ||||
| void setMaterialService(WxMpMaterialService materialService); | void setMaterialService(WxMpMaterialService materialService); | ||||
| @@ -437,4 +444,6 @@ public interface WxMpService { | |||||
| void setMemberCardService(WxMpMemberCardService memberCardService); | void setMemberCardService(WxMpMemberCardService memberCardService); | ||||
| void setMassMessageService(WxMpMassMessageService massMessageService); | void setMassMessageService(WxMpMassMessageService massMessageService); | ||||
| void setAiOpenService(WxMpAiOpenService aiOpenService); | |||||
| } | } | ||||
| @@ -26,11 +26,14 @@ import org.slf4j.LoggerFactory; | |||||
| import java.io.IOException; | import java.io.IOException; | ||||
| import java.util.concurrent.locks.Lock; | import java.util.concurrent.locks.Lock; | ||||
| public abstract class WxMpServiceBaseImpl<H, P> implements WxMpService, RequestHttp<H, P> { | |||||
| /** | |||||
| * @author someone | |||||
| */ | |||||
| public abstract class BaseWxMpServiceImpl<H, P> implements WxMpService, RequestHttp<H, P> { | |||||
| private static final JsonParser JSON_PARSER = new JsonParser(); | private static final JsonParser JSON_PARSER = new JsonParser(); | ||||
| protected final Logger log = LoggerFactory.getLogger(this.getClass()); | protected final Logger log = LoggerFactory.getLogger(this.getClass()); | ||||
| protected WxSessionManager sessionManager = new StandardSessionManager(); | protected WxSessionManager sessionManager = new StandardSessionManager(); | ||||
| protected WxMpConfigStorage wxMpConfigStorage; | protected WxMpConfigStorage wxMpConfigStorage; | ||||
| private WxMpKefuService kefuService = new WxMpKefuServiceImpl(this); | private WxMpKefuService kefuService = new WxMpKefuServiceImpl(this); | ||||
| @@ -49,6 +52,7 @@ public abstract class WxMpServiceBaseImpl<H, P> implements WxMpService, RequestH | |||||
| private WxMpShakeService shakeService = new WxMpShakeServiceImpl(this); | private WxMpShakeService shakeService = new WxMpShakeServiceImpl(this); | ||||
| private WxMpMemberCardService memberCardService = new WxMpMemberCardServiceImpl(this); | private WxMpMemberCardService memberCardService = new WxMpMemberCardServiceImpl(this); | ||||
| private WxMpMassMessageService massMessageService = new WxMpMassMessageServiceImpl(this); | private WxMpMassMessageService massMessageService = new WxMpMassMessageServiceImpl(this); | ||||
| private WxMpAiOpenService aiOpenService = new WxMpAiOpenServiceImpl(this); | |||||
| private int retrySleepMillis = 1000; | private int retrySleepMillis = 1000; | ||||
| private int maxRetryTimes = 5; | private int maxRetryTimes = 5; | ||||
| @@ -487,4 +491,14 @@ public abstract class WxMpServiceBaseImpl<H, P> implements WxMpService, RequestH | |||||
| public void setMassMessageService(WxMpMassMessageService massMessageService) { | public void setMassMessageService(WxMpMassMessageService massMessageService) { | ||||
| this.massMessageService = massMessageService; | this.massMessageService = massMessageService; | ||||
| } | } | ||||
| @Override | |||||
| public WxMpAiOpenService getAiOpenService() { | |||||
| return this.aiOpenService; | |||||
| } | |||||
| @Override | |||||
| public void setAiOpenService(WxMpAiOpenService aiOpenService) { | |||||
| this.aiOpenService = aiOpenService; | |||||
| } | |||||
| } | } | ||||
| @@ -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; | |||||
| /** | |||||
| * <pre> | |||||
| * Created by BinaryWang on 2018/6/9. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| 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)); | |||||
| } | |||||
| } | |||||
| @@ -22,7 +22,7 @@ import java.util.concurrent.locks.Lock; | |||||
| /** | /** | ||||
| * apache http client方式实现. | * apache http client方式实现. | ||||
| */ | */ | ||||
| public class WxMpServiceHttpClientImpl extends WxMpServiceBaseImpl<CloseableHttpClient, HttpHost> { | |||||
| public class WxMpServiceHttpClientImpl extends BaseWxMpServiceImpl<CloseableHttpClient, HttpHost> { | |||||
| private CloseableHttpClient httpClient; | private CloseableHttpClient httpClient; | ||||
| private HttpHost httpProxy; | private HttpHost httpProxy; | ||||
| @@ -15,7 +15,7 @@ import java.util.concurrent.locks.Lock; | |||||
| /** | /** | ||||
| * jodd-http方式实现 | * jodd-http方式实现 | ||||
| */ | */ | ||||
| public class WxMpServiceJoddHttpImpl extends WxMpServiceBaseImpl<HttpConnectionProvider, ProxyInfo> { | |||||
| public class WxMpServiceJoddHttpImpl extends BaseWxMpServiceImpl<HttpConnectionProvider, ProxyInfo> { | |||||
| private HttpConnectionProvider httpClient; | private HttpConnectionProvider httpClient; | ||||
| private ProxyInfo httpProxy; | private ProxyInfo httpProxy; | ||||
| @@ -15,7 +15,7 @@ import java.util.concurrent.locks.Lock; | |||||
| /** | /** | ||||
| * okhttp实现 | * okhttp实现 | ||||
| */ | */ | ||||
| public class WxMpServiceOkHttpImpl extends WxMpServiceBaseImpl<OkHttpClient, OkHttpProxyInfo> { | |||||
| public class WxMpServiceOkHttpImpl extends BaseWxMpServiceImpl<OkHttpClient, OkHttpProxyInfo> { | |||||
| private OkHttpClient httpClient; | private OkHttpClient httpClient; | ||||
| private OkHttpProxyInfo httpProxy; | private OkHttpProxyInfo httpProxy; | ||||
| @@ -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; | |||||
| /** | |||||
| * <pre> | |||||
| * Created by BinaryWang on 2018/6/9. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public class VoiceUploadApacheHttpRequestExecutor extends VoiceUploadRequestExecutor<CloseableHttpClient, HttpHost> { | |||||
| 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(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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; | |||||
| /** | |||||
| * <pre> | |||||
| * Created by BinaryWang on 2018/6/9. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public abstract class VoiceUploadRequestExecutor<H, P> implements RequestExecutor<Boolean, File> { | |||||
| protected RequestHttp<H, P> requestHttp; | |||||
| public VoiceUploadRequestExecutor(RequestHttp requestHttp) { | |||||
| this.requestHttp = requestHttp; | |||||
| } | |||||
| public static RequestExecutor<Boolean, File> create(RequestHttp requestHttp) { | |||||
| switch (requestHttp.getRequestType()) { | |||||
| case APACHE_HTTP: | |||||
| return new VoiceUploadApacheHttpRequestExecutor(requestHttp); | |||||
| case JODD_HTTP: | |||||
| case OK_HTTP: | |||||
| default: | |||||
| return null; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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; | |||||
| /** | |||||
| * <pre> | |||||
| * Created by BinaryWang on 2018/6/10. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| @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); | |||||
| } | |||||
| } | |||||
| @@ -4,6 +4,7 @@ import java.io.IOException; | |||||
| import java.io.InputStream; | import java.io.InputStream; | ||||
| import java.util.concurrent.locks.ReentrantLock; | import java.util.concurrent.locks.ReentrantLock; | ||||
| import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl; | |||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||
| @@ -28,7 +29,7 @@ public class ApiTestModule implements Module { | |||||
| TestConfigStorage config = this.fromXml(TestConfigStorage.class, inputStream); | TestConfigStorage config = this.fromXml(TestConfigStorage.class, inputStream); | ||||
| config.setAccessTokenLock(new ReentrantLock()); | config.setAccessTokenLock(new ReentrantLock()); | ||||
| WxMpService wxService = new WxMpServiceOkHttpImpl(); | |||||
| WxMpService wxService = new WxMpServiceHttpClientImpl(); | |||||
| wxService.setWxMpConfigStorage(config); | wxService.setWxMpConfigStorage(config); | ||||
| binder.bind(WxMpService.class).toInstance(wxService); | binder.bind(WxMpService.class).toInstance(wxService); | ||||