今日歌词分享:
生而为人无罪 你不需要抱歉
One day I will be you baby boy and you gon'be me
喧哗如果不停 让我陪你安静
I wish I could hug you till you're really really being free
哪朵玫瑰没有荆棘
最好的 报复是 美丽
最美的 盛开是 反击
别让谁去 改变了你
你是你 或是妳 都行
会有人 全心的 爱你
—— 《玫瑰少年》 Mayday
目录
2. 使用CommandLineRunner来进行创建全局唯一的client
1. SpeechClientFactory
import com.alibaba.nls.client.AccessToken;
import com.alibaba.nls.client.protocol.NlsClient;
import com.aliyuncs.exceptions.ClientException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @Description : SpeechClientFactory
* @Date : 2019/8/20
* @Author : pmdream
*/
@Slf4j
@Component
public class SpeechClientFactory {
private static final long TWO_HOURS = 60*60*2;
private static final int RETRY_NUM = 3;
private static final long RETRY_INTERVAL = 2000;
@Autowired
private SpeechProperties speechProperties;
/**
* 因为创建NlsClient相当消耗资源,所以全局创建一个即可,线程安全{@link com.situdata.situciticpru.startup.InitNlsClient#run(String...)}
* */
public static NlsClient client;
public static volatile AccessToken accessToken;
public String getToken(){
if (null == SpeechClientFactory.accessToken || SpeechClientFactory.accessToken.getExpireTime() - System.currentTimeMillis() < TWO_HOURS) {
synchronized (SpeechClientFactory.class){
if (null == SpeechClientFactory.accessToken || SpeechClientFactory.accessToken.getExpireTime() - System.currentTimeMillis()/1000 < TWO_HOURS) {
int m = 0;
//获取token时会出现异常,增加重试
while (m< RETRY_NUM){
try {
SpeechClientFactory.accessToken = AccessToken.apply(speechProperties.getAkId(), speechProperties.getAkSecrete());
break;
} catch (ClientException e) {
try {
Thread.sleep(RETRY_INTERVAL);
} catch (InterruptedException ex) {}
m++;
if(m == RETRY_NUM){
log.info("[SpeechClientFactory] getAccessToken error = {} " + e.getMessage());
return "";
}
}
}
}
}
}
return SpeechClientFactory.accessToken.getToken();
}
}
2. 使用CommandLineRunner来进行创建全局唯一的client
import com.alibaba.nls.client.protocol.NlsClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(value=1)
public class InitNlsClient implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
SpeechClientFactory.client = new NlsClient("");
}
}
这样的好处就是启动的时候,把这个类当做组件来进行IOC
3. service类
/**
* @Description : SpeechService
* @Date : 2019/8/20
* @Author : pmdream
*/
public interface SpeechService {
boolean speechSynthesizer(String content, String mp3Path);
}
4. impl
import com.alibaba.nls.client.protocol.NlsClient;
import com.alibaba.nls.client.protocol.OutputFormatEnum;
import com.alibaba.nls.client.protocol.SampleRateEnum;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizer;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizerListener;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizerResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* @Description : SpeechServiceImpl
* @Date : 2019/8/20
* @Author : pmdream
*/
@Slf4j
@Service
public class SpeechServiceImpl implements SpeechService {
private static final int RETRY_NUM = 3;
private static final long RETRY_INTERVAL = 2000;
@Autowired
private SpeechClientFactory speechClientFactory;
@Autowired
private SpeechProperties speechProperties;
@Override
public boolean speechSynthesizer(String content, String mp3Path) {
String token = speechClientFactory.getToken();
if(StringUtils.isBlank(token)){
return false;
}
SpeechClientFactory.client.setToken(token);
int i = 0;
while (i< RETRY_NUM){
int result = process(SpeechClientFactory.client, content, mp3Path);
if(result == 1){
break;
}
i++;
try {
Thread.sleep(RETRY_INTERVAL);
} catch (InterruptedException e) {}
}
return i!= RETRY_NUM;
}
private SpeechSynthesizerListener getSynthesizerListener(String mp3Path) {
SpeechSynthesizerListener listener = null;
try {
listener = new SpeechSynthesizerListener() {
File f = new File(mp3Path);
FileOutputStream fout = new FileOutputStream(f);
// 语音合成结束
@Override
public void onComplete(SpeechSynthesizerResponse response) {
// 状态码 20000000 表示识别成功
log.info("[SpeechServiceImpl] onComplete 语音合成结束; name = {}; status = {}; output file = {}",
response.getName(), response.getStatus(), f.getAbsolutePath());
}
// 语音合成的语音二进制数据
@Override
public void onMessage(ByteBuffer message) {
try {
byte[] bytesArray = new byte[message.remaining()];
message.get(bytesArray, 0, bytesArray.length);
fout.write(bytesArray);
} catch (IOException e) {
log.error("[SpeechServiceImpl] onMessage error ; Msg = {};" + e.getMessage());
}
}
};
} catch (Exception e) {
log.error("[SpeechServiceImpl] getSynthesizerListener error ; Msg = {};" + e.getMessage());
}
return listener;
}
private int process(NlsClient client, String content, String mp3Path) {
SpeechSynthesizer synthesizer = null;
try {
// Step1 创建实例,建立连接
synthesizer = new SpeechSynthesizer(client, getSynthesizerListener(mp3Path));
synthesizer.setAppKey(speechProperties.getAppKey());
// 设置返回音频的编码格式
synthesizer.setFormat(OutputFormatEnum.MP3);
// 设置返回音频的采样率
synthesizer.setSampleRate(SampleRateEnum.SAMPLE_RATE_16K);
// 设置用于语音合成的文本
synthesizer.setText(content);
// synthesizer.setText("合同中列示的产品、租金、租赁期限等内容已知悉");
synthesizer.setVolume(100);
// Step2 此方法将以上参数设置序列化为json发送给服务端,并等待服务端确认
synthesizer.start();
// Step3 等待语音合成结束
synthesizer.waitForComplete();
} catch (Exception e) {
log.info("[SpeechServiceImpl] ====语音合成失败==== ", e.getMessage());
return 0;
} finally {
// Step4 关闭连接
if (null != synthesizer) {
synthesizer.close();
}
}
return 1;
}
}
为了脱敏;所以有些import的类就删掉了,放在上述代码中;
5. 读取yml文件的两种方式
@ConfigurationProperties与@value的对比:
最大的优势,也就是业务中主要是@ConfigurationProperties可以进行批量的读取配置;
只需要属性名与yml配置进行对应就好了~
6. 总结
阿里官方api,还是应该多看SDK的文档;
文档总因为建议如果发生语音合成失败,某些错误时偶现的,建议再次尝试;
所以等待两秒再次尝试这样的方式;
获取ali的token的时候,也采用尝试三次,睡眠两秒的方式
还有一点,yml文件读取其实是晚于static方法的,所以如果使用懒汉式单例会出现问题;
所以思想还是lazyloading
附录
阿里sdk地址: https://help.aliyun.com/document_detail/84437.html
阿里的错误中也会包含偶现类的错误,上次一次业务异常就是发生了这个问题;
50000000 | 默认的服务端错误 | 如果偶现可以忽略,重复出现请提交工单 |
50000001 | 内部调用错误 | 如果偶现可以忽略,重复出现请提交工单 |
阿里获取token:https://help.aliyun.com/document_detail/72153.html?spm=a2c4g.11186623.2.19.2d99259eCsZsCJ
阿里的错误状态说明:https://www.alibabacloud.com/help/zh/doc-detail/43906.htm?spm=a2c4g.11186623.2.17.27987229J6gDTj
服务端错误码表,HttpCode是5xx,表示服务不可用。此时一般建议重试或联系商品页面的API服务商。
错误代码 | Http 状态码 | 语义 | 解决方案 |
---|---|---|---|
Internal Error | 500 | API 网关内部错误 | 建议重试。 |
Failed To Invoke Backend Service | 500 | 底层服务错误 | API 提供者底层服务错误,建议重试,如果重试多次仍然不可用,可联系 API 服务商解决。 |
Service Unavailable | 503 | 服务不可用 | 建议稍后重试。 |
Async Service | 504 | 后端服务超时 | 建议稍后重试。 |
所以如果失败,进行重试还是很有必要的。(不过一般错误都是因为欠费hhh)