Compare commits

..

No commits in common. "seedance" and "master" have entirely different histories.

11 changed files with 130 additions and 414 deletions

View File

@ -10,10 +10,10 @@ import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.LoginAiUser; 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.RandomStringUtil;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.TencentCosUtil;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -36,21 +36,13 @@ import java.util.regex.Pattern;
public class ByteApiController extends BaseController { public class ByteApiController extends BaseController {
private final IByteService byteService; private final IByteService byteService;
private final TencentCosUtil tencentCosUtil; private final AwsS3Util awsS3Util;
private final IAiOrderService aiOrderService; private final IAiOrderService aiOrderService;
private final IAiManagerService managerService; private final IAiManagerService managerService;
private final IAiTagService aiTagService; private final IAiTagService aiTagService;
@Value("${byteapi.callBackUrl}") @Value("${byteapi.callBackUrl}")
private String url; 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") @PostMapping("/promptToImg")
@ApiOperation("文生图") @ApiOperation("文生图")
public AjaxResult promptToImg(@RequestBody ByteApiRequest request) { public AjaxResult promptToImg(@RequestBody ByteApiRequest request) {
@ -59,12 +51,9 @@ public class ByteApiController extends BaseController {
return AjaxResult.error("functionType is null"); return AjaxResult.error("functionType is null");
} }
String mode = request.getMode() != null ? request.getMode() : "image-to-video";
AiManager aiManager = managerService.selectAiManagerByType(functionType); AiManager aiManager = managerService.selectAiManagerByType(functionType);
String tags = request.getTags(); String tags = request.getTags();
String text = request.getText(); String text = "";
// 如果使用标签系统生成prompt
if (StringUtils.isNotEmpty(tags)) { if (StringUtils.isNotEmpty(tags)) {
List<AiTag> aiTags = aiTagService.selectAiTagListByIds(request.getTags(), aiManager.getParentIdSort()); List<AiTag> aiTags = aiTagService.selectAiTagListByIds(request.getTags(), aiManager.getParentIdSort());
List<String> tagPrompts = new ArrayList<>(); List<String> tagPrompts = new ArrayList<>();
@ -81,29 +70,22 @@ public class ByteApiController extends BaseController {
tagPrompts.add(p); tagPrompts.add(p);
} }
text = StringUtils.replacePlaceholders(aiManager.getPrompt(), tagPrompts); text = StringUtils.replacePlaceholders(aiManager.getPrompt(), tagPrompts);
} else {
text = aiManager.getPrompt();
} }
if (StringUtils.isEmpty(text)) { if (StringUtils.isEmpty(text)) {
return AjaxResult.error("text is null"); return AjaxResult.error("text is null");
} }
AiOrder aiOrder = aiOrderService.getAiOrder(functionType); AiOrder aiOrder = aiOrderService.getAiOrder(functionType);
try { try {
if (aiOrder == null) { if (aiOrder == null) {
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);
aiOrder.setFunctionType(mode); // 记录生成模式
// 文生视频模式下不设置图片
if ("image-to-video".equals(mode) && firstUrl != null) {
aiOrder.setImg1(firstUrl.toString());
}
ByteBodyReq byteBodyReq = new ByteBodyReq(); ByteBodyReq byteBodyReq = new ByteBodyReq();
// model由前端传入默认为Seedance 2.0 byteBodyReq.setModel("ep-20251104104536-2gpgz");
byteBodyReq.setModel(StringUtils.isNotEmpty(request.getModel()) ?
request.getModel() : "ep-20260326165811-dlkth");
byteBodyReq.setPrompt(text); byteBodyReq.setPrompt(text);
byteBodyReq.setSequential_image_generation("disabled"); byteBodyReq.setSequential_image_generation("disabled");
byteBodyReq.setResponse_format("url"); byteBodyReq.setResponse_format("url");
@ -114,7 +96,7 @@ public class ByteApiController extends BaseController {
List<ByteDataRes> data = byteBodyRes.getData(); List<ByteDataRes> data = byteBodyRes.getData();
ByteDataRes byteDataRes = data.get(0); ByteDataRes byteDataRes = data.get(0);
String url = byteDataRes.getUrl(); String url = byteDataRes.getUrl();
url = tencentCosUtil.uploadFileByUrl(url); url = awsS3Util.uploadFileByUrl(url);
if (url == null) { if (url == null) {
// 判断生成失败退回金额逻辑 // 判断生成失败退回金额逻辑
aiOrderService.orderFailure(aiOrder); aiOrderService.orderFailure(aiOrder);
@ -193,7 +175,7 @@ public class ByteApiController extends BaseController {
List<ByteDataRes> data = byteBodyRes.getData(); List<ByteDataRes> data = byteBodyRes.getData();
ByteDataRes byteDataRes = data.get(0); ByteDataRes byteDataRes = data.get(0);
String url = byteDataRes.getUrl(); String url = byteDataRes.getUrl();
url = tencentCosUtil.uploadFileByUrl(url); url = awsS3Util.uploadFileByUrl(url);
if (url == null) { if (url == null) {
// 判断生成失败退回金额逻辑 // 判断生成失败退回金额逻辑
aiOrderService.orderFailure(aiOrder); aiOrderService.orderFailure(aiOrder);
@ -209,7 +191,7 @@ public class ByteApiController extends BaseController {
} }
@PostMapping("/imgToVideo") @PostMapping("/imgToVideo")
@ApiOperation("图生视频 (Seedance 2.0)") @ApiOperation("图生视频")
public AjaxResult imgToVideo(@RequestBody ByteApiRequest request) throws Exception { public AjaxResult imgToVideo(@RequestBody ByteApiRequest request) throws Exception {
String functionType = request.getFunctionType(); String functionType = request.getFunctionType();
if (null == functionType) { if (null == functionType) {
@ -253,56 +235,51 @@ 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");
} }
try { 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.setText(text);
aiOrder.setImg1(firstUrl.toString()); aiOrder.setImg1(firstUrl.toString());
Integer duration = request.getDuration();
Integer duration = request.getDuration() != null ? request.getDuration() : 4;
ByteBodyReq byteBodyReq = new ByteBodyReq(); ByteBodyReq byteBodyReq = new ByteBodyReq();
// model由前端传入默认为Seedance2.0 byteBodyReq.setModel("ep-20251113072240-cfxlz");
byteBodyReq.setModel(StringUtils.isNotEmpty(request.getModel()) ? byteBodyReq.setCallback_url(url + "/api/ai/callBack");
request.getModel() : "ep-20260326165811-dlkth"); List<ContentItem> content = new ArrayList<>();
byteBodyReq.setCallback_url(volcCallbackUrl); ContentItem contentItem = new ContentItem();
contentItem.setType("text");
contentItem.setText(text + " --dur " + duration + " --fps 24 --rs 720p --wm false --cf false");
content.add(contentItem);
// 构建符合火山引擎格式的content ContentItem contentItem1 = new ContentItem();
List<ContentItem> contentList = new ArrayList<>(); contentItem1.setType("image_url");
contentItem1.setRole("first_frame");
ImageUrl imageUrl1 = new ImageUrl();
imageUrl1.setUrl(firstUrl.toString());
contentItem1.setImageUrl(imageUrl1);
content.add(contentItem1);
// 文本提示词
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(); String lastUrl = request.getLastUrl();
if (StringUtils.isNotBlank(lastUrl)) { if (StringUtils.isNotBlank(lastUrl)) {
ContentItem lastFrameItem = new ContentItem(); ContentItem contentItem2 = new ContentItem();
lastFrameItem.setType("image_url"); contentItem2.setType("image_url");
lastFrameItem.setRole("last_frame"); contentItem2.setRole("last_frame");
ImageUrl lastImageUrl = new ImageUrl(); ImageUrl imageUrl2 = new ImageUrl();
lastImageUrl.setUrl(lastUrl); imageUrl2.setUrl(lastUrl);
lastFrameItem.setImageUrl(lastImageUrl); contentItem2.setImageUrl(imageUrl2);
contentList.add(lastFrameItem); content.add(contentItem2);
aiOrder.setImg2(lastUrl); aiOrder.setImg2(lastUrl);
} }
byteBodyReq.setContent(content);
byteBodyReq.setContent(contentList);
byteBodyReq.setDuration(duration);
byteBodyReq.setResolution("720p");
byteBodyReq.setRatio("3:4");
ByteBodyRes byteBodyRes = byteService.imgToVideo(byteBodyReq); ByteBodyRes byteBodyRes = byteService.imgToVideo(byteBodyReq);
String id = byteBodyRes.getId(); String id = byteBodyRes.getId();
if (id == null) { if (id == 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");
} }
@ -322,7 +299,7 @@ public class ByteApiController extends BaseController {
if ("succeeded".equals(byteBodyRes.getStatus())) { if ("succeeded".equals(byteBodyRes.getStatus())) {
content content = byteBodyRes.getContent(); content content = byteBodyRes.getContent();
String videoUrl = content.getVideo_url(); String videoUrl = content.getVideo_url();
videoUrl = tencentCosUtil.uploadFileByUrl(videoUrl); videoUrl = awsS3Util.uploadFileByUrl(videoUrl);
content.setVideo_url(videoUrl); content.setVideo_url(videoUrl);
AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(id); AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(id);
AiOrder aiOrder = new AiOrder(); AiOrder aiOrder = new AiOrder();
@ -334,34 +311,24 @@ public class ByteApiController extends BaseController {
return AjaxResult.success(byteBodyRes); return AjaxResult.success(byteBodyRes);
} }
@GetMapping(value = "/volcCallback") @GetMapping(value = "/callBack")
@ApiOperation("火山引擎视频回调") @ApiOperation("视频下载回调")
@Anonymous @Anonymous
public AjaxResult volcCallback(@RequestBody ByteBodyRes byteBodyRes) throws Exception { public AjaxResult callBack(@PathVariable("id") ByteBodyRes byteBodyRes) throws Exception {
if ("succeeded".equals(byteBodyRes.getStatus())) { if ("succeeded".equals(byteBodyRes.getStatus())) {
String id = byteBodyRes.getId(); String id = byteBodyRes.getId();
content contentObj = byteBodyRes.getContent(); content content = byteBodyRes.getContent();
if (contentObj != null && StringUtils.isNotEmpty(contentObj.getVideo_url())) { String videoUrl = content.getVideo_url();
String videoUrl = contentObj.getVideo_url(); videoUrl = awsS3Util.uploadFileByUrl(videoUrl);
videoUrl = tencentCosUtil.uploadFileByUrl(videoUrl); content.setVideo_url(videoUrl);
contentObj.setVideo_url(videoUrl); AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(id);
AiOrder aiOrder = new AiOrder();
AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(id); aiOrder.setId(aiOrderByResult.getId());
if (aiOrderByResult != null) { aiOrder.setResult(videoUrl);
AiOrder aiOrder = new AiOrder(); // aiOrder.setUpdateBy(SecurityUtils.getLoginAiUser().getUsername());
aiOrder.setId(aiOrderByResult.getId()); aiOrderService.updateAiOrder(aiOrder);
aiOrder.setResult(videoUrl);
aiOrderService.updateAiOrder(aiOrder);
}
}
} }
return AjaxResult.success("callback success"); return AjaxResult.success(byteBodyRes);
}
@PostMapping(value = "/{id}/cancel")
@ApiOperation("取消视频生成任务")
public AjaxResult cancelTask(@PathVariable("id") String id) throws Exception {
return byteService.cancelVideoTask(id);
} }
} }

View File

@ -1,7 +1,7 @@
package com.ruoyi.api; package com.ruoyi.api;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.TencentCosUtil; import com.ruoyi.common.utils.AwsS3Util;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiParam;
@ -18,7 +18,7 @@ import org.springframework.web.multipart.MultipartFile;
@Api(tags = "文件上传") @Api(tags = "文件上传")
@RequiredArgsConstructor(onConstructor_ = @Autowired) @RequiredArgsConstructor(onConstructor_ = @Autowired)
public class FileController { public class FileController {
private final TencentCosUtil tencentCosUtil; private final AwsS3Util awsS3Util;
/** /**
* 文件上传 * 文件上传
@ -30,7 +30,7 @@ public class FileController {
@ApiParam(name = "file", value = "文件", required = true) @ApiParam(name = "file", value = "文件", required = true)
@RequestParam("file") MultipartFile file) throws Exception { @RequestParam("file") MultipartFile file) throws Exception {
AjaxResult ajax = AjaxResult.success(); AjaxResult ajax = AjaxResult.success();
String uploadUrl = tencentCosUtil.uploadMultipartFile(file, true); String uploadUrl = awsS3Util.uploadMultipartFile(file, true);
ajax.put("url", uploadUrl); ajax.put("url", uploadUrl);
return ajax; return ajax;
} }

View File

@ -31,11 +31,6 @@ public class ByteApiRequest {
@ApiModelProperty(name = "标签字符串") @ApiModelProperty(name = "标签字符串")
private String tags; private String tags;
@ApiModelProperty(name = "使用的模型")
private String model;
@ApiModelProperty(name = "生成模式text-to-video 或 image-to-video")
private String mode = "text-to-video";
} }

View File

@ -4,6 +4,7 @@ import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.exception.base.BaseException; import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.AwsS3Util;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.TencentCosUtil; import com.ruoyi.common.utils.TencentCosUtil;
import com.ruoyi.common.utils.file.FileUploadUtils; import com.ruoyi.common.utils.file.FileUploadUtils;
@ -35,6 +36,7 @@ public class CommonController {
private static final Logger log = LoggerFactory.getLogger(CommonController.class); private static final Logger log = LoggerFactory.getLogger(CommonController.class);
private final ServerConfig serverConfig; private final ServerConfig serverConfig;
private final TencentCosUtil tencentCosUtil; private final TencentCosUtil tencentCosUtil;
private final AwsS3Util awsS3Util;
/** /**
* 通用下载请求 * 通用下载请求
@ -156,7 +158,7 @@ public class CommonController {
} }
/** /**
* 腾讯云COS上传请求单个 * AWS上传请求单个
*/ */
@ApiOperation("图片上传接口") @ApiOperation("图片上传接口")
@PostMapping("/aws/upload") @PostMapping("/aws/upload")
@ -164,7 +166,7 @@ public class CommonController {
AjaxResult ajax = AjaxResult.success(); AjaxResult ajax = AjaxResult.success();
String uploadUrl; String uploadUrl;
try { try {
uploadUrl = tencentCosUtil.uploadMultipartFile(file, true); uploadUrl = awsS3Util.uploadMultipartFile(file, true);
} catch (Exception e) { } catch (Exception e) {
return AjaxResult.error(e.getMessage()); return AjaxResult.error(e.getMessage());
} }

View File

@ -209,32 +209,24 @@ google:
redirect-uri: redirect-uri:
tencentCos: tencentCos:
accessKey: AKIDBE3dzBdLsHYfZLwKVSFArLchZDerrfHf accessKey:
secretKey: EDyUmsnX2IJ5f0oRn1QdeQ0TmrtqgQ1c secretKey:
endpoint: ap-guangzhou endpoint:
bucketName: seedance-1331490964 bucketName:
domain: https://seedance-1331490964.cos.ap-guangzhou.myqcloud.com domain:
# aws配置已替换为腾讯云COS请在环境变量或配置文件中设置腾讯云凭证 aws:
# aws: accessKey: AKIAYVMHEVDDZQGE3HVX
# accessKey: AKIAYVMHEVDDZQGE3HVX secretKey: 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
# secretKey: B9nxdferMhdRuxzoKeQam/NxiVvIhI7lSru6VfwG endpoint: ap-southeast-1
# endpoint: ap-southeast-1 bucketName: di-image
# bucketName: di-image domain: https://images.iqyjsnwv.com/
# domain: https://images.iqyjsnwv.com/
byteapi: byteapi:
url: https://ark.ap-southeast.bytepluses.com/api/v3 url: https://ark.ap-southeast.bytepluses.com/api/v3
apiKey: 3e33e034-7e25-4228-8864-b51b2a7a8f97 apiKey: 327d2815-2516-44c2-9e32-2dc50bf7afd7
callBackUrl: https://undressing.top callBackUrl: https://undressing.top
# 火山引擎 Ark API (Seedance 2.0)
volcengine:
ark:
baseUrl: https://ark.cn-beijing.volces.com
apiKey: 3e33e034-7e25-4228-8864-b51b2a7a8f97
callbackUrl: https://undressing.top/api/ai/volcCallback
jinsha: jinsha:
url: https://api.jinshapay.xyz url: https://api.jinshapay.xyz
appId: 1763617360 appId: 1763617360

View File

@ -13,13 +13,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream; 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; import java.util.UUID;
@Component @Component
@ -39,32 +33,22 @@ public class TencentCosUtil {
private String domain; private String domain;
/** //文件上传
* 上传MultipartFile到腾讯云COS返回文件访问地址 public String upload(MultipartFile file) {
* 与AwsS3Util.uploadMultipartFile方法接口兼容
*/
public String upload(MultipartFile file) throws Exception {
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(); COSClient cosClient = createCosClient();
// 生成唯一文件键格式与AWS一致yyyy/MM/dd/uuid_filename // 存储桶的命名格式为 BucketName-APPID此处填写的存储桶名称必须为此格式
String key = generateCosKey(file.getOriginalFilename()); // 对象键(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;
try { try {
//获取上传文件输入流
InputStream inputStream = file.getInputStream(); InputStream inputStream = file.getInputStream();
ObjectMetadata objectMetadata = new ObjectMetadata(); ObjectMetadata objectMetadata = new ObjectMetadata();
PutObjectRequest putObjectRequest = new PutObjectRequest( PutObjectRequest putObjectRequest = new PutObjectRequest(
@ -72,41 +56,17 @@ public class TencentCosUtil {
key, key,
inputStream, inputStream,
objectMetadata); objectMetadata);
// 高级接口会返回一个异步结果Upload
PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest); PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
// 返回COS文件访问地址 //返回上传文件路径
String url = domain + (domain.endsWith("/") ? "" : "/") + key; //https://ggkt-atguigu-1310644373.cos.ap-beijing.myqcloud.com/01.jpg
String url = domain + "/" + key;
return url; return url;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); 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;
} }
@ -118,107 +78,7 @@ public class TencentCosUtil {
ClientConfig clientConfig = new ClientConfig(region); ClientConfig clientConfig = new ClientConfig(region);
// 这里建议设置使用 https 协议 // 这里建议设置使用 https 协议
clientConfig.setHttpProtocol(HttpProtocol.https); clientConfig.setHttpProtocol(HttpProtocol.https);
clientConfig.setConnectionTimeout(30 * 1000); // 连接超时30秒
clientConfig.setSocketTimeout(60 * 1000); // 读取超时60秒
//1.3 生成cos客户端 //1.3 生成cos客户端
return new COSClient(credentials, clientConfig); 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());
}
};
}
} }

View File

@ -68,22 +68,6 @@ public class AiOrder extends BaseEntity {
@Excel(name = "是否置顶N-否 Y-是") @Excel(name = "是否置顶N-否 Y-是")
private String isTop; private String isTop;
/** 生成模式text-to-video 或 image-to-video */
@Excel(name = "生成模式")
private String mode;
/** 视频时长(秒) */
@Excel(name = "视频时长")
private Integer duration;
/** 分辨率(如 720p, 1080p */
@Excel(name = "分辨率")
private String resolution;
/** 宽高比(如 16:9, 9:16 */
@Excel(name = "宽高比")
private String ratio;
/** 首帧图片 */ /** 首帧图片 */
private String img1; private String img1;

View File

@ -30,10 +30,5 @@ public class ByteBodyReq {
@JsonProperty("content") @JsonProperty("content")
private List<ContentItem> content; private List<ContentItem> content;
private Integer duration;
private String resolution;
private String ratio;
private Integer seed;
} }

View File

@ -2,7 +2,6 @@ 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.common.core.domain.AjaxResult;
public interface IByteService { public interface IByteService {
@ -25,9 +24,4 @@ public interface IByteService {
* 下载视频 * 下载视频
*/ */
ByteBodyRes uploadVideo(String id) throws Exception; ByteBodyRes uploadVideo(String id) throws Exception;
/**
* 取消视频生成任务
*/
AjaxResult cancelVideoTask(String id) throws Exception;
} }

View File

@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
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.service.IByteService; import com.ruoyi.ai.service.IByteService;
import com.ruoyi.common.core.domain.AjaxResult;
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;
import okhttp3.*; import okhttp3.*;
@ -31,13 +30,6 @@ public class ByteService implements IByteService {
@Value("${byteapi.apiKey}") @Value("${byteapi.apiKey}")
private String 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 @Override
public ByteBodyRes promptToImg(ByteBodyReq req) throws Exception { public ByteBodyRes promptToImg(ByteBodyReq req) throws Exception {
return this.imgToImg(req); return this.imgToImg(req);
@ -83,92 +75,71 @@ public class ByteService implements IByteService {
@Override @Override
public ByteBodyRes imgToVideo(ByteBodyReq req) throws Exception { public ByteBodyRes imgToVideo(ByteBodyReq req) throws Exception {
if (req == null) {
throw new Exception("imgToVideo errorreq is null");
}
// 使用火山引擎配置 // 1. 验证请求参数可选根据业务需求
// if (StringUtils.isBlank(req.getPrompt())) {
// throw new Exception("imgToVideo errorprompt is null");
// }
// 2. 构建请求体JSON基于ByteBodyReq的字段
// 注意ByteBodyReq需包含与API参数对应的字段modelprompt等
String jsonBody = objectMapper.writeValueAsString(req); String jsonBody = objectMapper.writeValueAsString(req);
// 3. 构建请求
Request request = new Request.Builder() Request request = new Request.Builder()
.url(volcBaseUrl + "/api/v3/contents/generations/tasks") .url(API_URL + "/contents/generations/tasks")
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("Authorization", "Bearer " + volcApiKey) .header("Authorization", "Bearer " + apiKey)
.post(RequestBody.create( .post(RequestBody.create(
MediaType.parse("application/json; charset=utf-8"), 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() : "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. 验证请求参数可选根据业务需求
if (StringUtils.isBlank(id)) { if (StringUtils.isBlank(id)) {
throw new Exception("uploadVideo errorid is null"); throw new Exception("uploadVideo errorid is null");
} }
// 使用火山引擎配置查询任务状态 // 2. 构建请求体JSON基于ByteBodyReq的字段
// 注意ByteBodyReq需包含与API参数对应的字段modelprompt等
//String jsonBody = objectMapper.writeValueAsString(req);
// 3. 构建请求
Request request = new Request.Builder() Request request = new Request.Builder()
.url(volcBaseUrl + "/api/v3/contents/generations/tasks/" + id) .url(API_URL + "/contents/generations/tasks/" + id)
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("Authorization", "Bearer " + volcApiKey) .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);
} }
@Override
public AjaxResult cancelVideoTask(String id) throws Exception {
if (StringUtils.isBlank(id)) {
return AjaxResult.error("任务ID不能为空");
}
try {
// 向火山引擎发送 DELETE 请求取消任务
Request request = new Request.Builder()
.url(volcBaseUrl + "/api/v3/contents/generations/tasks/" + id)
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + volcApiKey)
.delete()
.build();
Response response = OkHttpUtils.newCall(request).execute();
if (!response.isSuccessful()) {
String errorMsg = response.body() != null ? response.body().string() : "cancel failed";
return AjaxResult.error("取消任务失败:" + errorMsg);
}
return AjaxResult.success("任务已取消,余额已退回");
} catch (Exception e) {
return AjaxResult.error("取消任务异常:" + e.getMessage());
}
}
} }

View File

@ -23,7 +23,6 @@ SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `ai_balance_change_record`; DROP TABLE IF EXISTS `ai_balance_change_record`;
CREATE TABLE `ai_balance_change_record` ( CREATE TABLE `ai_balance_change_record` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联订单号',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '删除标志0代表存在 2代表删除', `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '删除标志0代表存在 2代表删除',
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
@ -34,8 +33,7 @@ CREATE TABLE `ai_balance_change_record` (
`type` tinyint(1) NULL DEFAULT NULL COMMENT '操作类型', `type` tinyint(1) NULL DEFAULT NULL COMMENT '操作类型',
`change_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '变更金额', `change_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '变更金额',
`result_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '变更后金额', `result_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '变更后金额',
PRIMARY KEY (`id`) USING BTREE, PRIMARY KEY (`id`) USING BTREE
KEY `idx_order_no` (`order_no`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1159 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '余额使用记录' ROW_FORMAT = DYNAMIC; ) ENGINE = InnoDB AUTO_INCREMENT = 1159 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '余额使用记录' ROW_FORMAT = DYNAMIC;
-- ---------------------------- -- ----------------------------
@ -1451,7 +1449,7 @@ CREATE TABLE `ai_manager` (
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'AI标题', `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'AI标题',
`price` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '价格', `price` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '价格',
@ -1466,12 +1464,12 @@ CREATE TABLE `ai_manager` (
-- ---------------------------- -- ----------------------------
-- Records of ai_manager -- Records of ai_manager
-- ---------------------------- -- ----------------------------
INSERT INTO `ai_manager` VALUES (1, '0', 'admin', '2025-11-13 20:05:27', 'admin', '2025-12-23 14:30:07', NULL, '图生图', 10.00, 0, '1girl, solo, exact same character as reference image, (completely nude:1.2), naked, no clothes, breasts fully exposed, {胸部大小}, perfect anatomy, masterpiece, best quality', '11', NULL); INSERT INTO `ai_manager` VALUES (1, '0', 'admin', '2025-11-13 20:05:27', 'admin', '2025-12-23 14:30:07', NULL, '图生图1', 10.00, 0, '1girl, solo, exact same character as reference image, identical face eye color hairstyle accessories expression pose background lighting, (completely nude:1.2), naked, no clothes, breasts fully exposed, {胸部大小}, nipples perfectly matching skin tone, {动作}, pose that naturally conceals lower body from front view, legs positioned to avoid frontal genital exposure, side profile or back view emphasis, subtle natural body contours without explicit details, perfect anatomy, exactly two arms and two legs only, perfect hands with exactly 5 clearly separated fingers each no fusion no extra fingers, smooth natural skin texture, realistic proportions, masterpiece, best quality, ultra detailed, 8k, soft lighting, depth of field, high resolution, intricate details, cinematic composition, (if multiple characters: all characters following same rules no frontal exposure:1.1)(negative: clothes, bra, panties, underwear, bikini, swimsuit, any fabric even one pixel, censored, mosaic, bar censor, any censorship, pasties, nipple covers, frontal genital exposure, visible slit, visible pussy, exposed crotch, swollen labia, puffy labia, deformed genital area, dark mismatched crotch skin, any pubic details, extra arms, extra legs, extra hands, extra fingers, third arm, third leg, mutated limbs, more than two arms, more than two legs, fused fingers, deformed hands, bad hands, unnatural poses, violating human anatomy, loli, child, old, realistic photo, lowres:1.8, blurry, artifacts, overexposed, underexposed, pixelated, jpeg artifacts, watermark, text, signature, ugly, deformed, mutated, extra limbs, poorly drawn face, poorly drawn hands, missing limbs, floating limbs, disconnected limbs)', '11', '1,17');
INSERT INTO `ai_manager` VALUES (2, '0', 'admin', '2025-11-21 22:24:28', 'admin', '2025-12-14 20:20:56', NULL, '图生图-高级', 12.00, 0, '生成{主题},{风格}包含{细节},分辨率{技术参数}的图像', '12', NULL); INSERT INTO `ai_manager` VALUES (2, '0', 'admin', '2025-11-21 22:24:28', 'admin', '2025-12-14 20:20:56', NULL, '图生图2', 10.00, 0, '生成{主题},{风格}包含{细节},分辨率{技术参数}的图像', '12', NULL);
INSERT INTO `ai_manager` VALUES (3, '0', 'admin', '2025-11-13 20:06:02', 'admin', '2025-12-24 15:03:00', NULL, '一键换脸', 10.00, 1, '保持参考图1的内容风格不变用参考图2的脸部对参考图1的脸部进行替换', '13', NULL); INSERT INTO `ai_manager` VALUES (3, '0', 'admin', '2025-11-13 20:06:02', 'admin', '2025-12-24 15:03:00', NULL, '一键换脸', 10.00, 1, '保持参考图1的内容风格不变用参考图2的脸部对参考图1的脸部进行替换', '13', '');
INSERT INTO `ai_manager` VALUES (4, '0', 'admin', '2025-11-13 20:07:33', 'admin', '2025-12-23 14:47:10', NULL, '快捷生图', 8.00, 0, '1girl, solo, detailed face with {发型} {眼睛颜色} {配饰} {表情} {姿势} in {背景}, {服装描述} {胸部大小}, perfect anatomy, masterpiece, best quality', '11', NULL); INSERT INTO `ai_manager` VALUES (4, '0', 'admin', '2025-11-13 20:07:33', 'admin', '2025-12-23 14:47:10', NULL, '快捷生图', 8.00, 0, '1girl, solo, detailed face with {发型} {眼睛颜色} {配饰} {表情} {姿势} in {背景}, {服装描述} {胸部大小} no pubic hair at all, no body hair anywhere from neck to toes, pose that naturally conceals lower body from front view, legs positioned to avoid frontal genital exposure, side profile or back view emphasis, subtle natural body contours without explicit details, perfect anatomy, exactly two arms and two legs only, perfect hands with exactly 5 clearly separated fingers each no fusion no extra fingers, smooth natural skin texture, realistic proportions, masterpiece, best quality, ultra detailed, 8k, soft lighting, depth of field, high resolution, intricate details, cinematic composition, (if multiple characters: {多人描述}, all characters following same rules no frontal exposure:1.1)(negative: everyday clothes, casual outfit, school uniform, regular dress, any non-specified clothing, clothing glitch, fabric clipping, pubic hair, body hair, happy trail, hair on abdomen, hair on stomach, hair on torso, hair around navel, any hair below neck except head hair, frontal genital exposure, visible slit, visible pussy, exposed crotch, swollen labia, puffy labia, deformed genital area, dark mismatched crotch skin, any pubic details, extra arms, extra legs, extra hands, extra fingers, third arm, third leg, mutated limbs, more than two arms, more than two legs, fused fingers, deformed hands, bad hands, unnatural poses, violating human anatomy, bad anatomy, loli, child, old, realistic photo, lowres:1.9, blurry, artifacts, overexposed, underexposed, pixelated, jpeg artifacts, watermark, text, signature, ugly, deformed, mutated, extra limbs, poorly drawn face, poorly drawn hands, missing limbs, floating limbs, disconnected limbs, clothes if nude mode, bra if nude mode, panties if nude mode, underwear if nude mode, bikini if nude mode, swimsuit if nude mode, any fabric even one pixel if nude mode, censored if nude mode, mosaic if nude mode, bar censor if nude mode, any censorship if nude mode, pasties if nude mode, nipple covers if nude mode)\n', '1', '8,5,11,64,66,68,70,72,74');
INSERT INTO `ai_manager` VALUES (5, '0', 'admin', '2025-11-13 20:07:47', 'admin', '2026-01-08 14:58:37', NULL, '快捷生视频', 35.00, 0, '跳舞', '21', NULL); INSERT INTO `ai_manager` VALUES (5, '0', 'admin', '2025-11-13 20:07:47', 'admin', '2026-01-08 14:58:37', '', '快捷生视频', 35.00, 0, '跳舞', '21', '');
INSERT INTO `ai_manager` VALUES (7, '0', 'admin', '2025-11-25 19:41:23', 'admin', '2025-11-25 19:41:23', NULL, '视频换脸', 35.00, 1, '视频换脸功能', '22', NULL); INSERT INTO `ai_manager` VALUES (7, '0', 'admin', '2025-11-25 19:41:23', 'admin', '2025-11-25 19:41:23', NULL, '视频换脸', 35.00, 1, NULL, '22', NULL);
-- ---------------------------- -- ----------------------------
-- Table structure for ai_order -- Table structure for ai_order
@ -1496,13 +1494,7 @@ CREATE TABLE `ai_order` (
`is_top` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '是否置顶N-否 Y-是', `is_top` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '是否置顶N-否 Y-是',
`img1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '首帧图片', `img1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '首帧图片',
`img2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '第二张图片', `img2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '第二张图片',
`mode` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成模式text-to-video 或 image-to-video', PRIMARY KEY (`id`) USING BTREE
`duration` int NULL DEFAULT 5 COMMENT '视频时长(秒)',
`resolution` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '720p' COMMENT '分辨率',
`ratio` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '9:16' COMMENT '宽高比',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_order_num` (`order_num`) USING BTREE,
KEY `idx_user_id` (`user_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1310 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'API订单记录' ROW_FORMAT = DYNAMIC; ) ENGINE = InnoDB AUTO_INCREMENT = 1310 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'API订单记录' ROW_FORMAT = DYNAMIC;
-- ---------------------------- -- ----------------------------
@ -2800,7 +2792,6 @@ CREATE TABLE `ai_pay_setting` (
DROP TABLE IF EXISTS `ai_rebate_record`; DROP TABLE IF EXISTS `ai_rebate_record`;
CREATE TABLE `ai_rebate_record` ( CREATE TABLE `ai_rebate_record` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联订单号',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '删除标志0代表存在 2代表删除', `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '删除标志0代表存在 2代表删除',
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
@ -2810,8 +2801,7 @@ CREATE TABLE `ai_rebate_record` (
`superior_id` bigint NULL DEFAULT NULL COMMENT '上级ID', `superior_id` bigint NULL DEFAULT NULL COMMENT '上级ID',
`subordinate_id` bigint NULL DEFAULT NULL COMMENT '下级ID', `subordinate_id` bigint NULL DEFAULT NULL COMMENT '下级ID',
`amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '返佣金额', `amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '返佣金额',
PRIMARY KEY (`id`) USING BTREE, PRIMARY KEY (`id`) USING BTREE
KEY `idx_order_no` (`order_no`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '返佣记录' ROW_FORMAT = DYNAMIC; ) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '返佣记录' ROW_FORMAT = DYNAMIC;
-- ---------------------------- -- ----------------------------
@ -3113,7 +3103,6 @@ INSERT INTO `ai_sample_amount` VALUES (2, '0', 'admin', '2025-11-14 22:37:01', '
DROP TABLE IF EXISTS `ai_sample_amount_record`; DROP TABLE IF EXISTS `ai_sample_amount_record`;
CREATE TABLE `ai_sample_amount_record` ( CREATE TABLE `ai_sample_amount_record` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联订单号',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '删除标志0代表存在 2代表删除', `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '删除标志0代表存在 2代表删除',
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
@ -3124,8 +3113,7 @@ CREATE TABLE `ai_sample_amount_record` (
`sample_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '体验金额', `sample_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '体验金额',
`recycle_time` datetime NULL DEFAULT NULL COMMENT '回收时间', `recycle_time` datetime NULL DEFAULT NULL COMMENT '回收时间',
`status` tinyint NULL DEFAULT 0 COMMENT '回收状态0-已发放 1-已回收', `status` tinyint NULL DEFAULT 0 COMMENT '回收状态0-已发放 1-已回收',
PRIMARY KEY (`id`) USING BTREE, PRIMARY KEY (`id`) USING BTREE
KEY `idx_order_no` (`order_no`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '体验金领取记录' ROW_FORMAT = DYNAMIC; ) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '体验金领取记录' ROW_FORMAT = DYNAMIC;
-- ---------------------------- -- ----------------------------
@ -5825,36 +5813,4 @@ INSERT INTO `user_message` VALUES (2, 2);
INSERT INTO `user_message` VALUES (2, 3); INSERT INTO `user_message` VALUES (2, 3);
INSERT INTO `user_message` VALUES (2, 4); INSERT INTO `user_message` VALUES (2, 4);
-- ----------------------------
-- Table structure for ai_template
-- ----------------------------
DROP TABLE IF EXISTS `ai_template`;
CREATE TABLE `ai_template` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模版名称',
`chinese_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '模版中文内容',
`english_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '模版英文内容',
`image_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模版图片URL',
`ai_id` bigint NULL DEFAULT NULL COMMENT '关联AI类型ID',
`status` tinyint(1) NULL DEFAULT 1 COMMENT '状态0禁用 1启用',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志0代表存在 1代表删除',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_ai_id` (`ai_id`) USING BTREE,
KEY `idx_status` (`status`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1001 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'AI模板表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of ai_template
-- ----------------------------
INSERT INTO `ai_template` (`name`, `chinese_content`, `english_content`, `image_url`, `ai_id`, `status`, `remark`) VALUES
('默认写真模板', '一个穿着衣服的年轻女性,微笑面对镜头,高清写真风格', 'A young woman wearing clothes, smiling at the camera, high-definition portrait style', 'https://seedance-1331490964.cos.ap-guangzhou.myqcloud.com/ai/default-template.jpg', 11, 1, '默认写真模板'),
('艺术裸体模板', '一个优雅的艺术裸体女性,柔和光线,专业摄影风格', 'An elegant artistic nude female with soft lighting, professional photography style', 'https://seedance-1331490964.cos.ap-guangzhou.myqcloud.com/ai/nude-art.jpg', 11, 1, '艺术裸体模板'),
('时尚都市模板', '时尚都市年轻女性写真,现代潮流风格', 'Fashionable urban young woman portrait, modern trendy style', 'https://seedance-1331490964.cos.ap-guangzhou.myqcloud.com/ai/fashion.jpg', 21, 1, '时尚写真模板'),
('性感写真模板', '性感迷人女性写真,专业灯光和构图', 'Sexy and charming female portrait with professional lighting and composition', 'https://seedance-1331490964.cos.ap-guangzhou.myqcloud.com/ai/sexy.jpg', 11, 1, '性感写真模板');
SET FOREIGN_KEY_CHECKS = 1; SET FOREIGN_KEY_CHECKS = 1;