@[toc]
学习成果展示
成果链接如果哪一天打不开了就说明我删除它了
学习前提
- 开通并注册百炼大模型 地址链接获取apikey==必须==
- 开通并拥有OSS存储服务 OSS存储官网
或者你的音频链接是公网链接,可以直接被访问到然后下载==非必须== - 掌握springboot的基本用法
- java语法熟练这都不用多数
学习目标:
快速掌握springboot集成阿里云百炼大模型 阿里云产品购买链接
学习内容:
例如:
- 搭建 springboot项目
- 学习springboot如何集成百炼大模型上的CosyVoice2模型
- 掌握其克隆音色功能的使用
- 对音色有简单的增删改查能力
使用版本:
Spring boot 项目技术栈信息表
类别 | 具体信息 |
---|---|
项目基本信息 | - 项目名称:springboot-xinghai-alibaba-voice - 项目版本:0.0.1-SNAPSHOT - 描述:springboot-xinghai-alibaba-voice |
核心框架 | - Spring Boot 版本:3.5.3 (父依赖)- Java 版本:17 |
核心依赖 | 1. Web 开发:spring-boot-starter-web (Spring Boot Web 组件,支持 MVC、嵌入式服务器等)2. 阿里云服务: - dashscope-sdk-java:2.20.3 (阿里云 DashScope SDK,用于调用大模型等服务) - aliyun-sdk-oss:3.17.0 (阿里云 OSS SDK,用于对象存储操作)3. 工具类: - lombok:1.18.38 (简化 Java 代码,减少 getter/setter 等模板代码) |
测试依赖 | - spring-boot-starter-test (Spring Boot 测试组件,包含 JUnit、Mockito 等)- 作用域:test (仅测试环境生效) |
构建工具 | - 构建插件:spring-boot-maven-plugin (Spring Boot Maven 插件,支持打包、运行等功能)- 构建工具:Maven(基于 pom.xml 配置) |
其他配置 | - Lombok 可选配置:<optional>true</optional> (不传递依赖) |
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.xinghai</groupId>
<artifactId>springboot-xinghai-alibaba-voice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-xinghai-alibaba-voice</name>
<description>springboot-xinghai-alibaba-voice</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 参数校验支持:提供 @Valid、@NotNull、@Min 等校验注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 新增阿里云DashScope SDK依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>2.20.3</version>
</dependency>
<!-- 阿里云OSS SDK依赖,排除commons-logging -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.0</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
<version>1.18.38</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
<scope>provided</scope> <!-- 设置为不参与打包 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件
==按照自己的写本章节暂时用不到oss的配置可以先不写==
server:
port: 8080
alibaba:
api-key: sk-
oss:
access-key-id: LTAI5tP
secret-key-secret: QUwvHJyrc
endpoint: oss-cn-beijing.aliyuncs.com # 替换为您的OSS服务端点
bucket-name: tg # 替换为您的存储空间名称
# 安全提示:实际部署时请勿将密钥提交到版本库
其中的配置信息可以看我发布的文章,如果没有就将这个配置文件发给豆包然后询问这些信息应该怎么获得 提示:oss是非必须,apikey是必须
目录介绍
- controller用来写api接口
- dto 用来存储与前端交互的类
- service 服务层
- mapper 用来和数据库交流(暂时不需要)
- model 用来存储实体类
- utils 工具类
- exception 全局异常处理类
开始编码
GlobalExceptionHandler.java 全局异常处理类
package org.xinghai.springbootxinghaialibabavoice.exception;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.xinghai.springbootxinghaialibabavoice.model.Result;
import java.util.stream.Collectors;
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> handleValidationExceptions(MethodArgumentNotValidException ex) {
String errorMsg = ex.getBindingResult().getAllErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining(", "));
return Result.error(400, errorMsg);
}
// 处理JSON解析异常
@ExceptionHandler(HttpMessageNotReadableException.class)
public Result<?> handleJsonParseException(HttpMessageNotReadableException ex) {
return Result.error(400, "请求体JSON格式错误");
}
// 处理业务层异常
@ExceptionHandler(IllegalArgumentException.class)
public Result<?> handleBusinessException(IllegalArgumentException ex) {
return Result.error(400, ex.getMessage());
}
// 处理其他未捕获异常
@ExceptionHandler(Exception.class)
public Result<?> handleOtherExceptions(Exception ex) {
return Result.error(500, "系统繁忙,请稍后再试");
}
}
result结果类统一返回前端
package org.xinghai.springbootxinghaialibabavoice.model;
import lombok.Data;
import java.io.Serializable;
@Data
public class Result<T> implements Serializable {
private Integer code;
private String msg;
private T data;
// 成功(不带数据)
public static <T> Result<T> success() {
Result<T> result = new Result<>();
result.setCode(200);
result.setMsg("成功");
return result;
}
// 成功(带数据)
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMsg("成功");
result.setData(data);
return result;
}
// 错误(带错误信息)
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMsg(message);
return result;
}
// 错误(带错误码和信息)
public static <T> Result<T> error(int code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMsg(message);
return result;
}
// 链式调用设置扩展信息
public Result<T> withData(T data) {
this.data = data;
return this;
}
// 常用错误码枚举
public enum ErrorCode {
PARAM_ERROR(400, "参数错误"),
SYSTEM_ERROR(500, "系统错误");
private final int code;
private final String desc;
ErrorCode(int code, String desc) {
this.code = code;
this.desc = desc;
}
}
}
Voice实体类
package org.xinghai.springbootxinghaialibabavoice.model;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
@Data
@Builder
public class Voice {
private String voiceId;
private String voiceName;
private Date creatTime;
private Date updateTime;
}
Dto
- CreatVoiceDto 类用于和前端交流
package org.xinghai.springbootxinghaialibabavoice.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
@Data
public class CreatVoiceDto {
@NotBlank(message = "音色名称不能为空")
@Pattern(regexp = "^[^\\u4e00-\\u9fa5]*$",
message = "音色名称不能包含中文")
private String voiceName;
@NotBlank(message = "文件路径不能为空")
private String filePath;
}
FindVoiceDto.java
```java
@Data
public class FindVoiceDto {/** * 音色前缀过滤(支持模糊匹配,空值表示不过滤) */ private String prefix; /** * 分页页码(从0开始) */ @Min(value = 0, message = "分页页码不能小于0") private Integer pageIndex = 0; /** * 每页数量(默认10) */ @Min(value = 1, message = "每页数量最小为1") private Integer pageSize = 10;
}
3. UseVoiceDto
```java
@NotBlank(message = "文本不能为空")
private String text;
@NotBlank(message = "音色id不能为空")
private String voiceId;
private AudioFormat format = AudioFormat.MP3_22050HZ_MONO_256KBPS; // 设置默认值 // 音频格式(根据SDK的AudioFormat枚举值)
@Min(value = 0, message = "音量范围0-100")
@Max(value = 100)
private Integer volume = 50;
@DecimalMin("0.5") @DecimalMax("2.0")
private Float speechRate = 1.0f;// 语速
@DecimalMin("0.5") @DecimalMax("2.0")
private Float pitchRate = 1.0f;// 语调
创建音色
创建音色Controller
下面的所有代码全都写到同一个类中VoiceController VoiceService
package org.xinghai.springbootxinghaialibabavoice.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.xinghai.springbootxinghaialibabavoice.dto.CreatVoiceDto;
import org.xinghai.springbootxinghaialibabavoice.model.Result;
import org.xinghai.springbootxinghaialibabavoice.service.VoiceService;
@RestController
@RequestMapping("/api/voice")
public class VoiceController {
@Autowired
private VoiceService voiceService;
// 创建音色(语音注册)
@PostMapping("/createVoice")
public Result<String> createTimbre(@RequestBody CreatVoiceDto creatVoiceDto) {
// 调用服务层方法创建音色
String voiceId = voiceService.createVoice(creatVoiceDto);
return Result.success(voiceId);
}
}
创建音色Service
package org.xinghai.springbootxinghaialibabavoice.service.impl;
import com.alibaba.dashscope.audio.ttsv2.enrollment.Voice;
import com.alibaba.dashscope.audio.ttsv2.enrollment.VoiceEnrollmentService;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.xinghai.springbootxinghaialibabavoice.dto.CreatVoiceDto;
import org.xinghai.springbootxinghaialibabavoice.model.MyVoice;
import org.xinghai.springbootxinghaialibabavoice.service.VoiceService;
import java.util.Date;
/**
* 创建音色
*
* targetModel 声音复刻所使用的模型,请使用cosyvoice-v2。
* prefix 音色自定义前缀,仅允许数字和小写字母,小于十个字符。
* url 用于复刻音色的音频文件URL。该URL要求公网可访问。
* @return Voice 音色对象
* @throws NoApiKeyException 如果apikey为空
* @throws InputRequiredException 如果必须参数为空
*
* 由于重点不放在数据库的操作上,所以这里就省略了,实际上的实体类还会有创建的作者名字,
* 分类标签,描述信息,名字等。
* 注意:creatVoiceDto获取的名字在我的代码中实际上是作为前缀的,最后也保存到实体类或者数据库
* 中,所以在查询的时候,需要根据前缀来查询。
* 在实际开发中我建议将前缀和名字分开,前缀用来查询音色的id,名字用来显示音色的名称。
* @author xinghai -wx : L2545451941
* @version 1.0
* @since 2024/7/19
*
*/
@Service
public final class VoiceServiceImpl implements VoiceService {
@Value("${alibaba.api-key}")
private String apiKey;
private static final String targetModel = "cosyvoice-v2";
@Override
public String createVoice(CreatVoiceDto creatVoiceDto) {
VoiceEnrollmentService service = new VoiceEnrollmentService(apiKey);
//从dto中获取文件路径和音色名称(音色自定义前缀可以用来查询该音色的id)
String fileUrl = creatVoiceDto.getFilePath();
String prefix = creatVoiceDto.getVoiceName();
try {
// 创建音色
Voice voice = service.createVoice(targetModel, prefix, fileUrl);
//实际开发中坚决不要打印到控制台
System.out.println("request id为:" + service.getLastRequestId());
System.out.println("voice id为:" + voice.getVoiceId());
MyVoice myVoice = MyVoice.builder()
.voiceId(voice.getVoiceId())
.voiceName(creatVoiceDto.getVoiceName())
.creatTime(new Date())
.updateTime(new Date())
.build();
//保存到数据库中
//省略...
return myVoice.getVoiceId();
} catch (NoApiKeyException e) {
throw new RuntimeException(e);
} catch (InputRequiredException e) {
throw new RuntimeException(e);
}
}
}
使用音色输出音频文件
音色合成 Controller
@PostMapping("/useVoice")
public byte[] voiceSynthesis(
@RequestBody UseVoice useVoice
) {
return voiceService.useVoice(useVoice);
}
音色合成serviceimpl
@Override
public byte[] useVoice(UseVoice useVoice) {
ConvertAudioFormatUtils utils = new ConvertAudioFormatUtils();
//将前端传来的音频格式转换为SpeechSynthesisAudioFormat类型
SpeechSynthesisAudioFormat spformat = utils.convertAudioFormat(useVoice.getFormat());
// 使用复刻的声音来合成文本为语音
SpeechSynthesisParam param = SpeechSynthesisParam.builder()
.apiKey(apiKey)
.model(targetModel)
.voice(useVoice.getVoiceId())
// 添加类型转换
.format(spformat)
.volume(useVoice.getVolume())
.speechRate(useVoice.getSpeechRate())
.pitchRate(useVoice.getPitchRate())
.build();
try {
SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
ByteBuffer audioData = synthesizer.call( useVoice.getText());
// 将ByteBuffer转换为byte数组返回
byte[] bytes = new byte[audioData.remaining()];
audioData.get(bytes);
return bytes;
} catch (Exception e) {
throw new RuntimeException("语音合成失败", e);
}
当你看到这里,就证明你已经可以正式的开始你的语音合成啦!
不过你现在还需要将这些功能进一步完善才好,例如查询你创建的音色等等。
查询音色
查询音色Controller
// 查询音色列表
@GetMapping("/findVoice")
public Result<List<String>> findVoice(@RequestBody FindVoice findVoice
) {
// 调用阿里云API查询音色列表
Voice[] voices = voiceService.findVoice(findVoice);
// 使用Stream转换voiceId列表
List<String> voiceIds = Arrays.stream(voices)
.map(Voice::getVoiceId)
.collect(Collectors.toList());
return Result.success(voiceIds);
}
查询音色service
/**
* 查询音色列表
* @param findVoice 查询参数对象,包含以下字段:
* prefix - 音色前缀过滤条件(支持模糊匹配,null表示不过滤)
* pageIndex - 分页页码(从0开始计数)
* pageSize - 每页数量(建议10-100之间)
* @return
*/
@Override
public Voice[] findVoice(FindVoice findVoice) {
String prefix = findVoice.getPrefix();
Integer pageIndex = findVoice.getPageIndex();
Integer pageSize = findVoice.getPageSize();
//创建一个service对象
VoiceEnrollmentService service = new VoiceEnrollmentService(apiKey);
try {
Voice[] voices = service.listVoice(prefix, pageIndex, pageSize);
System.out.println("request id为:" + service.getLastRequestId());
return voices;
} catch (NoApiKeyException e) {
throw new RuntimeException(e);
} catch (InputRequiredException e) {
throw new RuntimeException(e);
}
}
删除音色
删除音色Controller
// 删除音色
@DeleteMapping("/timbre")
public Result<?> deleteTimbre(
@RequestParam(name = "voiceId")String voiceId) {
voiceService.deleteVoice(voiceId);
// 调用阿里云API删除指定音色
return Result.success();
}
删除音色Service
public void deleteVoice(String voiceId) {
VoiceEnrollmentService service = new VoiceEnrollmentService(apiKey);
// 删除音色
try {
service.deleteVoice(voiceId);
} catch (NoApiKeyException e) {
throw new RuntimeException(e);
} catch (InputRequiredException e) {
throw new RuntimeException(e);
}
}
接口测试
我使用的是apifox 接口测试工具,下面演示的也是这个工具演示
创建音色
- 创建成功展示
你可以在创建音色时候使用这段网址,这是我个人上到OSS容器中公网IP,暂时不删除,如果哪一天失败了,就说明被删除了
https://testlijiaxing.oss-cn-beijing.aliyuncs.com/%E9%9F%B3%E9%A2%91%E6%96%87%E4%BB%B6/jd_89e52b32-0a4b-4e4f-b7e9-45de827e98af_1750244029092_.mp - 查询naxidanao
- 错误我就不展示了,关于调用百炼这个克隆音色然后创建音色,有好几种错误,==你的url必须时公网可以直接被访问到的,而且格式也有一定的要求,还有很多错误我就不说明了==
查询音色
- 错误查询 pageSize不可以小于1,pageIndex不可以小于0
多的不再演示 - 成功演示 prefix是音色的前缀表使,当为空的时候会将你的apikey所有的音色查询出来
删除音色
- 成功案例
- 失败,==我的全局捕捉错误并未去捕捉当voiceId不存在时的错误,因此你们可以自行去查询资料,后续我会将有关CosyVoice2的使用文档写链接于下方。==
使用音色
成功案例
错误的话自行排查==我可以想到的容易解决的已经都用全局异常处理器捕获过了==如果真有错误,检查一下你发的文本和音频是不是正常人所能想到的了
- 音频链接这个链接如果没有意外的话将会一直保存
作者想说
这篇文章并没有涉及到OSS存储方面的知识,但是在实际开发中肯定是需要的,你需要将自子要克隆的音色存到OSS中,百炼中的CosyVoice2才能获取音频链接然后克隆出对应的音色。
下方链接是关于如何使用百炼大模型CosyVoice2克隆音色的官方文档,我个人认为我这篇文档写的已经很到位了,在==如何将其集成在springboot中==,官方的文档给的比较广泛,java,python,等等如何集成他们的产品 ----------==不是贬低官方只是官方写的太通了,我的这篇仅仅针对springboot如何集成CosyVoice2==
API接口文档- 这只是基础的玩法,还有进阶的玩法
bibi站对应的视频链接
以后会出对应的视频,请耐心等待