Compare commits

..

10 Commits

Author SHA1 Message Date
old burden e7ccd3638c fix: 支持视频音频的参考图模式,资产管理模块初步上线 2026-04-01 10:30:57 +08:00
old burden bb35045ff2 fix: 交互优化 2026-03-31 14:40:52 +08:00
old burden 15f5a20f7c fix: 仿照即梦出一版 2026-03-31 13:49:46 +08:00
old burden 45cff6ab27 fix: bug修改 result 写成 URL 2026-03-30 13:25:52 +08:00
old burden 8436c3515c fix: 页面优化,工具栏显示 2026-03-30 13:05:04 +08:00
old burden fc1ecf7bc9 fix: bug修改字段缺失 2026-03-30 12:30:04 +08:00
old burden 0f112c019d fix: 参数选择改到参数文件 2026-03-30 12:06:45 +08:00
old burden a69ae255c9 fix: 新页面 2026-03-30 11:07:33 +08:00
old burden 8a4c0f73c6 fix: 新需求 对接火山seedance 2026-03-27 15:27:19 +08:00
old burden 3a05e41f79 fix: 新需求 2026-03-27 10:51:32 +08:00
35 changed files with 1697 additions and 186 deletions

2
.gitignore vendored
View File

@ -4,7 +4,7 @@
.gradle .gradle
/build/ /build/
!gradle/wrapper/gradle-wrapper.jar !gradle/wrapper/gradle-wrapper.jar
*.sql
target/ target/
!.mvn/wrapper/maven-wrapper.jar !.mvn/wrapper/maven-wrapper.jar

View File

@ -51,6 +51,9 @@ public class AiManagerApiController extends BaseController {
@Anonymous @Anonymous
public AjaxResult selectInfo(String aiType) { public AjaxResult selectInfo(String aiType) {
AiManager aiManager = aiManagerService.selectAiManagerByType(aiType); AiManager aiManager = aiManagerService.selectAiManagerByType(aiType);
if (aiManager == null) {
return AjaxResult.error("该功能未配置或已停用");
}
aiManager.setPrompt(null); aiManager.setPrompt(null);
return AjaxResult.success(aiManager); return AjaxResult.success(aiManager);
} }

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,13 +36,21 @@ 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 AwsS3Util awsS3Util; private final TencentCosUtil tencentCosUtil;
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) {
@ -51,9 +59,12 @@ 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 = ""; String text = request.getText();
// 如果使用标签系统生成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<>();
@ -70,22 +81,24 @@ 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.setMode(mode); // 记录生成模式
ByteBodyReq byteBodyReq = new ByteBodyReq(); 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.setPrompt(text);
byteBodyReq.setSequential_image_generation("disabled"); byteBodyReq.setSequential_image_generation("disabled");
byteBodyReq.setResponse_format("url"); byteBodyReq.setResponse_format("url");
@ -96,7 +109,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 = awsS3Util.uploadFileByUrl(url); url = tencentCosUtil.uploadFileByUrl(url);
if (url == null) { if (url == null) {
// 判断生成失败退回金额逻辑 // 判断生成失败退回金额逻辑
aiOrderService.orderFailure(aiOrder); aiOrderService.orderFailure(aiOrder);
@ -175,7 +188,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 = awsS3Util.uploadFileByUrl(url); url = tencentCosUtil.uploadFileByUrl(url);
if (url == null) { if (url == null) {
// 判断生成失败退回金额逻辑 // 判断生成失败退回金额逻辑
aiOrderService.orderFailure(aiOrder); aiOrderService.orderFailure(aiOrder);
@ -191,7 +204,7 @@ public class ByteApiController extends BaseController {
} }
@PostMapping("/imgToVideo") @PostMapping("/imgToVideo")
@ApiOperation("图生视频") @ApiOperation("图生视频 (Seedance 2.0)")
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) {
@ -235,51 +248,56 @@ 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();
byteBodyReq.setModel("ep-20251113072240-cfxlz"); // model由前端传入默认为Seedance2.0
byteBodyReq.setCallback_url(url + "/api/ai/callBack"); byteBodyReq.setModel(StringUtils.isNotEmpty(request.getModel()) ?
List<ContentItem> content = new ArrayList<>(); request.getModel() : "ep-20260326165811-dlkth");
ContentItem contentItem = new ContentItem(); byteBodyReq.setCallback_url(volcCallbackUrl);
contentItem.setType("text");
contentItem.setText(text + " --dur " + duration + " --fps 24 --rs 720p --wm false --cf false");
content.add(contentItem);
ContentItem contentItem1 = new ContentItem(); // 构建符合火山引擎格式的content
contentItem1.setType("image_url"); List<ContentItem> contentList = new ArrayList<>();
contentItem1.setRole("first_frame");
ImageUrl imageUrl1 = new ImageUrl(); // 文本提示词
imageUrl1.setUrl(firstUrl.toString()); ContentItem textItem = new ContentItem();
contentItem1.setImageUrl(imageUrl1); textItem.setType("text");
content.add(contentItem1); 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 contentItem2 = new ContentItem(); ContentItem lastFrameItem = new ContentItem();
contentItem2.setType("image_url"); lastFrameItem.setType("image_url");
contentItem2.setRole("last_frame"); lastFrameItem.setRole("last_frame");
ImageUrl imageUrl2 = new ImageUrl(); ImageUrl lastImageUrl = new ImageUrl();
imageUrl2.setUrl(lastUrl); lastImageUrl.setUrl(lastUrl);
contentItem2.setImageUrl(imageUrl2); lastFrameItem.setImageUrl(lastImageUrl);
content.add(contentItem2); contentList.add(lastFrameItem);
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");
} }
@ -299,7 +317,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 = awsS3Util.uploadFileByUrl(videoUrl); videoUrl = tencentCosUtil.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();
@ -311,24 +329,34 @@ public class ByteApiController extends BaseController {
return AjaxResult.success(byteBodyRes); return AjaxResult.success(byteBodyRes);
} }
@GetMapping(value = "/callBack") @GetMapping(value = "/volcCallback")
@ApiOperation("视频下载回调") @ApiOperation("火山引擎视频回调")
@Anonymous @Anonymous
public AjaxResult callBack(@PathVariable("id") ByteBodyRes byteBodyRes) throws Exception { public AjaxResult volcCallback(@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 contentObj = byteBodyRes.getContent();
String videoUrl = content.getVideo_url(); if (contentObj != null && StringUtils.isNotEmpty(contentObj.getVideo_url())) {
videoUrl = awsS3Util.uploadFileByUrl(videoUrl); String videoUrl = contentObj.getVideo_url();
content.setVideo_url(videoUrl); videoUrl = tencentCosUtil.uploadFileByUrl(videoUrl);
AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(id); contentObj.setVideo_url(videoUrl);
AiOrder aiOrder = new AiOrder();
aiOrder.setId(aiOrderByResult.getId()); AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(id);
aiOrder.setResult(videoUrl); if (aiOrderByResult != null) {
// aiOrder.setUpdateBy(SecurityUtils.getLoginAiUser().getUsername()); AiOrder aiOrder = new AiOrder();
aiOrderService.updateAiOrder(aiOrder); aiOrder.setId(aiOrderByResult.getId());
aiOrder.setResult(videoUrl);
aiOrderService.updateAiOrder(aiOrder);
}
}
} }
return AjaxResult.success(byteBodyRes); return AjaxResult.success("callback success");
}
@PostMapping(value = "/{id}/cancel")
@ApiOperation("取消视频生成任务")
public AjaxResult cancelTask(@PathVariable("id") String id) throws Exception {
return byteService.cancelVideoTask(id);
} }
} }

View File

@ -0,0 +1,38 @@
package com.ruoyi.api;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.TencentCosUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* COS 上传兼容接口
*/
@RestController
@RequestMapping("/api/cos")
@Api(tags = "COS文件上传")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class CosController {
private final TencentCosUtil tencentCosUtil;
@ApiOperation("COS上传接口")
@PostMapping("/upload")
public AjaxResult upload(
@ApiParam(name = "file", value = "文件", required = true)
@RequestParam("file") MultipartFile file) throws Exception {
String uploadUrl = tencentCosUtil.uploadMultipartFile(file, true);
AjaxResult ajax = AjaxResult.success(uploadUrl);
ajax.put("url", uploadUrl);
ajax.put("oldName", file.getOriginalFilename());
return ajax;
}
}

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.AwsS3Util; 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 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 AwsS3Util awsS3Util; private final TencentCosUtil tencentCosUtil;
/** /**
* 文件上传 * 文件上传
@ -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 = awsS3Util.uploadMultipartFile(file, true); String uploadUrl = tencentCosUtil.uploadMultipartFile(file, true);
ajax.put("url", uploadUrl); ajax.put("url", uploadUrl);
return ajax; return ajax;
} }

View File

@ -0,0 +1,519 @@
package com.ruoyi.api;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ruoyi.ai.domain.AiOrder;
import com.ruoyi.ai.domain.ByteBodyReq;
import com.ruoyi.ai.domain.ByteBodyRes;
import com.ruoyi.ai.domain.ContentItem;
import com.ruoyi.ai.domain.ImageUrl;
import com.ruoyi.ai.domain.content;
import com.ruoyi.ai.service.IAiOrderService;
import com.ruoyi.ai.service.IByteDeptApiKeyService;
import com.ruoyi.ai.service.IByteService;
import com.ruoyi.api.request.PortalVideoGenRequest;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.TencentCosUtil;
import com.ruoyi.config.PortalVideoProperties;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 门户视频生成按用户二级部门 byte_api_key 调用火山任务列表含库表与火山过滤列表
*/
@Api(tags = "门户-视频生成")
@RestController
@RequestMapping("/api/portal/video")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class PortalVideoController extends BaseController {
private final IByteService byteService;
private final IByteDeptApiKeyService byteDeptApiKeyService;
private final IAiOrderService aiOrderService;
private final TencentCosUtil tencentCosUtil;
private final PortalVideoProperties portalVideoProperties;
@Value("${volcengine.ark.callbackUrl:}")
private String volcCallbackUrl;
private static final ObjectMapper OM = new ObjectMapper();
private String apiKey() {
return byteDeptApiKeyService.resolveVolcApiKey(SecurityUtils.getAiUserId());
}
/** 与 ai_manager.type、portal.video.function-type 对齐,用于扣费 */
private String resolveFunctionType(PortalVideoGenRequest req) {
if (StringUtils.isNotEmpty(req.getFunctionType())) {
return req.getFunctionType();
}
if (StringUtils.isNotEmpty(portalVideoProperties.getFunctionType())) {
return portalVideoProperties.getFunctionType();
}
return "21";
}
private void applyOptionalParams(ByteBodyReq body, PortalVideoGenRequest req) {
PortalVideoProperties.Defaults d = portalVideoProperties.getDefaults();
body.setDuration(req.getDuration() != null ? req.getDuration() : d.getDuration());
body.setResolution(StringUtils.isNotEmpty(req.getResolution()) ? req.getResolution() : d.getResolution());
body.setRatio(StringUtils.isNotEmpty(req.getRatio()) ? req.getRatio() : d.getRatio());
if (StringUtils.isNotEmpty(volcCallbackUrl)) {
body.setCallback_url(volcCallbackUrl);
}
}
private ByteBodyReq newVideoBody(PortalVideoGenRequest req, List<ContentItem> content) {
String modelId = StringUtils.isNotEmpty(req.getModel()) ? req.getModel() : portalVideoProperties.resolveDefaultModelId();
if (StringUtils.isEmpty(modelId)) {
throw new ServiceException("未配置门户视频模型,请在 application.yml 的 portal.video 中配置 models 与 defaults.model");
}
ByteBodyReq body = new ByteBodyReq();
body.setModel(modelId);
body.setContent(content);
applyOptionalParams(body, req);
return body;
}
private void applyOrderImages(AiOrder aiOrder, PortalVideoGenRequest req) {
if (StringUtils.isNotEmpty(req.getFirstUrl())) {
aiOrder.setImg1(req.getFirstUrl());
}
if (StringUtils.isNotEmpty(req.getLastUrl())) {
aiOrder.setImg2(req.getLastUrl());
}
if (StringUtils.isNotEmpty(req.getReferenceUrl())) {
aiOrder.setImg1(req.getReferenceUrl());
}
}
/**
* 写入订单提示词生成模式图床 URL以及模型/时长/分辨率/比例与完整 JSON 参数便于对账与审计
*/
private void fillVideoOrderRecord(AiOrder aiOrder, PortalVideoGenRequest req, String mode, ByteBodyReq body, String functionTypeResolved) {
aiOrder.setText(req.getText());
aiOrder.setMode(mode);
applyOrderImages(aiOrder, req);
if (req.getDuration() != null) {
aiOrder.setDuration(req.getDuration());
} else if (body.getDuration() != null) {
aiOrder.setDuration(body.getDuration());
}
if (StringUtils.isNotEmpty(body.getResolution())) {
aiOrder.setResolution(body.getResolution());
}
if (StringUtils.isNotEmpty(body.getRatio())) {
aiOrder.setRatio(body.getRatio());
}
if (StringUtils.isNotEmpty(body.getModel())) {
aiOrder.setModel(body.getModel());
}
try {
Map<String, Object> snap = new LinkedHashMap<>();
snap.put("generationMode", mode);
snap.put("prompt", req.getText());
snap.put("functionType", functionTypeResolved);
snap.put("model", body.getModel());
snap.put("duration", body.getDuration());
snap.put("resolution", body.getResolution());
snap.put("ratio", body.getRatio());
if (StringUtils.isNotEmpty(req.getFirstUrl())) {
snap.put("firstUrl", req.getFirstUrl());
}
if (StringUtils.isNotEmpty(req.getLastUrl())) {
snap.put("lastUrl", req.getLastUrl());
}
if (StringUtils.isNotEmpty(req.getReferenceUrl())) {
snap.put("referenceUrl", req.getReferenceUrl());
}
if (req.getContent() != null && !req.getContent().isEmpty()) {
snap.put("content", req.getContent());
}
aiOrder.setVideoParams(OM.writeValueAsString(snap));
} catch (Exception e) {
aiOrder.setVideoParams("{\"error\":\"video_params_serialize_failed\"}");
}
}
/** 写入 video_params.volcTaskId任务成功后 result 会改为成品 URL仍应用此 id 校验归属与轮询 */
private void mergeVolcTaskIdIntoVideoParams(AiOrder aiOrder, String volcTaskId) {
if (StringUtils.isEmpty(volcTaskId)) {
return;
}
try {
ObjectNode node;
if (StringUtils.isNotEmpty(aiOrder.getVideoParams())) {
JsonNode existing = OM.readTree(aiOrder.getVideoParams());
if (existing instanceof ObjectNode) {
node = (ObjectNode) existing;
} else {
node = OM.createObjectNode();
node.set("snapshotBeforeVolcId", existing);
}
} else {
node = OM.createObjectNode();
}
node.put("volcTaskId", volcTaskId);
aiOrder.setVideoParams(OM.writeValueAsString(node));
} catch (Exception e) {
aiOrder.setVideoParams("{\"volcTaskId\":\"" + volcTaskId.replace("\"", "") + "\"}");
}
}
private AjaxResult submitOrderAndCreate(PortalVideoGenRequest req, String mode, ByteBodyReq byteBodyReq) {
String functionType = resolveFunctionType(req);
AiOrder aiOrder = aiOrderService.getAiOrder(functionType);
if (aiOrder == null) {
return AjaxResult.error(-1, "You have a low balance, please recharge");
}
try {
fillVideoOrderRecord(aiOrder, req, mode, byteBodyReq, functionType);
aiOrderService.updateAiOrder(aiOrder);
String key = apiKey();
ByteBodyRes byteBodyRes = byteService.imgToVideo(byteBodyReq, key);
String id = byteBodyRes.getId();
if (id == null) {
aiOrderService.orderFailure(aiOrder);
return AjaxResult.error(-2, "generation failed, balance has been refunded");
}
mergeVolcTaskIdIntoVideoParams(aiOrder, id);
aiOrder.setResult(id);
aiOrderService.orderSuccess(aiOrder);
return AjaxResult.success(byteBodyRes);
} catch (Exception e) {
aiOrderService.orderFailure(aiOrder);
throw new RuntimeException(e);
}
}
@PostMapping("/text-to-video")
@ApiOperation("文生视频")
public AjaxResult textToVideo(@RequestBody PortalVideoGenRequest request) {
List<ContentItem> contentList;
if (request.getContent() != null && !request.getContent().isEmpty()) {
contentList = request.getContent();
} else {
if (StringUtils.isEmpty(request.getText())) {
return AjaxResult.error("请输入视频描述文本");
}
contentList = new ArrayList<>();
ContentItem textItem = new ContentItem();
textItem.setType("text");
textItem.setText(request.getText());
contentList.add(textItem);
}
ByteBodyReq body = newVideoBody(request, contentList);
return submitOrderAndCreate(request, "text-to-video", body);
}
@PostMapping("/image-first-frame")
@ApiOperation("图生视频-基于首帧")
public AjaxResult imageFirstFrame(@RequestBody PortalVideoGenRequest request) {
if (StringUtils.isEmpty(request.getFirstUrl())) {
return AjaxResult.error("请上传首帧图片");
}
if (StringUtils.isEmpty(request.getText())) {
return AjaxResult.error("请输入视频描述文本");
}
List<ContentItem> contentList = buildTextAndFirstFrame(request.getText(), request.getFirstUrl());
ByteBodyReq body = newVideoBody(request, contentList);
return submitOrderAndCreate(request, "image-first-frame", body);
}
@PostMapping("/image-first-last-frame")
@ApiOperation("图生视频-基于首尾帧")
public AjaxResult imageFirstLastFrame(@RequestBody PortalVideoGenRequest request) {
if (StringUtils.isEmpty(request.getFirstUrl()) || StringUtils.isEmpty(request.getLastUrl())) {
return AjaxResult.error("请同时上传首帧与尾帧图片");
}
if (StringUtils.isEmpty(request.getText())) {
return AjaxResult.error("请输入视频描述文本");
}
List<ContentItem> contentList = buildTextAndFirstFrame(request.getText(), request.getFirstUrl());
ContentItem lastFrameItem = new ContentItem();
lastFrameItem.setType("image_url");
lastFrameItem.setRole("last_frame");
ImageUrl lastImageUrl = new ImageUrl();
lastImageUrl.setUrl(request.getLastUrl());
lastFrameItem.setImageUrl(lastImageUrl);
contentList.add(lastFrameItem);
ByteBodyReq body = newVideoBody(request, contentList);
return submitOrderAndCreate(request, "image-first-last-frame", body);
}
@PostMapping("/image-reference")
@ApiOperation("图生视频-基于参考图")
public AjaxResult imageReference(@RequestBody PortalVideoGenRequest request) {
List<ContentItem> contentList;
if (request.getContent() != null && !request.getContent().isEmpty()) {
contentList = new ArrayList<>(request.getContent());
ContentItem head = contentList.get(0);
if (head == null || !"text".equals(head.getType()) || StringUtils.isEmpty(head.getText())) {
return AjaxResult.error("请输入视频描述文本(首条须为 type=text可含 [图片n]/[视频n]/[音频n] 占位)");
}
// 保留 text + 合法 reference_*//视频允许只有 text 没有参考素材
List<ContentItem> filtered = new ArrayList<>();
filtered.add(head);
for (int i = 1; i < contentList.size(); i++) {
ContentItem it = contentList.get(i);
if (isReferenceImageContentItem(it)
|| isReferenceAudioContentItem(it)
|| isReferenceVideoContentItem(it)) {
filtered.add(it);
}
}
contentList = filtered;
String firstRef = contentList.stream()
.skip(1)
.map(PortalVideoController::firstReferenceUrlFromItem)
.filter(StringUtils::isNotEmpty)
.findFirst()
.orElse(null);
// 无参考图也允许 text 提示词
if (StringUtils.isNotEmpty(firstRef) && StringUtils.isEmpty(request.getReferenceUrl())) {
request.setReferenceUrl(firstRef);
}
} else {
if (StringUtils.isEmpty(request.getReferenceUrl())) {
return AjaxResult.error("请上传参考图");
}
if (StringUtils.isEmpty(request.getText())) {
return AjaxResult.error("请输入视频描述文本");
}
contentList = new ArrayList<>();
ContentItem textItem = new ContentItem();
textItem.setType("text");
textItem.setText(request.getText());
contentList.add(textItem);
ContentItem refItem = new ContentItem();
refItem.setType("image_url");
refItem.setRole("reference_image");
ImageUrl refUrl = new ImageUrl();
refUrl.setUrl(request.getReferenceUrl());
refItem.setImageUrl(refUrl);
contentList.add(refItem);
}
ByteBodyReq body = newVideoBody(request, contentList);
return submitOrderAndCreate(request, "image-reference", body);
}
private static String firstReferenceUrlFromItem(ContentItem item) {
if (isReferenceImageContentItem(item)) {
return item.getImageUrl().getUrl();
}
if (isReferenceVideoContentItem(item)) {
return item.getVideoUrl().getUrl();
}
if (isReferenceAudioContentItem(item)) {
return item.getAudioUrl().getUrl();
}
return null;
}
private static boolean isValidReferenceAssetOrHttpUrl(String raw) {
if (StringUtils.isEmpty(raw)) {
return false;
}
String url = raw.trim().toLowerCase();
return url.startsWith("http://") || url.startsWith("https://") || url.startsWith("asset://");
}
private static boolean isReferenceImageContentItem(ContentItem item) {
if (item == null || !"image_url".equals(item.getType())) {
return false;
}
if (!"reference_image".equals(item.getRole())) {
return false;
}
ImageUrl iu = item.getImageUrl();
if (iu == null || StringUtils.isEmpty(iu.getUrl())) {
return false;
}
return isValidReferenceAssetOrHttpUrl(iu.getUrl());
}
private static boolean isReferenceVideoContentItem(ContentItem item) {
if (item == null || !"video_url".equals(item.getType())) {
return false;
}
if (!"reference_video".equals(item.getRole())) {
return false;
}
ImageUrl vu = item.getVideoUrl();
if (vu == null || StringUtils.isEmpty(vu.getUrl())) {
return false;
}
return isValidReferenceAssetOrHttpUrl(vu.getUrl());
}
private static boolean isReferenceAudioContentItem(ContentItem item) {
if (item == null || !"audio_url".equals(item.getType())) {
return false;
}
if (!"reference_audio".equals(item.getRole())) {
return false;
}
ImageUrl au = item.getAudioUrl();
if (au == null || StringUtils.isEmpty(au.getUrl())) {
return false;
}
return isValidReferenceAssetOrHttpUrl(au.getUrl());
}
private List<ContentItem> buildTextAndFirstFrame(String text, String firstUrl) {
List<ContentItem> 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);
firstFrameItem.setImageUrl(firstImageUrl);
contentList.add(firstFrameItem);
return contentList;
}
@GetMapping("/options")
@ApiOperation("门户视频生成可选参数(模型/比例/时长等,来自配置)")
public AjaxResult videoParamOptions() {
Map<String, Object> data = new LinkedHashMap<>();
data.put("defaults", portalVideoProperties.getDefaults());
data.put("models", portalVideoProperties.getModels());
data.put("ratios", portalVideoProperties.getRatios());
data.put("durations", portalVideoProperties.getDurations());
data.put("resolutions", portalVideoProperties.getResolutions());
return AjaxResult.success(data);
}
@GetMapping("/tasks")
@ApiOperation("查询视频生成任务列表(本用户库表分页)")
public TableDataInfo listMyVideoTasks(AiOrder aiOrder) {
aiOrder.setUserId(SecurityUtils.getAiUserId());
aiOrder.setType("21");
startPage();
List<AiOrder> list = aiOrderService.selectAiOrderList(aiOrder);
return getDataTable(list);
}
@GetMapping("/volc-tasks")
@ApiOperation("查询视频生成任务列表(火山平台,按本用户在库中的任务 id 过滤)")
public AjaxResult listVolcTasks(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "20") int pageSize) throws Exception {
Long uid = SecurityUtils.getAiUserId();
String key = apiKey();
String raw = byteService.listVideoGenerationTasks(pageNum, pageSize, key);
String filtered = filterVolcTasksJsonForUser(raw, uid);
return AjaxResult.success(OM.readTree(filtered));
}
private String filterVolcTasksJsonForUser(String raw, Long userId) throws Exception {
AiOrder query = new AiOrder();
query.setUserId(userId);
query.setType("21");
List<AiOrder> mine = aiOrderService.selectAiOrderList(query);
Set<String> allowed = mine.stream()
.map(AiOrder::getResult)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toSet());
JsonNode root = OM.readTree(raw);
if (!(root instanceof ObjectNode)) {
return raw;
}
ArrayNode arr = null;
String arrayKey = null;
if (root.has("items") && root.get("items").isArray()) {
arr = (ArrayNode) root.get("items");
arrayKey = "items";
} else if (root.has("data") && root.get("data").isArray()) {
arr = (ArrayNode) root.get("data");
arrayKey = "data";
}
if (arr == null) {
return raw;
}
ArrayNode out = OM.createArrayNode();
for (JsonNode n : arr) {
String tid = n.has("id") ? n.get("id").asText() : null;
if (tid != null && allowed.contains(tid)) {
out.add(n);
}
}
ObjectNode result = ((ObjectNode) root).deepCopy();
result.set(arrayKey, out);
result.put("filtered_total", out.size());
return OM.writeValueAsString(result);
}
@GetMapping("/tasks/{taskId}")
@ApiOperation("查询单个视频生成任务(火山)")
public AjaxResult getVolcTask(@PathVariable String taskId) throws Exception {
Long uid = SecurityUtils.getAiUserId();
AiOrder owned = aiOrderService.getAiOrderByPortalVideoTask(taskId);
if (owned == null || !uid.equals(owned.getUserId())) {
return AjaxResult.error("无权查看该任务");
}
String key = apiKey();
ByteBodyRes byteBodyRes = byteService.uploadVideo(taskId, key);
if ("succeeded".equals(byteBodyRes.getStatus())) {
content contentObj = byteBodyRes.getContent();
if (contentObj != null && StringUtils.isNotEmpty(contentObj.getVideo_url())) {
String videoUrl = tencentCosUtil.uploadFileByUrl(contentObj.getVideo_url());
if (videoUrl != null) {
contentObj.setVideo_url(videoUrl);
AiOrder aiOrder = new AiOrder();
aiOrder.setId(owned.getId());
aiOrder.setResult(videoUrl);
aiOrderService.updateAiOrder(aiOrder);
}
}
}
return AjaxResult.success(byteBodyRes);
}
@DeleteMapping("/tasks/{taskId}")
@ApiOperation("删除或取消视频生成任务")
public AjaxResult deleteOrCancelTask(@PathVariable String taskId) throws Exception {
Long uid = SecurityUtils.getAiUserId();
AiOrder owned = aiOrderService.getAiOrderByPortalVideoTask(taskId);
if (owned == null || !uid.equals(owned.getUserId())) {
return AjaxResult.error("无权操作该任务");
}
String key = apiKey();
AjaxResult cancelRes = byteService.cancelVideoTask(taskId, key);
if (cancelRes.isSuccess() && owned.getStatus() != null && owned.getStatus() == 0) {
aiOrderService.orderFailure(owned);
}
return cancelRes;
}
}

View File

@ -31,6 +31,11 @@ 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

@ -0,0 +1,38 @@
package com.ruoyi.api.request;
import com.ruoyi.ai.domain.ContentItem;
import lombok.Data;
import java.util.List;
/**
* 门户视频生成火山 Seedance请求体
*/
@Data
public class PortalVideoGenRequest {
private String text;
/**
* 文生视频时可选多段文本 + 参考图与火山 content 一致不传则仅使用 text 单行
*/
private List<ContentItem> content;
/** 默认与后台配置的视频计费类型一致 */
private String functionType = "21";
private String model;
private Integer duration;
private String resolution;
private String ratio;
private String firstUrl;
private String lastUrl;
/** 图生视频-参考图模式 */
private String referenceUrl;
}

View File

@ -0,0 +1,153 @@
package com.ruoyi.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 门户视频生成参数模型比例时长分辨率等从配置读取不在代码中写死业务默认值
*/
@Component
@ConfigurationProperties(prefix = "portal.video")
public class PortalVideoProperties {
private Defaults defaults = new Defaults();
private List<ModelOption> models = new ArrayList<>();
private List<String> ratios = new ArrayList<>();
private List<Integer> durations = new ArrayList<>();
private List<String> resolutions = new ArrayList<>();
/**
* ai_manager.type 一致用于扣费与订单库中需存在对应记录且 status=0del_flag=0
*/
private String functionType = "21";
public String getFunctionType() {
return functionType;
}
public void setFunctionType(String functionType) {
this.functionType = functionType != null ? functionType : "21";
}
public Defaults getDefaults() {
return defaults;
}
public void setDefaults(Defaults defaults) {
if (defaults != null) {
this.defaults = defaults;
}
}
public List<ModelOption> getModels() {
return models;
}
public void setModels(List<ModelOption> models) {
this.models = models != null ? models : new ArrayList<>();
}
public List<String> getRatios() {
return ratios;
}
public void setRatios(List<String> ratios) {
this.ratios = ratios != null ? ratios : new ArrayList<>();
}
public List<Integer> getDurations() {
return durations;
}
public void setDurations(List<Integer> durations) {
this.durations = durations != null ? durations : new ArrayList<>();
}
public List<String> getResolutions() {
return resolutions;
}
public void setResolutions(List<String> resolutions) {
this.resolutions = resolutions != null ? resolutions : new ArrayList<>();
}
/**
* defaults.model 为空时 models 第一项的 value
*/
public String resolveDefaultModelId() {
if (defaults.getModel() != null && !defaults.getModel().isEmpty()) {
return defaults.getModel();
}
if (!models.isEmpty() && models.get(0).getValue() != null) {
return models.get(0).getValue();
}
return null;
}
public static class Defaults {
private String model;
private Integer duration;
private String resolution;
private String ratio;
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public Integer getDuration() {
return duration;
}
public void setDuration(Integer duration) {
this.duration = duration;
}
public String getResolution() {
return resolution;
}
public void setResolution(String resolution) {
this.resolution = resolution;
}
public String getRatio() {
return ratio;
}
public void setRatio(String ratio) {
this.ratio = ratio;
}
}
public static class ModelOption {
private String label;
private String value;
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}

View File

@ -0,0 +1,120 @@
package com.ruoyi.web.controller.ai;
import java.util.List;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.ai.service.IAiUserService;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysDeptService;
/**
* AI 业务侧部门管理数据源与系统部门 sys_dept 一致权限独立
*/
@RestController
@RequestMapping("/ai/dept")
public class AiDeptController extends BaseController
{
@Autowired
private ISysDeptService deptService;
@Autowired
private IAiUserService aiUserService;
@PreAuthorize("@ss.hasPermi('ai:dept:list')")
@GetMapping("/list")
public AjaxResult list(SysDept dept)
{
List<SysDept> depts = deptService.selectDeptList(dept);
return success(depts);
}
@PreAuthorize("@ss.hasPermi('ai:dept:list')")
@GetMapping("/list/exclude/{deptId}")
public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId)
{
List<SysDept> depts = deptService.selectDeptList(new SysDept());
depts.removeIf(d -> d.getDeptId().intValue() == deptId || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + ""));
return success(depts);
}
@PreAuthorize("@ss.hasPermi('ai:dept:query')")
@GetMapping(value = "/{deptId}")
public AjaxResult getInfo(@PathVariable Long deptId)
{
deptService.checkDeptDataScope(deptId);
return success(deptService.selectDeptById(deptId));
}
@PreAuthorize("@ss.hasPermi('ai:dept:add')")
@Log(title = "AI部门管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody SysDept dept)
{
if (!deptService.checkDeptNameUnique(dept))
{
return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
}
dept.setCreateBy(getUsername());
return toAjax(deptService.insertDept(dept));
}
@PreAuthorize("@ss.hasPermi('ai:dept:edit')")
@Log(title = "AI部门管理", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody SysDept dept)
{
Long deptId = dept.getDeptId();
deptService.checkDeptDataScope(deptId);
if (!deptService.checkDeptNameUnique(dept))
{
return error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
}
else if (dept.getParentId().equals(deptId))
{
return error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
}
else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) && deptService.selectNormalChildrenDeptById(deptId) > 0)
{
return error("该部门包含未停用的子部门!");
}
dept.setUpdateBy(getUsername());
return toAjax(deptService.updateDept(dept));
}
@PreAuthorize("@ss.hasPermi('ai:dept:remove')")
@Log(title = "AI部门管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{deptId}")
public AjaxResult remove(@PathVariable Long deptId)
{
if (deptService.hasChildByDeptId(deptId))
{
return warn("存在下级部门,不允许删除");
}
if (deptService.checkDeptExistUser(deptId))
{
return warn("部门存在系统用户,不允许删除");
}
if (aiUserService.countAiUserByDeptId(deptId) > 0)
{
return warn("部门存在 AI 用户,不允许删除");
}
deptService.checkDeptDataScope(deptId);
return toAjax(deptService.deleteDeptById(deptId));
}
}

View File

@ -93,6 +93,20 @@ public class AiUserController extends BaseController {
return toAjax(aiUserService.updateAiUser(aiUser)); return toAjax(aiUserService.updateAiUser(aiUser));
} }
/**
* 分配归属部门deptId 为空表示不归属任何部门
*/
@ApiOperation("分配AI用户归属部门")
@PreAuthorize("@ss.hasPermi('ai:user:edit')")
@Log(title = "ai-用户信息", businessType = BusinessType.UPDATE)
@PutMapping("/dept")
public AjaxResult assignDept(@RequestBody AiUser aiUser) {
if (aiUser.getId() == null) {
return error("用户主键不能为空");
}
return toAjax(aiUserService.updateAiUserDept(aiUser.getId(), aiUser.getDeptId()));
}
/** /**
* 状态修改 * 状态修改
*/ */

View File

@ -4,7 +4,6 @@ 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;
@ -36,7 +35,6 @@ 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;
/** /**
* 通用下载请求 * 通用下载请求
@ -158,7 +156,7 @@ public class CommonController {
} }
/** /**
* AWS上传请求单个 * 腾讯云COS上传请求单个
*/ */
@ApiOperation("图片上传接口") @ApiOperation("图片上传接口")
@PostMapping("/aws/upload") @PostMapping("/aws/upload")
@ -166,7 +164,7 @@ public class CommonController {
AjaxResult ajax = AjaxResult.success(); AjaxResult ajax = AjaxResult.success();
String uploadUrl; String uploadUrl;
try { try {
uploadUrl = awsS3Util.uploadMultipartFile(file, true); uploadUrl = tencentCosUtil.uploadMultipartFile(file, true);
} catch (Exception e) { } catch (Exception e) {
return AjaxResult.error(e.getMessage()); return AjaxResult.error(e.getMessage());
} }

View File

@ -209,24 +209,68 @@ google:
redirect-uri: redirect-uri:
tencentCos: tencentCos:
accessKey: accessKey: AKIDBE3dzBdLsHYfZLwKVSFArLchZDerrfHf
secretKey: secretKey: EDyUmsnX2IJ5f0oRn1QdeQ0TmrtqgQ1c
endpoint: endpoint: ap-guangzhou
bucketName: bucketName: seedance-1331490964
domain: domain: https://seedance-1331490964.cos.ap-guangzhou.myqcloud.com
aws: # aws配置已替换为腾讯云COS请在环境变量或配置文件中设置腾讯云凭证
accessKey: AKIAYVMHEVDDZQGE3HVX # aws:
secretKey: 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 # accessKey: AKIAYVMHEVDDZQGE3HVX
endpoint: ap-southeast-1 # secretKey: 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
bucketName: di-image # endpoint: ap-southeast-1
domain: https://images.iqyjsnwv.com/ # bucketName: di-image
# 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: 327d2815-2516-44c2-9e32-2dc50bf7afd7 apiKey: 3e33e034-7e25-4228-8864-b51b2a7a8f97
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
# 门户视频生成页:模型 / 比例 / 时长 / 分辨率均由此处维护,前后端不写死业务枚举
portal:
video:
# 与库表 ai_manager.type 一致(用于扣费);若报错 functionType does not exist请插入对应 type 或改此处与库一致
function-type: "21"
defaults:
model: ep-20260326165811-dlkth
duration: 4
resolution: 720p
ratio: "3:4"
models:
- label: Seedance 2.0
value: ep-20260326165811-dlkth
- label: Seedance 2.0 Fast
value: ep-20260326170056-dkj9m
ratios:
- "16:9"
- "9:16"
- "3:4"
- "1:1"
- "4:3"
durations:
- 4
- 5
- 6
- 8
- 10
- 11
- 12
- 13
- 14
- 15
resolutions:
- "720p"
- "1080p"
jinsha: jinsha:
url: https://api.jinshapay.xyz url: https://api.jinshapay.xyz
appId: 1763617360 appId: 1763617360

View File

@ -117,12 +117,19 @@ public class AiUser extends BaseEntity {
*/ */
private String country; private String country;
/** 归属部门IDsys_dept.dept_id */
private Long deptId;
/** /**
* 上级用户昵称 * 上级用户昵称
*/ */
@TableField(exist = false) @TableField(exist = false)
private String superiorName; private String superiorName;
/** 归属部门名称 */
@TableField(exist = false)
private String deptName;
/** /**
* 上级用户ID * 上级用户ID
*/ */

View File

@ -51,6 +51,9 @@ public class SysDept extends BaseEntity
/** 父部门名称 */ /** 父部门名称 */
private String parentName; private String parentName;
/** Byte API Key */
private String byteApiKey;
/** 子部门 */ /** 子部门 */
private List<SysDept> children = new ArrayList<SysDept>(); private List<SysDept> children = new ArrayList<SysDept>();
@ -171,6 +174,16 @@ public class SysDept extends BaseEntity
this.parentName = parentName; this.parentName = parentName;
} }
public String getByteApiKey()
{
return byteApiKey;
}
public void setByteApiKey(String byteApiKey)
{
this.byteApiKey = byteApiKey;
}
public List<SysDept> getChildren() public List<SysDept> getChildren()
{ {
return children; return children;
@ -192,6 +205,7 @@ public class SysDept extends BaseEntity
.append("leader", getLeader()) .append("leader", getLeader())
.append("phone", getPhone()) .append("phone", getPhone())
.append("email", getEmail()) .append("email", getEmail())
.append("byteApiKey", getByteApiKey())
.append("status", getStatus()) .append("status", getStatus())
.append("delFlag", getDelFlag()) .append("delFlag", getDelFlag())
.append("createBy", getCreateBy()) .append("createBy", getCreateBy())

View File

@ -13,7 +13,13 @@ 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
@ -33,22 +39,32 @@ public class TencentCosUtil {
private String domain; private String domain;
//文件上传 /**
public String upload(MultipartFile file) { * 上传MultipartFile到腾讯云COS返回文件访问地址
* 与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();
// 存储桶的命名格式为 BucketName-APPID此处填写的存储桶名称必须为此格式 // 生成唯一文件键格式与AWS一致yyyy/MM/dd/uuid_filename
// 对象键(Key)是对象在存储桶中的唯一标识 998u-09iu-09i-333 String key = generateCosKey(file.getOriginalFilename());
//在文件名称前面添加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(
@ -56,17 +72,41 @@ public class TencentCosUtil {
key, key,
inputStream, inputStream,
objectMetadata); objectMetadata);
// 高级接口会返回一个异步结果Upload
PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest); PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
//返回上传文件路径 // 返回COS文件访问地址
//https://ggkt-atguigu-1310644373.cos.ap-beijing.myqcloud.com/01.jpg String url = domain + (domain.endsWith("/") ? "" : "/") + key;
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;
} }
@ -78,7 +118,107 @@ 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,6 +68,28 @@ 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;
/** 火山/方舟模型 endpoint */
private String model;
/** 视频类订单:本次提交的完整参数 JSON提示词、模型、时长等 */
private String videoParams;
/** 首帧图片 */ /** 首帧图片 */
private String img1; private String img1;

View File

@ -30,5 +30,10 @@ 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

@ -15,6 +15,12 @@ public class ContentItem {
@JsonProperty("image_url") @JsonProperty("image_url")
private ImageUrl imageUrl; private ImageUrl imageUrl;
@JsonProperty("video_url")
private ImageUrl videoUrl;
@JsonProperty("audio_url")
private ImageUrl audioUrl;
@JsonProperty("role") @JsonProperty("role")
private String role; private String role;
} }

View File

@ -22,5 +22,8 @@ public interface AiOrderMapper extends BaseMapper<AiOrder> {
AiOrder getAiOrderByResult(@Param("result") String result); AiOrder getAiOrderByResult(@Param("result") String result);
/** 门户视频:按火山任务 id 查单result 可能已被替换为成品 URL故兼查 video_params.volcTaskId */
AiOrder getAiOrderByPortalVideoTask(@Param("taskId") String taskId);
BigDecimal getSumAmountByUserId(@Param("userId") String userId); BigDecimal getSumAmountByUserId(@Param("userId") String userId);
} }

View File

@ -6,6 +6,7 @@ import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.common.core.domain.entity.AiUser; import com.ruoyi.common.core.domain.entity.AiUser;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
/** /**
* ai-用户信息Mapper接口 * ai-用户信息Mapper接口
@ -22,4 +23,9 @@ public interface AiUserMapper extends BaseMapper<AiUser> {
AiUser selectAiUserById(Long id); AiUser selectAiUserById(Long id);
AiUser getUserInfo(Long id); AiUser getUserInfo(Long id);
int countAiUserByDeptId(@Param("deptId") Long deptId);
@Update("update ai_user set dept_id = #{deptId}, update_time = sysdate() where id = #{userId}")
int updateAiUserDeptId(@Param("userId") Long userId, @Param("deptId") Long deptId);
} }

View File

@ -79,5 +79,7 @@ public interface IAiOrderService {
AiOrder getAiOrderByResult(String result); AiOrder getAiOrderByResult(String result);
AiOrder getAiOrderByPortalVideoTask(String taskId);
BigDecimal getSumAmountByUserId(String userId); BigDecimal getSumAmountByUserId(String userId);
} }

View File

@ -99,6 +99,16 @@ public interface IAiUserService {
int updatePassword(AiUser aiUser); int updatePassword(AiUser aiUser);
/**
* 部门下 AI 用户数量用于删除部门前校验
*/
int countAiUserByDeptId(Long deptId);
/**
* 分配 AI 用户归属部门deptId 为空则清空
*/
int updateAiUserDept(Long userId, Long deptId);
public void deductSampleAmount(String userId); public void deductSampleAmount(String userId);
void handleRebate(String orderNo, Long userId, BigDecimal rechargeAmount); void handleRebate(String orderNo, Long userId, BigDecimal rechargeAmount);

View File

@ -0,0 +1,12 @@
package com.ruoyi.ai.service;
/**
* 门户用户火山方舟 API Key取自所属二级部门 {@code sys_dept.byte_api_key} Redis 缓存
*/
public interface IByteDeptApiKeyService {
/**
* Redis {userId}_byte_api_key优先读缓存再读库均无有效 key 则抛出业务异常
*/
String resolveVolcApiKey(Long aiUserId);
}

View File

@ -2,6 +2,7 @@ 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 {
@ -16,12 +17,31 @@ public interface IByteService {
ByteBodyRes imgToImg(ByteBodyReq req) throws Exception; ByteBodyRes imgToImg(ByteBodyReq req) throws Exception;
/** /**
* 首尾帧图生视频 * 首尾帧图生视频使用全局配置的 Ark API Key
*/ */
ByteBodyRes imgToVideo(ByteBodyReq req) throws Exception; ByteBodyRes imgToVideo(ByteBodyReq req) throws Exception;
/**
* 视频生成任务创建指定 Ark API Key门户按部门密钥调用
*/
ByteBodyRes imgToVideo(ByteBodyReq req, String arkApiKey) throws Exception;
/** /**
* 下载视频 * 下载视频
*/ */
ByteBodyRes uploadVideo(String id) throws Exception; ByteBodyRes uploadVideo(String id) throws Exception;
ByteBodyRes uploadVideo(String id, String arkApiKey) throws Exception;
/**
* 取消视频生成任务
*/
AjaxResult cancelVideoTask(String id) throws Exception;
AjaxResult cancelVideoTask(String id, String arkApiKey) throws Exception;
/**
* GET 查询视频生成任务列表火山 list 文档返回原始 JSON 字符串
*/
String listVideoGenerationTasks(int pageNum, int pageSize, String arkApiKey) throws Exception;
} }

View File

@ -124,6 +124,8 @@ public class AiManagerServiceImpl implements IAiManagerService {
public AiManager selectAiManagerByType(String aiType) { public AiManager selectAiManagerByType(String aiType) {
QueryWrapper<AiManager> queryWrapper = new QueryWrapper<>(); QueryWrapper<AiManager> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("type", aiType); queryWrapper.eq("type", aiType);
queryWrapper.eq("del_flag", "0");
queryWrapper.eq("status", 0);
return aiManagerMapper.selectOne(queryWrapper); return aiManagerMapper.selectOne(queryWrapper);
} }
} }

View File

@ -147,7 +147,9 @@ public class AiOrderServiceImpl implements IAiOrderService {
public AiOrder getAiOrder(String aiType) { public AiOrder getAiOrder(String aiType) {
AiManager aiManager = aiManagerService.selectAiManagerByType(aiType); AiManager aiManager = aiManagerService.selectAiManagerByType(aiType);
if (aiManager == null) { if (aiManager == null) {
throw new ServiceException("Corresponding functionType does not exist", HttpStatus.BAD_REQUEST); throw new ServiceException(
"未找到可用的功能类型请在「AI管理」中新增 type=" + aiType + " 且状态为正常的记录,或执行 sql/seed_ai_manager_type_21.sql 初始化",
HttpStatus.BAD_REQUEST);
} }
// 判断用户余额是否足够 // 判断用户余额是否足够
AiUser aiUser = aiUserService.selectAiUserById(SecurityUtils.getAiUserId()); AiUser aiUser = aiUserService.selectAiUserById(SecurityUtils.getAiUserId());
@ -201,6 +203,11 @@ public class AiOrderServiceImpl implements IAiOrderService {
return aiOrderMapper.selectOne(query); return aiOrderMapper.selectOne(query);
} }
@Override
public AiOrder getAiOrderByPortalVideoTask(String taskId) {
return aiOrderMapper.getAiOrderByPortalVideoTask(taskId);
}
public int getChangerType(String aiType) { public int getChangerType(String aiType) {
switch (aiType) { switch (aiType) {
case "11": case "11":

View File

@ -132,6 +132,19 @@ public class AiUserServiceImpl implements IAiUserService {
return aiUserMapper.updateById(aiUser); return aiUserMapper.updateById(aiUser);
} }
@Override
public int countAiUserByDeptId(Long deptId) {
if (deptId == null) {
return 0;
}
return aiUserMapper.countAiUserByDeptId(deptId);
}
@Override
public int updateAiUserDept(Long userId, Long deptId) {
return aiUserMapper.updateAiUserDeptId(userId, deptId);
}
/** /**
* 批量删除ai-用户信息 * 批量删除ai-用户信息
* *

View File

@ -0,0 +1,99 @@
package com.ruoyi.ai.service.impl;
import com.ruoyi.ai.service.IByteDeptApiKeyService;
import com.ruoyi.ai.service.IAiUserService;
import com.ruoyi.common.core.domain.entity.AiUser;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysDeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class ByteDeptApiKeyServiceImpl implements IByteDeptApiKeyService {
private static final String NO_DEPT_MSG = "用户未分配部门:请在后台为门户用户设置 ai_user.dept_id关联 sys_dept.dept_id";
private static final String NO_DEPT_ROW_MSG = "用户所属部门不存在或已删除,请核对 ai_user.dept_id";
private static final String NO_API_KEY_MSG = "部门未配置火山 API Key请在 sys_dept 为用户所在部门(或其上级二级部门)配置 byte_api_key";
private static final int CACHE_HOURS = 1;
@Autowired
private RedisCache redisCache;
@Autowired
private IAiUserService aiUserService;
@Autowired
private ISysDeptService sysDeptService;
@Override
public String resolveVolcApiKey(Long aiUserId) {
if (aiUserId == null) {
throw new ServiceException(NO_DEPT_MSG);
}
String cacheKey = aiUserId + "_byte_api_key";
String cached = redisCache.getCacheObject(cacheKey);
if (StringUtils.isNotEmpty(cached)) {
return cached;
}
AiUser aiUser = aiUserService.selectAiUserById(aiUserId);
if (aiUser == null || aiUser.getDeptId() == null) {
throw new ServiceException(NO_DEPT_MSG);
}
SysDept userDept = sysDeptService.selectDeptById(aiUser.getDeptId());
if (userDept == null) {
throw new ServiceException(NO_DEPT_ROW_MSG);
}
// 优先使用用户直接归属部门的 Key多数业务把用户挂在分公司 101并在该节点配 byte_api_key
String apiKey = trimKey(userDept.getByteApiKey());
if (StringUtils.isEmpty(apiKey)) {
Long fallbackDeptId = resolveSecondLevelDeptId(userDept);
if (!fallbackDeptId.equals(userDept.getDeptId())) {
SysDept keyDept = sysDeptService.selectDeptById(fallbackDeptId);
if (keyDept != null) {
apiKey = trimKey(keyDept.getByteApiKey());
}
}
}
if (StringUtils.isEmpty(apiKey)) {
throw new ServiceException(NO_API_KEY_MSG);
}
redisCache.setCacheObject(cacheKey, apiKey, CACHE_HOURS, TimeUnit.HOURS);
return apiKey;
}
private static String trimKey(String raw) {
return raw == null ? null : raw.trim();
}
/**
* 二级部门祖级路径中紧接在一级ancestors 第二段之下的部门节点
* 深度不足时退回用户当前部门
*/
private Long resolveSecondLevelDeptId(SysDept userDept) {
String ancestors = userDept.getAncestors();
if (StringUtils.isEmpty(ancestors)) {
return userDept.getDeptId();
}
String[] parts = ancestors.split(",");
if (parts.length >= 3) {
try {
return Long.parseLong(parts[2].trim());
} catch (NumberFormatException ignored) {
return userDept.getDeptId();
}
}
if (parts.length == 2) {
try {
return Long.parseLong(parts[1].trim());
} catch (NumberFormatException ignored) {
return userDept.getDeptId();
}
}
return userDept.getDeptId();
}
}

View File

@ -6,8 +6,10 @@ 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.HttpUrl;
import okhttp3.*; 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;
@ -30,6 +32,13 @@ 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);
@ -75,71 +84,148 @@ public class ByteService implements IByteService {
@Override @Override
public ByteBodyRes imgToVideo(ByteBodyReq req) throws Exception { public ByteBodyRes imgToVideo(ByteBodyReq req) throws Exception {
return imgToVideo(req, volcApiKey);
}
// 1. 验证请求参数可选根据业务需求 @Override
// if (StringUtils.isBlank(req.getPrompt())) { public ByteBodyRes imgToVideo(ByteBodyReq req, String arkApiKey) throws Exception {
// throw new Exception("imgToVideo errorprompt is null"); if (req == null) {
// } throw new Exception("imgToVideo errorreq is null");
}
if (StringUtils.isBlank(arkApiKey)) {
throw new Exception("imgToVideo errorapiKey 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(API_URL + "/contents/generations/tasks") .url(volcBaseUrl + "/api/v3/contents/generations/tasks")
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("Authorization", "Bearer " + apiKey) .header("Authorization", "Bearer " + arkApiKey)
.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. 验证请求参数可选根据业务需求 return uploadVideo(id, volcApiKey);
}
@Override
public ByteBodyRes uploadVideo(String id, String arkApiKey) throws Exception {
if (StringUtils.isBlank(id)) { if (StringUtils.isBlank(id)) {
throw new Exception("uploadVideo errorid is null"); throw new Exception("uploadVideo errorid is null");
} }
if (StringUtils.isBlank(arkApiKey)) {
throw new Exception("uploadVideo errorapiKey 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(API_URL + "/contents/generations/tasks/" + id) .url(volcBaseUrl + "/api/v3/contents/generations/tasks/" + id)
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("Authorization", "Bearer " + apiKey) .header("Authorization", "Bearer " + arkApiKey)
.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 {
return cancelVideoTask(id, volcApiKey);
}
@Override
public AjaxResult cancelVideoTask(String id, String arkApiKey) throws Exception {
if (StringUtils.isBlank(id)) {
return AjaxResult.error("任务ID不能为空");
}
if (StringUtils.isBlank(arkApiKey)) {
return AjaxResult.error("API Key 无效");
}
try {
Request request = new Request.Builder()
.url(volcBaseUrl + "/api/v3/contents/generations/tasks/" + id)
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + arkApiKey)
.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());
}
}
@Override
public String listVideoGenerationTasks(int pageNum, int pageSize, String arkApiKey) throws Exception {
if (StringUtils.isBlank(arkApiKey)) {
throw new Exception("listVideoGenerationTasks errorapiKey is null");
}
int pn = pageNum > 0 ? pageNum : 1;
int ps = pageSize > 0 ? Math.min(pageSize, 500) : 10;
HttpUrl parsed = HttpUrl.parse(volcBaseUrl + "/api/v3/contents/generations/tasks");
if (parsed == null) {
throw new Exception("listVideoGenerationTasks errorinvalid base url");
}
HttpUrl url = parsed.newBuilder()
.addQueryParameter("page_num", String.valueOf(pn))
.addQueryParameter("page_size", String.valueOf(ps))
.build();
Request request = new Request.Builder()
.url(url)
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + arkApiKey)
.get()
.build();
Response response = OkHttpUtils.newCall(request).execute();
if (response.body() == null) {
throw new Exception("listVideoGenerationTasks response null");
}
String body = response.body().string();
if (!response.isSuccessful()) {
throw new Exception("listVideoGenerationTasks error" + body);
}
return body;
}
} }

View File

@ -21,10 +21,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="source" column="source" /> <result property="source" column="source" />
<result property="text" column="text" /> <result property="text" column="text" />
<result property="isTop" column="is_top" /> <result property="isTop" column="is_top" />
<result property="img1" column="img1" />
<result property="img2" column="img2" />
<result property="mode" column="mode" />
<result property="duration" column="duration" />
<result property="resolution" column="resolution" />
<result property="ratio" column="ratio" />
<result property="model" column="model" />
<result property="videoParams" column="video_params" />
</resultMap> </resultMap>
<sql id="selectAiOrderVo"> <sql id="selectAiOrderVo">
select ao.id, ao.del_flag, ao.create_by, ao.create_time, ao.update_by, ao.update_time, ao.remark, ao.order_num, ao.user_id, ao.type, ao.amount, ao.result, ao.status, ao.source, ao.text, ao.is_top, au.user_id uuid from ai_order ao select ao.id, ao.del_flag, ao.create_by, ao.create_time, ao.update_by, ao.update_time, ao.remark, ao.order_num, ao.user_id, ao.type, ao.amount, ao.result, ao.status, ao.source, ao.text, ao.is_top, ao.img1, ao.img2, ao.mode, ao.duration, ao.resolution, ao.ratio, ao.model, ao.video_params, au.user_id uuid from ai_order ao
left join ai_user au on au.id = ao.user_id left join ai_user au on au.id = ao.user_id
</sql> </sql>
@ -61,6 +69,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where result = #{result} where result = #{result}
</select> </select>
<select id="getAiOrderByPortalVideoTask" resultMap="AiOrderResult">
<include refid="selectAiOrderVo"/>
where ao.del_flag = '0'
and ao.type = '21'
and (
ao.result = #{taskId}
or (
ao.video_params is not null and ao.video_params != ''
and JSON_VALID(ao.video_params)
and JSON_UNQUOTE(JSON_EXTRACT(ao.video_params, '$.volcTaskId')) = #{taskId}
)
)
limit 1
</select>
<select id="getSumAmountByUserId" resultType="java.math.BigDecimal"> <select id="getSumAmountByUserId" resultType="java.math.BigDecimal">
SELECT COALESCE(sum(amount), 0) from ai_order where user_id = #{userId} SELECT COALESCE(sum(amount), 0) from ai_order where user_id = #{userId}
</select> </select>
@ -118,7 +141,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="amount != null">amount = #{amount},</if> <if test="amount != null">amount = #{amount},</if>
<if test="result != null">result = #{result},</if> <if test="result != null">result = #{result},</if>
<if test="status != null">status = #{status},</if> <if test="status != null">status = #{status},</if>
<if test="source != null">status = #{source},</if> <if test="source != null">source = #{source},</if>
<if test="text != null">text = #{text},</if> <if test="text != null">text = #{text},</if>
<if test="isTop != null">is_top = #{isTop},</if> <if test="isTop != null">is_top = #{isTop},</if>
</trim> </trim>

View File

@ -33,15 +33,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="source" column="source" /> <result property="source" column="source" />
<result property="ip" column="ip" /> <result property="ip" column="ip" />
<result property="country" column="country" /> <result property="country" column="country" />
<result property="deptId" column="dept_id" />
<result property="deptName" column="dept_name" />
</resultMap> </resultMap>
<sql id="selectAiUserVo"> <sql id="selectAiUserVo">
select id, del_flag, create_by, create_time, update_by, update_time, remark, username, nickname, gender, avatar, phone, password, openid, status, email, birthday, invitation_code, payment_url, login_time, balance, superior_id, user_id, source, ip, country from ai_user select id, del_flag, create_by, create_time, update_by, update_time, remark, username, nickname, gender, avatar, phone, password, openid, status, email, birthday, invitation_code, payment_url, login_time, balance, superior_id, user_id, source, ip, country, dept_id from ai_user
</sql> </sql>
<select id="selectAiUserList" parameterType="AiUser" resultMap="AiUserResult"> <select id="selectAiUserList" parameterType="AiUser" resultMap="AiUserResult">
select u.id, u.del_flag, u.create_by, u.create_time, u.update_by, u.update_time, u.remark, u.username, u.nickname, u.gender, u.avatar, u.phone, u.password, u.openid, u.status, u.email, u.birthday, u.invitation_code, u.payment_url, u.login_time, u.balance, u.superior_id, u.user_id, u.source, u.ip, u.country, au.user_id superiorUuid, au.username superiorName from ai_user u select u.id, u.del_flag, u.create_by, u.create_time, u.update_by, u.update_time, u.remark, u.username, u.nickname, u.gender, u.avatar, u.phone, u.password, u.openid, u.status, u.email, u.birthday, u.invitation_code, u.payment_url, u.login_time, u.balance, u.superior_id, u.user_id, u.source, u.ip, u.country, u.dept_id, d.dept_name, au.user_id superiorUuid, au.username superiorName from ai_user u
left join ai_user au on au.id = u.superior_id left join ai_user au on au.id = u.superior_id
left join sys_dept d on d.dept_id = u.dept_id and d.del_flag = '0'
<where> <where>
<if test="nickname != null and nickname != ''"> and u.nickname like concat('%', #{nickname}, '%')</if> <if test="nickname != null and nickname != ''"> and u.nickname like concat('%', #{nickname}, '%')</if>
<if test="username != null and username != ''"> and u.username like concat('%', #{username}, '%')</if> <if test="username != null and username != ''"> and u.username like concat('%', #{username}, '%')</if>
@ -63,19 +66,26 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="superiorId != null "> and u.superior_id = #{superiorId}</if> <if test="superiorId != null "> and u.superior_id = #{superiorId}</if>
<if test="id != null "> and u.id = #{id}</if> <if test="id != null "> and u.id = #{id}</if>
<if test="source != null "> and u.source = #{source}</if> <if test="source != null "> and u.source = #{source}</if>
<if test="deptId != null "> and u.dept_id = #{deptId}</if>
</where> </where>
order by u.id desc order by u.id desc
</select> </select>
<select id="selectAiUserById" parameterType="Long" resultMap="AiUserResult"> <select id="selectAiUserById" parameterType="Long" resultMap="AiUserResult">
<include refid="selectAiUserVo"/> select u.id, u.del_flag, u.create_by, u.create_time, u.update_by, u.update_time, u.remark, u.username, u.nickname, u.gender, u.avatar, u.phone, u.password, u.openid, u.status, u.email, u.birthday, u.invitation_code, u.payment_url, u.login_time, u.balance, u.superior_id, u.user_id, u.source, u.ip, u.country, u.dept_id, d.dept_name
where id = #{id} from ai_user u
left join sys_dept d on d.dept_id = u.dept_id and d.del_flag = '0'
where u.id = #{id}
</select> </select>
<select id="getUserInfo" parameterType="Long" resultMap="AiUserResult"> <select id="getUserInfo" parameterType="Long" resultMap="AiUserResult">
select del_flag, create_by, create_time, update_by, update_time, remark, username, nickname, gender, avatar, phone, password, openid, status, email, birthday, invitation_code, payment_url, login_time, balance, superior_id, user_id, source from ai_user select del_flag, create_by, create_time, update_by, update_time, remark, username, nickname, gender, avatar, phone, password, openid, status, email, birthday, invitation_code, payment_url, login_time, balance, superior_id, user_id, source, ip, country, dept_id from ai_user
where id = #{id} where id = #{id}
</select> </select>
<select id="countAiUserByDeptId" resultType="int">
select count(1) from ai_user where del_flag = '0' and dept_id = #{deptId}
</select>
<select id="selectPasswordById" resultType="java.lang.String"> <select id="selectPasswordById" resultType="java.lang.String">
select password from ai_user where id = #{id} select password from ai_user where id = #{id}
</select> </select>
@ -108,6 +118,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="source != null">source,</if> <if test="source != null">source,</if>
<if test="ip != null">ip,</if> <if test="ip != null">ip,</if>
<if test="country != null">country,</if> <if test="country != null">country,</if>
<if test="deptId != null">dept_id,</if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="delFlag != null and delFlag != ''">#{delFlag},</if> <if test="delFlag != null and delFlag != ''">#{delFlag},</if>
@ -134,6 +145,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="userId != null">#{userId},</if> <if test="userId != null">#{userId},</if>
<if test="source != null">#{source},</if> <if test="source != null">#{source},</if>
<if test="country != null">#{country},</if> <if test="country != null">#{country},</if>
<if test="deptId != null">#{deptId},</if>
</trim> </trim>
</insert> </insert>
@ -163,8 +175,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="superiorId != null">superior_id = #{superiorId},</if> <if test="superiorId != null">superior_id = #{superiorId},</if>
<if test="userId != null">user_id = #{userId},</if> <if test="userId != null">user_id = #{userId},</if>
<if test="source != null">source = #{source},</if> <if test="source != null">source = #{source},</if>
<if test="ip != null">source = #{ip},</if> <if test="ip != null">ip = #{ip},</if>
<if test="country != null">country = #{country},</if> <if test="country != null">country = #{country},</if>
<if test="deptId != null">dept_id = #{deptId},</if>
</trim> </trim>
where id = #{id} where id = #{id}
</update> </update>

View File

@ -13,6 +13,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="leader" column="leader" /> <result property="leader" column="leader" />
<result property="phone" column="phone" /> <result property="phone" column="phone" />
<result property="email" column="email" /> <result property="email" column="email" />
<result property="byteApiKey" column="byte_api_key" />
<result property="status" column="status" /> <result property="status" column="status" />
<result property="delFlag" column="del_flag" /> <result property="delFlag" column="del_flag" />
<result property="parentName" column="parent_name" /> <result property="parentName" column="parent_name" />
@ -23,7 +24,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectDeptVo"> <sql id="selectDeptVo">
select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.byte_api_key, d.status, d.del_flag, d.create_by, d.create_time
from sys_dept d from sys_dept d
</sql> </sql>
@ -59,7 +60,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="selectDeptById" parameterType="Long" resultMap="SysDeptResult"> <select id="selectDeptById" parameterType="Long" resultMap="SysDeptResult">
select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.byte_api_key, d.status,
(select dept_name from sys_dept where dept_id = d.parent_id) parent_name (select dept_name from sys_dept where dept_id = d.parent_id) parent_name
from sys_dept d from sys_dept d
where d.dept_id = #{deptId} where d.dept_id = #{deptId}
@ -101,6 +102,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="leader != null and leader != ''">leader,</if> <if test="leader != null and leader != ''">leader,</if>
<if test="phone != null and phone != ''">phone,</if> <if test="phone != null and phone != ''">phone,</if>
<if test="email != null and email != ''">email,</if> <if test="email != null and email != ''">email,</if>
<if test="byteApiKey != null">byte_api_key,</if>
<if test="status != null">status,</if> <if test="status != null">status,</if>
<if test="createBy != null and createBy != ''">create_by,</if> <if test="createBy != null and createBy != ''">create_by,</if>
create_time create_time
@ -113,6 +115,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="leader != null and leader != ''">#{leader},</if> <if test="leader != null and leader != ''">#{leader},</if>
<if test="phone != null and phone != ''">#{phone},</if> <if test="phone != null and phone != ''">#{phone},</if>
<if test="email != null and email != ''">#{email},</if> <if test="email != null and email != ''">#{email},</if>
<if test="byteApiKey != null">#{byteApiKey},</if>
<if test="status != null">#{status},</if> <if test="status != null">#{status},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="createBy != null and createBy != ''">#{createBy},</if>
sysdate() sysdate()
@ -129,6 +132,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="leader != null">leader = #{leader},</if> <if test="leader != null">leader = #{leader},</if>
<if test="phone != null">phone = #{phone},</if> <if test="phone != null">phone = #{phone},</if>
<if test="email != null">email = #{email},</if> <if test="email != null">email = #{email},</if>
<if test="byteApiKey != null">byte_api_key = #{byteApiKey},</if>
<if test="status != null and status != ''">status = #{status},</if> <if test="status != null and status != ''">status = #{status},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = sysdate() update_time = sysdate()

View File

@ -23,6 +23,7 @@ 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 '创建时间',
@ -33,7 +34,8 @@ 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;
-- ---------------------------- -- ----------------------------
@ -1449,7 +1451,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 COMMENT '更新时间', `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE 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 '价格',
@ -1464,12 +1466,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, '图生图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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (7, '0', 'admin', '2025-11-25 19:41:23', 'admin', '2025-11-25 19:41:23', NULL, '视频换脸', 35.00, 1, NULL, '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, '视频换脸功能', '22', NULL);
-- ---------------------------- -- ----------------------------
-- Table structure for ai_order -- Table structure for ai_order
@ -1494,7 +1496,15 @@ 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 '第二张图片',
PRIMARY KEY (`id`) USING BTREE `mode` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成模式text-to-video 或 image-to-video',
`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 '宽高比',
`model` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '火山模型 endpoint',
`video_params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '视频生成提交参数JSON',
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;
-- ---------------------------- -- ----------------------------
@ -2792,6 +2802,7 @@ 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 '创建时间',
@ -2801,7 +2812,8 @@ 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;
-- ---------------------------- -- ----------------------------
@ -3103,6 +3115,7 @@ 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 '创建时间',
@ -3113,7 +3126,8 @@ 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;
-- ---------------------------- -- ----------------------------
@ -3527,6 +3541,7 @@ CREATE TABLE `ai_user` (
`source` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '来源', `source` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '来源',
`ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户IP', `ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户IP',
`country` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '隶属国家', `country` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '隶属国家',
`dept_id` bigint NULL DEFAULT NULL COMMENT '归属部门sys_dept.dept_id',
PRIMARY KEY (`id`) USING BTREE, PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uuid`(`user_id` ASC) USING BTREE, UNIQUE INDEX `uuid`(`user_id` ASC) USING BTREE,
UNIQUE INDEX `invitation_code`(`invitation_code` ASC) USING BTREE, UNIQUE INDEX `invitation_code`(`invitation_code` ASC) USING BTREE,
@ -3537,35 +3552,37 @@ CREATE TABLE `ai_user` (
-- ---------------------------- -- ----------------------------
-- Records of ai_user -- Records of ai_user
-- ---------------------------- -- ----------------------------
INSERT INTO `ai_user` VALUES (14, '0', '', '2025-11-28 11:42:33', '', '2026-01-06 16:04:21', NULL, 'ww123', NULL, 2, '', '', '$2a$10$bxNPP2cfo9OlJxiOQJJDiu4QOrQet8enUOCiu3QF6qLHaOSLA6G1.', NULL, 0, 'xpchb40121@outlook.com', NULL, 'RM3XZ8z3', NULL, '2026-01-06 16:04:21', 235.00, NULL, 'L8AJXFPT', 'XM001', '2605:52c0:1:446:a425:64ff:fe47:36c6', '美国'); INSERT INTO `ai_user` VALUES (14, '0', '', '2025-11-28 11:42:33', '', '2026-01-06 16:04:21', NULL, 'ww123', NULL, 2, '', '', '$2a$10$bxNPP2cfo9OlJxiOQJJDiu4QOrQet8enUOCiu3QF6qLHaOSLA6G1.', NULL, 0, 'xpchb40121@outlook.com', NULL, 'RM3XZ8z3', NULL, '2026-01-06 16:04:21', 235.00, NULL, 'L8AJXFPT', 'XM001', '2605:52c0:1:446:a425:64ff:fe47:36c6', '美国', NULL);
INSERT INTO `ai_user` VALUES (15, '0', '', '2025-12-07 14:52:38', '', '2025-12-08 17:20:37', NULL, 'u30', NULL, 2, '', '', '$2a$10$tXkZP66AWniOcCbhk2LJpOFOSzHEIMLDbGsow4B7maYswco5HShg.', NULL, 0, 'u30@163.com', NULL, 'wHSf4N2v', NULL, '2025-12-08 17:20:37', 10.00, NULL, 'SNLMZXPH', 'XM001', '208.86.32.27', '美国'); INSERT INTO `ai_user` VALUES (15, '0', '', '2025-12-07 14:52:38', '', '2025-12-08 17:20:37', NULL, 'u30', NULL, 2, '', '', '$2a$10$tXkZP66AWniOcCbhk2LJpOFOSzHEIMLDbGsow4B7maYswco5HShg.', NULL, 0, 'u30@163.com', NULL, 'wHSf4N2v', NULL, '2025-12-08 17:20:37', 10.00, NULL, 'SNLMZXPH', 'XM001', '208.86.32.27', '美国', NULL);
INSERT INTO `ai_user` VALUES (16, '0', '', '2025-12-07 17:15:46', '', '2026-01-08 09:08:03', NULL, 'test001', NULL, 2, '', '', '$2a$10$M4ZD5kRehFycoTmgrA1b4.XNnwsvULaQm9ySdSxttRzPVn7ngbE6y', NULL, 0, 'fgerghbrthbrt@gmail.com', NULL, 'XrtN5DDW', NULL, '2026-01-08 09:08:03', 99921.00, NULL, '2LZ9RAC2', '', '15.204.58.244', '美国'); INSERT INTO `ai_user` VALUES (16, '0', '', '2025-12-07 17:15:46', '', '2026-01-08 09:08:03', NULL, 'test001', NULL, 2, '', '', '$2a$10$M4ZD5kRehFycoTmgrA1b4.XNnwsvULaQm9ySdSxttRzPVn7ngbE6y', NULL, 0, 'fgerghbrthbrt@gmail.com', NULL, 'XrtN5DDW', NULL, '2026-01-08 09:08:03', 99921.00, NULL, '2LZ9RAC2', '', '15.204.58.244', '美国', NULL);
INSERT INTO `ai_user` VALUES (17, '0', '', '2025-12-07 17:28:57', '', '2025-12-07 09:28:57', NULL, 'zz3534217@gmail.com', NULL, 2, '', '', '$2a$10$sxNmfnayqUroy66CgCATf.VkmxsECKv1jTUgFUMN.CQY.3yFBWGO.', NULL, 0, 'zz3534217@gmail.com', NULL, 'qnUwmXYS', NULL, '2025-12-07 21:18:48', 0.00, NULL, '82FR6YTZ', 'XM001', NULL, NULL); INSERT INTO `ai_user` VALUES (17, '0', '', '2025-12-07 17:28:57', '', '2025-12-07 09:28:57', NULL, 'zz3534217@gmail.com', NULL, 2, '', '', '$2a$10$sxNmfnayqUroy66CgCATf.VkmxsECKv1jTUgFUMN.CQY.3yFBWGO.', NULL, 0, 'zz3534217@gmail.com', NULL, 'qnUwmXYS', NULL, '2025-12-07 21:18:48', 0.00, NULL, '82FR6YTZ', 'XM001', NULL, NULL, NULL);
INSERT INTO `ai_user` VALUES (18, '0', '', '2025-12-08 12:53:23', '', '2025-12-08 17:00:57', NULL, '3545486624@qq.com', NULL, 2, '', '', '$2a$10$9siQceU./ZfmW.iDwVf9sehBJWsxzyP10fJvkzStAbyzw4yQEHAUa', NULL, 0, '3545486624@qq.com', NULL, 'ujfTuFNA', NULL, '2025-12-08 17:00:56', 0.00, NULL, 'OEEO8YJQ', 'XM001', '119.246.86.101', '香港'); INSERT INTO `ai_user` VALUES (18, '0', '', '2025-12-08 12:53:23', '', '2025-12-08 17:00:57', NULL, '3545486624@qq.com', NULL, 2, '', '', '$2a$10$9siQceU./ZfmW.iDwVf9sehBJWsxzyP10fJvkzStAbyzw4yQEHAUa', NULL, 0, '3545486624@qq.com', NULL, 'ujfTuFNA', NULL, '2025-12-08 17:00:56', 0.00, NULL, 'OEEO8YJQ', 'XM001', '119.246.86.101', '香港', NULL);
INSERT INTO `ai_user` VALUES (19, '0', '', '2025-12-08 15:47:59', '', '2025-12-08 15:48:14', NULL, 'walb0501', NULL, 2, '', '', '$2a$10$KnHIeNIOtva0saZVasbfBeJhvCKpU/cjZXEwBJ655jrJcBnCR1s/q', NULL, 0, 'pereadesen2@gmail.com', NULL, 'DqmbQS8q', NULL, '2025-12-08 15:48:14', 8.00, NULL, '9Q889UMZ', 'XM001', '188.253.121.195', '新加坡'); INSERT INTO `ai_user` VALUES (19, '0', '', '2025-12-08 15:47:59', '', '2025-12-08 15:48:14', NULL, 'walb0501', NULL, 2, '', '', '$2a$10$KnHIeNIOtva0saZVasbfBeJhvCKpU/cjZXEwBJ655jrJcBnCR1s/q', NULL, 0, 'pereadesen2@gmail.com', NULL, 'DqmbQS8q', NULL, '2025-12-08 15:48:14', 8.00, NULL, '9Q889UMZ', 'XM001', '188.253.121.195', '新加坡', NULL);
INSERT INTO `ai_user` VALUES (20, '0', '', '2025-12-08 15:50:03', '', '2025-12-08 15:50:23', NULL, 'xu89026719@gmail.com', NULL, 2, '', '', '$2a$10$xdGHiEVaGC7gyXldojpiUO0GOgyu5Pcgtw/WRTuf.TA1e.NbB8Ogu', NULL, 0, 'xu89026719@gmail.com', NULL, 'XnQAfPvA', NULL, '2025-12-08 15:50:23', 0.00, NULL, '209HPBTF', 'XM001', '38.207.136.194', '日本'); INSERT INTO `ai_user` VALUES (20, '0', '', '2025-12-08 15:50:03', '', '2025-12-08 15:50:23', NULL, 'xu89026719@gmail.com', NULL, 2, '', '', '$2a$10$xdGHiEVaGC7gyXldojpiUO0GOgyu5Pcgtw/WRTuf.TA1e.NbB8Ogu', NULL, 0, 'xu89026719@gmail.com', NULL, 'XnQAfPvA', NULL, '2025-12-08 15:50:23', 0.00, NULL, '209HPBTF', 'XM001', '38.207.136.194', '日本', NULL);
INSERT INTO `ai_user` VALUES (21, '0', '', '2025-12-08 17:20:31', '', '2025-12-08 17:22:26', NULL, '1686228', NULL, 2, '', '', '$2a$10$MpUYTeWBI6gVTqjtJBz.3.sMOz6ul.eqLQyrvY3VL2WRbFeK666fW', NULL, 0, '1686228@gmail.com', NULL, 'RyZlK7YG', NULL, '2025-12-08 17:22:26', 10.00, NULL, '9CUIDFW4', 'XM001', '208.86.32.27', '美国'); INSERT INTO `ai_user` VALUES (21, '0', '', '2025-12-08 17:20:31', '', '2025-12-08 17:22:26', NULL, '1686228', NULL, 2, '', '', '$2a$10$MpUYTeWBI6gVTqjtJBz.3.sMOz6ul.eqLQyrvY3VL2WRbFeK666fW', NULL, 0, '1686228@gmail.com', NULL, 'RyZlK7YG', NULL, '2025-12-08 17:22:26', 10.00, NULL, '9CUIDFW4', 'XM001', '208.86.32.27', '美国', NULL);
INSERT INTO `ai_user` VALUES (22, '0', '', '2025-12-08 19:45:16', '', '2025-12-09 03:12:37', NULL, 'a424569211@gmail.com', NULL, 2, '', '', '$2a$10$3TEa4ufju1sMPJdw8K0Ik.ic10m6Ms4LQFxYK2h27/pbI2qR5nJDK', NULL, 0, 'a424569211@gmail.com', NULL, '4JLEc4vd', NULL, '2025-12-09 03:12:37', 0.00, NULL, '0N5NEU38', 'XM001', '205.185.125.114', '美国'); INSERT INTO `ai_user` VALUES (22, '0', '', '2025-12-08 19:45:16', '', '2025-12-09 03:12:37', NULL, 'a424569211@gmail.com', NULL, 2, '', '', '$2a$10$3TEa4ufju1sMPJdw8K0Ik.ic10m6Ms4LQFxYK2h27/pbI2qR5nJDK', NULL, 0, 'a424569211@gmail.com', NULL, '4JLEc4vd', NULL, '2025-12-09 03:12:37', 0.00, NULL, '0N5NEU38', 'XM001', '205.185.125.114', '美国', NULL);
INSERT INTO `ai_user` VALUES (23, '0', '', '2025-12-09 10:33:05', '', '2025-12-09 10:33:21', NULL, 'cc123', NULL, 2, 'https://images.iqyjsnwv.com/2025/12/09/37b754c5_706d42be-ad9a-4f11-a3ec-c0742e97ed64.png', '', '$2a$10$V.SDRP4RgM/qfAmNMSqIdOOM259j064oJCvAbDkhrSZLB99L9nhaK', NULL, 0, 'cherrylux098@gmail.com', NULL, 'rlmhwbhF', NULL, '2025-12-09 10:33:21', 4.00, NULL, 'NU8BE6J2', 'XM001', '2605:52c0:3:1a3:c05b:4bff:fe2c:115a', '美国'); INSERT INTO `ai_user` VALUES (23, '0', '', '2025-12-09 10:33:05', '', '2025-12-09 10:33:21', NULL, 'cc123', NULL, 2, 'https://images.iqyjsnwv.com/2025/12/09/37b754c5_706d42be-ad9a-4f11-a3ec-c0742e97ed64.png', '', '$2a$10$V.SDRP4RgM/qfAmNMSqIdOOM259j064oJCvAbDkhrSZLB99L9nhaK', NULL, 0, 'cherrylux098@gmail.com', NULL, 'rlmhwbhF', NULL, '2025-12-09 10:33:21', 4.00, NULL, 'NU8BE6J2', 'XM001', '2605:52c0:3:1a3:c05b:4bff:fe2c:115a', '美国', NULL);
INSERT INTO `ai_user` VALUES (25, '0', '', '2025-12-09 13:46:13', '', '2025-12-09 14:31:51', NULL, 'test1', NULL, 2, '', '', '$2a$10$7v6PPfozY7PHR.ep.GKobePag1aq0ZuS9j.R6lWcKloy9j1R6HgoS', NULL, 0, '123@gmail.com', NULL, 'VBb72Ym3', NULL, '2025-12-09 14:31:50', 8.00, NULL, 'UQ9WZN3V', 'XM001', '2407:cdc0:b00a::19', '香港'); INSERT INTO `ai_user` VALUES (25, '0', '', '2025-12-09 13:46:13', '', '2025-12-09 14:31:51', NULL, 'test1', NULL, 2, '', '', '$2a$10$7v6PPfozY7PHR.ep.GKobePag1aq0ZuS9j.R6lWcKloy9j1R6HgoS', NULL, 0, '123@gmail.com', NULL, 'VBb72Ym3', NULL, '2025-12-09 14:31:50', 8.00, NULL, 'UQ9WZN3V', 'XM001', '2407:cdc0:b00a::19', '香港', NULL);
INSERT INTO `ai_user` VALUES (26, '0', '', '2025-12-09 14:13:17', '', '2025-12-09 14:53:48', NULL, 'test2', NULL, 2, '', '', '$2a$10$OirrwiDFruMJQLLnNufEv.BiQzyL.KxCz1x.XVWt5R5e.ggq5m/RS', NULL, 0, '1@gmail.com', NULL, '3WFmGuER', NULL, '2025-12-09 14:53:48', 10.00, NULL, '0NEK09R6', 'XM001', '2407:cdc0:b00a::19', '香港'); INSERT INTO `ai_user` VALUES (26, '0', '', '2025-12-09 14:13:17', '', '2025-12-09 14:53:48', NULL, 'test2', NULL, 2, '', '', '$2a$10$OirrwiDFruMJQLLnNufEv.BiQzyL.KxCz1x.XVWt5R5e.ggq5m/RS', NULL, 0, '1@gmail.com', NULL, '3WFmGuER', NULL, '2025-12-09 14:53:48', 10.00, NULL, '0NEK09R6', 'XM001', '2407:cdc0:b00a::19', '香港', NULL);
INSERT INTO `ai_user` VALUES (27, '0', '', '2025-12-09 14:57:14', '', '2025-12-09 15:01:08', NULL, 'test3', NULL, 2, '', '', '$2a$10$aCcA6Mq/.xOfs6FCJwPot.ttBFv7YjULPQsF3axMz3Gm98MzMafrO', NULL, 0, '2@gmail.com', NULL, 'pqU7mW5R', NULL, '2025-12-09 15:01:08', 10000.00, NULL, '4R80E3NI', 'XM001', '2407:cdc0:b00a::19', '香港'); INSERT INTO `ai_user` VALUES (27, '0', '', '2025-12-09 14:57:14', '', '2025-12-09 15:01:08', NULL, 'test3', NULL, 2, '', '', '$2a$10$aCcA6Mq/.xOfs6FCJwPot.ttBFv7YjULPQsF3axMz3Gm98MzMafrO', NULL, 0, '2@gmail.com', NULL, 'pqU7mW5R', NULL, '2025-12-09 15:01:08', 10000.00, NULL, '4R80E3NI', 'XM001', '2407:cdc0:b00a::19', '香港', NULL);
INSERT INTO `ai_user` VALUES (29, '0', '', '2025-12-11 10:48:59', '', '2025-12-11 10:49:08', NULL, 'test4', NULL, 2, '', '', '$2a$10$he3GMBisX7UeEHFRQYVxU.vcavkDxWWNyCSuz2QvfPKBAEZd17Xuu', NULL, 0, '3@gmail.com', NULL, 'FWzJWjjQ', NULL, '2025-12-11 10:49:08', 0.00, NULL, 'KLSC4HK0', 'ALL', '2407:cdc0:b00a::19', '香港'); INSERT INTO `ai_user` VALUES (29, '0', '', '2025-12-11 10:48:59', '', '2025-12-11 10:49:08', NULL, 'test4', NULL, 2, '', '', '$2a$10$he3GMBisX7UeEHFRQYVxU.vcavkDxWWNyCSuz2QvfPKBAEZd17Xuu', NULL, 0, '3@gmail.com', NULL, 'FWzJWjjQ', NULL, '2025-12-11 10:49:08', 0.00, NULL, 'KLSC4HK0', 'ALL', '2407:cdc0:b00a::19', '香港', NULL);
INSERT INTO `ai_user` VALUES (30, '0', '', '2025-12-11 10:50:31', '', '2025-12-11 11:00:47', NULL, 'test5', NULL, 2, '', '', '$2a$10$QVUfOv2FbwhRwj35ETn0we.k76tfHNqxXZPKy0vvzxfz1jgh2cB7m', NULL, 0, '4@gmail.com', NULL, 'Rdp7l3db', NULL, '2025-12-11 11:00:47', 28.22, NULL, '1S8WYOXT', 'ALL', '2407:cdc0:b00a::19', '香港'); INSERT INTO `ai_user` VALUES (30, '0', '', '2025-12-11 10:50:31', '', '2025-12-11 11:00:47', NULL, 'test5', NULL, 2, '', '', '$2a$10$QVUfOv2FbwhRwj35ETn0we.k76tfHNqxXZPKy0vvzxfz1jgh2cB7m', NULL, 0, '4@gmail.com', NULL, 'Rdp7l3db', NULL, '2025-12-11 11:00:47', 28.22, NULL, '1S8WYOXT', 'ALL', '2407:cdc0:b00a::19', '香港', NULL);
INSERT INTO `ai_user` VALUES (31, '0', '', '2025-12-14 01:35:44', '', '2025-12-13 17:35:44', NULL, 'whsksmhxud', NULL, 2, '', '', '$2a$10$sJ1pA9uBfgzv8O5X5Ui5G.7uVKRoZDqDqQ9BG848y7njgFtlgzWpq', NULL, 0, 'w809277959@outlook.com', NULL, 'bZEVdu4b', NULL, NULL, 0.00, NULL, '7I7TJC30', 'XM001', '103.82.93.21', '印尼'); INSERT INTO `ai_user` VALUES (31, '0', '', '2025-12-14 01:35:44', '', '2025-12-13 17:35:44', NULL, 'whsksmhxud', NULL, 2, '', '', '$2a$10$sJ1pA9uBfgzv8O5X5Ui5G.7uVKRoZDqDqQ9BG848y7njgFtlgzWpq', NULL, 0, 'w809277959@outlook.com', NULL, 'bZEVdu4b', NULL, NULL, 0.00, NULL, '7I7TJC30', 'XM001', '103.82.93.21', '印尼', NULL);
INSERT INTO `ai_user` VALUES (32, '0', '', '2025-12-14 01:58:22', '', '2025-12-14 01:58:47', NULL, 'devin', NULL, 2, '', '', '$2a$10$NUNiFbS6WEXznA0LzlW10eH5FjX437NxO05VSnMFII4FIsPQOuoxe', NULL, 0, 'devinhe46@gmail.com', NULL, 'BZNhTE5M', NULL, '2025-12-14 01:58:47', 0.00, NULL, 'BN7FJCGI', 'XM001', '134.122.184.12', '日本'); INSERT INTO `ai_user` VALUES (32, '0', '', '2025-12-14 01:58:22', '', '2025-12-14 01:58:47', NULL, 'devin', NULL, 2, '', '', '$2a$10$NUNiFbS6WEXznA0LzlW10eH5FjX437NxO05VSnMFII4FIsPQOuoxe', NULL, 0, 'devinhe46@gmail.com', NULL, 'BZNhTE5M', NULL, '2025-12-14 01:58:47', 0.00, NULL, 'BN7FJCGI', 'XM001', '134.122.184.12', '日本', NULL);
INSERT INTO `ai_user` VALUES (33, '0', '', '2025-12-14 10:22:49', '', '2025-12-14 10:23:08', NULL, 'djwnx1123', NULL, 2, '', '', '$2a$10$z5dYmkZzGdi9G2nVSPZqJutN2Z/8FN0.cPRilsZdjzgVZKc2QCydi', NULL, 0, '2451855921@qq.com', NULL, 'nc3yv7WQ', NULL, '2025-12-14 10:23:08', 0.00, NULL, '4HF1953W', 'XM001', '103.147.45.37', '香港'); INSERT INTO `ai_user` VALUES (33, '0', '', '2025-12-14 10:22:49', '', '2025-12-14 10:23:08', NULL, 'djwnx1123', NULL, 2, '', '', '$2a$10$z5dYmkZzGdi9G2nVSPZqJutN2Z/8FN0.cPRilsZdjzgVZKc2QCydi', NULL, 0, '2451855921@qq.com', NULL, 'nc3yv7WQ', NULL, '2025-12-14 10:23:08', 0.00, NULL, '4HF1953W', 'XM001', '103.147.45.37', '香港', NULL);
INSERT INTO `ai_user` VALUES (34, '0', '', '2025-12-14 14:52:58', '', '2025-12-14 14:54:08', NULL, 'djwnx', NULL, 2, '', '', '$2a$10$YwtgWJOnElfAn4vuvYRprO5zXrTBZx8fvx/lVIyopVdph7RkeSisS', NULL, 0, 'tianyangyu1123@gmail.com', NULL, 'pFpHVX2y', NULL, '2025-12-14 14:54:08', 0.00, NULL, 'BH9DLPCJ', 'XM001', '43.228.227.211', '香港'); INSERT INTO `ai_user` VALUES (34, '0', '', '2025-12-14 14:52:58', '', '2025-12-14 14:54:08', NULL, 'djwnx', NULL, 2, '', '', '$2a$10$YwtgWJOnElfAn4vuvYRprO5zXrTBZx8fvx/lVIyopVdph7RkeSisS', NULL, 0, 'tianyangyu1123@gmail.com', NULL, 'pFpHVX2y', NULL, '2025-12-14 14:54:08', 0.00, NULL, 'BH9DLPCJ', 'XM001', '43.228.227.211', '香港', NULL);
INSERT INTO `ai_user` VALUES (35, '0', '', '2025-12-15 18:05:01', '', '2025-12-24 15:08:10', NULL, 'test6', NULL, 2, '', '', '$2a$10$AOJg.25lz16afHa3OfPMhuxu8oorFGeL4MqMgcxpRKjd4iY0LsmAG', NULL, 0, '5@gmail.com', NULL, 'heNUTHUX', NULL, '2025-12-24 15:08:09', 323.00, 30, 'PJCZNE1M', 'ALL', '2602:fbf1:b001::36', '美国'); INSERT INTO `ai_user` VALUES (35, '0', '', '2025-12-15 18:05:01', '', '2025-12-24 15:08:10', NULL, 'test6', NULL, 2, '', '', '$2a$10$AOJg.25lz16afHa3OfPMhuxu8oorFGeL4MqMgcxpRKjd4iY0LsmAG', NULL, 0, '5@gmail.com', NULL, 'heNUTHUX', NULL, '2025-12-24 15:08:09', 323.00, 30, 'PJCZNE1M', 'ALL', '2602:fbf1:b001::36', '美国', NULL);
INSERT INTO `ai_user` VALUES (36, '0', '', '2025-12-15 18:07:56', '', '2026-01-08 14:53:02', NULL, 'test7', NULL, 2, '', '', '$2a$10$xXtOsMjz5Zd9dsJvUBz5be.6ZphexWOedfe.TLW9bdDtzFBYESzIG', NULL, 0, '6@gmail.com', NULL, 'RXQLfyjy', NULL, '2026-01-08 14:53:01', 100419.00, 35, '24SRSLFG', 'ALL', '2407:cdc0:d002::195', '新加坡'); INSERT INTO `ai_user` VALUES (36, '0', '', '2025-12-15 18:07:56', '', '2026-01-08 14:53:02', NULL, 'test7', NULL, 2, '', '', '$2a$10$xXtOsMjz5Zd9dsJvUBz5be.6ZphexWOedfe.TLW9bdDtzFBYESzIG', NULL, 0, '6@gmail.com', NULL, 'RXQLfyjy', NULL, '2026-01-08 14:53:01', 100419.00, 35, '24SRSLFG', 'ALL', '2407:cdc0:d002::195', '新加坡', NULL);
INSERT INTO `ai_user` VALUES (37, '0', '', '2025-12-16 14:07:46', '', '2025-12-16 14:08:05', NULL, 'test8', NULL, 2, '', '', '$2a$10$Z967MUQ6I5CCgfVJTV0l2ug2BysZmfRNvpq6c.vBVlVIX7ekM3GJi', NULL, 0, '7@gmail.com', NULL, 'kEZx2eps', NULL, '2025-12-16 14:08:04', 0.00, NULL, 'H4AFA8CH', 'ALL', '37.9.33.202', '美国'); INSERT INTO `ai_user` VALUES (37, '0', '', '2025-12-16 14:07:46', '', '2025-12-16 14:08:05', NULL, 'test8', NULL, 2, '', '', '$2a$10$Z967MUQ6I5CCgfVJTV0l2ug2BysZmfRNvpq6c.vBVlVIX7ekM3GJi', NULL, 0, '7@gmail.com', NULL, 'kEZx2eps', NULL, '2025-12-16 14:08:04', 0.00, NULL, 'H4AFA8CH', 'ALL', '37.9.33.202', '美国', NULL);
INSERT INTO `ai_user` VALUES (38, '0', '', '2025-12-19 22:04:18', '', '2025-12-24 20:57:53', NULL, 'ghypnus', NULL, 2, '', '', '$2a$10$Cr7AQ7D9Jxk6BRE7cYn1su5B4x4aOxkf9JQMwk06Tyt5X1dn8Qr6K', NULL, 0, '1954579286@qq.com', NULL, '3tMwDlMH', NULL, '2025-12-24 20:57:52', 0.00, NULL, 'JPL28UHV', 'XM001', '85.234.69.52', '英国'); INSERT INTO `ai_user` VALUES (38, '0', '', '2025-12-19 22:04:18', '', '2025-12-24 20:57:53', NULL, 'ghypnus', NULL, 2, '', '', '$2a$10$Cr7AQ7D9Jxk6BRE7cYn1su5B4x4aOxkf9JQMwk06Tyt5X1dn8Qr6K', NULL, 0, '1954579286@qq.com', NULL, '3tMwDlMH', NULL, '2025-12-24 20:57:52', 0.00, NULL, 'JPL28UHV', 'XM001', '85.234.69.52', '英国', NULL);
INSERT INTO `ai_user` VALUES (39, '0', '', '2025-12-27 16:48:23', '', '2026-01-06 16:32:09', NULL, 'xiaxiaxia', NULL, 2, '', '', '$2a$10$aci4cfcpANfKt1EPdVY1cORUACqXJHGx49gjD72Cc2p62K5TKT6bG', NULL, 0, 'pianochen606@gmail.com', NULL, 'yWQK4jBC', NULL, '2026-01-06 16:32:09', 9829.00, NULL, 'K0G1NBAI', 'XM001', '216.167.64.103', '美国'); INSERT INTO `ai_user` VALUES (39, '0', '', '2025-12-27 16:48:23', '', '2026-01-06 16:32:09', NULL, 'xiaxiaxia', NULL, 2, '', '', '$2a$10$aci4cfcpANfKt1EPdVY1cORUACqXJHGx49gjD72Cc2p62K5TKT6bG', NULL, 0, 'pianochen606@gmail.com', NULL, 'yWQK4jBC', NULL, '2026-01-06 16:32:09', 9829.00, NULL, 'K0G1NBAI', 'XM001', '216.167.64.103', '美国', NULL);
INSERT INTO `ai_user` VALUES (40, '0', '', '2025-12-30 11:28:25', '', '2025-12-30 11:28:35', NULL, 'test9', NULL, 2, '', '', '$2a$10$RrqVCQk2eKBrk4LqVlVLg.MCSmKlIJidSpw/aH3jlA4oGf8AEvbYi', NULL, 0, '8@gmail.com', NULL, 'wSsxXKeH', NULL, '2025-12-30 11:28:35', 336.00, 35, 'Z6UI3X6S', 'ALL', '2602:fbf1:b001::36', '美国'); INSERT INTO `ai_user` VALUES (40, '0', '', '2025-12-30 11:28:25', '', '2025-12-30 11:28:35', NULL, 'test9', NULL, 2, '', '', '$2a$10$RrqVCQk2eKBrk4LqVlVLg.MCSmKlIJidSpw/aH3jlA4oGf8AEvbYi', NULL, 0, '8@gmail.com', NULL, 'wSsxXKeH', NULL, '2025-12-30 11:28:35', 336.00, 35, 'Z6UI3X6S', 'ALL', '2602:fbf1:b001::36', '美国', NULL);
INSERT INTO `ai_user` VALUES (41, '0', '', '2025-12-30 11:58:13', '', '2025-12-30 11:58:20', NULL, 'test10', NULL, 2, '', '', '$2a$10$iyc.quT3QzzzROdjLN1S8.GLE.epyFAo4FvBDOlokDbu4JMODrr3S', NULL, 0, '9@gmail.com', NULL, 'lRbwts88', NULL, '2025-12-30 11:58:20', 110.00, 40, 'LJSSMAQO', 'ALL', '2602:fbf1:b001::36', '美国'); INSERT INTO `ai_user` VALUES (41, '0', '', '2025-12-30 11:58:13', '', '2025-12-30 11:58:20', NULL, 'test10', NULL, 2, '', '', '$2a$10$iyc.quT3QzzzROdjLN1S8.GLE.epyFAo4FvBDOlokDbu4JMODrr3S', NULL, 0, '9@gmail.com', NULL, 'lRbwts88', NULL, '2025-12-30 11:58:20', 110.00, 40, 'LJSSMAQO', 'ALL', '2602:fbf1:b001::36', '美国', NULL);
INSERT INTO `ai_user` VALUES (42, '0', '', '2025-12-30 13:42:02', '', '2025-12-30 13:42:09', NULL, 'test11', NULL, 2, '', '', '$2a$10$ke66b3aOZ67NaUT2WQepyO2EmVYkSEBYLB/NFSRID.S3ejHaU5Og.', NULL, 0, '10@gmail.com', NULL, 'BrEg6XmM', NULL, '2025-12-30 13:42:09', 225.00, NULL, 'IHOF63N5', 'ALL', '2602:fbf1:b001::36', '美国'); INSERT INTO `ai_user` VALUES (42, '0', '', '2025-12-30 13:42:02', '', '2025-12-30 13:42:09', NULL, 'test11', NULL, 2, '', '', '$2a$10$ke66b3aOZ67NaUT2WQepyO2EmVYkSEBYLB/NFSRID.S3ejHaU5Og.', NULL, 0, '10@gmail.com', NULL, 'BrEg6XmM', NULL, '2025-12-30 13:42:09', 225.00, NULL, 'IHOF63N5', 'ALL', '2602:fbf1:b001::36', '美国', NULL);
INSERT INTO `ai_user` VALUES (43, '0', '', '2025-12-31 14:20:06', '', '2025-12-31 14:20:12', NULL, 'a001', NULL, 2, '', '', '$2a$10$Z02VVJ5/LaiTzTp22b2zXOTV8YE/6BMNVrboTj5cuEthXuPpP97Mi', NULL, 0, '11@gmail.com', NULL, 'Xf38CXD6', NULL, '2025-12-31 14:20:12', 1089.00, NULL, 'LXNFP1VS', 'ALL', '2602:fbf1:b001::36', '美国'); INSERT INTO `ai_user` VALUES (43, '0', '', '2025-12-31 14:20:06', '', '2025-12-31 14:20:12', NULL, 'a001', NULL, 2, '', '', '$2a$10$Z02VVJ5/LaiTzTp22b2zXOTV8YE/6BMNVrboTj5cuEthXuPpP97Mi', NULL, 0, '11@gmail.com', NULL, 'Xf38CXD6', NULL, '2025-12-31 14:20:12', 1089.00, NULL, 'LXNFP1VS', 'ALL', '2602:fbf1:b001::36', '美国', NULL);
INSERT INTO `ai_user` VALUES (44, '0', '', '2025-12-31 14:26:20', '', '2025-12-31 14:26:25', NULL, 'a002', NULL, 2, '', '', '$2a$10$4ZnF/zh3q3E07GBpthtL.ufsxQOt2u/eu8YzcFnmPO5dUa1RJ6mkG', NULL, 0, 'c46258724hk@gmail.com', NULL, 'knt2yT2r', NULL, '2025-12-31 14:26:25', 230.00, 43, 'JW07BAHC', 'ALL', '2602:fbf1:b001::36', '美国'); INSERT INTO `ai_user` VALUES (44, '0', '', '2025-12-31 14:26:20', '', '2025-12-31 14:26:25', NULL, 'a002', NULL, 2, '', '', '$2a$10$4ZnF/zh3q3E07GBpthtL.ufsxQOt2u/eu8YzcFnmPO5dUa1RJ6mkG', NULL, 0, 'c46258724hk@gmail.com', NULL, 'knt2yT2r', NULL, '2025-12-31 14:26:25', 230.00, 43, 'JW07BAHC', 'ALL', '2602:fbf1:b001::36', '美国', NULL);
-- 已有库升级ALTER TABLE ai_user ADD COLUMN dept_id bigint NULL DEFAULT NULL COMMENT '归属部门sys_dept.dept_id' AFTER country;
-- ---------------------------- -- ----------------------------
-- Table structure for ai_user_message -- Table structure for ai_user_message
@ -4178,6 +4195,7 @@ CREATE TABLE `sys_dept` (
`leader` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '负责人', `leader` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '负责人',
`phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话', `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话',
`email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱', `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
`byte_api_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'Byte API Key',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '部门状态0正常 1停用', `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '部门状态0正常 1停用',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志0代表存在 2代表删除', `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci 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 '创建者',
@ -4190,21 +4208,23 @@ CREATE TABLE `sys_dept` (
-- ---------------------------- -- ----------------------------
-- Records of sys_dept -- Records of sys_dept
-- ---------------------------- -- ----------------------------
INSERT INTO `sys_dept` VALUES (100, 0, '0', '若依科技', 0, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-11-09 01:56:46', '', NULL); INSERT INTO `sys_dept` VALUES (100, 0, '0', '若依科技', 0, '若依', '15888888888', 'ry@qq.com', NULL, '0', '0', 'admin', '2025-11-09 01:56:46', '', NULL);
INSERT INTO `sys_dept` VALUES (101, 100, '0,100', '深圳总公司', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-11-09 01:56:47', '', NULL); INSERT INTO `sys_dept` VALUES (101, 100, '0,100', '深圳总公司', 1, '若依', '15888888888', 'ry@qq.com', NULL, '0', '0', 'admin', '2025-11-09 01:56:47', '', NULL);
INSERT INTO `sys_dept` VALUES (102, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-11-09 01:56:47', '', NULL); INSERT INTO `sys_dept` VALUES (102, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', NULL, '0', '0', 'admin', '2025-11-09 01:56:47', '', NULL);
INSERT INTO `sys_dept` VALUES (103, 101, '0,100,101', '研发部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-11-09 01:56:47', '', NULL); INSERT INTO `sys_dept` VALUES (103, 101, '0,100,101', '研发部门', 1, '若依', '15888888888', 'ry@qq.com', NULL, '0', '0', 'admin', '2025-11-09 01:56:47', '', NULL);
INSERT INTO `sys_dept` VALUES (104, 101, '0,100,101', '市场部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-11-09 01:56:47', '', NULL); INSERT INTO `sys_dept` VALUES (104, 101, '0,100,101', '市场部门', 2, '若依', '15888888888', 'ry@qq.com', NULL, '0', '0', 'admin', '2025-11-09 01:56:47', '', NULL);
INSERT INTO `sys_dept` VALUES (105, 101, '0,100,101', '测试部门', 3, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-11-09 01:56:48', '', NULL); INSERT INTO `sys_dept` VALUES (105, 101, '0,100,101', '测试部门', 3, '若依', '15888888888', 'ry@qq.com', NULL, '0', '0', 'admin', '2025-11-09 01:56:48', '', NULL);
INSERT INTO `sys_dept` VALUES (106, 101, '0,100,101', '财务部门', 4, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-11-09 01:56:48', '', NULL); INSERT INTO `sys_dept` VALUES (106, 101, '0,100,101', '财务部门', 4, '若依', '15888888888', 'ry@qq.com', NULL, '0', '0', 'admin', '2025-11-09 01:56:48', '', NULL);
INSERT INTO `sys_dept` VALUES (107, 101, '0,100,101', '运维部门', 5, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-11-09 01:56:49', '', NULL); INSERT INTO `sys_dept` VALUES (107, 101, '0,100,101', '运维部门', 5, '若依', '15888888888', 'ry@qq.com', NULL, '0', '0', 'admin', '2025-11-09 01:56:49', '', NULL);
INSERT INTO `sys_dept` VALUES (108, 102, '0,100,102', '市场部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-11-09 01:56:49', '', NULL); INSERT INTO `sys_dept` VALUES (108, 102, '0,100,102', '市场部门', 1, '若依', '15888888888', 'ry@qq.com', NULL, '0', '0', 'admin', '2025-11-09 01:56:49', '', NULL);
INSERT INTO `sys_dept` VALUES (109, 102, '0,100,102', '财务部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-11-09 01:56:49', '', NULL); INSERT INTO `sys_dept` VALUES (109, 102, '0,100,102', '财务部门', 2, '若依', '15888888888', 'ry@qq.com', NULL, '0', '0', 'admin', '2025-11-09 01:56:49', '', NULL);
INSERT INTO `sys_dept` VALUES (200, 100, '0,100', '胸部', 3, NULL, NULL, NULL, '0', '2', 'admin', '2025-11-25 13:07:38', '', NULL); INSERT INTO `sys_dept` VALUES (200, 100, '0,100', '胸部', 3, NULL, NULL, NULL, NULL, '0', '2', 'admin', '2025-11-25 13:07:38', '', NULL);
INSERT INTO `sys_dept` VALUES (201, 200, '0,100,200', '大奶', 1, NULL, NULL, NULL, '0', '2', 'admin', '2025-11-25 13:07:48', '', NULL); INSERT INTO `sys_dept` VALUES (201, 200, '0,100,200', '大奶', 1, NULL, NULL, NULL, NULL, '0', '2', 'admin', '2025-11-25 13:07:48', '', NULL);
INSERT INTO `sys_dept` VALUES (202, 201, '0,100,200,201', '水滴型', 1, NULL, NULL, NULL, '0', '2', 'admin', '2025-11-25 13:08:04', '', NULL); INSERT INTO `sys_dept` VALUES (202, 201, '0,100,200,201', '水滴型', 1, NULL, NULL, NULL, NULL, '0', '2', 'admin', '2025-11-25 13:08:04', '', NULL);
INSERT INTO `sys_dept` VALUES (203, 200, '0,100,200', '小奶', 2, NULL, NULL, NULL, '0', '2', 'admin', '2025-11-25 13:08:13', '', NULL); INSERT INTO `sys_dept` VALUES (203, 200, '0,100,200', '小奶', 2, NULL, NULL, NULL, NULL, '0', '2', 'admin', '2025-11-25 13:08:13', '', NULL);
INSERT INTO `sys_dept` VALUES (204, 201, '0,100,200,201', '粉色', 2, NULL, NULL, NULL, '0', '2', 'admin', '2025-11-25 13:08:25', '', NULL); INSERT INTO `sys_dept` VALUES (204, 201, '0,100,200,201', '粉色', 2, NULL, NULL, NULL, NULL, '0', '2', 'admin', '2025-11-25 13:08:25', '', NULL);
-- 已有数据库升级可执行ALTER TABLE sys_dept ADD COLUMN byte_api_key varchar(255) NULL DEFAULT NULL COMMENT 'Byte API Key' AFTER email;
-- ---------------------------- -- ----------------------------
-- Table structure for sys_dict_data -- Table structure for sys_dict_data
@ -4672,6 +4692,11 @@ INSERT INTO `sys_menu` VALUES (1058, '导入代码', 116, 4, '#', '', '', '', 1,
INSERT INTO `sys_menu` VALUES (1059, '预览代码', 116, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', '2025-11-09 01:57:14', '', NULL, ''); INSERT INTO `sys_menu` VALUES (1059, '预览代码', 116, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', '2025-11-09 01:57:14', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1060, '生成代码', 116, 6, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', '2025-11-09 01:57:14', '', NULL, ''); INSERT INTO `sys_menu` VALUES (1060, '生成代码', 116, 6, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', '2025-11-09 01:57:14', '', NULL, '');
INSERT INTO `sys_menu` VALUES (2000, '综合管理', 0, 4, 'ai-manager', NULL, NULL, '', 1, 0, 'M', '0', '0', '', 'redis-list', 'admin', '2025-11-12 20:19:15', 'admin', '2025-11-20 20:06:49', ''); INSERT INTO `sys_menu` VALUES (2000, '综合管理', 0, 4, 'ai-manager', NULL, NULL, '', 1, 0, 'M', '0', '0', '', 'redis-list', 'admin', '2025-11-12 20:19:15', 'admin', '2025-11-20 20:06:49', '');
INSERT INTO `sys_menu` VALUES (2100, 'AI部门管理', 2003, 0, 'aiDept', 'ai/dept/index', NULL, '', 1, 0, 'C', '0', '0', 'ai:dept:list', 'tree', 'admin', '2026-03-30 10:00:00', '', NULL, '');
INSERT INTO `sys_menu` VALUES (2101, 'AI部门查询', 2100, 1, '', '', NULL, '', 1, 0, 'F', '0', '0', 'ai:dept:query', '#', 'admin', '2026-03-30 10:00:00', '', NULL, '');
INSERT INTO `sys_menu` VALUES (2102, 'AI部门新增', 2100, 2, '', '', NULL, '', 1, 0, 'F', '0', '0', 'ai:dept:add', '#', 'admin', '2026-03-30 10:00:00', '', NULL, '');
INSERT INTO `sys_menu` VALUES (2103, 'AI部门修改', 2100, 3, '', '', NULL, '', 1, 0, 'F', '0', '0', 'ai:dept:edit', '#', 'admin', '2026-03-30 10:00:00', '', NULL, '');
INSERT INTO `sys_menu` VALUES (2104, 'AI部门删除', 2100, 4, '', '', NULL, '', 1, 0, 'F', '0', '0', 'ai:dept:remove', '#', 'admin', '2026-03-30 10:00:00', '', NULL, '');
INSERT INTO `sys_menu` VALUES (2001, 'AI用户管理', 2003, 1, 'aiUser', 'ai/user/index', NULL, '', 1, 0, 'C', '0', '0', 'ai:user:list', 'user', 'admin', '2025-11-12 20:45:34', 'admin', '2025-11-13 22:19:09', ''); INSERT INTO `sys_menu` VALUES (2001, 'AI用户管理', 2003, 1, 'aiUser', 'ai/user/index', NULL, '', 1, 0, 'C', '0', '0', 'ai:user:list', 'user', 'admin', '2025-11-12 20:45:34', 'admin', '2025-11-13 22:19:09', '');
INSERT INTO `sys_menu` VALUES (2002, 'AI管理', 2000, 4, 'aiManager', 'ai/manager/index', NULL, '', 1, 0, 'C', '0', '0', 'ai:manager:list', 'online', 'admin', '2025-11-13 19:18:54', 'admin', '2025-11-13 22:49:28', ''); INSERT INTO `sys_menu` VALUES (2002, 'AI管理', 2000, 4, 'aiManager', 'ai/manager/index', NULL, '', 1, 0, 'C', '0', '0', 'ai:manager:list', 'online', 'admin', '2025-11-13 19:18:54', 'admin', '2025-11-13 22:49:28', '');
INSERT INTO `sys_menu` VALUES (2003, '用户管理', 2000, 1, 'aiUserManger', NULL, NULL, '', 1, 0, 'M', '0', '0', NULL, 'peoples', 'admin', '2025-11-13 22:18:42', '', NULL, ''); INSERT INTO `sys_menu` VALUES (2003, '用户管理', 2000, 1, 'aiUserManger', NULL, NULL, '', 1, 0, 'M', '0', '0', NULL, 'peoples', 'admin', '2025-11-13 22:18:42', '', NULL, '');
@ -5813,4 +5838,36 @@ 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;

BIN
接口文档.pdf Normal file

Binary file not shown.