后端原本代码
// controller中ApiResponse是接口返回的统一数据模型,其中data返回字节数组
public ResponseEntity<ApiResponse<byte[]>> requestTextToAudio(@RequestParam("text") String text) {
// headers设置contentType为字节流,这里导致了下面的报错
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
报错如下
Exception: No converter for [class com.money.aiaudioservice.response.ApiResponse] with preset Content-Type 'application/octet-stream'
大意是无法解析application/octet-stream
原因
因为 Spring Boot 在处理响应时,找不到合适的消息转换器将 ApiResponse
对象转换为 application/octet-stream
类型的响应。下面详细分析可能的原因并给出相应的解决办法。
返回的Gson格式中包裹了byte[]二进制流导致无法解析
解决方案
将返回的二进制流改成用base64编码的字符串,客户端调用接口后再将字符串转换成bytes[]字节流,再保存为文件即可。
springboot核心代码如下:
byteBuffer = convertTextToAudio(text);
// 将 ByteBuffer 转换为字节数组
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
//将原本的bytes[]转换成Base64编码字符串
String base64AudioData = Base64.getEncoder().encodeToString(bytes);
// 创建统一返回对象,返回数据模型ApiResponse中改为传base64编码的字符串
ApiResponse<String> apiResponse = new ApiResponse<>(200, "File download success", base64AudioData);
// 设置响应头,响应头从APPLICATION_OCTET_STREAM改为APPLICATION_JSON
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// springboot不需要设置contentLength,会自动计算并返回
客户端修改后的核心代码如下:
// ApiService中返回数据模型ApiResponse<String>中data为string
suspend fun requestTextToAudio(@Query("text") text: String): Response<ApiResponse<String>>
// 返回的data为base64编码,这里解码为ByteArray格式
val result = withContext(Dispatchers.IO) {
val audioData = android.util.Base64.decode(response.data, android.util.Base64.DEFAULT)
repository.saveAudioFile(text, audioData)
}
// 保存字节流为文件
suspend fun saveAudioFile(text:String, audioData: ByteArray):String? {
var outputStream: FileOutputStream? = null
try {
val relativePath = "aiAudio/" + FileNameFormatUtil.convertToValidFileName(text, MAX_FILE_LENGTH) + MP3_FILE
val file = File(application.filesDir, relativePath)
FileUtils.createFileDirs("aiAudio")
outputStream = FileOutputStream(file)
outputStream.write(audioData)
return file.absolutePath
} catch (e: IOException) {
e.printStackTrace()
} finally {
outputStream?.close()
}
return null
}