From 3a05e41f790cbad84bb82f3fe0c10fed24abf8de Mon Sep 17 00:00:00 2001 From: old burden Date: Fri, 27 Mar 2026 10:51:32 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=96=B0=E9=9C=80=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ruoyi/api/ByteApiController.java | 127 +++++++------ .../java/com/ruoyi/api/FileController.java | 6 +- .../com/ruoyi/api/request/ByteApiRequest.java | 2 + .../controller/common/CommonController.java | 6 +- .../src/main/resources/application.yml | 30 +-- .../ruoyi/common/utils/TencentCosUtil.java | 172 ++++++++++++++++-- .../java/com/ruoyi/ai/domain/ByteBodyReq.java | 5 + .../ruoyi/ai/service/impl/ByteService.java | 50 ++--- 8 files changed, 285 insertions(+), 113 deletions(-) diff --git a/ruoyi-admin/src/main/java/com/ruoyi/api/ByteApiController.java b/ruoyi-admin/src/main/java/com/ruoyi/api/ByteApiController.java index eb69649..5dc5081 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/api/ByteApiController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/api/ByteApiController.java @@ -10,10 +10,10 @@ import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.model.LoginAiUser; -import com.ruoyi.common.utils.AwsS3Util; import com.ruoyi.common.utils.RandomStringUtil; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.TencentCosUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; @@ -36,13 +36,21 @@ import java.util.regex.Pattern; public class ByteApiController extends BaseController { private final IByteService byteService; - private final AwsS3Util awsS3Util; + private final TencentCosUtil tencentCosUtil; private final IAiOrderService aiOrderService; private final IAiManagerService managerService; private final IAiTagService aiTagService; @Value("${byteapi.callBackUrl}") private String url; + // 火山引擎配置 + @Value("${volcengine.ark.apiKey}") + private String volcApiKey; + @Value("${volcengine.ark.baseUrl}") + private String volcBaseUrl; + @Value("${volcengine.ark.callbackUrl}") + private String volcCallbackUrl; + @PostMapping("/promptToImg") @ApiOperation("文生图") public AjaxResult promptToImg(@RequestBody ByteApiRequest request) { @@ -85,7 +93,9 @@ public class ByteApiController extends BaseController { } aiOrder.setText(text); ByteBodyReq byteBodyReq = new ByteBodyReq(); - byteBodyReq.setModel("ep-20251104104536-2gpgz"); + // model由前端传入,默认为Seedance 2.0 + byteBodyReq.setModel(StringUtils.isNotEmpty(request.getModel()) ? + request.getModel() : "ep-20260326165811-dlkth"); byteBodyReq.setPrompt(text); byteBodyReq.setSequential_image_generation("disabled"); byteBodyReq.setResponse_format("url"); @@ -96,7 +106,7 @@ public class ByteApiController extends BaseController { List data = byteBodyRes.getData(); ByteDataRes byteDataRes = data.get(0); String url = byteDataRes.getUrl(); - url = awsS3Util.uploadFileByUrl(url); + url = tencentCosUtil.uploadFileByUrl(url); if (url == null) { // 判断生成失败,退回金额逻辑 aiOrderService.orderFailure(aiOrder); @@ -175,7 +185,7 @@ public class ByteApiController extends BaseController { List data = byteBodyRes.getData(); ByteDataRes byteDataRes = data.get(0); String url = byteDataRes.getUrl(); - url = awsS3Util.uploadFileByUrl(url); + url = tencentCosUtil.uploadFileByUrl(url); if (url == null) { // 判断生成失败,退回金额逻辑 aiOrderService.orderFailure(aiOrder); @@ -191,7 +201,7 @@ public class ByteApiController extends BaseController { } @PostMapping("/imgToVideo") - @ApiOperation("图生视频") + @ApiOperation("图生视频 (Seedance 2.0)") public AjaxResult imgToVideo(@RequestBody ByteApiRequest request) throws Exception { String functionType = request.getFunctionType(); if (null == functionType) { @@ -235,51 +245,56 @@ public class ByteApiController extends BaseController { return AjaxResult.error(-1, "You have a low balance, please recharge"); } try { -// String text = request.getText(); -// if (StringUtils.isBlank(text)) { -// return AjaxResult.error("text is null"); -// } -// String tags = request.getTags(); -// if (StringUtils.isNotBlank(tags)) { -// text = "(优先考虑以下关键词:" + tags + ")"; -// } aiOrder.setText(text); - aiOrder.setImg1(firstUrl.toString()); - Integer duration = request.getDuration(); + + Integer duration = request.getDuration() != null ? request.getDuration() : 4; + ByteBodyReq byteBodyReq = new ByteBodyReq(); - byteBodyReq.setModel("ep-20251113072240-cfxlz"); - byteBodyReq.setCallback_url(url + "/api/ai/callBack"); - List content = new ArrayList<>(); - ContentItem contentItem = new ContentItem(); - contentItem.setType("text"); - contentItem.setText(text + " --dur " + duration + " --fps 24 --rs 720p --wm false --cf false"); - content.add(contentItem); + // model由前端传入,默认为Seedance2.0 + byteBodyReq.setModel(StringUtils.isNotEmpty(request.getModel()) ? + request.getModel() : "ep-20260326165811-dlkth"); + byteBodyReq.setCallback_url(volcCallbackUrl); - ContentItem contentItem1 = new ContentItem(); - contentItem1.setType("image_url"); - contentItem1.setRole("first_frame"); - ImageUrl imageUrl1 = new ImageUrl(); - imageUrl1.setUrl(firstUrl.toString()); - contentItem1.setImageUrl(imageUrl1); - content.add(contentItem1); + // 构建符合火山引擎格式的content + List contentList = new ArrayList<>(); + + // 文本提示词 + ContentItem textItem = new ContentItem(); + textItem.setType("text"); + textItem.setText(text); + contentList.add(textItem); + // 首帧图片 + ContentItem firstFrameItem = new ContentItem(); + firstFrameItem.setType("image_url"); + firstFrameItem.setRole("first_frame"); + ImageUrl firstImageUrl = new ImageUrl(); + firstImageUrl.setUrl(firstUrl.toString()); + firstFrameItem.setImageUrl(firstImageUrl); + contentList.add(firstFrameItem); + + // 如果有尾帧 String lastUrl = request.getLastUrl(); if (StringUtils.isNotBlank(lastUrl)) { - ContentItem contentItem2 = new ContentItem(); - contentItem2.setType("image_url"); - contentItem2.setRole("last_frame"); - ImageUrl imageUrl2 = new ImageUrl(); - imageUrl2.setUrl(lastUrl); - contentItem2.setImageUrl(imageUrl2); - content.add(contentItem2); + ContentItem lastFrameItem = new ContentItem(); + lastFrameItem.setType("image_url"); + lastFrameItem.setRole("last_frame"); + ImageUrl lastImageUrl = new ImageUrl(); + lastImageUrl.setUrl(lastUrl); + lastFrameItem.setImageUrl(lastImageUrl); + contentList.add(lastFrameItem); aiOrder.setImg2(lastUrl); } - byteBodyReq.setContent(content); + + byteBodyReq.setContent(contentList); + byteBodyReq.setDuration(duration); + byteBodyReq.setResolution("720p"); + byteBodyReq.setRatio("3:4"); + ByteBodyRes byteBodyRes = byteService.imgToVideo(byteBodyReq); String id = byteBodyRes.getId(); if (id == null) { - // 判断生成失败,退回金额逻辑 aiOrderService.orderFailure(aiOrder); return AjaxResult.error(-2, "generation failed, balance has been refunded"); } @@ -299,7 +314,7 @@ public class ByteApiController extends BaseController { if ("succeeded".equals(byteBodyRes.getStatus())) { content content = byteBodyRes.getContent(); String videoUrl = content.getVideo_url(); - videoUrl = awsS3Util.uploadFileByUrl(videoUrl); + videoUrl = tencentCosUtil.uploadFileByUrl(videoUrl); content.setVideo_url(videoUrl); AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(id); AiOrder aiOrder = new AiOrder(); @@ -311,24 +326,28 @@ public class ByteApiController extends BaseController { return AjaxResult.success(byteBodyRes); } - @GetMapping(value = "/callBack") - @ApiOperation("视频下载回调") + @GetMapping(value = "/volcCallback") + @ApiOperation("火山引擎视频回调") @Anonymous - public AjaxResult callBack(@PathVariable("id") ByteBodyRes byteBodyRes) throws Exception { + public AjaxResult volcCallback(@RequestBody ByteBodyRes byteBodyRes) throws Exception { if ("succeeded".equals(byteBodyRes.getStatus())) { String id = byteBodyRes.getId(); - content content = byteBodyRes.getContent(); - String videoUrl = content.getVideo_url(); - videoUrl = awsS3Util.uploadFileByUrl(videoUrl); - content.setVideo_url(videoUrl); - AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(id); - AiOrder aiOrder = new AiOrder(); - aiOrder.setId(aiOrderByResult.getId()); - aiOrder.setResult(videoUrl); -// aiOrder.setUpdateBy(SecurityUtils.getLoginAiUser().getUsername()); - aiOrderService.updateAiOrder(aiOrder); + content contentObj = byteBodyRes.getContent(); + if (contentObj != null && StringUtils.isNotEmpty(contentObj.getVideo_url())) { + String videoUrl = contentObj.getVideo_url(); + videoUrl = tencentCosUtil.uploadFileByUrl(videoUrl); + contentObj.setVideo_url(videoUrl); + + AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(id); + if (aiOrderByResult != null) { + AiOrder aiOrder = new AiOrder(); + aiOrder.setId(aiOrderByResult.getId()); + aiOrder.setResult(videoUrl); + aiOrderService.updateAiOrder(aiOrder); + } + } } - return AjaxResult.success(byteBodyRes); + return AjaxResult.success("callback success"); } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/api/FileController.java b/ruoyi-admin/src/main/java/com/ruoyi/api/FileController.java index fefd1e0..2dcc9bb 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/api/FileController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/api/FileController.java @@ -1,7 +1,7 @@ package com.ruoyi.api; import com.ruoyi.common.core.domain.AjaxResult; -import com.ruoyi.common.utils.AwsS3Util; +import com.ruoyi.common.utils.TencentCosUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -18,7 +18,7 @@ import org.springframework.web.multipart.MultipartFile; @Api(tags = "文件上传") @RequiredArgsConstructor(onConstructor_ = @Autowired) public class FileController { - private final AwsS3Util awsS3Util; + private final TencentCosUtil tencentCosUtil; /** * 文件上传 @@ -30,7 +30,7 @@ public class FileController { @ApiParam(name = "file", value = "文件", required = true) @RequestParam("file") MultipartFile file) throws Exception { AjaxResult ajax = AjaxResult.success(); - String uploadUrl = awsS3Util.uploadMultipartFile(file, true); + String uploadUrl = tencentCosUtil.uploadMultipartFile(file, true); ajax.put("url", uploadUrl); return ajax; } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/api/request/ByteApiRequest.java b/ruoyi-admin/src/main/java/com/ruoyi/api/request/ByteApiRequest.java index 9c04dab..9d2e5b0 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/api/request/ByteApiRequest.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/api/request/ByteApiRequest.java @@ -31,6 +31,8 @@ public class ByteApiRequest { @ApiModelProperty(name = "标签字符串") private String tags; + @ApiModelProperty(name = "使用的模型") + private String model; } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java index 843c9ba..0f2ff29 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java @@ -4,7 +4,6 @@ import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.exception.base.BaseException; -import com.ruoyi.common.utils.AwsS3Util; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.TencentCosUtil; import com.ruoyi.common.utils.file.FileUploadUtils; @@ -36,7 +35,6 @@ public class CommonController { private static final Logger log = LoggerFactory.getLogger(CommonController.class); private final ServerConfig serverConfig; private final TencentCosUtil tencentCosUtil; - private final AwsS3Util awsS3Util; /** * 通用下载请求 @@ -158,7 +156,7 @@ public class CommonController { } /** - * AWS上传请求(单个) + * 腾讯云COS上传请求(单个) */ @ApiOperation("图片上传接口") @PostMapping("/aws/upload") @@ -166,7 +164,7 @@ public class CommonController { AjaxResult ajax = AjaxResult.success(); String uploadUrl; try { - uploadUrl = awsS3Util.uploadMultipartFile(file, true); + uploadUrl = tencentCosUtil.uploadMultipartFile(file, true); } catch (Exception e) { return AjaxResult.error(e.getMessage()); } diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index c5f4be9..116f1a0 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -209,24 +209,32 @@ google: redirect-uri: tencentCos: - accessKey: - secretKey: - endpoint: - bucketName: - domain: + accessKey: ${TENCENT_COS_SECRET_ID:} + secretKey: ${TENCENT_COS_SECRET_KEY:} + endpoint: ap-guangzhou + bucketName: seedance-1331490964 + domain: https://seedance-1331490964.cos.ap-guangzhou.myqcloud.com -aws: - accessKey: AKIAYVMHEVDDZQGE3HVX - secretKey: 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 - endpoint: ap-southeast-1 - bucketName: di-image - domain: https://images.iqyjsnwv.com/ +# aws配置已替换为腾讯云COS,请在环境变量或配置文件中设置腾讯云凭证 +# aws: +# accessKey: AKIAYVMHEVDDZQGE3HVX +# secretKey: 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 +# endpoint: ap-southeast-1 +# bucketName: di-image +# domain: https://images.iqyjsnwv.com/ byteapi: url: https://ark.ap-southeast.bytepluses.com/api/v3 apiKey: 327d2815-2516-44c2-9e32-2dc50bf7afd7 callBackUrl: https://undressing.top +# 火山引擎 Ark API (Seedance 2.0) +volcengine: + ark: + baseUrl: https://ark.cn-beijing.volces.com + apiKey: ${VOLCENGINE_ARK_API_KEY:sk-XXXXXXXXXXXXXXXX} + callbackUrl: https://undressing.top/api/ai/volcCallback + jinsha: url: https://api.jinshapay.xyz appId: 1763617360 diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/TencentCosUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/TencentCosUtil.java index dde4e7d..743ae46 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/TencentCosUtil.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/TencentCosUtil.java @@ -13,7 +13,13 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.UUID; @Component @@ -33,22 +39,32 @@ public class TencentCosUtil { private String domain; - //文件上传 + /** + * 上传MultipartFile到腾讯云COS,返回文件访问地址 + * 与AwsS3Util.uploadMultipartFile方法接口兼容 + */ public String upload(MultipartFile file) { + return uploadMultipartFile(file, true); + } + + /** + * 上传MultipartFile到腾讯云COS,返回文件访问地址 + * + * @param file 前端上传的MultipartFile + * @param isPublic 是否公开访问(当前实现中忽略,使用domain配置) + * @return 文件访问地址(URL字符串) + */ + public String uploadMultipartFile(MultipartFile file, boolean isPublic) throws Exception { + if (file.isEmpty()) { + throw new IllegalArgumentException("上传文件不能为空"); + } - // 3 生成 cos 客户端。 COSClient cosClient = createCosClient(); - // 存储桶的命名格式为 BucketName-APPID,此处填写的存储桶名称必须为此格式 - // 对象键(Key)是对象在存储桶中的唯一标识。 998u-09iu-09i-333 - //在文件名称前面添加uuid值 - String key = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8) + "_" - + file.getOriginalFilename(); - //对上传文件分组,根据当前日期 /2022/11/11 - String dateTime = new DateTime().toString("yyyy/MM/dd"); - key = dateTime + "/" + key; + // 生成唯一文件键,格式与AWS一致:yyyy/MM/dd/uuid_filename + String key = generateCosKey(file.getOriginalFilename()); + try { - //获取上传文件输入流 InputStream inputStream = file.getInputStream(); ObjectMetadata objectMetadata = new ObjectMetadata(); PutObjectRequest putObjectRequest = new PutObjectRequest( @@ -56,17 +72,41 @@ public class TencentCosUtil { key, inputStream, objectMetadata); - // 高级接口会返回一个异步结果Upload + PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest); - //返回上传文件路径 - //https://ggkt-atguigu-1310644373.cos.ap-beijing.myqcloud.com/01.jpg - String url = domain + "/" + key; + // 返回COS文件访问地址 + String url = domain + (domain.endsWith("/") ? "" : "/") + key; return url; } catch (Exception e) { e.printStackTrace(); + throw new RuntimeException("上传文件到COS失败: " + e.getMessage(), e); + } finally { + cosClient.shutdown(); + } + } + + /** + * 通过URL下载文件并上传到COS + * 与AwsS3Util.uploadFileByUrl方法接口兼容 + */ + public String uploadFileByUrl(String fileUrl) throws Exception { + return uploadFileByUrl(fileUrl, true); + } + + public String uploadFileByUrl(String fileUrl, boolean isPublic) throws Exception { + if (fileUrl == null || fileUrl.trim().isEmpty()) { + throw new IllegalArgumentException("文件下载链接不能为空"); + } + + Path tempPath = downloadFileToTemp(fileUrl); + try { + // 使用临时文件上传 + MultipartFile multipartFile = createMultipartFileFromPath(tempPath, extractFileNameFromUrl(fileUrl)); + return uploadMultipartFile(multipartFile, isPublic); + } finally { + Files.deleteIfExists(tempPath); } - return null; } @@ -78,7 +118,107 @@ public class TencentCosUtil { ClientConfig clientConfig = new ClientConfig(region); // 这里建议设置使用 https 协议 clientConfig.setHttpProtocol(HttpProtocol.https); + clientConfig.setConnectionTimeout(30 * 1000); // 连接超时30秒 + clientConfig.setSocketTimeout(60 * 1000); // 读取超时60秒 //1.3 生成cos客户端 return new COSClient(credentials, clientConfig); } + + /** + * 生成COS文件键,与AWS保持一致的命名格式 + */ + private String generateCosKey(String originalFileName) { + String uuid = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8); + String dateTime = new DateTime().toString("yyyy/MM/dd"); + return dateTime + "/" + uuid + "_" + originalFileName; + } + + /** + * 下载文件到临时路径 + */ + private Path downloadFileToTemp(String fileUrl) throws Exception { + String suffix = getFileSuffixFromUrl(fileUrl); + Path tempPath = Files.createTempFile("url-upload-", suffix); + + HttpURLConnection connection = null; + InputStream in = null; + OutputStream out = null; + + try { + URL url = new URL(fileUrl); + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(10000); + + int responseCode = connection.getResponseCode(); + if (responseCode < 200 || responseCode >= 300) { + throw new RuntimeException("文件下载失败,状态码:" + responseCode); + } + + in = connection.getInputStream(); + out = Files.newOutputStream(tempPath); + + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + } finally { + if (out != null) { + try { out.close(); } catch (IOException e) { e.printStackTrace(); } + } + if (in != null) { + try { in.close(); } catch (IOException e) { e.printStackTrace(); } + } + if (connection != null) { + connection.disconnect(); + } + } + + return tempPath; + } + + private String extractFileNameFromUrl(String fileUrl) { + String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1); + if (fileName.contains("?")) { + fileName = fileName.split("\\?")[0]; + } + return fileName.isEmpty() ? "default_file" : fileName; + } + + private String getFileSuffixFromUrl(String fileUrl) { + String fileName = extractFileNameFromUrl(fileUrl); + if (fileName.contains(".")) { + return fileName.substring(fileName.lastIndexOf(".")); + } + return ".tmp"; + } + + /** + * 将Path转换为MultipartFile(简单实现,用于uploadFileByUrl) + */ + private MultipartFile createMultipartFileFromPath(Path path, String originalFilename) throws IOException { + byte[] bytes = Files.readAllBytes(path); + return new MultipartFile() { + @Override + public String getName() { return "file"; } + @Override + public String getOriginalFilename() { return originalFilename; } + @Override + public String getContentType() { return null; } + @Override + public boolean isEmpty() { return bytes.length == 0; } + @Override + public long getSize() { return bytes.length; } + @Override + public byte[] getBytes() throws IOException { return bytes; } + @Override + public InputStream getInputStream() throws IOException { return Files.newInputStream(path); } + @Override + public void transferTo(java.io.File dest) throws IOException, IllegalStateException { + Files.copy(path, dest.toPath()); + } + }; + } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/ai/domain/ByteBodyReq.java b/ruoyi-system/src/main/java/com/ruoyi/ai/domain/ByteBodyReq.java index 63d46fa..1d1a320 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/ai/domain/ByteBodyReq.java +++ b/ruoyi-system/src/main/java/com/ruoyi/ai/domain/ByteBodyReq.java @@ -30,5 +30,10 @@ public class ByteBodyReq { @JsonProperty("content") private List content; + private Integer duration; + private String resolution; + private String ratio; + private Integer seed; + } diff --git a/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/ByteService.java b/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/ByteService.java index cc315b8..dbf3c95 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/ByteService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/ByteService.java @@ -30,6 +30,13 @@ public class ByteService implements IByteService { @Value("${byteapi.apiKey}") private String apiKey; + // 火山引擎配置 + @Value("${volcengine.ark.baseUrl:https://ark.cn-beijing.volces.com}") + private String volcBaseUrl; + + @Value("${volcengine.ark.apiKey}") + private String volcApiKey; + @Override public ByteBodyRes promptToImg(ByteBodyReq req) throws Exception { return this.imgToImg(req); @@ -75,70 +82,63 @@ public class ByteService implements IByteService { @Override public ByteBodyRes imgToVideo(ByteBodyReq req) throws Exception { + if (req == null) { + throw new Exception("imgToVideo error:req is null"); + } - // 1. 验证请求参数(可选,根据业务需求) -// if (StringUtils.isBlank(req.getPrompt())) { -// throw new Exception("imgToVideo error:prompt is null"); -// } - - // 2. 构建请求体JSON(基于ByteBodyReq的字段) - // 注意:ByteBodyReq需包含与API参数对应的字段(model、prompt等) + // 使用火山引擎配置 String jsonBody = objectMapper.writeValueAsString(req); - // 3. 构建请求 + Request request = new Request.Builder() - .url(API_URL + "/contents/generations/tasks") + .url(volcBaseUrl + "/api/v3/contents/generations/tasks") .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + apiKey) + .header("Authorization", "Bearer " + volcApiKey) .post(RequestBody.create( MediaType.parse("application/json; charset=utf-8"), jsonBody )) .build(); - // 4. 发送同步请求(因方法需要返回值,使用execute而非enqueue) + Response response = OkHttpUtils.newCall(request).execute(); - // 5. 处理响应 + if (!response.isSuccessful()) { - // 非200状态:返回错误信息(假设ByteBodyRes有error字段) String errorMsg = response.body() != null ? response.body().string() : "imgToVideo error"; throw new Exception("imgToVideo error:" + errorMsg); } - // 6. 解析成功响应为ByteBodyRes + if (response.body() == null) { throw new Exception("imgToVideo response null"); } + String responseBody = response.body().string(); return objectMapper.readValue(responseBody, ByteBodyRes.class); } @Override public ByteBodyRes uploadVideo(String id) throws Exception { - // 1. 验证请求参数(可选,根据业务需求) if (StringUtils.isBlank(id)) { throw new Exception("uploadVideo error:id is null"); } - // 2. 构建请求体JSON(基于ByteBodyReq的字段) - // 注意:ByteBodyReq需包含与API参数对应的字段(model、prompt等) - //String jsonBody = objectMapper.writeValueAsString(req); - // 3. 构建请求 + // 使用火山引擎配置查询任务状态 Request request = new Request.Builder() - .url(API_URL + "/contents/generations/tasks/" + id) + .url(volcBaseUrl + "/api/v3/contents/generations/tasks/" + id) .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + apiKey) + .header("Authorization", "Bearer " + volcApiKey) .get() .build(); - // 4. 发送同步请求(因方法需要返回值,使用execute而非enqueue) + Response response = OkHttpUtils.newCall(request).execute(); - // 5. 处理响应 + if (!response.isSuccessful()) { - // 非200状态:返回错误信息(假设ByteBodyRes有error字段) String errorMsg = response.body() != null ? response.body().string() : "uploadVideo error"; throw new Exception("uploadVideo error:" + errorMsg); } - // 6. 解析成功响应为ByteBodyRes + if (response.body() == null) { throw new Exception("uploadVideo response null"); } + String responseBody = response.body().string(); return objectMapper.readValue(responseBody, ByteBodyRes.class); }