@@ -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(); | |||
/** | |||
* 返回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); | |||
} |
@@ -26,11 +26,14 @@ import org.slf4j.LoggerFactory; | |||
import java.io.IOException; | |||
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(); | |||
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<H, P> 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<H, P> 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; | |||
} | |||
} |
@@ -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方式实现. | |||
*/ | |||
public class WxMpServiceHttpClientImpl extends WxMpServiceBaseImpl<CloseableHttpClient, HttpHost> { | |||
public class WxMpServiceHttpClientImpl extends BaseWxMpServiceImpl<CloseableHttpClient, HttpHost> { | |||
private CloseableHttpClient httpClient; | |||
private HttpHost httpProxy; | |||
@@ -15,7 +15,7 @@ import java.util.concurrent.locks.Lock; | |||
/** | |||
* jodd-http方式实现 | |||
*/ | |||
public class WxMpServiceJoddHttpImpl extends WxMpServiceBaseImpl<HttpConnectionProvider, ProxyInfo> { | |||
public class WxMpServiceJoddHttpImpl extends BaseWxMpServiceImpl<HttpConnectionProvider, ProxyInfo> { | |||
private HttpConnectionProvider httpClient; | |||
private ProxyInfo httpProxy; | |||
@@ -15,7 +15,7 @@ import java.util.concurrent.locks.Lock; | |||
/** | |||
* okhttp实现 | |||
*/ | |||
public class WxMpServiceOkHttpImpl extends WxMpServiceBaseImpl<OkHttpClient, OkHttpProxyInfo> { | |||
public class WxMpServiceOkHttpImpl extends BaseWxMpServiceImpl<OkHttpClient, OkHttpProxyInfo> { | |||
private OkHttpClient httpClient; | |||
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.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); | |||