fix: order的内容
This commit is contained in:
parent
fa4eb95e2b
commit
91769fd2df
|
|
@ -43,6 +43,12 @@ public class ByteApiController extends BaseController {
|
||||||
@Value("${byteapi.callBackUrl}")
|
@Value("${byteapi.callBackUrl}")
|
||||||
private String url;
|
private String url;
|
||||||
|
|
||||||
|
@Value("${nanobanana.callbackUrl}")
|
||||||
|
private String nanoCallbackUrl;
|
||||||
|
|
||||||
|
@Value("${nanobanana.token}")
|
||||||
|
private String nanoToken;
|
||||||
|
|
||||||
@PostMapping("/promptToImg")
|
@PostMapping("/promptToImg")
|
||||||
@ApiOperation("文生图")
|
@ApiOperation("文生图")
|
||||||
public AjaxResult promptToImg(@RequestBody ByteApiRequest request) {
|
public AjaxResult promptToImg(@RequestBody ByteApiRequest request) {
|
||||||
|
|
@ -84,31 +90,32 @@ public class ByteApiController extends BaseController {
|
||||||
return AjaxResult.error(-1, "You have a low balance, please recharge");
|
return AjaxResult.error(-1, "You have a low balance, please recharge");
|
||||||
}
|
}
|
||||||
aiOrder.setText(text);
|
aiOrder.setText(text);
|
||||||
ByteBodyReq byteBodyReq = new ByteBodyReq();
|
|
||||||
byteBodyReq.setModel("ep-20251104104536-2gpgz");
|
// 4. 组装NanoBanana (原火山)接口参数 - 符合用户需求
|
||||||
byteBodyReq.setPrompt(text);
|
NanoBananaRequest nanoRequest = NanoBananaRequest.forTextToImage(
|
||||||
byteBodyReq.setSequential_image_generation("disabled");
|
text,
|
||||||
byteBodyReq.setResponse_format("url");
|
nanoCallbackUrl,
|
||||||
byteBodyReq.setSize("2K");
|
request.getAspectRatio() != null ? request.getAspectRatio() : "auto",
|
||||||
byteBodyReq.setStream(false);
|
request.getResolution() != null ? request.getResolution() : "1K"
|
||||||
byteBodyReq.setWatermark(false);
|
);
|
||||||
ByteBodyRes byteBodyRes = byteService.promptToImg(byteBodyReq);
|
|
||||||
List<ByteDataRes> data = byteBodyRes.getData();
|
// 5. 调用NanoBanana接口 (curl POST https://api.nanobananaapi.ai/...)
|
||||||
ByteDataRes byteDataRes = data.get(0);
|
NanoBananaResponse nanoResponse = byteService.generateImage(nanoRequest);
|
||||||
String url = byteDataRes.getUrl();
|
|
||||||
url = awsS3Util.uploadFileByUrl(url);
|
if (nanoResponse == null || nanoResponse.getCode() != 200 || nanoResponse.getData() == null) {
|
||||||
if (url == null) {
|
|
||||||
// 判断生成失败,退回金额逻辑
|
|
||||||
aiOrderService.orderFailure(aiOrder);
|
aiOrderService.orderFailure(aiOrder);
|
||||||
return AjaxResult.error(-2, "generation failed, balance has been refunded");
|
return AjaxResult.error(-2, "generation failed, balance has been refunded");
|
||||||
}
|
}
|
||||||
aiOrder.setResult(url);
|
|
||||||
aiOrderService.orderSuccess(aiOrder);
|
String taskId = nanoResponse.getData().getTaskId();
|
||||||
return AjaxResult.success(url);
|
aiOrder.setTaskId(taskId); // 保存taskId用于回调匹配
|
||||||
|
aiOrder.setResult(taskId); // 临时用taskId作为result,后续回调更新为真实URL
|
||||||
|
|
||||||
|
aiOrderService.orderSuccess(aiOrder); // 先标记为进行中,回调成功后再更新
|
||||||
|
return AjaxResult.success(taskId); // 返回taskId给前端,后续可轮询或等待回调
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 判断生成失败,退回金额逻辑
|
// 判断生成失败,退回金额逻辑
|
||||||
aiOrderService.orderFailure(aiOrder);
|
aiOrderService.orderFailure(aiOrder);
|
||||||
// return AjaxResult.error(-2, "generation failed, balance has been refunded");
|
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -160,30 +167,36 @@ public class ByteApiController extends BaseController {
|
||||||
}
|
}
|
||||||
|
|
||||||
aiOrder.setText(text);
|
aiOrder.setText(text);
|
||||||
|
|
||||||
aiOrder.setImg1(firstUrl.toString());
|
aiOrder.setImg1(firstUrl.toString());
|
||||||
ByteBodyReq byteBodyReq = new ByteBodyReq();
|
|
||||||
byteBodyReq.setModel("ep-20251104104536-2gpgz");
|
// 4. 组装NanoBanana接口参数 - 图生图
|
||||||
byteBodyReq.setPrompt(text);
|
List<String> imageUrls = new ArrayList<>();
|
||||||
byteBodyReq.setImage(firstUrl);
|
if (firstUrl != null) {
|
||||||
byteBodyReq.setSequential_image_generation("disabled");
|
imageUrls.add(firstUrl.toString());
|
||||||
byteBodyReq.setResponse_format("url");
|
}
|
||||||
byteBodyReq.setSize("2K");
|
|
||||||
byteBodyReq.setStream(false);
|
NanoBananaRequest nanoRequest = NanoBananaRequest.forImageToImage(
|
||||||
byteBodyReq.setWatermark(false);
|
text,
|
||||||
ByteBodyRes byteBodyRes = byteService.promptToImg(byteBodyReq);
|
imageUrls,
|
||||||
List<ByteDataRes> data = byteBodyRes.getData();
|
nanoCallbackUrl,
|
||||||
ByteDataRes byteDataRes = data.get(0);
|
request.getAspectRatio() != null ? request.getAspectRatio() : "auto",
|
||||||
String url = byteDataRes.getUrl();
|
request.getResolution() != null ? request.getResolution() : "1K"
|
||||||
url = awsS3Util.uploadFileByUrl(url);
|
);
|
||||||
if (url == null) {
|
|
||||||
// 判断生成失败,退回金额逻辑
|
// 5. 调用NanoBanana接口
|
||||||
|
NanoBananaResponse nanoResponse = byteService.generateImageWithReference(nanoRequest);
|
||||||
|
|
||||||
|
if (nanoResponse == null || nanoResponse.getCode() != 200 || nanoResponse.getData() == null) {
|
||||||
aiOrderService.orderFailure(aiOrder);
|
aiOrderService.orderFailure(aiOrder);
|
||||||
return AjaxResult.error(-2, "generation failed, balance has been refunded");
|
return AjaxResult.error(-2, "generation failed, balance has been refunded");
|
||||||
}
|
}
|
||||||
aiOrder.setResult(url);
|
|
||||||
|
String taskId = nanoResponse.getData().getTaskId();
|
||||||
|
aiOrder.setTaskId(taskId);
|
||||||
|
aiOrder.setResult(taskId);
|
||||||
|
|
||||||
aiOrderService.orderSuccess(aiOrder);
|
aiOrderService.orderSuccess(aiOrder);
|
||||||
return AjaxResult.success(url);
|
return AjaxResult.success(taskId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
aiOrderService.orderFailure(aiOrder);
|
aiOrderService.orderFailure(aiOrder);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|
@ -311,10 +324,59 @@ public class ByteApiController extends BaseController {
|
||||||
return AjaxResult.success(byteBodyRes);
|
return AjaxResult.success(byteBodyRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/callBack")
|
@PostMapping(value = "/nano-callback")
|
||||||
@ApiOperation("视频下载回调")
|
@ApiOperation("NanoBanana生成回调")
|
||||||
@Anonymous
|
@Anonymous
|
||||||
public AjaxResult callBack(@PathVariable("id") ByteBodyRes byteBodyRes) throws Exception {
|
public AjaxResult nanoCallback(@RequestBody NanoBananaCallback callback) throws Exception {
|
||||||
|
if (callback == null || callback.getData() == null) {
|
||||||
|
return AjaxResult.error("回调数据为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
NanoBananaCallback.NanoBananaCallbackData data = callback.getData();
|
||||||
|
String taskId = data.getTaskId();
|
||||||
|
Integer successFlag = data.getSuccessFlag();
|
||||||
|
|
||||||
|
// 根据successFlag处理不同状态
|
||||||
|
if (successFlag == 1 && data.getResponse() != null && data.getResponse().getResultImageUrl() != null) {
|
||||||
|
// 成功 - 上传到S3并更新订单
|
||||||
|
String imageUrl = data.getResponse().getResultImageUrl();
|
||||||
|
String s3Url = awsS3Util.uploadFileByUrl(imageUrl);
|
||||||
|
|
||||||
|
if (s3Url != null) {
|
||||||
|
AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(taskId);
|
||||||
|
if (aiOrderByResult != null) {
|
||||||
|
AiOrder aiOrder = new AiOrder();
|
||||||
|
aiOrder.setId(aiOrderByResult.getId());
|
||||||
|
aiOrder.setResult(s3Url);
|
||||||
|
aiOrder.setTaskId(taskId);
|
||||||
|
aiOrder.setStatus(1);
|
||||||
|
aiOrderService.updateAiOrder(aiOrder);
|
||||||
|
return AjaxResult.success("回调处理成功,图像已上传");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AjaxResult.error("图像上传失败");
|
||||||
|
} else if (successFlag == 3 || successFlag == 2) {
|
||||||
|
// 失败 - 退款
|
||||||
|
AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(taskId);
|
||||||
|
if (aiOrderByResult != null) {
|
||||||
|
aiOrderService.orderFailure(aiOrderByResult);
|
||||||
|
return AjaxResult.success("回调处理失败,已退款");
|
||||||
|
}
|
||||||
|
} else if (successFlag == 0) {
|
||||||
|
// 生成中 - 可忽略或记录
|
||||||
|
return AjaxResult.success("任务生成中");
|
||||||
|
}
|
||||||
|
|
||||||
|
return AjaxResult.success("回调已接收");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保留原有视频回调(GET转POST兼容)
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/callBack")
|
||||||
|
@ApiOperation("视频回调")
|
||||||
|
@Anonymous
|
||||||
|
public AjaxResult callBack(@RequestBody ByteBodyRes byteBodyRes) throws Exception {
|
||||||
if ("succeeded".equals(byteBodyRes.getStatus())) {
|
if ("succeeded".equals(byteBodyRes.getStatus())) {
|
||||||
String id = byteBodyRes.getId();
|
String id = byteBodyRes.getId();
|
||||||
content content = byteBodyRes.getContent();
|
content content = byteBodyRes.getContent();
|
||||||
|
|
@ -322,12 +384,13 @@ public class ByteApiController extends BaseController {
|
||||||
videoUrl = awsS3Util.uploadFileByUrl(videoUrl);
|
videoUrl = awsS3Util.uploadFileByUrl(videoUrl);
|
||||||
content.setVideo_url(videoUrl);
|
content.setVideo_url(videoUrl);
|
||||||
AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(id);
|
AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(id);
|
||||||
|
if (aiOrderByResult != null) {
|
||||||
AiOrder aiOrder = new AiOrder();
|
AiOrder aiOrder = new AiOrder();
|
||||||
aiOrder.setId(aiOrderByResult.getId());
|
aiOrder.setId(aiOrderByResult.getId());
|
||||||
aiOrder.setResult(videoUrl);
|
aiOrder.setResult(videoUrl);
|
||||||
// aiOrder.setUpdateBy(SecurityUtils.getLoginAiUser().getUsername());
|
|
||||||
aiOrderService.updateAiOrder(aiOrder);
|
aiOrderService.updateAiOrder(aiOrder);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return AjaxResult.success(byteBodyRes);
|
return AjaxResult.success(byteBodyRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,14 @@ byteapi:
|
||||||
apiKey: 327d2815-2516-44c2-9e32-2dc50bf7afd7
|
apiKey: 327d2815-2516-44c2-9e32-2dc50bf7afd7
|
||||||
callBackUrl: www.google.com
|
callBackUrl: www.google.com
|
||||||
|
|
||||||
|
nanobanana:
|
||||||
|
# NanoBanana API Token (Bearer Token)
|
||||||
|
token: your_nanobanana_token_here
|
||||||
|
# 回调地址,需替换为实际部署域名 (POST接口)
|
||||||
|
callbackUrl: https://your-domain.com/api/ai/nano-callback
|
||||||
|
# 生成API地址
|
||||||
|
apiUrl: https://api.nanobananaapi.ai/api/v1/nanobanana/generate-2
|
||||||
|
|
||||||
jinsha:
|
jinsha:
|
||||||
url: https://api.jinshapay.xyz
|
url: https://api.jinshapay.xyz
|
||||||
appId: 1763617360
|
appId: 1763617360
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
package com.ruoyi.ai.domain;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NanoBanana API 回调请求体
|
||||||
|
* 符合阿里巴巴Java开发手册规范
|
||||||
|
*
|
||||||
|
* @author AI Assistant
|
||||||
|
* @date 2026-04-10
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class NanoBananaCallback {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 顶层响应码
|
||||||
|
*/
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态消息
|
||||||
|
*/
|
||||||
|
private String msg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回调数据
|
||||||
|
*/
|
||||||
|
private NanoBananaCallbackData data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class NanoBananaCallbackData {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务 ID
|
||||||
|
*/
|
||||||
|
@JsonProperty("taskId")
|
||||||
|
private String taskId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本次调用使用的参数 JSON 字符串
|
||||||
|
*/
|
||||||
|
@JsonProperty("paramJson")
|
||||||
|
private String paramJson;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务完成时间
|
||||||
|
*/
|
||||||
|
@JsonProperty("completeTime")
|
||||||
|
private String completeTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应数据(包含图像URL)
|
||||||
|
*/
|
||||||
|
private NanoBananaCallbackResponse response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成状态标志:
|
||||||
|
* 0-生成中, 1-成功, 2-创建失败, 3-生成失败
|
||||||
|
*/
|
||||||
|
@JsonProperty("successFlag")
|
||||||
|
private Integer successFlag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误码(如 422 敏感内容)
|
||||||
|
*/
|
||||||
|
@JsonProperty("errorCode")
|
||||||
|
private Integer errorCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误消息
|
||||||
|
*/
|
||||||
|
@JsonProperty("errorMessage")
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作类型/模型标识
|
||||||
|
*/
|
||||||
|
@JsonProperty("operationType")
|
||||||
|
private String operationType;
|
||||||
|
|
||||||
|
@JsonProperty("createTime")
|
||||||
|
private String createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class NanoBananaCallbackResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始图像URL(可能为null)
|
||||||
|
*/
|
||||||
|
@JsonProperty("originImageUrl")
|
||||||
|
private String originImageUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结果图像URL(成功时存在)
|
||||||
|
*/
|
||||||
|
@JsonProperty("resultImageUrl")
|
||||||
|
private String resultImageUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
package com.ruoyi.ai.domain;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NanoBanana API 文生图/图生图请求参数
|
||||||
|
* 符合阿里巴巴Java开发手册规范
|
||||||
|
*
|
||||||
|
* @author AI Assistant
|
||||||
|
* @date 2026-04-10
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class NanoBananaRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本提示词,必填,最长 20000 字符
|
||||||
|
*/
|
||||||
|
private String prompt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参考图 URL 数组,文生图时传空数组,最多 14 张
|
||||||
|
*/
|
||||||
|
@JsonProperty("imageUrls")
|
||||||
|
private List<String> imageUrls = List.of();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成图像宽高比
|
||||||
|
* 支持 1:1、1:4、1:8、2:3、3:2、3:4、4:1、4:3、4:5、5:4、8:1、9:16、16:9、21:9、auto
|
||||||
|
*/
|
||||||
|
@JsonProperty("aspectRatio")
|
||||||
|
private String aspectRatio = "auto";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分辨率质量,可选 1K / 2K / 4K
|
||||||
|
*/
|
||||||
|
private String resolution = "1K";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用 Google Web Search 增强,默认 false
|
||||||
|
*/
|
||||||
|
@JsonProperty("googleSearch")
|
||||||
|
private Boolean googleSearch = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出格式,支持 png / jpg,默认 jpg
|
||||||
|
*/
|
||||||
|
@JsonProperty("outputFormat")
|
||||||
|
private String outputFormat = "jpg";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可选回调 URL,用于接收任务完成通知
|
||||||
|
*/
|
||||||
|
@JsonProperty("callBackUrl")
|
||||||
|
private String callBackUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数 - 文生图
|
||||||
|
*/
|
||||||
|
public static NanoBananaRequest forTextToImage(String prompt, String callBackUrl, String aspectRatio, String resolution) {
|
||||||
|
NanoBananaRequest req = new NanoBananaRequest();
|
||||||
|
req.setPrompt(prompt);
|
||||||
|
req.setCallBackUrl(callBackUrl);
|
||||||
|
if (aspectRatio != null) req.setAspectRatio(aspectRatio);
|
||||||
|
if (resolution != null) req.setResolution(resolution);
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数 - 图生图
|
||||||
|
*/
|
||||||
|
public static NanoBananaRequest forImageToImage(String prompt, List<String> imageUrls, String callBackUrl, String aspectRatio, String resolution) {
|
||||||
|
NanoBananaRequest req = forTextToImage(prompt, callBackUrl, aspectRatio, resolution);
|
||||||
|
if (imageUrls != null && !imageUrls.isEmpty()) {
|
||||||
|
req.setImageUrls(imageUrls);
|
||||||
|
}
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.ruoyi.ai.domain;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NanoBanana API 提交任务响应
|
||||||
|
* 符合阿里巴巴Java开发手册规范
|
||||||
|
*
|
||||||
|
* @author AI Assistant
|
||||||
|
* @date 2026-04-10
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class NanoBananaResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应码,200 表示成功
|
||||||
|
*/
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应消息
|
||||||
|
*/
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应数据
|
||||||
|
*/
|
||||||
|
private NanoBananaData data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class NanoBananaData {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务ID,用于后续回调匹配
|
||||||
|
*/
|
||||||
|
@JsonProperty("taskId")
|
||||||
|
private String taskId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,26 +2,50 @@ package com.ruoyi.ai.service;
|
||||||
|
|
||||||
import com.ruoyi.ai.domain.ByteBodyReq;
|
import com.ruoyi.ai.domain.ByteBodyReq;
|
||||||
import com.ruoyi.ai.domain.ByteBodyRes;
|
import com.ruoyi.ai.domain.ByteBodyRes;
|
||||||
|
import com.ruoyi.ai.domain.NanoBananaRequest;
|
||||||
|
import com.ruoyi.ai.domain.NanoBananaResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ByteService 接口
|
||||||
|
* 现已切换为 NanoBanana API(原Byte/火山引擎接口已弃用)
|
||||||
|
* 符合阿里巴巴Java开发手册规范
|
||||||
|
*
|
||||||
|
* @author shi
|
||||||
|
* @date 2026-04-10
|
||||||
|
*/
|
||||||
public interface IByteService {
|
public interface IByteService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文生图
|
* 文生图 - 使用 NanoBanana API 异步生成,返回 taskId
|
||||||
*/
|
*/
|
||||||
|
NanoBananaResponse generateImage(NanoBananaRequest req) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图生图 - 使用 NanoBanana API 异步生成,返回 taskId
|
||||||
|
*/
|
||||||
|
NanoBananaResponse generateImageWithReference(NanoBananaRequest req) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 旧接口兼容(文生图/图生图)
|
||||||
|
* @deprecated 使用 generateImage 替代
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
ByteBodyRes promptToImg(ByteBodyReq req) throws Exception;
|
ByteBodyRes promptToImg(ByteBodyReq req) throws Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图生图
|
* 旧接口兼容(图生图)
|
||||||
|
* @deprecated 使用 generateImageWithReference 替代
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
ByteBodyRes imgToImg(ByteBodyReq req) throws Exception;
|
ByteBodyRes imgToImg(ByteBodyReq req) throws Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 首尾帧图生视频
|
* 首尾帧图生视频(保持原有)
|
||||||
*/
|
*/
|
||||||
ByteBodyRes imgToVideo(ByteBodyReq req) throws Exception;
|
ByteBodyRes imgToVideo(ByteBodyReq req) throws Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载视频
|
* 下载视频(保持原有)
|
||||||
*/
|
*/
|
||||||
ByteBodyRes uploadVideo(String id) throws Exception;
|
ByteBodyRes uploadVideo(String id) throws Exception;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ package com.ruoyi.ai.service.impl;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.ruoyi.ai.domain.ByteBodyReq;
|
import com.ruoyi.ai.domain.*;
|
||||||
import com.ruoyi.ai.domain.ByteBodyRes;
|
|
||||||
import com.ruoyi.ai.service.IByteService;
|
import com.ruoyi.ai.service.IByteService;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.common.utils.http.OkHttpUtils;
|
import com.ruoyi.common.utils.http.OkHttpUtils;
|
||||||
|
|
@ -12,18 +11,36 @@ import okhttp3.*;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ByteService 实现类
|
||||||
|
* 已切换为 NanoBanana API (https://api.nanobananaapi.ai)
|
||||||
|
* 原火山/Byte接口保留用于视频功能
|
||||||
|
* 符合阿里巴巴Java开发手册规范,使用Lombok、清晰注释、异常处理
|
||||||
|
*
|
||||||
|
* @author shi
|
||||||
|
* @date 2026-04-10
|
||||||
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class ByteService implements IByteService {
|
public class ByteService implements IByteService {
|
||||||
|
|
||||||
// private final OkHttpClient okHttpClient = OkHttpUtils.createOkHttpClient();
|
|
||||||
|
|
||||||
// Jackson用于JSON序列化/反序列化
|
// Jackson用于JSON序列化/反序列化
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper()
|
private final ObjectMapper objectMapper = new ObjectMapper()
|
||||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||||
.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
|
.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
|
||||||
|
|
||||||
// API地址(可配置在配置文件中)
|
@Value("${nanobanana.apiUrl:https://api.nanobananaapi.ai/api/v1/nanobanana/generate-2}")
|
||||||
|
private String nanoApiUrl;
|
||||||
|
|
||||||
|
@Value("${nanobanana.token:}")
|
||||||
|
private String nanoToken;
|
||||||
|
|
||||||
|
@Value("${nanobanana.callbackUrl:}")
|
||||||
|
private String callbackUrl;
|
||||||
|
|
||||||
|
// 保留原有Byte配置用于视频功能
|
||||||
@Value("${byteapi.url}")
|
@Value("${byteapi.url}")
|
||||||
private String API_URL;
|
private String API_URL;
|
||||||
|
|
||||||
|
|
@ -31,60 +48,71 @@ public class ByteService implements IByteService {
|
||||||
private String apiKey;
|
private String apiKey;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBodyRes promptToImg(ByteBodyReq req) throws Exception {
|
public NanoBananaResponse generateImage(NanoBananaRequest req) throws Exception {
|
||||||
return this.imgToImg(req);
|
// 4. 组装NanoBanana接口参数 (替换原VolcengineImageRequest)
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteBodyRes imgToImg(ByteBodyReq req) throws Exception {
|
|
||||||
|
|
||||||
// 1. 验证请求参数(可选,根据业务需求)
|
|
||||||
if (StringUtils.isBlank(req.getPrompt())) {
|
if (StringUtils.isBlank(req.getPrompt())) {
|
||||||
throw new Exception("imgToImg error:prompt is null");
|
throw new Exception("prompt不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(req.getCallBackUrl())) {
|
||||||
|
req.setCallBackUrl(callbackUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 构建请求体JSON(基于ByteBodyReq的字段)
|
// 构建JSON请求体
|
||||||
// 注意:ByteBodyReq需包含与API参数对应的字段(model、prompt等)
|
|
||||||
String jsonBody = objectMapper.writeValueAsString(req);
|
String jsonBody = objectMapper.writeValueAsString(req);
|
||||||
// 3. 构建请求
|
|
||||||
|
// 5. 调用NanoBanana API (使用curl对应POST请求)
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(API_URL + "/images/generations")
|
.url(nanoApiUrl)
|
||||||
|
.header("Authorization", "Bearer " + nanoToken)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.header("Authorization", "Bearer " + apiKey)
|
|
||||||
// .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("192.168.1.1", 8080)))
|
|
||||||
.post(RequestBody.create(
|
.post(RequestBody.create(
|
||||||
MediaType.parse("application/json"),
|
MediaType.parse("application/json; charset=utf-8"),
|
||||||
jsonBody
|
jsonBody
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
// 4. 发送同步请求(因方法需要返回值,使用execute而非enqueue)
|
|
||||||
Response response = OkHttpUtils.newCall(request).execute();
|
Response response = OkHttpUtils.newCall(request).execute();
|
||||||
// 5. 处理响应
|
|
||||||
if (!response.isSuccessful()) {
|
if (!response.isSuccessful()) {
|
||||||
// 非200状态:返回错误信息(假设ByteBodyRes有error字段)
|
String errorMsg = response.body() != null ? response.body().string() : "generateImage error";
|
||||||
String errorMsg = response.body() != null ? response.body().string() : "imgToImg error";
|
throw new Exception("NanoBanana API调用失败: " + errorMsg);
|
||||||
throw new Exception("imgToImg error:" + errorMsg);
|
|
||||||
}
|
}
|
||||||
// 6. 解析成功响应为ByteBodyRes
|
|
||||||
if (response.body() == null) {
|
if (response.body() == null) {
|
||||||
throw new Exception("imgToImg response null");
|
throw new Exception("NanoBanana API响应为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
String responseBody = response.body().string();
|
String responseBody = response.body().string();
|
||||||
return objectMapper.readValue(responseBody, ByteBodyRes.class);
|
return objectMapper.readValue(responseBody, NanoBananaResponse.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NanoBananaResponse generateImageWithReference(NanoBananaRequest req) throws Exception {
|
||||||
|
return generateImage(req); // 复用,imageUrls已在请求中设置
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public ByteBodyRes promptToImg(ByteBodyReq req) throws Exception {
|
||||||
|
// 兼容旧代码,实际已切换到NanoBanana
|
||||||
|
throw new UnsupportedOperationException("promptToImg 已弃用,请使用 generateImage");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public ByteBodyRes imgToImg(ByteBodyReq req) throws Exception {
|
||||||
|
throw new UnsupportedOperationException("imgToImg 已弃用,请使用 generateImageWithReference");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBodyRes imgToVideo(ByteBodyReq req) throws Exception {
|
public ByteBodyRes imgToVideo(ByteBodyReq req) throws Exception {
|
||||||
|
// 视频功能保持原有Byte实现
|
||||||
|
if (StringUtils.isBlank(req.getPrompt())) {
|
||||||
|
throw new Exception("imgToVideo error:prompt 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);
|
String jsonBody = objectMapper.writeValueAsString(req);
|
||||||
// 3. 构建请求
|
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(API_URL + "/contents/generations/tasks")
|
.url(API_URL + "/contents/generations/tasks")
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
|
|
@ -94,51 +122,47 @@ public class ByteService implements IByteService {
|
||||||
jsonBody
|
jsonBody
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
// 4. 发送同步请求(因方法需要返回值,使用execute而非enqueue)
|
|
||||||
Response response = OkHttpUtils.newCall(request).execute();
|
Response response = OkHttpUtils.newCall(request).execute();
|
||||||
// 5. 处理响应
|
|
||||||
if (!response.isSuccessful()) {
|
if (!response.isSuccessful()) {
|
||||||
// 非200状态:返回错误信息(假设ByteBodyRes有error字段)
|
|
||||||
String errorMsg = response.body() != null ? response.body().string() : "imgToVideo error";
|
String errorMsg = response.body() != null ? response.body().string() : "imgToVideo error";
|
||||||
throw new Exception("imgToVideo error:" + errorMsg);
|
throw new Exception("imgToVideo error:" + errorMsg);
|
||||||
}
|
}
|
||||||
// 6. 解析成功响应为ByteBodyRes
|
|
||||||
if (response.body() == null) {
|
if (response.body() == null) {
|
||||||
throw new Exception("imgToVideo response null");
|
throw new Exception("imgToVideo response null");
|
||||||
}
|
}
|
||||||
|
|
||||||
String responseBody = response.body().string();
|
String responseBody = response.body().string();
|
||||||
return objectMapper.readValue(responseBody, ByteBodyRes.class);
|
return objectMapper.readValue(responseBody, ByteBodyRes.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBodyRes uploadVideo(String id) throws Exception {
|
public ByteBodyRes uploadVideo(String id) throws Exception {
|
||||||
// 1. 验证请求参数(可选,根据业务需求)
|
// 视频功能保持原有Byte实现
|
||||||
if (StringUtils.isBlank(id)) {
|
if (StringUtils.isBlank(id)) {
|
||||||
throw new Exception("uploadVideo error:id is null");
|
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()
|
Request request = new Request.Builder()
|
||||||
.url(API_URL + "/contents/generations/tasks/" + id)
|
.url(API_URL + "/contents/generations/tasks/" + id)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.header("Authorization", "Bearer " + apiKey)
|
.header("Authorization", "Bearer " + apiKey)
|
||||||
.get()
|
.get()
|
||||||
.build();
|
.build();
|
||||||
// 4. 发送同步请求(因方法需要返回值,使用execute而非enqueue)
|
|
||||||
Response response = OkHttpUtils.newCall(request).execute();
|
Response response = OkHttpUtils.newCall(request).execute();
|
||||||
// 5. 处理响应
|
|
||||||
if (!response.isSuccessful()) {
|
if (!response.isSuccessful()) {
|
||||||
// 非200状态:返回错误信息(假设ByteBodyRes有error字段)
|
|
||||||
String errorMsg = response.body() != null ? response.body().string() : "uploadVideo error";
|
String errorMsg = response.body() != null ? response.body().string() : "uploadVideo error";
|
||||||
throw new Exception("uploadVideo error:" + errorMsg);
|
throw new Exception("uploadVideo error:" + errorMsg);
|
||||||
}
|
}
|
||||||
// 6. 解析成功响应为ByteBodyRes
|
|
||||||
if (response.body() == null) {
|
if (response.body() == null) {
|
||||||
throw new Exception("uploadVideo response null");
|
throw new Exception("uploadVideo response null");
|
||||||
}
|
}
|
||||||
|
|
||||||
String responseBody = response.body().string();
|
String responseBody = response.body().string();
|
||||||
return objectMapper.readValue(responseBody, ByteBodyRes.class);
|
return objectMapper.readValue(responseBody, ByteBodyRes.class);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue