阿里云实时语音识别:https://help.aliyun.com/document_detail/84430.html?spm=a2c4g.324262.0.0.564f73e9O6yq25
阿里云录音识别:https://help.aliyun.com/document_detail/90727.html?spm=a2c4g.90726.0.0.662d73e9qr8DqE
语音识别的流程为:前端和后端构建websocket连接,然后传二进制音频流给后端,后端拿到音频流,后阿里云构建websocket连接,转发音频流,阿里云收到后进行翻译,再返回给后端,后端再返回给前端
录音识别流程为:前端上传一段录音到阿里云oss上,返回录音的url,然后调用阿里云的录音识别拿到录音并解析,将结果返回给后端,后端再将结果返回给前端
实现
pom.xml
<dependencies>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.76.Final</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.18</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.aliyun</groupId>-->
<!-- <artifactId>aliyun-java-sdk-core</artifactId>-->
<!-- <version>3.7.1</version>-->
<!-- </dependency>-->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--阿里云语音识别-->
<dependency>
<groupId>com.alibaba.nls</groupId>
<artifactId>nls-sdk-transcriber</artifactId>
<version>2.2.1</version>
</dependency>
<!--swagger3-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!-- 阿里云OSS -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springblade</groupId>-->
<!-- <artifactId>blade-core-boot</artifactId>-->
<!-- <version>3.0.1.RELEASE</version>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.springblade</groupId>-->
<!-- <artifactId>blade-core-cloud</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!-- </dependency>-->
<!--redis客户端-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- lettuce连接池 -->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
ApplicationService 启动时构建阿里云的nls连接,并获取token放入redis中
@Slf4j
@Service
public class ApplicationService implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
RedisTemplate<String, String> redisTemplate;
public static ApplicationService application = null;
@Value("${aliyun.nls.accessKeyId}")
private String accessKeyId;
@Value("${aliyun.nls.accessKeySecret}")
private String accessKeySecret;
@Value("${aliyun.nls.url}")
private String url;
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
//启动客户端
if (contextRefreshedEvent.getApplicationContext().getParent() == null) {
log.info("客户端启动-------------------------->");
synchronized (this) {
ApplicationService.application = this;
new NlsClientService(accessKeyId, accessKeySecret, url);
log.info("阿里云 NlsClient 初始化完毕");
AccessToken accessToken = NlsClientService.getAccessToken();
redisTemplate.opsForValue().set("nlp:token", accessToken.getToken(), accessToken.getExpireTime(), TimeUnit.SECONDS);
}
}
}
}
构建nls和获取token的具体实现
@Slf4j
public class NlsClientService {
private static NlsClient client;
private static AccessToken accessToken;
public NlsClientService(String accessKeyId, String accessKeySecret, String url) {
//创建NlsClient实例,应用全局创建一个即可,生命周期可和整个应用保持一致,默认服务地址为阿里云线上服务地址
applyAccessToken(accessKeyId, accessKeySecret);
if (url.isEmpty()) {
client = new NlsClient(accessToken.getToken());
} else {
client = new NlsClient(url, accessToken.getToken());
}
}
public static AccessToken getAccessToken() {
return accessToken;
}
public static void applyAccessToken(String accessKeyId, String accessKeySecret) {
accessToken = new AccessToken(accessKeyId, accessKeySecret);
try {
accessToken.apply();
log.info("get token: " + accessToken.getToken() + ", expire time: " + accessToken.getExpireTime());
} catch (IOException e) {
log.error("https获取accessToken失败!" + e.getMessage());
}
}
public static NlsClient getNlsClient() {
return client;
}
}
netty服务端
@Slf4j
@Configuration
public class NettyWebSocketServer {
public static final int WEB_SOCKET_PORT = 9000;
// 创建线程池执行器
private EventLoopGroup bossGroup = new NioEventLoopGroup(1);
private EventLoopGroup workerGroup = new NioEventLoopGroup(NettyRuntime.availableProcessors());
/**
* 启动 ws server
*
* @return
* @throws InterruptedException
*/
@PostConstruct
public void start() throws InterruptedException {
run();
}
/**
* 销毁
*/
@PreDestroy
public void destroy() {
Future<?> future = bossGroup.shutdownGracefully();
Future<?> future1 = workerGroup.shutdownGracefully();
future.syncUninterruptibly();
future1.syncUninterruptibly();
log.info("关闭 ws server 成功");
}
public void run() throws InterruptedException {
// 服务器启动引导对象
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new LoggingHandler(LogLevel.INFO)) // 为 bossGroup 添加 日志处理器
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//ws升级为wss
SslContext sslCtx = SslUtil.createSSLContext();
pipeline.addLast(sslCtx.newHandler(socketChannel.alloc()));
//10秒客户端没有向服务器发送心跳则关闭连接
pipeline.addLast(new IdleStateHandler(10, 10, 0));
// 因为使用http协议,所以需要使用http的编码器,解码器
pipeline.addLast(new HttpServerCodec());
// 以块方式写,添加 chunkedWriter 处理器
pipeline.addLast(new ChunkedWriteHandler());
/**
* 说明:
* 1. http数据在传输过程中是分段的,HttpObjectAggregator可以把多个段聚合起来;
* 2. 这就是为什么当浏览器发送大量数据时,就会发出多次 http请求的原因
*/
pipeline.addLast(new HttpObjectAggregator(8192));
/**
* 说明:
* 1. 对于 WebSocket,它的数据是以帧frame 的形式传递的;
* 2. 可以看到 WebSocketFrame 下面有6个子类
* 3. 浏览器发送请求时: ws://localhost:7000/hello 表示请求的uri
* 4. WebSocketServerProtocolHandler 核心功能是把 http协议升级为 ws 协议,保持长连接;
* 是通过一个状态码 101 来切换的
*/
pipeline.addLast(new WebSocketServerProtocolHandler("/websocket"));
// 自定义handler ,处理业务逻辑
pipeline.addLast(new NettyWebSocketServerHandler());
}
});
// 启动服务器,监听端口,阻塞直到启动成功
serverBootstrap.bind(WEB_SOCKET_PORT).sync();
}
}
语音识别handler
/**
* 自定义handler
*/
@Slf4j
@Component
public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler<BinaryWebSocketFrame> {
private NlpService nlpService;
// 当web客户端连接后,触发该方法
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
this.nlpService = getService();
}
private NlpService getService() {
return SpringUtil.getBean(NlpService.class);
}
/**
* netty断联
*
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
SpeechTranscriber transcriber = (SpeechTranscriber) transcriberMap.get(ctx.channel());
if (Objects.nonNull(transcriber)) {
//阿里云netty10s断开,连接状态变为STATE_CLOSED
if (SpeechReqProtocol.State.STATE_CLOSED.toString() != transcriber.getState().toString()) {
transcriber.stop();
}
transcriberMap.remove(ctx.channel());
transcriber.close();
}
log.warn("{} 已经断开", ctx.channel());
}
/**
* 处理异常
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.warn("{} 已经异常断开 异常是{}", ctx.channel(), cause.getMessage());
ctx.channel().close();
}
/**
* 心跳检查
*
* @param ctx
* @param evt
* @throws Exception
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
// 心跳检测超时事件
if (idleStateEvent.state() == IdleState.READER_IDLE) {
log.warn("{} 已经 10s 没有读到数据了,关闭连接", ctx.channel());
} else if (idleStateEvent.state() == IdleState.WRITER_IDLE) {
log.warn("{} 已经 10s 没有写出数据了,关闭连接", ctx.channel());
}
ctx.channel().close();
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws Exception {
if (msg instanceof BinaryWebSocketFrame) {
//读取音频二进制流
ByteBuf byteBuf = msg.content();
byte[] byteArray = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(byteArray);
//调用阿里云语音翻译
nlpService.speechTranslation(ctx.channel(), byteArray);
}
}
}
音频流格式,3200B的字节数组
byte[] byteArray = {-1, -2, -51, -4, 105, -7, 84, -9, -2, -11, -56, -12, -64, -13, 47, -13, -28, -13, 110, -12, 25, -11, 78, -10, -95, -9, -21, -7, -123, -4, 106, -2, -109, 0, 85, 3, -119, 5, -63, 6, -19, 7, -33, 9, -35, 10, 72, 11, -72, 11, -75, 11, 34, 11, 103, 10, 89, 9, 74, 7, -99, 5, 11, 4, -59, 1, -109, -1, 66, -2, -47, -4, 39, -5, -111, -6, -46, -7, 71, -7, -127, -7, 116, -7, -105, -7, 93, -6, 118, -5, -111, -4, 25, -2, -23, -1, -119, 1, 103, 3, -4, 4, 45, 6, 32, 7, -41, 7, 70, 8, -91, 8, 116, 8, -25, 7, -94, 7, -96, 6, 52, 5, -79, 3, -41, 1, -38, -1, 65, -2, 100, -4, 42, -6, 113, -8, 33, -9, -118, -11, 12, -12, 77, -13, 23, -13, 75, -13, -99, -13, 37, -12, 9, -11, 40, -10, -85, -9, 9, -7, 2, -5, 92, -3, 126, -1, -47, 1, -25, 3, -19, 5, 112, 7, -106, 8, -37, 9, -85, 10, 93, 11, -77, 11, 72, 11, 2, 11, 32, 10, -4, 8, -72, 7, -33, 5, 123, 4, -51, 2, -125, 0, 59, -2, 48, -4, -57, -6, -27, -8, 106, -9, -70, -10, 51, -10, -27, -11, -53, -11, -10, -11, -91, -10, -59, -9, -26, -8, -8, -7, -121, -5, -120, -3, 88, -1, -44, 0, -60, 2, -80, 4, 21, 6, 90, 7, 92, 8, 3, 9, -97, 9, 115, 9, -69, 8, 1, 8, 70, 7, 12, 6, 84, 4, -34, 2, 127, 1, -6, -1, -20, -3, 8, -4, -28, -6, -21, -7, 18, -7, 71, -8, 22, -8, 123, -8, -53, -8, 12, -7, -88, -7, 46, -5, -28, -4, 17, -2, -75, -1, -77, 1, 94, 3, -28, 4, 69, 6, 85, 7, -124, 8, -110, 9, 4, 10, 5, 10, -83, 9, 34, 9, 24, 8, 124, 6, 37, 5, -111, 3, 17, 2, -115, 0, -65, -2, -2, -4, 83, -5, -100, -7, -63, -9, -6, -11, 35, -11, 120, -12, 29, -12, 67, -12, -96, -12, 106, -11, 60, -10, -86, -9, 61, -7, -81, -6, -31, -4, -32, -2, 120, 0, 65, 2, 30, 4, -19, 5, 80, 7, -91, 8, -14, 9, -33, 10, -122, 11, 99, 11, -56, 10, 19, 10, 104, 9, 28, 8, 100, 6, -20, 4, 103, 3, -86, 1, 109, -1, 122, -3, 8, -4, -116, -6, -12, -8, 123, -9, 114, -10, 18, -10, -104, -11, 1, -11, 81, -11, 64, -10, 29, -9, -81, -9, 105, -8, -60, -7, 109, -5, -18, -4, 53, -2, -123, -1, 127, 1, 6, 3, -113, 3, -114, 4, -90, 5, 119, 6, -77, 6, 91, 6, 73, 6, 101, 6, -18, 5, -11, 4, 33, 4, -76, 3, 50, 3, -39, 1, -66, 0, 72, 0, 114, -1, -121, -2, -18, -3, 100, -3, 39, -3, 74, -3, 49, -3, 32, -3, -92, -3, 118, -2, -45, -2, 64, -1, 27, 0, -21, 0, -48, 1, 112, 2, -4, 2, -21, 3, -60, 4, 63, 5, -94, 5, 32, 6, 99, 6, -35, 5, 76, 5, -25, 4, 27, 4, 86, 3, -104, 2, 93, 1, 18, 0, 60, -1, 82, -2, -24, -4, 55, -4, -19, -5, 31, -5, -65, -6, 125, -6, 34, -6, 126, -6, 1, -5, 98, -5, 52, -4, -92, -3, -58, -2, 105, -1, 118, 0, -40, 1, -23, 2, 0, 4, 11, 5, -118, 5, 18, 6, 118, 6, 111, 6, 52, 6, -59, 5, 116, 5, -61, 4, -97, 3, -91, 2, -122, 1, 51, 0, -30, -2, -63, -3, -87, -4, -54, -5, -18, -6, -25, -7, 77, -7, 14, -7, 24, -7, -27, -8, 2, -7, -78, -7, 80, -6, -2, -6, -6, -5, 0, -3, 49, -2, 51, -1, -13, -1, -61, 0, -65, 1, -102, 2, 58, 3, -43, 3, 64, 4, 72, 4, 38, 4, -23, 3, -117, 3, -24, 2, 87, 2, -104, 1, -100, 0, -117, -1, -121, -2, -93, -3, -104, -4, -72, -5, 65, -5, -49, -6, 109, -6, 53, -6, 57, -6, 119, -6, -82, -6, 40, -5, -24, -5, -70, -4, -114, -3, -80, -2, 16, 0, -58, 0, -28, 1, 104, 3, 83, 4, 81, 5, 49, 6, -45, 6, 78, 7, 108, 7, 85, 7, -22, 6, -77, 6, 109, 6, 77, 5, 91, 4, -74, 3, -121, 2, 67, 1, 44, 0, 36, -1, 79, -2, -56, -3, 2, -3, 63, -4, -41, -5, -20, -5, -53, -5, -121, -5, 75, -4, 32, -3, -68, -3, 125, -2, 32, -1, -7, -1, 1, 1, 17, 2, -23, 2, -49, 3, -57, 4, 95, 5, 87, 5, 125, 5, -82, 5, -109, 5, 110, 5, 44, 5, -102, 4, 3, 4, 72, 3, 43, 2, 69, 1, 96, 0, 81, -1, 92, -2, -106, -3, -15, -4, -42, -5, 40, -5, -30, -6, 111, -6, 55, -6, 46, -6, 70, -6, -127, -6, -50, -6, 64, -5, -45, -5, -109, -4, 85, -3, -4, -3, -85, -2, -111, -1, 104, 0, 6, 1, 113, 1, -14, 1, 101, 2, -81, 2, -83, 2, -101, 2, -77, 2, -105, 2, 39, 2, -96, 1, 32, 1, -85, 0, -22, -1, -32, -2, 57, -2, -85, -3, -22, -4, 53, -4, 117, -5, 5, -5, -68, -6, 115, -6, 85, -6, 57, -6, -126, -6, -20, -6, 46, -5, -95, -5, 52, -4, -30, -4, 119, -3, 16, -2, -11, -2, -59, -1, 107, 0, 21, 1, -90, 1, 83, 2, -33, 2, 79, 3, -93, 3, -17, 3, 58, 4, 67, 4, 87, 4, 90, 4, 123, 4, -117, 4, 71, 4, 84, 4, 80, 4, 26, 4, -20, 3, -81, 3, 101, 3, 48, 3, -34, 2, 96, 2, 11, 2, -91, 1, 23, 1, -88, 0, 92, 0, -5, -1, -118, -1, 54, -1, -7, -2, -108, -2, 99, -2, 84, -2, 66, -2, 54, -2, 51, -2, 121, -2, -88, -2, -58, -2, 65, -1, -95, -1, 13, 0, -62, 0, 21, 1, 70, 1, -86, 1, -4, 1, 9, 2, 37, 2, 78, 2, 85, 2, 100, 2, 82, 2, -21, 1, -84, 1, -110, 1, 27, 1, 115, 0, 61, 0, -4, -1, 76, -1, -68, -2, 121, -2, 53, -2, -28, -3, -74, -3, 116, -3, 118, -3, -84, -3, -103, -3, -115, -3, -44, -3, 58, -2, -56, -2, 4, -1, 68, -1, -53, -1, 27, 0, 110, 0, -122, 0, -77, 0, -25, 0, -46, 0, -77, 0, 104, 0, 59, 0, 35, 0, -85, -1, 46, -1, -49, -2, 34, -2, -108, -3, 19, -3, 126, -4, 25, -4, -89, -5, 70, -5, -43, -6, 126, -6, 118, -6, 98, -6, 73, -6, -110, -6, -13, -6, 101, -5, -15, -5, -126, -4, 43, -3, -42, -3, -53, -2, -85, -1, -110, 0, -70, 1, -97, 2, 105, 3, 55, 4, 13, 5, -77, 5, 96, 6, 16, 7, 101, 7, -60, 7, 13, 8, 44, 8, 53, 8, 29, 8, -28, 7, -116, 7, 81, 7, 9, 7, 124, 6, -24, 5, 119, 5, -25, 4, 59, 4, -128, 3, -65, 2, -14, 1, 51, 1, 80, 0, 115, -1, -58, -2, 1, -2, 57, -3, -117, -4, -15, -5, 127, -5, 10, -5, -106, -6, 87, -6, 60, -6, 72, -6, 83, -6, 113, -6, -49, -6, 78, -5, -43, -5, -116, -4, 66, -3, 9, -2, -46, -2, -111, -1, 119, 0, 85, 1, 46, 2, 11, 3, -97, 3, 59, 4, -65, 4, 9, 5, 87, 5, 112, 5, 111, 5, 90, 5, 15, 5, -112, 4, -3, 3, 79, 3, -109, 2, -66, 1, -76, 0, -62, -1, -42, -2, -76, -3, -104, -4, -96, -5, -41, -6, 20, -6, 107, -7, 8, -7, -82, -8, -109, -8, -86, -8, -61, -8, 30, -7, -64, -7, 102, -6, -12, -6, -87, -5, -99, -4, -123, -3, 76, -2, 21, -1, -32, -1, -100, 0, 68, 1, -83, 1, -10, 1, 67, 2, 92, 2, 63, 2, 43, 2, -13, 1, -118, 1, -18, 0, 56, 0, -113, -1, -2, -2, 117, -2, -58, -3, 44, -3, -64, -4, 79, -4, 17, -4, -18, -5, -18, -5, 44, -4, -97, -4, 48, -3, -35, -3, -51, -2, -57, -1, -78, 0, -32, 1, 62, 3, 127, 4, -43, 5, -18, 6, -37, 7, -41, 8, -68, 9, 101, 10, -42, 10, 63, 11, 103, 11, 40, 11, -108, 10, -40, 9, -3, 8, 7, 8, -17, 6, -123, 5, 34, 4, -73, 2, 46, 1, -100, -1, 32, -2, -7, -4, -52, -5, -61, -6, -23, -7, 49, -7, -37, -8, -64, -8, -36, -8, 68, -7, -31, -7, -80, -6, -91, -5, -107, -4, -60, -3, 29, -1, 98, 0, -96, 1, -35, 2, 27, 4, 30, 5, -15, 5, -86, 6, 41, 7, 108, 7, -108, 7, 122, 7, 5, 7, 119, 6, -38, 5, -43, 4, -110, 3, 106, 2, 20, 1, -96, -1, 50, -2, -73, -4, 54, -5, 5, -6, 4, -7, -18, -9, 59, -9, -28, -10, -109, -10, 123, -10, -92, -10, -6, -10, -116, -9, 88, -8, 61, -7, 72, -6, 119, -5, -80, -4, -61, -3, -43, -2, 18, 0, 37, 1, 13, 2, -28, 2, 104, 3, -62, 3, 20, 4, 41, 4, 6, 4, -74, 3, 83, 3, -83, 2, -34, 1, 30, 1, 68, 0, 89, -1, 126, -2, -128, -3, -112, -4, -38, -5, 62, -5, -97, -6, 31, -6, -1, -7, -16, -7, -18, -7, 37, -6, -118, -6, 34, -5, -66, -5, 124, -4, 70, -3, 35, -2, 28, -1, 2, 0, -45, 0, -88, 1, 124, 2, 62, 3, -39, 3, 104, 4, -21, 4, 47, 5, 94, 5, -99, 5, -61, 5, -46, 5, -24, 5, -27, 5, -56, 5, -72, 5, -105, 5, 77, 5, -3, 4, -26, 4, -88, 4, 83, 4, 44, 4, -46, 3, 113, 3, 23, 3, -93, 2, 79, 2, 1, 2, -87, 1, 67, 1, -38, 0, 116, 0, 23, 0, -76, -1, 75, -1, -12, -2, -93, -2, 113, -2, 43, -2, -17, -3, 0, -2, -5, -3, -4, -3, 46, -2, 98, -2, -74, -2, 43, -1, -125, -1, -32, -1, 83, 0, -32, 0, 98, 1, -71, 1, 45, 2, -116, 2, -47, 2, 22, 3, 41, 3, 41, 3, 39, 3, -50, 2, 52, 2, -92, 1, -21, 0, 41, 0, 87, -1, 120, -2, -83, -3, -53, -4, -17, -5, 66, -5, -102, -6, 41, -6, -37, -7, -86, -7, -87, -7, -98, -7, -66, -7, 27, -6, -109, -6, 54, -5, -5, -5, -37, -4, -55, -3, -72, -2, -82, -1, -87, 0, -116, 1, 82, 2, 10, 3, -107, 3, -19, 3, 14, 4, 14, 4, -5, 3, -74, 3, 65, 3, -83, 2, -22, 1, -1, 0, 7, 0, 5, -1, 14, -2, 30, -3, 33, -4, 67, -5, 115, -6, -57, -7, 62, -7, -65, -8, -128, -8, -120, -8, -86, -8, -24, -8, 85, -7, -9, -7, -46, -6, -75, -5, -68, -4, -13, -3, 39, -1, 98, 0, -102, 1, -35, 2, 23, 4, 23, 5, 9, 6, -6, 6, -83, 7, 60, 8, -87, 8, -16, 8, 22, 9, -10, 8, -74, 8, 91, 8, -59, 7, 27, 7, 80, 6, 76, 5, 97, 4, 103, 3, 67, 2, 58, 1, 54, 0, 52, -1, 69, -2, 127, -3, -44, -4, 40, -4, -63, -5, -115, -5, 106, -5, 103, -5, -104, -5, -24, -5, 82, -4, -31, -4, 121, -3, 44, -2, -15, -2, -72, -1, 127, 0, 63, 1, 25, 2, -48, 2, 84, 3, -35, 3, 55, 4, 115, 4, -116, 4, 101, 4, 47, 4, -35, 3, 95, 3, -71, 2, -27, 1, 8, 1, 28, 0, 51, -1, 86, -2, 101, -3, -118, -4, -33, -5, 64, -5, -64, -6, 116, -6, 77, -6, 74, -6, 116, -6, -55, -6, 63, -5, -44, -5, -124, -4, 74, -3, 31, -2, 18, -1, 6, 0, -40, 0, -85, 1, 109, 2, 33, 3, -70, 3, 32, 4, 106, 4, -116, 4, -116, 4, 110, 4, 17, 4, -102, 3, 6, 3, 65, 2, 107, 1, 123, 0, -124, -1, -125, -2, 110, -3, 87, -4, 94, -5, 114, -6, -93, -7, -1, -8, -117, -8, 60, -8, -14, -9, -37, -9, 16, -8, 86, -8, -53, -8, 108, -7, 5, -6, -64, -6, -93, -5, -124, -4, 105, -3, 75, -2, 66, -1, 61, 0, 27, 1, 1, 2, -41, 2, -108, 3, 78, 4, -28, 4, 100, 5, -29, 5, 79, 6, -94, 6, -40, 6, 15, 7, 81, 7, 87, 7, 57, 7, 34, 7, -10, 6, -77, 6, 91, 6, -31, 5, 117, 5, -13, 4, 86, 4, -80, 3, -9, 2, 58, 2, 115, 1, -96, 0, -18, -1, 51, -1, 108, -2, -71, -3, 6, -3, 111, -4, 2, -4, -72, -5, -123, -5, 98, -5, 115, -5, -66, -5, 20, -4, 113, -4, -28, -4, 118, -3, 39, -2, -43, -2, -114, -1, 90, 0, 29, 1, -36, 1, -111, 2, 34, 3, -103, 3, -13, 3, 26, 4, 36, 4, -20, 3, -114, 3, 13, 3, 72, 2, -122, 1, -60, 0, -35, -1, -6, -2, 17, -2, 55, -3, -121, -4, -32, -5, 97, -5, 36, -5, -4, -6, -17, -6, 9, -5, 97, -5, -13, -5, -116, -4, 73, -3, 53, -2, 28, -1, 6, 0, -26, 0, -83, 1, -126, 2, 58, 3, -50, 3, 62, 4, 109, 4, 126, 4, 95, 4, 11, 4, -92, 3, 0, 3, 34, 2, 52, 1, 50, 0, 46, -1, 28, -2, 6, -3, 11, -4, 29, -5, 57, -6, 113, -7, -57, -8, 75, -8, -2, -9, -50, -9, -27, -9, 48, -8, -99, -8, 56, -7, -16, -7, -40, -6, -30, -5, -26, -4, 11, -2, 51, -1, 57, 0, 48, 1, 14, 2, -37, 2, -98, 3, 60, 4, -58, 4, 56, 5, -84, 5, 9, 6, 66, 6, -127, 6, -77, 6, -62, 6, -30, 6, 11, 7, 23, 7, 21, 7, 0, 7, -35, 6, -92, 6, 91, 6, 20, 6, -67, 5, 80, 5, -49, 4, 52, 4, -107, 3, -33, 2, 20, 2, 70, 1, 110, 0, -114, -1, -74, -2, -25, -3, 59, -3, -83, -4, 34, -4, -61, -5, -94, -5, -98, -5, -57, -5, 21, -4, 114, -4, 1, -3, -84, -3, 111, -2, 86, -1, 60, 0, 52, 1, 44, 2, 26, 3, 8, 4, -47, 4, 120, 5, -32, 5, 5, 6, 0, 6, -77, 5, 48, 5, 116, 4, 126, 3, 113, 2, 57, 1, -30, -1, -95, -2, 112, -3, 77, -4, 69, -5, 93, -6, -99, -7, 18, -7, -56, -8, -76, -8, -71, -8, -9, -8, 118, -7, 4, -6, -80, -6, -121, -5, 107, -4, 93, -3, 83, -2, 73, -1, 28, 0, -54, 0, 108, 1, -20, 1, 55, 2, 86, 2, 67, 2, -6, 1, -106, 1, 13, 1, 115, 0, -48, -1, 21, -1, 89, -2, -111, -3, -46, -4, 55, -4, -111, -5, 16, -5, -67, -6, 118, -6, 80, -6, 40, -6, 37, -6, 82, -6, -115, -6, -13, -6, 117, -5, 13, -4, -88, -4, 62, -3, 1, -2, -65, -2, 82, -1, -38, -1, 100, 0, -32, 0, 64, 1, -102, 1, 3, 2, 121, 2, -34, 2, 70, 3, -56, 3, 93, 4, -10, 4, 120, 5, 15, 6, -73, 6, 58, 7, -71, 7, 45, 8, 118, 8, -84, 8, -77, 8, 116, 8, 29, 8, -98, 7, -9, 6, 58, 6, 72, 5, 71, 4, 57, 3, 8, 2, -50, 0, -112, -1, 90, -2, 60, -3, 46, -4, 85, -5, -82, -6, 69, -6, 38, -6, 34, -6, 98, -6, -14, -6, -86, -5, -100, -4, -81, -3, -38, -2, 32, 0, 90, 1, -109, 2, -54, 3, -24, 4, -23, 5, -51, 6, -123, 7, 22, 8, 98, 8, 90, 8, 19, 8, -112, 7, -75, 6, -112, 5, 34, 4, -126, 2, -73, 0, -51, -2, -37, -4, -5, -6, 82, -7, -32, -9, -111, -10, -92, -11, 6, -11, -84, -12, -94, -12, -24, -12, 126, -11, 78, -10, 82, -9, -121, -8, -37, -7, 85, -5, -49, -4, 72, -2, -58, -1, 49, 1, -124, 2, -77, 3, -84, 4, 94, 5, -45, 5, -9, 5, -64, 5, 62, 5, -127, 4, -96, 3, -127, 2, 73, 1, 25, 0, -24, -2, -62, -3, -80, -4, -79, -5, -53, -6, -5, -7, 47, -7, 127, -8, -3, -9, -87, -9, -124, -9, -114, -9, -42, -9, 102, -8, 46, -7, 49, -6, 82, -5, 124, -4, -54, -3, 24, -1, 67, 0, 90, 1, 75, 2, 20, 3, -87, 3, 20, 4, 116, 4, -76, 4, -30, 4, -2, 4, 22, 5, 73, 5, -125, 5, -70, 5, -8, 5, 73, 6, -89, 6, -26, 6, 13, 7, 54, 7, 82, 7, 88, 7, 59, 7, -2, 6, -84, 6, 75, 6, -71, 5, 18, 5, 79, 4, 105, 3, 107, 2, 63, 1, 18, 0, -20, -2, -75, -3, -112, -4, -118, -5, -78, -6, 18, -6, -92, -7, -127, -7, -81, -7, 31, -6, -52, -6, -101, -5, -97, -4, -41, -3, 11, -1, 76, 0, -109, 1, -47, 2, -2, 3, 1, 5, -40, 5, -107, 6, 29, 7, 104, 7, -126, 7, 62, 7, -60, 6, 3, 6, -28, 4, -110, 3, -5, 1, 47, 0, 78, -2, 85, -4, 119, -6, -66, -8, 65, -9, 38, -10, 78, -11, -22, -12, -11, -12, 84, -11, 40, -10, 69, -9, -91, -8, 63, -6, -31, -5, -99, -3, 100, -1, 7, 1, -97, 2, 40, 4, -128, 5, -93, 6, -121, 7, 52, 8, -100, 8, -90, 8, 77, 8, -113, 7, 115, 6, 14, 5, 84, 3, 94, 1, 79, -1, 51, -3, 53, -5, 110, -7, -26, -9, -111, -10, 115, -11, -93, -12, 27, -12, -73, -13, -98, -13, -80, -13, -16, -13, 94, -12, -13, -12, -49, -11, -21, -10, 71, -8, -13, -7, -46, -5, -36, -3, -13, -1, 9, 2, 6, 4, -54, 5, 80, 7, -126, 8, 91, 9, -69, 9, -72, 9, 110, 9, -4, 8, 122, 8, -24, 7, 100, 7, -10, 6, -111, 6, 69, 6, 1, 6, -28, 5, -45, 5, -63, 5, -91, 5, 117, 5, 71, 5, 17, 5, -59, 4, -116, 4, 101, 4, 55, 4, -7, 3, -96, 3, 54, 3, -75, 2, 19, 2, 73, 1, 108, 0, 120, -1, 116, -2, 118, -3, -124, -4, -83, -5, -12, -6, 118, -6, 73, -6, 92, -6, -87, -6, 33, -5, -52, -5, -87, -4, -113, -3, -122, -2, 115, -1, 73, 0, 15, 1, -65, 1, 116, 2, 38, 3, -67, 3, 62, 4, -80, 4, -6, 4, -6, 4, -56, 4, 93, 4, -91, 3, -97, 2, 67, 1, -84, -1, -13, -3, 31, -4, 72, -6, -106, -8, 37, -9, 11, -10, 101, -11, 52, -11, 127, -11, 77, -10
nls业务类
public interface NlpService {
/**
* 语音翻译
*
* @param channel
* @param byteArray
*/
void speechTranslation(Channel channel, byte[] byteArray);
/**
* 录音翻译
*
* @param fileUrl
*/
R recordingTranslation(String fileUrl);
}
@Slf4j
@Service
public class NlpServiceImpl implements NlpService {
//private final String token = "6d1216b898ef40e090871b243f4e66a1";
@Value("${aliyun.nls.appKey}")
private String appKey;
@Value("${aliyun.nls.accessKeyId}")
private String accessKeyId;
@Value("${aliyun.nls.accessKeySecret}")
private String accessKeySecret;
@Autowired
RedisTemplate<String, String> redisTemplate;
@Override
public void speechTranslation(Channel channel, byte[] byteArray) {
String key = "nlp:token";
String token;
Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS);
//token过期
if (ttl == null && ttl < 0) {
NlsClientService.applyAccessToken(accessKeyId, accessKeySecret);
AccessToken accessToken = NlsClientService.getAccessToken();
token = accessToken.getToken();
redisTemplate.opsForValue().set(key, token, accessToken.getExpireTime(), TimeUnit.SECONDS);
NlsClientService.getNlsClient().setToken(token);
} else {
token = redisTemplate.opsForValue().get(key);
}
SpeechTranslationService service = new SpeechTranslationService(appKey, token, channel);
service.process(channel, byteArray);
}
@Override
public R recordingTranslation(String fileUrl) {
RecordingTranslationService service = new RecordingTranslationService(accessKeyId, accessKeySecret);
// 第一步:提交录音文件识别请求,获取任务ID用于后续的识别结果轮询。
String taskId = service.submitFileTransRequest(appKey, fileUrl);
if (taskId == null) {
log.error("录音文件识别请求失败!");
return R.fail("录音文件识别请求失败!");
}
log.info("录音文件识别请求成功,task_id: " + taskId);
// 第二步:根据任务ID轮询识别结果。
String result = service.getFileTransResult(taskId);
if (result == null) {
log.error("录音文件识别结果查询失败!");
return R.fail("录音文件识别结果查询失败!");
}
log.info("录音文件识别结果查询成功:" + result);
Example example = JSON.parseObject(result, Example.class);
log.info("录音结果映射后:{}", JSON.toJSONString(example));
return R.data(example );
}
}
语音识别
/**
* 实时语音识别
*/
public class SpeechTranslationService {
private String appKey;
private String accessToken;
private Channel channel;
NlsClient client;
public static Map<Channel, Object> transcriberMap = new ConcurrentHashMap<>(32);
public SpeechTranslationService(String appKey, String token) {
this.appKey = appKey;
this.accessToken = token;
client = NlsClientService.getNlsClient();
}
public SpeechTranslationService(String appKey, String token, Channel channel) {
this.appKey = appKey;
this.accessToken = token;
this.channel = channel;
client = NlsClientService.getNlsClient();
}
public SpeechTranslationService(String appKey, String token, String url) {
this.appKey = appKey;
this.accessToken = token;
//创建NlsClient实例,应用全局创建一个即可,用户指定服务地址
client = new NlsClient(url, accessToken);
}
public SpeechTranscriberListener getTranscriberListener() {
SpeechTranscriberListener listener = new SpeechTranscriberListener() {
//识别出中间结果.服务端识别出一个字或词时会返回此消息.仅当setEnableIntermediateResult(true)时,才会有此类消息返回
@Override
public void onTranscriptionResultChange(SpeechTranscriberResponse response) {
// 重要提示: task_id很重要,是调用方和服务端通信的唯一ID标识,当遇到问题时,需要提供此task_id以便排查
//System.out.println("onTranscriptionResultChange:" + "name: " + response.getName() +
// //状态码 20000000 表示正常识别
// ", status: " + response.getStatus() +
// //句子编号,从1开始递增
// ", index: " + response.getTransSentenceIndex() +
// //当前的识别结果
// ", result: " + response.getTransSentenceText() +
// //当前已处理的音频时长,单位是毫秒
// ", time: " + response.getTransSentenceTime());
}
@Override
public void onTranscriberStart(SpeechTranscriberResponse response) {
System.out.println("onTranscriberStart:" + "task_id: " + response.getTaskId() +
"name: " + response.getName() +
", status: " + response.getStatus());
}
@Override
public void onSentenceBegin(SpeechTranscriberResponse response) {
System.out.println("onSentenceBegin:" + "task_id: " + response.getTaskId() +
"name: " + response.getName() +
", status: " + response.getStatus());
}
//识别出一句话.服务端会智能断句,当识别到一句话结束时会返回此消息
@Override
public void onSentenceEnd(SpeechTranscriberResponse response) {
channel.writeAndFlush(new TextWebSocketFrame(response.getTransSentenceText()));
System.out.println("onSentenceEnd:" + "name: " + response.getName() +
//状态码 20000000 表示正常识别
", status: " + response.getStatus() +
//句子编号,从1开始递增
", index: " + response.getTransSentenceIndex() +
//当前的识别结果
", result: " + response.getTransSentenceText() +
//置信度
", confidence: " + response.getConfidence() +
//开始时间
", begin_time: " + response.getSentenceBeginTime() +
//当前已处理的音频时长,单位是毫秒
", time: " + response.getTransSentenceTime() +
//关键词
", words: " + response.getWords());
}
//识别完毕
@Override
public void onTranscriptionComplete(SpeechTranscriberResponse response) {
System.out.println("onTranscriptionComplete:" + "task_id: " + response.getTaskId() +
", name: " + response.getName() +
", status: " + response.getStatus());
}
@Override
public void onFail(SpeechTranscriberResponse response) {
// 重要提示: task_id很重要,是调用方和服务端通信的唯一ID标识,当遇到问题时,需要提供此task_id以便排查
System.out.println("onFail:" +
"task_id: " + response.getTaskId() +
//状态码 20000000 表示识别成功
", status: " + response.getStatus() +
//错误信息
", status_text: " + response.getStatusText());
}
};
return listener;
}
public void process(Channel channel, byte[] byteArray) {
SpeechTranscriber transcriber = (SpeechTranscriber) transcriberMap.get(channel);
try {
if (Objects.isNull(transcriber)) {
// 创建实例,建立连接
transcriber = new SpeechTranscriber(client, getTranscriberListener());
transcriber.setAppKey(appKey);
// 输入音频编码方式
transcriber.setFormat(InputFormatEnum.PCM);
// 输入音频采样率
transcriber.setSampleRate(SampleRateEnum.SAMPLE_RATE_16K);
// 是否返回中间识别结果
transcriber.setEnableIntermediateResult(false);
// 是否生成并返回标点符号
transcriber.setEnablePunctuation(true);
// 是否将返回结果规整化,比如将一百返回为100
transcriber.setEnableITN(false);
//开启词模式
transcriber.addCustomedParam("enable_words", true);
//此方法将以上参数设置序列化为json发送给服务端,并等待服务端确认
transcriber.start();
transcriberMap.put(channel, transcriber);
}
transcriber.send(byteArray, byteArray.length);
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
public void shutdown() {
client.shutdown();
}
}
录音识别
/**
* 录音文件识别
*/
public class RecordingTranslationService {
// 地域ID,常量,固定值。
public static final String REGIONID = "cn-shanghai";
public static final String ENDPOINTNAME = "cn-shanghai";
public static final String PRODUCT = "nls-filetrans";
public static final String DOMAIN = "filetrans.cn-shanghai.aliyuncs.com";
public static final String API_VERSION = "2018-08-17";
public static final String POST_REQUEST_ACTION = "SubmitTask";
public static final String GET_REQUEST_ACTION = "GetTaskResult";
// 请求参数
public static final String KEY_APP_KEY = "appkey";
public static final String KEY_FILE_LINK = "file_link";
public static final String KEY_VERSION = "version";
public static final String AUTO_SPLIT = "auto_split";
public static final String KEY_ENABLE_WORDS = "enable_words";
public static final String ENABLE_DISFLUENCY = "enable_disfluency";
public static final String FIRST_CHANNEL_ONLY = "first_channel_only";
public static final String ENABLE_SAMPLE_RATE_ADAPTIVE = "enable_sample_rate_adaptive";
public static final String ENABLE_SEMANTIC_SENTENCE_DETECTION = "enable_semantic_sentence_detection";
// 响应参数
public static final String KEY_TASK = "Task";
public static final String KEY_TASK_ID = "TaskId";
public static final String KEY_STATUS_TEXT = "StatusText";
public static final String KEY_RESULT = "Result";
// 状态值
public static final String STATUS_SUCCESS = "SUCCESS";
private static final String STATUS_RUNNING = "RUNNING";
private static final String STATUS_QUEUEING = "QUEUEING";
// 阿里云鉴权client
IAcsClient client;
public RecordingTranslationService(String accessKeyId, String accessKeySecret) {
// 设置endpoint
try {
DefaultProfile.addEndpoint(ENDPOINTNAME, REGIONID, PRODUCT, DOMAIN);
} catch (ClientException e) {
e.printStackTrace();
}
// 创建DefaultAcsClient实例并初始化
DefaultProfile profile = DefaultProfile.getProfile(REGIONID, accessKeyId, accessKeySecret);
this.client = new DefaultAcsClient(profile);
}
/**
* 提交录音文件
* @param appKey
* @param fileLink
* @return
*/
public String submitFileTransRequest(String appKey, String fileLink) {
/**
* 1. 创建CommonRequest,设置请求参数。
*/
CommonRequest postRequest = new CommonRequest();
// 设置域名
postRequest.setDomain(DOMAIN);
// 设置API的版本号,格式为YYYY-MM-DD。
postRequest.setVersion(API_VERSION);
// 设置action
postRequest.setAction(POST_REQUEST_ACTION);
// 设置产品名称
postRequest.setProduct(PRODUCT);
postRequest.setHttpContent(new byte[]{},"", FormatType.JSON);
/**
* 2. 设置录音文件识别请求参数,以JSON字符串的格式设置到请求Body中。
*/
JSONObject taskObject = new JSONObject();
// 设置appkey
taskObject.put(KEY_APP_KEY, appKey);
// 设置音频文件访问链接
taskObject.put(KEY_FILE_LINK, fileLink);
// 新接入请使用4.0版本,已接入(默认2.0)如需维持现状,请注释掉该参数设置。
taskObject.put(KEY_VERSION, "4.0");
// 设置是否输出词信息,默认为false,开启时需要设置version为4.0及以上。
taskObject.put(KEY_ENABLE_WORDS, true);
//开启智能分轨
taskObject.put(AUTO_SPLIT, true);
//是否只识别首个声道,搭配启智能分轨使用
taskObject.put(FIRST_CHANNEL_ONLY, true);
//是否启⽤语义断句
taskObject.put(ENABLE_SAMPLE_RATE_ADAPTIVE, true);
//语义断句
taskObject.put(ENABLE_SEMANTIC_SENTENCE_DETECTION, false);
//语气词优化
taskObject.put(ENABLE_DISFLUENCY, false);
String task = taskObject.toJSONString();
System.out.println(task);
// 设置以上JSON字符串为Body参数。
postRequest.putBodyParameter(KEY_TASK, task);
// 设置为POST方式的请求。
postRequest.setMethod(MethodType.POST);
/**
* 3. 提交录音文件识别请求,获取录音文件识别请求任务的ID,以供识别结果查询使用。
*/
String taskId = null;
try {
CommonResponse postResponse = client.getCommonResponse(postRequest);
System.err.println("提交录音文件识别请求的响应:" + postResponse.getData());
if (postResponse.getHttpStatus() == 200) {
JSONObject result = JSONObject.parseObject(postResponse.getData());
String statusText = result.getString(KEY_STATUS_TEXT);
if (STATUS_SUCCESS.equals(statusText)) {
taskId = result.getString(KEY_TASK_ID);
}
}
} catch (ClientException e) {
e.printStackTrace();
}
return taskId;
}
/**
* 查询录音结果
* @param taskId
* @return
*/
public String getFileTransResult(String taskId) {
/**
* 1. 创建CommonRequest,设置任务ID。
*/
CommonRequest getRequest = new CommonRequest();
// 设置域名
getRequest.setDomain(DOMAIN);
// 设置API版本
getRequest.setVersion(API_VERSION);
// 设置action
getRequest.setAction(GET_REQUEST_ACTION);
// 设置产品名称
getRequest.setProduct(PRODUCT);
// 设置任务ID为查询参数
getRequest.putQueryParameter(KEY_TASK_ID, taskId);
// 设置为GET方式的请求
getRequest.setMethod(MethodType.GET);
/**
* 2. 提交录音文件识别结果查询请求
* 以轮询的方式进行识别结果的查询,直到服务端返回的状态描述为“SUCCESS”或错误描述,则结束轮询。
*/
String result = null;
while (true) {
try {
CommonResponse getResponse = client.getCommonResponse(getRequest);
//System.err.println("识别查询结果:" + getResponse.getData());
if (getResponse.getHttpStatus() != 200) {
break;
}
JSONObject rootObj = JSONObject.parseObject(getResponse.getData());
String statusText = rootObj.getString(KEY_STATUS_TEXT);
if (STATUS_RUNNING.equals(statusText) || STATUS_QUEUEING.equals(statusText)) {
// 继续轮询,注意设置轮询时间间隔。
Thread.sleep(10000);
}
else {
// 状态信息为成功,返回识别结果;状态信息为异常,返回空。
if (STATUS_SUCCESS.equals(statusText)) {
result = rootObj.getString(KEY_RESULT);
// 状态信息为成功,但没有识别结果,则可能是由于文件里全是静音、噪音等导致识别为空。
if(result == null) {
result = "";
}
}
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
return result;
}
}
录音上传
@RestController
@RequestMapping("/api/v1/oss")
@Slf4j
public class OssController {
@Autowired
OssConfig ossConfig;
@Autowired
NlpService nlpService;
private static final Long KEEP_ALIVE_TIME = 60L;
private static final int APS = Runtime.getRuntime().availableProcessors();
private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
APS * 2,
APS * 4,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(256),
new ThreadFactoryBuilder().setNameFormat("OSS上传-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
@ApiOperation("阿里云oss上传")
@PostMapping("upload")
public R uploadFile(
@ApiParam(value = "文件上传", required = true) @RequestPart("file") MultipartFile file
) {
//原始文件名称 xxx.jpg
String originalFileName = file.getOriginalFilename().trim();
log.info("OSS文件上传,上传文件名称:【{}】,文件大小:【{}】", originalFileName, file.getSize());
if (file.getSize() > 512 * 1024 * 1024) {
return R.fail("不能大于512M 支持单轨和双轨的WAV、MP3、MP4、M4A、WMA、AAC、OGG、AMR、FLAC格式录音文件识别");
}
//获取相关配置
String bucketName = ossConfig.getBucketName();
String endpoint = ossConfig.getEndpoint();
String accessKeyId = ossConfig.getAccessKeyId();
String accessSecret = ossConfig.getAccessSecret();
String region = ossConfig.getRegion();
String roleArn = ossConfig.getRoleArn();
//创建OSS对象
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessSecret);
//JDK8 日期格式化
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd");
DateTimeFormatter yyyyMMdd = DateTimeFormatter.ofPattern("yyyyMMdd");
//扩展名
String extension = StringUtils.isNotBlank(originalFileName) ? originalFileName.substring(originalFileName.lastIndexOf(".")) : "jpg";
//OSS上的文件名
String uuid = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
String fileName = yyyyMMdd.format(ldt) + "_" + uuid.substring(0, 10) + extension;
//拼装路径,oss上存储的路径 2021/05/16/xxx.jpg
String newFileName = "xxx/" + dtf.format(ldt) + "/" + fileName;
//推送到oss
try {
AtomicBoolean successAtomic = new AtomicBoolean(false);
CountDownLatch latch = new CountDownLatch(1);
THREAD_POOL_EXECUTOR.execute(() -> {
try {
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType("audio/mpeg"); // 设置录音文件的内容类型
objectMetadata.setContentLength(file.getSize()); // 设置文件长度
ossClient.putObject(bucketName, newFileName, file.getInputStream(), objectMetadata);
} catch (IOException e) {
log.error("oss文件上传失败:{}", e);
successAtomic.set(true);
} finally {
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
}
if (successAtomic.get()) {
return R.fail("上传文件失败,请稍后重试");
}
//拼装返回url
String fileUrl = "https://" + bucketName + "." + endpoint + "/" + newFileName;
log.info("OSS文件上传成功:{}", fileUrl);
URL url = null;
try {
STSAssumeRoleSessionCredentialsProvider credentialsProvider = CredentialsProviderFactory
.newSTSAssumeRoleSessionCredentialsProvider(
region,
accessKeyId,
accessSecret,
roleArn
);
OSS client = new OSSClientBuilder().build(endpoint, credentialsProvider);
// 设置签名URL过期时间,单位为毫秒。本示例以设置过期时间为1小时为例。
Date expiration = DateUtil.plusDays(new Date(), 7);
// 生成以GET方法访问的签名URL,访客可以直接通过浏览器访问相关内容。
url = client.generatePresignedUrl(bucketName, newFileName, expiration);
} catch (ClientException e) {
log.error("请求oss临时url异常:{}", e.getMessage());
}
return nlpService.recordingTranslation(url.toString());
} finally {
//oss关闭
ossClient.shutdown();
}
}
}
阿里云oss配置文件
/**
* 阿里云oss配置文件
*/
@Data
@Configuration
public class OssConfig {
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.accessKeyId}")
private String accessKeyId;
@Value("${aliyun.oss.accessSecret}")
private String accessSecret;
@Value("${aliyun.oss.bucketName}")
private String bucketName;
@Value("${aliyun.oss.region}")
private String region;
@Value("${aliyun.oss.roleArn}")
private String roleArn;
}
配置文件
spring.servlet.multipart.max-file-size=512MB
spring.servlet.multipart.max-request-size=512MB
# oss
aliyun.oss.endpoint=oss-cn-hangzhou.aliyuncs.com
aliyun.oss.accessKeyId=xxx
aliyun.oss.accessSecret=xxx
aliyun.oss.bucketName=xxx
aliyun.oss.region=cn-hangzhou
aliyun.oss.roleArn=xxx
# redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456
spring.redis.database=12
spring.redis.ssl= false
spring.redis.lettuce.pool.max-active = 200
spring.redis.lettuce.pool.max-idle = 100
spring.redis.lettuce.pool.min-idle = 10
spring.redis.lettuce.pool.max-wait= -1ms
spring.redis.client-type = lettuce
#nls
aliyun.nls.appKey=xxx
aliyun.nls.accessKeyId=xxx
aliyun.nls.accessKeySecret=xxx
aliyun.nls.url=wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1