Compare commits
No commits in common. "592c2595d671ba4aab8b2633de85aa68c63cb7c7" and "4cb3f4eb883c9d048144b44fbb0db098a28db73f" have entirely different histories.
592c2595d6
...
4cb3f4eb88
|
|
@ -5,4 +5,4 @@ VUE_APP_TITLE = 管理系统
|
|||
ENV = 'production'
|
||||
|
||||
# 若依管理系统/生产环境
|
||||
VUE_APP_BASE_API = 'http://101.96.201.225:8011'
|
||||
VUE_APP_BASE_API = 'http://111.230.37.169:10009'
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
.gradle
|
||||
/build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
*.sql
|
||||
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
server {
|
||||
listen 80;
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
|
||||
server_name undressing.top www.undressing.top;
|
||||
index index.html index.htm;
|
||||
ssl_certificate ssl/undressing.top.crt;
|
||||
ssl_certificate_key ssl/undressing.top.key;
|
||||
ssl_session_timeout 5m;
|
||||
# 优化加密套件配置,移除不安全的算法
|
||||
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4:!3DES;
|
||||
# 移除不安全的 TLSv1.1,只保留 TLSv1.2 和 TLSv1.3
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers on;
|
||||
# 启用 SSL session 缓存,提高性能
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_tickets off;
|
||||
|
||||
root /data/web/client_web;
|
||||
autoindex off; # 禁用目录列表
|
||||
|
||||
# 安全头部
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
location ~* (\.git|\.env|composer\.json|\.log|\.sql)$ {
|
||||
deny all;
|
||||
}
|
||||
location ^~ /api/ {
|
||||
proxy_pass http://10.0.0.167:8110;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Real-Port $remote_port;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 600s;
|
||||
proxy_read_timeout 600s;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
# 缓冲配置,提高性能
|
||||
proxy_buffering on;
|
||||
proxy_buffer_size 4k;
|
||||
proxy_buffers 8 4k;
|
||||
}
|
||||
location / {
|
||||
root /data/web/client_web;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
|
@ -51,9 +51,6 @@ public class AiManagerApiController extends BaseController {
|
|||
@Anonymous
|
||||
public AjaxResult selectInfo(String aiType) {
|
||||
AiManager aiManager = aiManagerService.selectAiManagerByType(aiType);
|
||||
if (aiManager == null) {
|
||||
return AjaxResult.error("该功能未配置或已停用");
|
||||
}
|
||||
aiManager.setPrompt(null);
|
||||
return AjaxResult.success(aiManager);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@ package com.ruoyi.api;
|
|||
|
||||
import com.ruoyi.ai.domain.AiSampleAmount;
|
||||
import com.ruoyi.ai.domain.AiSampleAmountRecord;
|
||||
import com.ruoyi.ai.domain.AiStatistics;
|
||||
import com.ruoyi.ai.domain.enums.AiConfigEnum;
|
||||
import com.ruoyi.ai.mapper.AiSampleAmountMapper;
|
||||
import com.ruoyi.ai.service.EmailVerifyService;
|
||||
import com.ruoyi.ai.service.IAiSampleAmountRecordService;
|
||||
import com.ruoyi.ai.service.IAiStatisticsService;
|
||||
import com.ruoyi.ai.service.IAiUserService;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.constant.BalanceChangerConstants;
|
||||
|
|
@ -19,8 +22,10 @@ import com.ruoyi.common.core.domain.model.RegisterAiUserBody;
|
|||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.exception.job.TaskException;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.IpCountryQueryByApi;
|
||||
import com.ruoyi.common.utils.MessageUtils;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.ip.IpUtils;
|
||||
import com.ruoyi.framework.web.service.SysLoginService;
|
||||
import com.ruoyi.quartz.domain.SysJob;
|
||||
import com.ruoyi.quartz.service.ISysJobService;
|
||||
|
|
@ -35,10 +40,7 @@ import org.springframework.validation.annotation.Validated;
|
|||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -102,11 +104,8 @@ public class AiUserApiController extends BaseController {
|
|||
// 查询启用状态体验金活动
|
||||
AiSampleAmount aiSampleAmount = aiSampleAmountMapper.getSampleAmount();
|
||||
if (aiSampleAmount != null) {
|
||||
String uuid = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8);
|
||||
String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||
String orderNo = dateTime + uuid;
|
||||
// 余额变更记录
|
||||
aiUserService.addUserBalance(orderNo, aiUser.getId(), aiSampleAmount.getAmount(), BalanceChangerConstants.EXPERIENCE_GOLD_GIFT);
|
||||
aiUserService.addUserBalance(aiUser.getId(), aiSampleAmount.getAmount(), BalanceChangerConstants.EXPERIENCE_GOLD_GIFT);
|
||||
// 新增体验金记录
|
||||
AiSampleAmountRecord aiSampleAmountRecord = new AiSampleAmountRecord();
|
||||
aiSampleAmountRecord.setUserId(aiUser.getId());
|
||||
|
|
@ -219,7 +218,6 @@ public class AiUserApiController extends BaseController {
|
|||
|
||||
/**
|
||||
* 查询返佣比例配置
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/getRebateConfig")
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ import com.ruoyi.common.annotation.Anonymous;
|
|||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.model.LoginAiUser;
|
||||
import com.ruoyi.common.utils.AwsS3Util;
|
||||
import com.ruoyi.common.utils.RandomStringUtil;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.TencentCosUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
|
@ -36,21 +36,13 @@ import java.util.regex.Pattern;
|
|||
public class ByteApiController extends BaseController {
|
||||
|
||||
private final IByteService byteService;
|
||||
private final TencentCosUtil tencentCosUtil;
|
||||
private final AwsS3Util awsS3Util;
|
||||
private final IAiOrderService aiOrderService;
|
||||
private final IAiManagerService managerService;
|
||||
private final IAiTagService aiTagService;
|
||||
@Value("${byteapi.callBackUrl}")
|
||||
private String url;
|
||||
|
||||
// 火山引擎配置
|
||||
@Value("${volcengine.ark.apiKey}")
|
||||
private String volcApiKey;
|
||||
@Value("${volcengine.ark.baseUrl}")
|
||||
private String volcBaseUrl;
|
||||
@Value("${volcengine.ark.callbackUrl}")
|
||||
private String volcCallbackUrl;
|
||||
|
||||
@PostMapping("/promptToImg")
|
||||
@ApiOperation("文生图")
|
||||
public AjaxResult promptToImg(@RequestBody ByteApiRequest request) {
|
||||
|
|
@ -59,12 +51,9 @@ public class ByteApiController extends BaseController {
|
|||
return AjaxResult.error("functionType is null");
|
||||
}
|
||||
|
||||
String mode = request.getMode() != null ? request.getMode() : "image-to-video";
|
||||
AiManager aiManager = managerService.selectAiManagerByType(functionType);
|
||||
String tags = request.getTags();
|
||||
String text = request.getText();
|
||||
|
||||
// 如果使用标签系统生成prompt
|
||||
String text = "";
|
||||
if (StringUtils.isNotEmpty(tags)) {
|
||||
List<AiTag> aiTags = aiTagService.selectAiTagListByIds(request.getTags(), aiManager.getParentIdSort());
|
||||
List<String> tagPrompts = new ArrayList<>();
|
||||
|
|
@ -81,24 +70,22 @@ public class ByteApiController extends BaseController {
|
|||
tagPrompts.add(p);
|
||||
}
|
||||
text = StringUtils.replacePlaceholders(aiManager.getPrompt(), tagPrompts);
|
||||
} else {
|
||||
text = aiManager.getPrompt();
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(text)) {
|
||||
return AjaxResult.error("text is null");
|
||||
}
|
||||
|
||||
|
||||
AiOrder aiOrder = aiOrderService.getAiOrder(functionType);
|
||||
try {
|
||||
if (aiOrder == null) {
|
||||
return AjaxResult.error(-1, "You have a low balance, please recharge");
|
||||
}
|
||||
aiOrder.setText(text);
|
||||
aiOrder.setMode(mode); // 记录生成模式
|
||||
|
||||
ByteBodyReq byteBodyReq = new ByteBodyReq();
|
||||
// model由前端传入,默认为Seedance 2.0
|
||||
byteBodyReq.setModel(StringUtils.isNotEmpty(request.getModel()) ?
|
||||
request.getModel() : "ep-20260326165811-dlkth");
|
||||
byteBodyReq.setModel("ep-20251104104536-2gpgz");
|
||||
byteBodyReq.setPrompt(text);
|
||||
byteBodyReq.setSequential_image_generation("disabled");
|
||||
byteBodyReq.setResponse_format("url");
|
||||
|
|
@ -109,7 +96,7 @@ public class ByteApiController extends BaseController {
|
|||
List<ByteDataRes> data = byteBodyRes.getData();
|
||||
ByteDataRes byteDataRes = data.get(0);
|
||||
String url = byteDataRes.getUrl();
|
||||
url = tencentCosUtil.uploadFileByUrl(url);
|
||||
url = awsS3Util.uploadFileByUrl(url);
|
||||
if (url == null) {
|
||||
// 判断生成失败,退回金额逻辑
|
||||
aiOrderService.orderFailure(aiOrder);
|
||||
|
|
@ -188,7 +175,7 @@ public class ByteApiController extends BaseController {
|
|||
List<ByteDataRes> data = byteBodyRes.getData();
|
||||
ByteDataRes byteDataRes = data.get(0);
|
||||
String url = byteDataRes.getUrl();
|
||||
url = tencentCosUtil.uploadFileByUrl(url);
|
||||
url = awsS3Util.uploadFileByUrl(url);
|
||||
if (url == null) {
|
||||
// 判断生成失败,退回金额逻辑
|
||||
aiOrderService.orderFailure(aiOrder);
|
||||
|
|
@ -204,7 +191,7 @@ public class ByteApiController extends BaseController {
|
|||
}
|
||||
|
||||
@PostMapping("/imgToVideo")
|
||||
@ApiOperation("图生视频 (Seedance 2.0)")
|
||||
@ApiOperation("图生视频")
|
||||
public AjaxResult imgToVideo(@RequestBody ByteApiRequest request) throws Exception {
|
||||
String functionType = request.getFunctionType();
|
||||
if (null == functionType) {
|
||||
|
|
@ -248,56 +235,51 @@ public class ByteApiController extends BaseController {
|
|||
return AjaxResult.error(-1, "You have a low balance, please recharge");
|
||||
}
|
||||
try {
|
||||
// String text = request.getText();
|
||||
// if (StringUtils.isBlank(text)) {
|
||||
// return AjaxResult.error("text is null");
|
||||
// }
|
||||
// String tags = request.getTags();
|
||||
// if (StringUtils.isNotBlank(tags)) {
|
||||
// text = "(优先考虑以下关键词:" + tags + ")";
|
||||
// }
|
||||
aiOrder.setText(text);
|
||||
|
||||
aiOrder.setImg1(firstUrl.toString());
|
||||
|
||||
Integer duration = request.getDuration() != null ? request.getDuration() : 4;
|
||||
|
||||
Integer duration = request.getDuration();
|
||||
ByteBodyReq byteBodyReq = new ByteBodyReq();
|
||||
// model由前端传入,默认为Seedance2.0
|
||||
byteBodyReq.setModel(StringUtils.isNotEmpty(request.getModel()) ?
|
||||
request.getModel() : "ep-20260326165811-dlkth");
|
||||
byteBodyReq.setCallback_url(volcCallbackUrl);
|
||||
byteBodyReq.setModel("ep-20251113072240-cfxlz");
|
||||
byteBodyReq.setCallback_url(url + "/api/ai/callBack");
|
||||
List<ContentItem> content = new ArrayList<>();
|
||||
ContentItem contentItem = new ContentItem();
|
||||
contentItem.setType("text");
|
||||
contentItem.setText(text + " --dur " + duration + " --fps 24 --rs 720p --wm false --cf false");
|
||||
content.add(contentItem);
|
||||
|
||||
// 构建符合火山引擎格式的content
|
||||
List<ContentItem> contentList = new ArrayList<>();
|
||||
|
||||
// 文本提示词
|
||||
ContentItem textItem = new ContentItem();
|
||||
textItem.setType("text");
|
||||
textItem.setText(text);
|
||||
contentList.add(textItem);
|
||||
ContentItem contentItem1 = new ContentItem();
|
||||
contentItem1.setType("image_url");
|
||||
contentItem1.setRole("first_frame");
|
||||
ImageUrl imageUrl1 = new ImageUrl();
|
||||
imageUrl1.setUrl(firstUrl.toString());
|
||||
contentItem1.setImageUrl(imageUrl1);
|
||||
content.add(contentItem1);
|
||||
|
||||
// 首帧图片
|
||||
ContentItem firstFrameItem = new ContentItem();
|
||||
firstFrameItem.setType("image_url");
|
||||
firstFrameItem.setRole("first_frame");
|
||||
ImageUrl firstImageUrl = new ImageUrl();
|
||||
firstImageUrl.setUrl(firstUrl.toString());
|
||||
firstFrameItem.setImageUrl(firstImageUrl);
|
||||
contentList.add(firstFrameItem);
|
||||
|
||||
// 如果有尾帧
|
||||
String lastUrl = request.getLastUrl();
|
||||
if (StringUtils.isNotBlank(lastUrl)) {
|
||||
ContentItem lastFrameItem = new ContentItem();
|
||||
lastFrameItem.setType("image_url");
|
||||
lastFrameItem.setRole("last_frame");
|
||||
ImageUrl lastImageUrl = new ImageUrl();
|
||||
lastImageUrl.setUrl(lastUrl);
|
||||
lastFrameItem.setImageUrl(lastImageUrl);
|
||||
contentList.add(lastFrameItem);
|
||||
ContentItem contentItem2 = new ContentItem();
|
||||
contentItem2.setType("image_url");
|
||||
contentItem2.setRole("last_frame");
|
||||
ImageUrl imageUrl2 = new ImageUrl();
|
||||
imageUrl2.setUrl(lastUrl);
|
||||
contentItem2.setImageUrl(imageUrl2);
|
||||
content.add(contentItem2);
|
||||
aiOrder.setImg2(lastUrl);
|
||||
}
|
||||
|
||||
byteBodyReq.setContent(contentList);
|
||||
byteBodyReq.setDuration(duration);
|
||||
byteBodyReq.setResolution("720p");
|
||||
byteBodyReq.setRatio("3:4");
|
||||
|
||||
byteBodyReq.setContent(content);
|
||||
ByteBodyRes byteBodyRes = byteService.imgToVideo(byteBodyReq);
|
||||
String id = byteBodyRes.getId();
|
||||
if (id == null) {
|
||||
// 判断生成失败,退回金额逻辑
|
||||
aiOrderService.orderFailure(aiOrder);
|
||||
return AjaxResult.error(-2, "generation failed, balance has been refunded");
|
||||
}
|
||||
|
|
@ -317,7 +299,7 @@ public class ByteApiController extends BaseController {
|
|||
if ("succeeded".equals(byteBodyRes.getStatus())) {
|
||||
content content = byteBodyRes.getContent();
|
||||
String videoUrl = content.getVideo_url();
|
||||
videoUrl = tencentCosUtil.uploadFileByUrl(videoUrl);
|
||||
videoUrl = awsS3Util.uploadFileByUrl(videoUrl);
|
||||
content.setVideo_url(videoUrl);
|
||||
AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(id);
|
||||
AiOrder aiOrder = new AiOrder();
|
||||
|
|
@ -329,34 +311,24 @@ public class ByteApiController extends BaseController {
|
|||
return AjaxResult.success(byteBodyRes);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/volcCallback")
|
||||
@ApiOperation("火山引擎视频回调")
|
||||
@GetMapping(value = "/callBack")
|
||||
@ApiOperation("视频下载回调")
|
||||
@Anonymous
|
||||
public AjaxResult volcCallback(@RequestBody ByteBodyRes byteBodyRes) throws Exception {
|
||||
public AjaxResult callBack(@PathVariable("id") ByteBodyRes byteBodyRes) throws Exception {
|
||||
if ("succeeded".equals(byteBodyRes.getStatus())) {
|
||||
String id = byteBodyRes.getId();
|
||||
content contentObj = byteBodyRes.getContent();
|
||||
if (contentObj != null && StringUtils.isNotEmpty(contentObj.getVideo_url())) {
|
||||
String videoUrl = contentObj.getVideo_url();
|
||||
videoUrl = tencentCosUtil.uploadFileByUrl(videoUrl);
|
||||
contentObj.setVideo_url(videoUrl);
|
||||
|
||||
AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(id);
|
||||
if (aiOrderByResult != null) {
|
||||
AiOrder aiOrder = new AiOrder();
|
||||
aiOrder.setId(aiOrderByResult.getId());
|
||||
aiOrder.setResult(videoUrl);
|
||||
aiOrderService.updateAiOrder(aiOrder);
|
||||
}
|
||||
}
|
||||
content content = byteBodyRes.getContent();
|
||||
String videoUrl = content.getVideo_url();
|
||||
videoUrl = awsS3Util.uploadFileByUrl(videoUrl);
|
||||
content.setVideo_url(videoUrl);
|
||||
AiOrder aiOrderByResult = aiOrderService.getAiOrderByResult(id);
|
||||
AiOrder aiOrder = new AiOrder();
|
||||
aiOrder.setId(aiOrderByResult.getId());
|
||||
aiOrder.setResult(videoUrl);
|
||||
// aiOrder.setUpdateBy(SecurityUtils.getLoginAiUser().getUsername());
|
||||
aiOrderService.updateAiOrder(aiOrder);
|
||||
}
|
||||
return AjaxResult.success("callback success");
|
||||
}
|
||||
|
||||
@PostMapping(value = "/{id}/cancel")
|
||||
@ApiOperation("取消视频生成任务")
|
||||
public AjaxResult cancelTask(@PathVariable("id") String id) throws Exception {
|
||||
return byteService.cancelVideoTask(id);
|
||||
return AjaxResult.success(byteBodyRes);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.ruoyi.api;
|
||||
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.utils.TencentCosUtil;
|
||||
import com.ruoyi.common.utils.AwsS3Util;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
|
|
@ -18,7 +18,7 @@ import org.springframework.web.multipart.MultipartFile;
|
|||
@Api(tags = "文件上传")
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
public class FileController {
|
||||
private final TencentCosUtil tencentCosUtil;
|
||||
private final AwsS3Util awsS3Util;
|
||||
|
||||
/**
|
||||
* 文件上传
|
||||
|
|
@ -30,7 +30,7 @@ public class FileController {
|
|||
@ApiParam(name = "file", value = "文件", required = true)
|
||||
@RequestParam("file") MultipartFile file) throws Exception {
|
||||
AjaxResult ajax = AjaxResult.success();
|
||||
String uploadUrl = tencentCosUtil.uploadMultipartFile(file, true);
|
||||
String uploadUrl = awsS3Util.uploadMultipartFile(file, true);
|
||||
ajax.put("url", uploadUrl);
|
||||
return ajax;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ public class KadaPaymentJava {
|
|||
map.put("pm", "QRPH");
|
||||
map.put("ref", orderNo);
|
||||
map.put("payer", payer);
|
||||
map.put("redirect", "https://undressing.top/recharge");
|
||||
map.put("callbackUrl", "https://undressing.top/api/pay/kada-callBack");
|
||||
map.put("redirect", "www.google.com/recharge");
|
||||
map.put("callbackUrl", "www.google.com/api/pay/kada-callBack");
|
||||
|
||||
return JSON.toJSONString(map);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@ import com.ruoyi.ai.service.IAiRechargeService;
|
|||
import com.ruoyi.ai.service.IJinShaService;
|
||||
import com.ruoyi.ai.service.IKaDaService;
|
||||
import com.ruoyi.ai.service.IYuZhouService;
|
||||
import com.ruoyi.ai.service.IVmService;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.utils.ip.IpUtils;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
|
@ -27,7 +25,6 @@ public class PayController {
|
|||
private final IKaDaService kaDaService;
|
||||
private final IAiRechargeService aiRechargeService;
|
||||
private final IYuZhouService yuZhouService;
|
||||
private final IVmService vmService;
|
||||
|
||||
/**
|
||||
* jinsha请求支付
|
||||
|
|
@ -88,109 +85,4 @@ public class PayController {
|
|||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* VM请求支付
|
||||
*/
|
||||
@PostMapping("/vm-pay")
|
||||
@ApiOperation("VM请求支付")
|
||||
public AjaxResult vmPay(@RequestBody VmPayReq req, HttpServletRequest request) throws Exception {
|
||||
// 获取用户真实IP地址
|
||||
String clientIp = IpUtils.getIpAddr(request);
|
||||
PayResVO payResVO = vmService.vmPay(req.getGearId(), req.getVmCardInfo(), clientIp);
|
||||
return AjaxResult.success(payResVO);
|
||||
}
|
||||
|
||||
@PostMapping("/vm-callBack")
|
||||
@ApiOperation("VM支付回调")
|
||||
@Anonymous
|
||||
public String vmCallBack(HttpServletRequest request) {
|
||||
// 记录请求的Content-Type
|
||||
String contentType = request.getContentType();
|
||||
|
||||
// 从请求中解析表单参数
|
||||
VmCallBackReq req = new VmCallBackReq();
|
||||
|
||||
// 记录所有请求参数用于调试
|
||||
java.util.Enumeration<String> paramNames = request.getParameterNames();
|
||||
java.util.Map<String, String> allParams = new java.util.HashMap<>();
|
||||
while (paramNames.hasMoreElements()) {
|
||||
String paramName = paramNames.nextElement();
|
||||
String paramValue = request.getParameter(paramName);
|
||||
allParams.put(paramName, paramValue);
|
||||
}
|
||||
org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PayController.class);
|
||||
log.info("VM支付回调请求参数: {}, Content-Type: {}", allParams, contentType);
|
||||
|
||||
req.setMchNo(request.getParameter("mchNo"));
|
||||
req.setAppId(request.getParameter("appId"));
|
||||
req.setMchOrderNo(request.getParameter("mchOrderNo"));
|
||||
req.setPayOrderId(request.getParameter("payOrderId"));
|
||||
req.setWayCode(request.getParameter("wayCode"));
|
||||
|
||||
// 解析整数类型字段
|
||||
String amountStr = request.getParameter("amount");
|
||||
if (amountStr != null && !amountStr.isEmpty()) {
|
||||
try {
|
||||
req.setAmount(Integer.parseInt(amountStr));
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("VM支付回调amount解析失败: {}", amountStr);
|
||||
}
|
||||
}
|
||||
|
||||
req.setCurrency(request.getParameter("currency"));
|
||||
req.setIfCode(request.getParameter("ifCode"));
|
||||
req.setSubject(request.getParameter("subject"));
|
||||
req.setBody(request.getParameter("body"));
|
||||
req.setClientIp(request.getParameter("clientIp"));
|
||||
|
||||
// 解析订单状态(文档字段名:state)
|
||||
String stateStr = request.getParameter("state");
|
||||
if (stateStr != null && !stateStr.isEmpty()) {
|
||||
try {
|
||||
req.setState(Integer.parseInt(stateStr));
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("VM支付回调state解析失败: {}", stateStr);
|
||||
}
|
||||
}
|
||||
|
||||
req.setChannelOrderNo(request.getParameter("channelOrderNo"));
|
||||
req.setErrCode(request.getParameter("errCode"));
|
||||
req.setErrMsg(request.getParameter("errMsg"));
|
||||
req.setExtParam(request.getParameter("extParam"));
|
||||
|
||||
// 解析时间戳:createdAt、successTime、reqTime(文档字段名)
|
||||
String createdAtStr = request.getParameter("createdAt");
|
||||
if (createdAtStr != null && !createdAtStr.isEmpty()) {
|
||||
try {
|
||||
req.setCreatedAt(Long.parseLong(createdAtStr));
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("VM支付回调createdAt解析失败: {}", createdAtStr);
|
||||
}
|
||||
}
|
||||
String successTimeStr = request.getParameter("successTime");
|
||||
if (successTimeStr != null && !successTimeStr.isEmpty()) {
|
||||
try {
|
||||
req.setSuccessTime(Long.parseLong(successTimeStr));
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("VM支付回调successTime解析失败: {}", successTimeStr);
|
||||
}
|
||||
}
|
||||
String reqTimeStr = request.getParameter("reqTime");
|
||||
if (reqTimeStr != null && !reqTimeStr.isEmpty()) {
|
||||
try {
|
||||
req.setReqTime(Long.parseLong(reqTimeStr));
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("VM支付回调reqTime解析失败: {}", reqTimeStr);
|
||||
}
|
||||
}
|
||||
|
||||
req.setSign(request.getParameter("sign"));
|
||||
req.setSignType(request.getParameter("signType"));
|
||||
|
||||
log.info("VM支付回调解析后的对象: mchOrderNo={}, state={}, sign={}",
|
||||
req.getMchOrderNo(), req.getState(), req.getSign());
|
||||
|
||||
return vmService.vmCallBack(req);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,519 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -31,11 +31,6 @@ public class ByteApiRequest {
|
|||
@ApiModelProperty(name = "标签字符串")
|
||||
private String tags;
|
||||
|
||||
@ApiModelProperty(name = "使用的模型")
|
||||
private String model;
|
||||
|
||||
@ApiModelProperty(name = "生成模式:text-to-video 或 image-to-video")
|
||||
private String mode = "text-to-video";
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
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=0、del_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.ruoyi.web.controller.ai;
|
||||
package com.ruoyi.ai.controller;
|
||||
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
|
|
|||
|
|
@ -1,27 +1,33 @@
|
|||
package com.ruoyi.web.controller.ai;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import com.ruoyi.ai.service.EmailVerifyService;
|
||||
import com.ruoyi.ai.service.IAiUserService;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.constant.BalanceChangerConstants;
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.AiUser;
|
||||
import com.ruoyi.common.core.domain.model.RegisterAiUserBody;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.MessageUtils;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
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.*;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* ai-用户信息Controller
|
||||
|
|
@ -71,6 +77,7 @@ public class AiUserController extends BaseController {
|
|||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 新增ai-用户信息
|
||||
*/
|
||||
|
|
@ -93,20 +100,6 @@ public class AiUserController extends BaseController {
|
|||
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()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 状态修改
|
||||
*/
|
||||
|
|
@ -130,12 +123,9 @@ public class AiUserController extends BaseController {
|
|||
aiUser.setUpdateBy(getUsername());
|
||||
// 获取原余额
|
||||
AiUser u = aiUserService.selectAiUserById(aiUser.getId());
|
||||
String uuid = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8);
|
||||
String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||
String orderNo = dateTime + uuid;
|
||||
// 计算变更余额
|
||||
BigDecimal amount = NumberUtil.sub(aiUser.getBalance(), u.getBalance());
|
||||
aiUserService.addUserBalance(orderNo, aiUser.getId(), amount, BalanceChangerConstants.SYSTEM_OPERATION);
|
||||
aiUserService.addUserBalance(aiUser.getId(), amount, BalanceChangerConstants.SYSTEM_OPERATION);
|
||||
return toAjax(1);
|
||||
}
|
||||
|
||||
|
|
@ -161,6 +151,8 @@ public class AiUserController extends BaseController {
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// @GetMapping("/addUserBalance")
|
||||
// public AjaxResult addUserBalance(BigDecimal amount) {
|
||||
// aiUserService.addUserBalance(SecurityUtils.getAiUserId(), amount, BalanceChangerConstants.RECHARGE);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.ruoyi.common.annotation.Anonymous;
|
|||
import com.ruoyi.common.config.RuoYiConfig;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.exception.base.BaseException;
|
||||
import com.ruoyi.common.utils.AwsS3Util;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.TencentCosUtil;
|
||||
import com.ruoyi.common.utils.file.FileUploadUtils;
|
||||
|
|
@ -35,6 +36,7 @@ public class CommonController {
|
|||
private static final Logger log = LoggerFactory.getLogger(CommonController.class);
|
||||
private final ServerConfig serverConfig;
|
||||
private final TencentCosUtil tencentCosUtil;
|
||||
private final AwsS3Util awsS3Util;
|
||||
|
||||
/**
|
||||
* 通用下载请求
|
||||
|
|
@ -156,7 +158,7 @@ public class CommonController {
|
|||
}
|
||||
|
||||
/**
|
||||
* 腾讯云COS上传请求(单个)
|
||||
* AWS上传请求(单个)
|
||||
*/
|
||||
@ApiOperation("图片上传接口")
|
||||
@PostMapping("/aws/upload")
|
||||
|
|
@ -164,7 +166,7 @@ public class CommonController {
|
|||
AjaxResult ajax = AjaxResult.success();
|
||||
String uploadUrl;
|
||||
try {
|
||||
uploadUrl = tencentCosUtil.uploadMultipartFile(file, true);
|
||||
uploadUrl = awsS3Util.uploadMultipartFile(file, true);
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ spring:
|
|||
druid:
|
||||
# 主库数据源
|
||||
master:
|
||||
url: jdbc:mysql://database-1.cvs822qoc391.ap-southeast-1.rds.amazonaws.com:3306/byteai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
url: jdbc:mysql://xxxxx:xxxxx/byteai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: mkMReisAKl6I7rVqEY90
|
||||
password: xxxxxx
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
|
||||
# 初始连接数
|
||||
|
|
@ -41,8 +41,8 @@ spring:
|
|||
allow:
|
||||
url-pattern: /druid/*
|
||||
# 控制台管理用户名和密码
|
||||
login-username: ruoyi
|
||||
login-password: cvs822qoc391
|
||||
login-username:
|
||||
login-password:
|
||||
filter:
|
||||
stat:
|
||||
enabled: true
|
||||
|
|
@ -54,4 +54,3 @@ spring:
|
|||
config:
|
||||
multi-statement-allow: true
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -47,16 +47,12 @@ user:
|
|||
|
||||
# Spring配置
|
||||
spring:
|
||||
# 邮件发送配置
|
||||
# 邮件发送配置
|
||||
mail:
|
||||
# host: mailcow-self.undressing.name # QQ邮箱SMTP服务器(163邮箱:smtp.163.com;Gmail:smtp.gmail.com)
|
||||
# port: 465 # SMTP端口(SSL加密端口:465,非加密:587,优先用587)
|
||||
# username: undressing@mail.undressing.name # 你的邮箱地址
|
||||
# password: aRtHEN39 # 邮箱授权码(不是登录密码!)
|
||||
host: smtpdm-ap-southeast-1.aliyun.com # QQ邮箱SMTP服务器(163邮箱:smtp.163.com;Gmail:smtp.gmail.com)
|
||||
host: smtp.qq.com # QQ邮箱SMTP服务器(163邮箱:smtp.163.com;Gmail:smtp.gmail.com)
|
||||
port: 465 # SMTP端口(SSL加密端口:465,非加密:587,优先用587)
|
||||
username: undressing@undressing.name # 你的邮箱地址
|
||||
password: 1284GOvkho # 邮箱授权码(不是登录密码!)
|
||||
username: # 你的邮箱地址
|
||||
password: # 邮箱授权码(不是登录密码!)
|
||||
default-encoding: UTF-8 # 编码格式
|
||||
protocol: smtp
|
||||
# SSL/TLS配置(可选,根据邮箱要求)
|
||||
|
|
@ -93,17 +89,15 @@ spring:
|
|||
# redis 配置
|
||||
redis:
|
||||
# 地址
|
||||
host: master.redis.chguac.apse1.cache.amazonaws.com
|
||||
host: localhost
|
||||
# 端口,默认为6379
|
||||
port: 6379
|
||||
# 数据库索引
|
||||
database: 2
|
||||
# 密码
|
||||
user: root
|
||||
password: mkMReisAKl6I7rVqEY90
|
||||
password:
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
ssl: true
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池中的最小空闲连接
|
||||
|
|
@ -114,6 +108,7 @@ spring:
|
|||
max-active: 8
|
||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
|
||||
# 自定义验证码配置
|
||||
verify:
|
||||
code:
|
||||
|
|
@ -209,109 +204,42 @@ google:
|
|||
redirect-uri:
|
||||
|
||||
tencentCos:
|
||||
accessKey: AKIDBE3dzB
|
||||
secretKey: EDyUmsnX2IJ
|
||||
endpoint: ap-guangzhou
|
||||
bucketName: seed
|
||||
domain: https://seedaguangzhou.myqcloud.com
|
||||
accessKey:
|
||||
secretKey:
|
||||
endpoint:
|
||||
bucketName:
|
||||
domain:
|
||||
|
||||
# aws配置已替换为腾讯云COS,请在环境变量或配置文件中设置腾讯云凭证
|
||||
# aws:
|
||||
# accessKey: AKIAYVMHEVDDZQGE3HVX
|
||||
# secretKey: B9nxdferMhdRuxzoKeQam/NxiVvIhI7lSru6VfwG
|
||||
# endpoint: ap-southeast-1
|
||||
# bucketName: di-image
|
||||
# domain: https://images.iqyjsnwv.com/
|
||||
aws:
|
||||
accessKey: AKIAYVMHEVDDZQGE3HVX
|
||||
secretKey: B9nxdferMhdRuxzoKeQam/NxiVvIhI7lSru6VfwG
|
||||
endpoint: ap-southeast-1
|
||||
bucketName: di-image
|
||||
domain: https://images.iqyjsnwv.com/
|
||||
|
||||
byteapi:
|
||||
url: https://ark.ap-southeast.bytepluses.com/api/v3
|
||||
apiKey:
|
||||
callBackUrl: https://undressing.top
|
||||
|
||||
# 火山引擎 Ark API (Seedance 2.0)
|
||||
volcengine:
|
||||
ark:
|
||||
baseUrl: https://ark.cn-beijing.volces.com
|
||||
apiKey:
|
||||
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"
|
||||
apiKey: 327d2815-2516-44c2-9e32-2dc50bf7afd7
|
||||
callBackUrl: www.google.com
|
||||
|
||||
jinsha:
|
||||
url: https://api.jinshapay.xyz
|
||||
appId: 1763617360
|
||||
secret: a201e7969af5045dcd62d203b26121ae
|
||||
notifyUrl: https://undressing.top
|
||||
returnUrl: https://undressing.top
|
||||
notifyUrl: www.google.com
|
||||
returnUrl: www.google.com
|
||||
|
||||
kada:
|
||||
url: https://rapi.openkada.xyz
|
||||
appId: d1743d48fb8fc24f38b7268015cf800e3b49f0fd
|
||||
secret: b0CH/+tVEsz+1j2mfBzd9Kgu6UylJxr0056TwTbkKfHWw9UW/6TaQyQHv+teBnGbqWy5ObaLUMvnrs9adpymebEqjI3ipNpJa7YPQbMYm0VGuYUEgeM+fjakhWuYx2XEVzmjdIvvfhNsfr2YHTmDUzwIKPbp/OJvfG9KhSPMzpw=
|
||||
notifyUrl: https://undressing.top
|
||||
returnUrl: https://undressing.top
|
||||
|
||||
yuzhou:
|
||||
url: https://api.fast-vip.store
|
||||
appId: PM20251211091945
|
||||
publicKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCiTl8fg6eM6uUJqxAjGtzskt+ESIgferomy6uUtjRx4yhu6I4cVBgaw9ErJq9KMNQpMVl44GEese6PRDmNPdvXBktI/skpCfyNvT+1LqYm69Hh+rFre2Ve+0XIVoln0H3EGNUHL/KPOCm2tYXLXlZ3r02z+AQeS3rxNhE4jr32oQIDAQAB
|
||||
secretKey: MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOyqpe98vNWLL/1ja4WnUkCGiHAVnNJtlw7c9bdacCc7xgGSlXIq1bRQWXMjLZeSC31BUFcrIMk5eMr1AWUTYnVOmAJ4IsLZCUMQOBU9S6BJdqnaus4K6Kul35lrb/Zt6ju6fY1acL7q3NWJGdeRtmYaGXZbF+RH6UbA6aH4vkHBAgMBAAECgYAEh+8EbveOYZCuGdpil82w9KUVpe5Hj5JQANOMpU+RdWPLiX7xDX2jqv2kFmEeiPeNbXk7AAABJjzoKtO84lz/n0Vp8hwqIgeMdmLZBBTsJhXgOfgnvwjb/1Ev2Yjg3KGp7nKvkyPpoU9e0W8+PR5yTclylV0bCYqsP260BFLnIQJBAPmqUzQLQN739dKoO3uVpB9qU3exZOv+zlTQZj2C0cXt3/+JzJs78DBieN9+b3ZLq307namt8QAUVV+QwRoNPxECQQDyq+QoAakpi1tQoGfYUecucDcZf8GDPdEkCGhGoIq/FkzL0gGub45Rz0oXzAzBME6DcobfVVippcUB54pueTexAkBxDTtX2bCqRkW9+gfVUlFbGF5rWJyGcH8l0Kg7Oj2bDrfbkp5fvKhqgGyTZ0E5o9InhxNBfk4e5xYxi+6kyVLBAkBcPahA6LizOOxhzkcKu78jMLZQ8/XLfCDWEHgKeJWkosZYJyBKfM7dG+zu9LnYaRM+9bZ8h8Vm3sLuwWMmMN9RAkBdOSqC71e9248Bmw70zX7x6ZmszGQ64toA6mhhcdYb4wAlDtmm0OJjJRmpyXdhgdIHF3i7vNe7sXpPfNKFc1Yf
|
||||
redirectUrl: https://undressing.top
|
||||
callbackUrl: https://undressing.top
|
||||
vm:
|
||||
url: http://payment-api.togame.top
|
||||
mchNo: M1768983012
|
||||
appId: 697089e4f41a4f456f159408
|
||||
secret: 120tzr4snoq11yus8la9gx7cbutw1uore4pervckvqmsswrt1hl9qkd0ug5r6twwv94jex03ajpsmsky2za4x1kghd2l54z4nn7t5fcy4gewsvwjjxrce5q1f7u2yeqj
|
||||
notifyUrl: https://undressing.top
|
||||
# 支付方式,固定为BUZHI_VM(国际VM卡支付)
|
||||
wayCode: BUZHI_VM
|
||||
# 货币代码,默认USD
|
||||
currency: USD
|
||||
appId: c70f1719017e290354017d1c101d0cc288d06ceb
|
||||
secret: ME2VRe6tWH6weK/NAUJA5lhmewHkB23rA6CdWlrHrAs+/E/E3j3eG3io/GCHbQKqMMurfTNrBj/R4Yy84UziM5YJheiKFKbsWQc5xRoE46E3/0EYy4ZjbK9jhwGyHS+C
|
||||
notifyUrl: www.google.com
|
||||
returnUrl: www.google.com
|
||||
|
||||
# 汇率服务配置
|
||||
exchange-rate:
|
||||
enabled: true
|
||||
apikey: 0bed27315c87475f8dd6a0792a632cc5
|
||||
api-url: https://api.currencyfreaks.com/v2.0/rates/latest
|
||||
# base 货币(API返回的基准货币,通常为USD)
|
||||
base-currency: USD
|
||||
# 备用汇率(当API调用失败时使用)
|
||||
fallback-rate-jinsha: 90
|
||||
fallback-rate-kada: 60
|
||||
# PHP比索的备用汇率(当API调用失败时使用,默认使用JinSha的汇率)
|
||||
fallback-rate-php: 90
|
||||
yuzhou:
|
||||
url: https://pay.joinus6688.cc
|
||||
appId: PM20251211091945
|
||||
publicKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCiTl8fg6eM6uUJqxAjGtzskt+ESIgferomy6uUtjRx4yhu6I4cVBgaw9ErJq9KMNQpMVl44GEese6PRDmNPdvXBktI/skpCfyNvT+1LqYm69Hh+rFre2Ve+0XIVoln0H3EGNUHL/KPOCm2tYXLXlZ3r02z+AQeS3rxNhE4jr32oQIDAQAB
|
||||
secretKey: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAL7U/f7yb9Z9j56dauCUb0B/I0ONAcZDK+TtOnAgLEjV4qrirYHYuCumxYbPFvt6qYggjbBpphFhihbWzf8IPS7iD6VXoSX1T1iAlWFL1ZaBscqQSvPxFGtgpTBiRFS3KkZV70WswcCL770OCkY8+DJpdyk9OkD6vBa0TYU1wxJFAgMBAAECgYBpF+t5iBJHUYbSl2bQn25VWq8U+IbNpRh7TposPculoQTfj052f9+NSp7liw7hF8Bdk2/0g3pNgCYIRevUU7k9MEIKqHCiOWkyavtsfqGYI37PZ4/0uMzB5eibTqKTEkcyskSJ9GxrL4uGKgTGNc213i3VOcZZ4xEfvuDQCHF8gQJBAPATuhQeFNNAIE9TkGiESHFGChSZZgzp1xfGrAt8BwidBSe+r9duAcGJSeNJatxneeu0w6NuwQ6iq9ztnqtoG90CQQDLfSHrOTloHSa+DdBc2SFQWa/P4K2Tznb8Y5ng8L/t+a9sYvGjWOln0R3Bq9TrImm0AjWnq/saaMg2nYD1wr2JAkA36XA5xTO2a0XbE6wbG0u/zb8FQyCIO2GTsPpahl0g/Wi48+kB9CXGjBHANFYF1LeJVIUHqACgRvRdtJ1ycAGlAkEAqASVWiTw2p+fWrQLRG7gS/kR6uIIUI/cvT78UrhWsYdFqof0Hz0N0/PdzwkzkEbk4oYkiWK+viqgjj/0uHfoiQJAQhPYVVLHD7xiJApc/Aga6g0OFF5O7zy8KTsq+KTXqRlJREBH5nirSponHwYalEbUvtQrVs+Z4BCBEGCU8m2GEw==
|
||||
redirectUrl: www.google.com
|
||||
callbackUrl: www.google.com
|
||||
|
|
|
|||
|
|
@ -71,12 +71,6 @@
|
|||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons Codec for Base64 encoding/decoding -->
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- excel工具 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
|
|
|
|||
|
|
@ -117,19 +117,12 @@ public class AiUser extends BaseEntity {
|
|||
*/
|
||||
private String country;
|
||||
|
||||
/** 归属部门ID(sys_dept.dept_id) */
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 上级用户昵称
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String superiorName;
|
||||
|
||||
/** 归属部门名称 */
|
||||
@TableField(exist = false)
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 上级用户ID
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -51,9 +51,6 @@ public class SysDept extends BaseEntity
|
|||
|
||||
/** 父部门名称 */
|
||||
private String parentName;
|
||||
|
||||
/** Byte API Key */
|
||||
private String byteApiKey;
|
||||
|
||||
/** 子部门 */
|
||||
private List<SysDept> children = new ArrayList<SysDept>();
|
||||
|
|
@ -174,16 +171,6 @@ public class SysDept extends BaseEntity
|
|||
this.parentName = parentName;
|
||||
}
|
||||
|
||||
public String getByteApiKey()
|
||||
{
|
||||
return byteApiKey;
|
||||
}
|
||||
|
||||
public void setByteApiKey(String byteApiKey)
|
||||
{
|
||||
this.byteApiKey = byteApiKey;
|
||||
}
|
||||
|
||||
public List<SysDept> getChildren()
|
||||
{
|
||||
return children;
|
||||
|
|
@ -205,7 +192,6 @@ public class SysDept extends BaseEntity
|
|||
.append("leader", getLeader())
|
||||
.append("phone", getPhone())
|
||||
.append("email", getEmail())
|
||||
.append("byteApiKey", getByteApiKey())
|
||||
.append("status", getStatus())
|
||||
.append("delFlag", getDelFlag())
|
||||
.append("createBy", getCreateBy())
|
||||
|
|
|
|||
|
|
@ -13,13 +13,7 @@ import org.springframework.beans.factory.annotation.Value;
|
|||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
|
|
@ -39,32 +33,22 @@ public class TencentCosUtil {
|
|||
private String domain;
|
||||
|
||||
|
||||
/**
|
||||
* 上传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("上传文件不能为空");
|
||||
}
|
||||
//文件上传
|
||||
public String upload(MultipartFile file) {
|
||||
|
||||
// 3 生成 cos 客户端。
|
||||
COSClient cosClient = createCosClient();
|
||||
|
||||
// 生成唯一文件键,格式与AWS一致:yyyy/MM/dd/uuid_filename
|
||||
String key = generateCosKey(file.getOriginalFilename());
|
||||
|
||||
// 存储桶的命名格式为 BucketName-APPID,此处填写的存储桶名称必须为此格式
|
||||
// 对象键(Key)是对象在存储桶中的唯一标识。 998u-09iu-09i-333
|
||||
//在文件名称前面添加uuid值
|
||||
String key = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8) + "_"
|
||||
+ file.getOriginalFilename();
|
||||
//对上传文件分组,根据当前日期 /2022/11/11
|
||||
String dateTime = new DateTime().toString("yyyy/MM/dd");
|
||||
key = dateTime + "/" + key;
|
||||
try {
|
||||
//获取上传文件输入流
|
||||
InputStream inputStream = file.getInputStream();
|
||||
ObjectMetadata objectMetadata = new ObjectMetadata();
|
||||
PutObjectRequest putObjectRequest = new PutObjectRequest(
|
||||
|
|
@ -72,41 +56,17 @@ public class TencentCosUtil {
|
|||
key,
|
||||
inputStream,
|
||||
objectMetadata);
|
||||
|
||||
// 高级接口会返回一个异步结果Upload
|
||||
PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
|
||||
|
||||
// 返回COS文件访问地址
|
||||
String url = domain + (domain.endsWith("/") ? "" : "/") + key;
|
||||
//返回上传文件路径
|
||||
//https://ggkt-atguigu-1310644373.cos.ap-beijing.myqcloud.com/01.jpg
|
||||
String url = domain + "/" + key;
|
||||
return url;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("上传文件到COS失败: " + e.getMessage(), e);
|
||||
} finally {
|
||||
cosClient.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过URL下载文件并上传到COS
|
||||
* 与AwsS3Util.uploadFileByUrl方法接口兼容
|
||||
*/
|
||||
public String uploadFileByUrl(String fileUrl) throws Exception {
|
||||
return uploadFileByUrl(fileUrl, true);
|
||||
}
|
||||
|
||||
public String uploadFileByUrl(String fileUrl, boolean isPublic) throws Exception {
|
||||
if (fileUrl == null || fileUrl.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("文件下载链接不能为空");
|
||||
}
|
||||
|
||||
Path tempPath = downloadFileToTemp(fileUrl);
|
||||
try {
|
||||
// 使用临时文件上传
|
||||
MultipartFile multipartFile = createMultipartFileFromPath(tempPath, extractFileNameFromUrl(fileUrl));
|
||||
return uploadMultipartFile(multipartFile, isPublic);
|
||||
} finally {
|
||||
Files.deleteIfExists(tempPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -118,107 +78,7 @@ public class TencentCosUtil {
|
|||
ClientConfig clientConfig = new ClientConfig(region);
|
||||
// 这里建议设置使用 https 协议
|
||||
clientConfig.setHttpProtocol(HttpProtocol.https);
|
||||
clientConfig.setConnectionTimeout(30 * 1000); // 连接超时30秒
|
||||
clientConfig.setSocketTimeout(60 * 1000); // 读取超时60秒
|
||||
//1.3 生成cos客户端
|
||||
return new COSClient(credentials, clientConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成COS文件键,与AWS保持一致的命名格式
|
||||
*/
|
||||
private String generateCosKey(String originalFileName) {
|
||||
String uuid = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8);
|
||||
String dateTime = new DateTime().toString("yyyy/MM/dd");
|
||||
return dateTime + "/" + uuid + "_" + originalFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件到临时路径
|
||||
*/
|
||||
private Path downloadFileToTemp(String fileUrl) throws Exception {
|
||||
String suffix = getFileSuffixFromUrl(fileUrl);
|
||||
Path tempPath = Files.createTempFile("url-upload-", suffix);
|
||||
|
||||
HttpURLConnection connection = null;
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
|
||||
try {
|
||||
URL url = new URL(fileUrl);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setReadTimeout(10000);
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode < 200 || responseCode >= 300) {
|
||||
throw new RuntimeException("文件下载失败,状态码:" + responseCode);
|
||||
}
|
||||
|
||||
in = connection.getInputStream();
|
||||
out = Files.newOutputStream(tempPath);
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
} finally {
|
||||
if (out != null) {
|
||||
try { out.close(); } catch (IOException e) { e.printStackTrace(); }
|
||||
}
|
||||
if (in != null) {
|
||||
try { in.close(); } catch (IOException e) { e.printStackTrace(); }
|
||||
}
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
private String extractFileNameFromUrl(String fileUrl) {
|
||||
String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1);
|
||||
if (fileName.contains("?")) {
|
||||
fileName = fileName.split("\\?")[0];
|
||||
}
|
||||
return fileName.isEmpty() ? "default_file" : fileName;
|
||||
}
|
||||
|
||||
private String getFileSuffixFromUrl(String fileUrl) {
|
||||
String fileName = extractFileNameFromUrl(fileUrl);
|
||||
if (fileName.contains(".")) {
|
||||
return fileName.substring(fileName.lastIndexOf("."));
|
||||
}
|
||||
return ".tmp";
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Path转换为MultipartFile(简单实现,用于uploadFileByUrl)
|
||||
*/
|
||||
private MultipartFile createMultipartFileFromPath(Path path, String originalFilename) throws IOException {
|
||||
byte[] bytes = Files.readAllBytes(path);
|
||||
return new MultipartFile() {
|
||||
@Override
|
||||
public String getName() { return "file"; }
|
||||
@Override
|
||||
public String getOriginalFilename() { return originalFilename; }
|
||||
@Override
|
||||
public String getContentType() { return null; }
|
||||
@Override
|
||||
public boolean isEmpty() { return bytes.length == 0; }
|
||||
@Override
|
||||
public long getSize() { return bytes.length; }
|
||||
@Override
|
||||
public byte[] getBytes() throws IOException { return bytes; }
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException { return Files.newInputStream(path); }
|
||||
@Override
|
||||
public void transferTo(java.io.File dest) throws IOException, IllegalStateException {
|
||||
Files.copy(path, dest.toPath());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,14 @@
|
|||
package com.ruoyi.common.utils.sign;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -21,29 +16,25 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class RSAUtils {
|
||||
|
||||
// RSA最大加密明文大小
|
||||
// RSA最⼤加密明⽂⼤⼩
|
||||
private static final int MAX_ENCRYPT_BLOCK = 117;
|
||||
// 不仅可以使用DSA算法,同样也可以使用RSA算法做数字签名
|
||||
// 不仅可以使⽤DSA算法,同样也可以使⽤RSA算法做数字签名
|
||||
private static final String KEY_ALGORITHM = "RSA";
|
||||
|
||||
public static String encryptByPrivateKey(String str, String privateKey)
|
||||
throws InvalidKeySpecException, NoSuchAlgorithmException, javax.crypto.NoSuchPaddingException, java.security.InvalidKeyException,
|
||||
javax.crypto.IllegalBlockSizeException, javax.crypto.BadPaddingException, UnsupportedEncodingException {
|
||||
public static String encryptByPrivateKey(String str, String privateKey) throws Exception {
|
||||
// base64编码的公钥
|
||||
byte[] keyBytes = decryptBASE64(privateKey);
|
||||
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM)
|
||||
.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
|
||||
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
|
||||
// RSA加密
|
||||
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, priKey);
|
||||
byte[] data = str.getBytes("UTF-8");
|
||||
// 加密时超过117字节就报错。为此采用分段加密的办法来加密
|
||||
// 加密时超过117字节就报错。为此采⽤分段加密的办法来加密
|
||||
byte[] enBytes = null;
|
||||
for (int i = 0; i < data.length; i += MAX_ENCRYPT_BLOCK) {
|
||||
// 注意要使用2的倍数,否则会出现加密后的内容再解密时为乱码
|
||||
// 注意要使⽤2的倍数,否则会出现加密后的内容再解密时为乱码
|
||||
byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_ENCRYPT_BLOCK));
|
||||
enBytes = ArrayUtils.addAll(enBytes, doFinal);
|
||||
}
|
||||
|
|
@ -52,22 +43,15 @@ public class RSAUtils {
|
|||
}
|
||||
|
||||
private static String encryptBASE64(byte[] data) {
|
||||
return new String(Base64.encodeBase64(data));
|
||||
return new String(Base64.encode(data));
|
||||
}
|
||||
|
||||
private static byte[] decryptBASE64(String data) {
|
||||
return Base64.decodeBase64(data);
|
||||
return Base64.decode(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify signature
|
||||
*
|
||||
* @param params
|
||||
* @return
|
||||
*/
|
||||
public static boolean verifySign(JSONObject params, String publickey) {
|
||||
String platSign = params.getStr("signature"); // sign
|
||||
log.info("signature:" + platSign);
|
||||
List<String> paramNameList = new ArrayList<>();
|
||||
for (String key : params.keySet()) {
|
||||
if (!"signature".equals(key)) {
|
||||
|
|
@ -80,14 +64,13 @@ public class RSAUtils {
|
|||
String name = paramNameList.get(i);
|
||||
stringBuilder.append(params.getStr(name));
|
||||
}
|
||||
log.info("keys:" + stringBuilder);
|
||||
String decryptSign = "";
|
||||
try {
|
||||
decryptSign = publicDecrypt(platSign, getPublicKey(publickey));
|
||||
decryptSign = publicDecrypt(platSign, getPublicKey(publickey)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.error("Error decrypting signature", e);
|
||||
System.out.println(e.toString());
|
||||
}
|
||||
log.info("decryptSign:" + decryptSign);
|
||||
if (!stringBuilder.toString().equalsIgnoreCase(decryptSign)) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -106,12 +89,11 @@ public class RSAUtils {
|
|||
stringBuilder.append(createMap.get(key)); // 拼接参数
|
||||
}
|
||||
String keyStr = stringBuilder.toString(); // 得到待加密的字符串
|
||||
log.info("keyStr:" + keyStr);
|
||||
String signedStr = "";
|
||||
try {
|
||||
signedStr = privateEncrypt(keyStr, getPrivateKey(MCH_PRIVATE_KEY)); // 私钥加密
|
||||
} catch (Exception e) {
|
||||
log.error("Error encrypting signature", e);
|
||||
System.out.println(e.toString());
|
||||
}
|
||||
return signedStr;
|
||||
}
|
||||
|
|
@ -128,70 +110,47 @@ public class RSAUtils {
|
|||
stringBuilder.append(createMap.get(key)); // 拼接参数
|
||||
}
|
||||
String keyStr = stringBuilder.toString(); // 得到待加密的字符串
|
||||
log.info("keyStr:" + keyStr);
|
||||
String signedStr = "";
|
||||
try {
|
||||
signedStr = privateEncrypt(keyStr, getPrivateKey(MCH_PRIVATE_KEY)); // 私钥加密
|
||||
} catch (Exception e) {
|
||||
log.error("Error encrypting signature", e);
|
||||
System.out.println(e.toString());
|
||||
}
|
||||
return signedStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* private key encryption
|
||||
*
|
||||
* @param data
|
||||
* @param privateKey
|
||||
* @return
|
||||
*/
|
||||
public static String privateEncrypt(String data, RSAPrivateKey privateKey) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
|
||||
return Base64.encodeBase64String(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes("UTF-8"), privateKey.getModulus().bitLength()));
|
||||
return Base64.encode(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes("UTF-8"), privateKey.getModulus().bitLength()));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Exception encountered while encrypting string [" + data + "]", e);
|
||||
throw new RuntimeException("Exception encountered while encry pting string [" + data + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* public key decryption
|
||||
*/
|
||||
public static String publicDecrypt(String data, RSAPublicKey publicKey) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.DECRYPT_MODE, publicKey);
|
||||
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), publicKey.getModulus().bitLength()), "UTF-8");
|
||||
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decode(data), publicKey.getModulus().bitLength()), "UTF-8");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("An exception was encountered while decrypting the string[" + data + "]", e);
|
||||
throw new RuntimeException("An exception was encountered whil e decrypting the string[" + data + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get private key
|
||||
*
|
||||
* @param privateKey key string (base64 encoded)
|
||||
* @throws Exception
|
||||
*/
|
||||
public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
public static RSAPrivateKey getPrivateKey(String privateKey) throws Exception {
|
||||
//Get the private key object through the PKCS#8 encoded Key instruction
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
|
||||
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
|
||||
RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the public key
|
||||
*
|
||||
* @param publicKey key string (base64 encoded)
|
||||
* @throws Exception
|
||||
*/
|
||||
public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
public static RSAPublicKey getPublicKey(String publicKey) throws Exception {
|
||||
//Get the public key object through the X509 encoded Key instruction
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
|
||||
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decode(publicKey));
|
||||
RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
|
||||
return key;
|
||||
}
|
||||
|
|
@ -220,7 +179,7 @@ public class RSAUtils {
|
|||
offSet = i * maxBlock;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("An exception occurred when encrypting and decrypting data whose threshold is [" + maxBlock + "]", e);
|
||||
throw new RuntimeException("An exception occurred when encryp ting and decrypting data whose threshold is [" + maxBlock + "]", e);
|
||||
}
|
||||
byte[] resultDatas = out.toByteArray();
|
||||
IOUtils.closeQuietly(out);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
package com.ruoyi.ai.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 余额使用记录对象 ai_balance_change_record
|
||||
|
|
@ -22,23 +24,13 @@ public class AiBalanceChangeRecord extends BaseEntity {
|
|||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
/** 主键ID */
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 2代表删除)
|
||||
*/
|
||||
/** 删除标志(0代表存在 2代表删除) */
|
||||
private String delFlag;
|
||||
|
||||
/**
|
||||
* 关联订单号
|
||||
*/
|
||||
@Excel(name = "关联订单号")
|
||||
private String orderNo;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
|
|
@ -50,27 +42,19 @@ public class AiBalanceChangeRecord extends BaseEntity {
|
|||
@TableField(exist = false)
|
||||
private String uuid;
|
||||
|
||||
/**
|
||||
* 操作类型:0-充值 1-返佣 2-充值赠送 3-体验金赠送 4-体验金回收 5-图生图 6-一键换脸 7-快捷生图 8-快捷生视频 9-退款 10-系统操作
|
||||
*/
|
||||
/** 操作类型:0-充值 1-返佣 2-充值赠送 3-体验金赠送 4-体验金回收 5-图生图 6-一键换脸 7-快捷生图 8-快捷生视频 9-退款 10-系统操作 */
|
||||
@Excel(name = "操作类型")
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 变更金额
|
||||
*/
|
||||
/** 变更金额 */
|
||||
@Excel(name = "变更金额")
|
||||
private BigDecimal changeAmount;
|
||||
|
||||
/**
|
||||
* 变更后金额
|
||||
*/
|
||||
/** 变更后金额 */
|
||||
@Excel(name = "变更后金额")
|
||||
private BigDecimal resultAmount;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
/** 用户昵称 */
|
||||
@TableField(exist = false)
|
||||
@Excel(name = "用户昵称")
|
||||
private String nickname;
|
||||
|
|
|
|||
|
|
@ -68,28 +68,6 @@ public class AiOrder extends BaseEntity {
|
|||
@Excel(name = "是否置顶:N-否 Y-是")
|
||||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
package com.ruoyi.ai.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
/**
|
||||
* 返佣记录对象 ai_rebate_record
|
||||
*
|
||||
|
|
@ -22,38 +23,22 @@ public class AiRebateRecord extends BaseEntity {
|
|||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
/** 主键ID */
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 关联订单号
|
||||
*/
|
||||
@Excel(name = "关联订单号")
|
||||
private String orderNo;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 2代表删除)
|
||||
*/
|
||||
/** 删除标志(0代表存在 2代表删除) */
|
||||
private String delFlag;
|
||||
|
||||
/**
|
||||
* 上级ID
|
||||
*/
|
||||
/** 上级ID */
|
||||
@Excel(name = "上级ID")
|
||||
private Long superiorId;
|
||||
|
||||
/**
|
||||
* 下级ID
|
||||
*/
|
||||
/** 下级ID */
|
||||
@Excel(name = "下级ID")
|
||||
private Long subordinateId;
|
||||
|
||||
/**
|
||||
* 返佣金额
|
||||
*/
|
||||
/** 返佣金额 */
|
||||
@Excel(name = "返佣金额")
|
||||
private BigDecimal amount;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
package com.ruoyi.ai.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
/**
|
||||
* 体验金领取记录对象 ai_sample_amount_record
|
||||
*
|
||||
|
|
@ -24,38 +25,22 @@ public class AiSampleAmountRecord extends BaseEntity {
|
|||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
/** 主键ID */
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 关联订单号
|
||||
*/
|
||||
@Excel(name = "关联订单号")
|
||||
private String orderNo;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 2代表删除)
|
||||
*/
|
||||
/** 删除标志(0代表存在 2代表删除) */
|
||||
private String delFlag;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
/** 用户ID */
|
||||
@Excel(name = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 体验金额
|
||||
*/
|
||||
/** 体验金额 */
|
||||
@Excel(name = "体验金额")
|
||||
private BigDecimal sampleAmount;
|
||||
|
||||
/**
|
||||
* 回收时间
|
||||
*/
|
||||
/** 回收时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Excel(name = "回收时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date recycleTime;
|
||||
|
|
|
|||
|
|
@ -43,10 +43,6 @@ public class AiTemplate extends BaseEntity {
|
|||
@Excel(name = "模版图片URL")
|
||||
private String imageUrl;
|
||||
|
||||
/** AI类型 */
|
||||
@Excel(name = "AI类型")
|
||||
private Long aiId;
|
||||
|
||||
/** 状态:0-禁用,1-启用 */
|
||||
@Excel(name = "状态:0-禁用,1-启用")
|
||||
private Integer status;
|
||||
|
|
|
|||
|
|
@ -30,10 +30,5 @@ public class ByteBodyReq {
|
|||
@JsonProperty("content")
|
||||
private List<ContentItem> content;
|
||||
|
||||
private Integer duration;
|
||||
private String resolution;
|
||||
private String ratio;
|
||||
private Integer seed;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,12 +15,6 @@ public class ContentItem {
|
|||
@JsonProperty("image_url")
|
||||
private ImageUrl imageUrl;
|
||||
|
||||
@JsonProperty("video_url")
|
||||
private ImageUrl videoUrl;
|
||||
|
||||
@JsonProperty("audio_url")
|
||||
private ImageUrl audioUrl;
|
||||
|
||||
@JsonProperty("role")
|
||||
private String role;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ public class JinShaBodyReq {
|
|||
|
||||
private String notify_url;
|
||||
|
||||
private String return_url;
|
||||
|
||||
private String sign;
|
||||
private String cardname;
|
||||
private String cardno;
|
||||
|
||||
/**
|
||||
* 档位ID
|
||||
|
|
|
|||
|
|
@ -1,119 +0,0 @@
|
|||
package com.ruoyi.ai.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* VM支付回调请求
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-XX
|
||||
*/
|
||||
@Data
|
||||
public class VmCallBackReq {
|
||||
|
||||
/**
|
||||
* 商户号
|
||||
*/
|
||||
private String mchNo;
|
||||
|
||||
/**
|
||||
* 应用ID
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
private String mchOrderNo;
|
||||
|
||||
/**
|
||||
* 支付订单号
|
||||
*/
|
||||
private String payOrderId;
|
||||
|
||||
/**
|
||||
* 支付方式
|
||||
*/
|
||||
private String wayCode;
|
||||
|
||||
/**
|
||||
* 支付金额,单位分
|
||||
*/
|
||||
private Integer amount;
|
||||
|
||||
/**
|
||||
* 货币代码
|
||||
*/
|
||||
private String currency;
|
||||
|
||||
/**
|
||||
* 支付接口编码
|
||||
*/
|
||||
private String ifCode;
|
||||
|
||||
/**
|
||||
* 商品标题
|
||||
*/
|
||||
private String subject;
|
||||
|
||||
/**
|
||||
* 商品描述
|
||||
*/
|
||||
private String body;
|
||||
|
||||
/**
|
||||
* 订单状态:0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已撤销, 5-已退款, 6-订单关闭
|
||||
* 文档字段名:state
|
||||
*/
|
||||
private Integer state;
|
||||
|
||||
/**
|
||||
* 客户端IP(可选)
|
||||
*/
|
||||
private String clientIp;
|
||||
|
||||
/**
|
||||
* 渠道订单号(可选)
|
||||
*/
|
||||
private String channelOrderNo;
|
||||
|
||||
/**
|
||||
* 渠道错误码(可选)
|
||||
*/
|
||||
private String errCode;
|
||||
|
||||
/**
|
||||
* 渠道错误描述(可选)
|
||||
*/
|
||||
private String errMsg;
|
||||
|
||||
/**
|
||||
* 扩展参数,回调时会原样返回
|
||||
*/
|
||||
private String extParam;
|
||||
|
||||
/**
|
||||
* 订单创建时间,13位时间戳
|
||||
*/
|
||||
private Long createdAt;
|
||||
|
||||
/**
|
||||
* 订单支付成功时间,13位时间戳(可选)
|
||||
*/
|
||||
private Long successTime;
|
||||
|
||||
/**
|
||||
* 通知请求时间,13位时间戳。文档字段名:reqTime
|
||||
*/
|
||||
private Long reqTime;
|
||||
|
||||
/**
|
||||
* 签名值
|
||||
*/
|
||||
private String sign;
|
||||
|
||||
/**
|
||||
* 签名类型(文档回调示例无此字段,不参与签名)
|
||||
*/
|
||||
private String signType;
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
package com.ruoyi.ai.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* VM卡信息
|
||||
* 当wayCode为BUZHI_VM时,需要在extParam中传入此信息的JSON字符串
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-XX
|
||||
*/
|
||||
@Data
|
||||
public class VmCardInfo {
|
||||
|
||||
/**
|
||||
* 信用卡卡号
|
||||
*/
|
||||
private String number;
|
||||
|
||||
/**
|
||||
* CVC
|
||||
*/
|
||||
private String cvc;
|
||||
|
||||
/**
|
||||
* 过期年(四位数字,如:2027)
|
||||
*/
|
||||
private String expYear;
|
||||
|
||||
/**
|
||||
* 过期月(两位数,如:11)
|
||||
*/
|
||||
private String expMonth;
|
||||
|
||||
/**
|
||||
* 信用卡持有者电子邮件
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 持卡人名字
|
||||
*/
|
||||
private String firstName;
|
||||
|
||||
/**
|
||||
* 持卡人姓氏
|
||||
*/
|
||||
private String lastName;
|
||||
|
||||
/**
|
||||
* 国家: 双字母 ISO 国家代码(两位字母)
|
||||
*/
|
||||
private String country;
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package com.ruoyi.ai.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* VM支付请求
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-XX
|
||||
*/
|
||||
@Data
|
||||
public class VmPayReq {
|
||||
|
||||
/**
|
||||
* 档位ID
|
||||
*/
|
||||
private Long gearId;
|
||||
|
||||
/**
|
||||
* VM卡信息(可选,如果为null则需要在extParam中传入)
|
||||
*/
|
||||
private VmCardInfo vmCardInfo;
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
package com.ruoyi.ai.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* VM统一下单请求
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-XX
|
||||
*/
|
||||
@Data
|
||||
public class VmUnifiedOrderReq {
|
||||
|
||||
/**
|
||||
* 商户号
|
||||
*/
|
||||
private String mchNo;
|
||||
|
||||
/**
|
||||
* 应用ID
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
private String mchOrderNo;
|
||||
|
||||
/**
|
||||
* 支付方式,如:BUZHI_VM(国际VM卡支付)
|
||||
*/
|
||||
private String wayCode;
|
||||
|
||||
/**
|
||||
* 支付金额,单位分
|
||||
*/
|
||||
private Integer amount;
|
||||
|
||||
/**
|
||||
* 货币代码,三位货币代码,人民币:cny 美元:USD
|
||||
*/
|
||||
private String currency;
|
||||
|
||||
/**
|
||||
* 客户端IPV4地址
|
||||
*/
|
||||
private String clientIp;
|
||||
|
||||
/**
|
||||
* 商品标题
|
||||
*/
|
||||
private String subject;
|
||||
|
||||
/**
|
||||
* 商品描述
|
||||
*/
|
||||
private String body;
|
||||
|
||||
/**
|
||||
* 异步通知地址
|
||||
*/
|
||||
private String notifyUrl;
|
||||
|
||||
/**
|
||||
* 订单失效时间,单位秒,默认2小时
|
||||
*/
|
||||
private Integer expiredTime;
|
||||
|
||||
/**
|
||||
* 特定渠道发起的额外参数,json格式字符串
|
||||
*/
|
||||
private String channelExtra;
|
||||
|
||||
/**
|
||||
* 商户扩展参数,回调时会原样返回
|
||||
* 当wayCode为BUZHI_VM时,需要传入VM卡信息的JSON字符串
|
||||
*/
|
||||
private String extParam;
|
||||
|
||||
/**
|
||||
* 请求接口时间,13位时间戳
|
||||
*/
|
||||
private Long reqTime;
|
||||
|
||||
/**
|
||||
* 接口版本号,固定:1.0
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* 签名值
|
||||
*/
|
||||
private String sign;
|
||||
|
||||
/**
|
||||
* 签名类型,目前只支持MD5方式
|
||||
*/
|
||||
private String signType;
|
||||
}
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
package com.ruoyi.ai.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* VM统一下单响应
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-XX
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class VmUnifiedOrderRes {
|
||||
|
||||
/**
|
||||
* 响应码,0表示成功
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
*/
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
private VmUnifiedOrderData data;
|
||||
|
||||
/**
|
||||
* 响应签名(API 返回)
|
||||
*/
|
||||
private String sign;
|
||||
|
||||
@Data
|
||||
public static class VmUnifiedOrderData {
|
||||
/**
|
||||
* 支付订单号(必填)
|
||||
*/
|
||||
private String payOrderId;
|
||||
|
||||
/**
|
||||
* 商户订单号(必填)
|
||||
*/
|
||||
private String mchOrderNo;
|
||||
|
||||
/**
|
||||
* 订单状态(必填)
|
||||
* 0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已撤销, 5-已退款, 6-订单关闭
|
||||
*/
|
||||
private Integer orderState;
|
||||
|
||||
/**
|
||||
* 支付数据类型(必填)
|
||||
* payUrl-跳转链接的方式
|
||||
* form-表单方式
|
||||
* wxapp-微信支付参数(微信公众号,小程序,app支付时)
|
||||
* aliapp-支付宝app支付参数
|
||||
* ysfapp-云闪付app支付参数
|
||||
* codeUrl-二维码地址
|
||||
* codeImgUrl-二维码图片地址
|
||||
* none-空支付参数
|
||||
*/
|
||||
private String payDataType;
|
||||
|
||||
/**
|
||||
* 支付数据(可选)
|
||||
* 发起支付用到的支付参数,根据payDataType不同而不同
|
||||
* 如果payDataType为payUrl,则此字段为跳转链接
|
||||
*/
|
||||
private String payData;
|
||||
|
||||
/**
|
||||
* 渠道错误码(可选)
|
||||
*/
|
||||
private String errCode;
|
||||
|
||||
/**
|
||||
* 渠道错误描述(可选)
|
||||
*/
|
||||
private String errMsg;
|
||||
|
||||
// 以下字段为兼容旧版本保留
|
||||
/**
|
||||
* 支付链接(兼容字段,优先使用payData)
|
||||
*/
|
||||
private String payUrl;
|
||||
|
||||
/**
|
||||
* 订单号(兼容字段)
|
||||
*/
|
||||
private String orderId;
|
||||
|
||||
/**
|
||||
* 支付参数(兼容字段)
|
||||
*/
|
||||
private String payParams;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,8 +22,5 @@ public interface AiOrderMapper extends BaseMapper<AiOrder> {
|
|||
|
||||
AiOrder getAiOrderByResult(@Param("result") String result);
|
||||
|
||||
/** 门户视频:按火山任务 id 查单(result 可能已被替换为成品 URL,故兼查 video_params.volcTaskId) */
|
||||
AiOrder getAiOrderByPortalVideoTask(@Param("taskId") String taskId);
|
||||
|
||||
BigDecimal getSumAmountByUserId(@Param("userId") String userId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import java.util.List;
|
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.common.core.domain.entity.AiUser;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
/**
|
||||
* ai-用户信息Mapper接口
|
||||
|
|
@ -23,9 +22,4 @@ public interface AiUserMapper extends BaseMapper<AiUser> {
|
|||
|
||||
AiUser selectAiUserById(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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,5 @@ public interface IAiOrderService {
|
|||
|
||||
AiOrder getAiOrderByResult(String result);
|
||||
|
||||
AiOrder getAiOrderByPortalVideoTask(String taskId);
|
||||
|
||||
BigDecimal getSumAmountByUserId(String userId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,9 +87,9 @@ public interface IAiUserService {
|
|||
|
||||
void updateEmail(String email, String code);
|
||||
|
||||
void addUserBalance(String orderNo, Long userId, BigDecimal amount, int recharge);
|
||||
void addUserBalance(Long userId, BigDecimal amount, int recharge);
|
||||
|
||||
void addUserBalance(String orderNo, Long userId, BigDecimal amount, int recharge, String remark);
|
||||
void addUserBalance(Long userId, BigDecimal amount, int recharge, String remark);
|
||||
|
||||
AiUser getUserByEmail(String email);
|
||||
|
||||
|
|
@ -99,19 +99,9 @@ public interface IAiUserService {
|
|||
|
||||
int updatePassword(AiUser aiUser);
|
||||
|
||||
/**
|
||||
* 部门下 AI 用户数量(用于删除部门前校验)
|
||||
*/
|
||||
int countAiUserByDeptId(Long deptId);
|
||||
|
||||
/**
|
||||
* 分配 AI 用户归属部门,deptId 为空则清空
|
||||
*/
|
||||
int updateAiUserDept(Long userId, Long deptId);
|
||||
|
||||
public void deductSampleAmount(String userId);
|
||||
|
||||
void handleRebate(String orderNo, Long userId, BigDecimal rechargeAmount);
|
||||
void handleRebate(Long userId, BigDecimal rechargeAmount);
|
||||
|
||||
String getRebateConfig();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
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);
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.ruoyi.ai.service;
|
|||
|
||||
import com.ruoyi.ai.domain.ByteBodyReq;
|
||||
import com.ruoyi.ai.domain.ByteBodyRes;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
|
||||
public interface IByteService {
|
||||
|
||||
|
|
@ -17,31 +16,12 @@ public interface IByteService {
|
|||
ByteBodyRes imgToImg(ByteBodyReq req) throws Exception;
|
||||
|
||||
/**
|
||||
* 首尾帧图生视频(使用全局配置的 Ark API Key)
|
||||
* 首尾帧图生视频
|
||||
*/
|
||||
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, 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
package com.ruoyi.ai.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 汇率服务接口
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-XX
|
||||
*/
|
||||
public interface IExchangeRateService {
|
||||
|
||||
/**
|
||||
* 获取汇率
|
||||
*
|
||||
* @param fromCurrency 源货币代码(如:CNY)
|
||||
* @param toCurrency 目标货币代码(如:PHP)
|
||||
* @return 汇率值
|
||||
* @throws Exception 获取汇率失败时抛出异常
|
||||
*/
|
||||
BigDecimal getExchangeRate(String fromCurrency, String toCurrency) throws Exception;
|
||||
|
||||
/**
|
||||
* 转换金额
|
||||
*
|
||||
* @param amount 原始金额
|
||||
* @param fromCurrency 源货币代码(如:CNY)
|
||||
* @param toCurrency 目标货币代码(如:PHP)
|
||||
* @return 转换后的金额
|
||||
* @throws Exception 获取汇率失败时抛出异常
|
||||
*/
|
||||
BigDecimal convertAmount(BigDecimal amount, String fromCurrency, String toCurrency) throws Exception;
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ public interface IJinShaService {
|
|||
|
||||
/**
|
||||
* 支付
|
||||
* @param gearId 档位ID
|
||||
*/
|
||||
PayResVO jinShaPay(Long gearId) throws Exception;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
package com.ruoyi.ai.service;
|
||||
|
||||
import com.ruoyi.ai.domain.PayResVO;
|
||||
import com.ruoyi.ai.domain.VmCallBackReq;
|
||||
|
||||
/**
|
||||
* VM支付服务接口
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-XX
|
||||
*/
|
||||
public interface IVmService {
|
||||
|
||||
/**
|
||||
* VM支付
|
||||
*
|
||||
* @param gearId 档位ID
|
||||
* @param vmCardInfo VM卡信息(可选,如果为null则需要在extParam中传入)
|
||||
* @param clientIp 客户端IP地址
|
||||
* @return 支付结果
|
||||
* @throws Exception 支付异常
|
||||
*/
|
||||
PayResVO vmPay(Long gearId, com.ruoyi.ai.domain.VmCardInfo vmCardInfo, String clientIp) throws Exception;
|
||||
|
||||
/**
|
||||
* VM支付回调。文档要求返回字符串 "success" 表示成功,非 success 表示失败,支付中心将再次通知。
|
||||
*
|
||||
* @param req 回调请求
|
||||
* @return "success" 表示处理成功,其他字符串表示失败
|
||||
*/
|
||||
String vmCallBack(VmCallBackReq req);
|
||||
}
|
||||
|
|
@ -124,8 +124,6 @@ public class AiManagerServiceImpl implements IAiManagerService {
|
|||
public AiManager selectAiManagerByType(String aiType) {
|
||||
QueryWrapper<AiManager> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("type", aiType);
|
||||
queryWrapper.eq("del_flag", "0");
|
||||
queryWrapper.eq("status", 0);
|
||||
return aiManagerMapper.selectOne(queryWrapper);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,40 @@
|
|||
package com.ruoyi.ai.service.impl;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ai.domain.AiManager;
|
||||
import com.ruoyi.ai.domain.AiOrder;
|
||||
import com.ruoyi.ai.domain.AiStatistics;
|
||||
import com.ruoyi.ai.mapper.AiOrderMapper;
|
||||
import com.ruoyi.ai.service.IAiManagerService;
|
||||
import com.ruoyi.ai.service.IAiOrderService;
|
||||
import com.ruoyi.ai.service.IAiStatisticsService;
|
||||
import com.ruoyi.ai.service.IAiUserService;
|
||||
import com.ruoyi.common.constant.BalanceChangerConstants;
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.domain.entity.AiUser;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.MessageUtils;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ai.domain.AiManager;
|
||||
import com.ruoyi.ai.domain.AiStatistics;
|
||||
import com.ruoyi.ai.service.IAiManagerService;
|
||||
import com.ruoyi.ai.service.IAiStatisticsService;
|
||||
import com.ruoyi.ai.service.IAiUserService;
|
||||
import com.ruoyi.common.constant.BalanceChangerConstants;
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.AiUser;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.MessageUtils;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.ai.mapper.AiOrderMapper;
|
||||
import com.ruoyi.ai.domain.AiOrder;
|
||||
import com.ruoyi.ai.service.IAiOrderService;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 订单管理Service业务层处理
|
||||
*
|
||||
*
|
||||
* @author shi
|
||||
* @date 2025-11-13
|
||||
*/
|
||||
|
|
@ -54,7 +56,7 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
|||
|
||||
/**
|
||||
* 查询订单管理
|
||||
*
|
||||
*
|
||||
* @param id 订单管理主键
|
||||
* @return 订单管理
|
||||
*/
|
||||
|
|
@ -65,7 +67,7 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
|||
|
||||
/**
|
||||
* 查询订单管理列表
|
||||
*
|
||||
*
|
||||
* @param aiOrder 订单管理
|
||||
* @return 订单管理
|
||||
*/
|
||||
|
|
@ -89,7 +91,7 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
|||
|
||||
/**
|
||||
* 新增订单管理
|
||||
*
|
||||
*
|
||||
* @param aiOrder 订单管理
|
||||
* @return 结果
|
||||
*/
|
||||
|
|
@ -103,7 +105,7 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
|||
|
||||
/**
|
||||
* 修改订单管理
|
||||
*
|
||||
*
|
||||
* @param aiOrder 订单管理
|
||||
* @return 结果
|
||||
*/
|
||||
|
|
@ -116,29 +118,30 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
|||
|
||||
/**
|
||||
* 批量删除订单管理
|
||||
*
|
||||
*
|
||||
* @param ids 需要删除的订单管理主键
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteAiOrderByIds(Long[] ids) {
|
||||
public int deleteAiOrderByIds(Long[] ids)
|
||||
{
|
||||
return aiOrderMapper.deleteAiOrderByIds(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除订单管理信息
|
||||
*
|
||||
*
|
||||
* @param id 订单管理主键
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteAiOrderById(Long id) {
|
||||
public int deleteAiOrderById(Long id)
|
||||
{
|
||||
return aiOrderMapper.deleteAiOrderById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成订单
|
||||
*
|
||||
* @param aiType 对应的AI类型
|
||||
* @return
|
||||
*/
|
||||
|
|
@ -147,9 +150,7 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
|||
public AiOrder getAiOrder(String aiType) {
|
||||
AiManager aiManager = aiManagerService.selectAiManagerByType(aiType);
|
||||
if (aiManager == null) {
|
||||
throw new ServiceException(
|
||||
"未找到可用的功能类型:请在「AI管理」中新增 type=" + aiType + " 且状态为正常的记录,或执行 sql/seed_ai_manager_type_21.sql 初始化",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
throw new ServiceException("Corresponding functionType does not exist", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
// 判断用户余额是否足够
|
||||
AiUser aiUser = aiUserService.selectAiUserById(SecurityUtils.getAiUserId());
|
||||
|
|
@ -169,7 +170,7 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
|||
aiOrder.setSource(aiUser.getSource());
|
||||
aiOrderMapper.insert(aiOrder);
|
||||
// 执行余额变更
|
||||
aiUserService.addUserBalance(orderno, SecurityUtils.getAiUserId(), NumberUtil.mul(-1, aiManager.getPrice()), getChangerType(aiType));
|
||||
aiUserService.addUserBalance(SecurityUtils.getAiUserId(), NumberUtil.mul(-1, aiManager.getPrice()), getChangerType(aiType));
|
||||
return aiOrder;
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +181,7 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
|||
String remark = MessageUtils.message("order.number.generation.failed", aiOrder.getOrderNum());
|
||||
aiOrder.setRemark(remark);
|
||||
aiOrderMapper.updateById(aiOrder);
|
||||
aiUserService.addUserBalance(aiOrder.getOrderNum(), SecurityUtils.getAiUserId(), aiOrder.getAmount(), BalanceChangerConstants.REFUND, remark);
|
||||
aiUserService.addUserBalance(SecurityUtils.getAiUserId(), aiOrder.getAmount(), BalanceChangerConstants.REFUND, remark);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -190,7 +191,7 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
|||
aiOrderMapper.updateById(aiOrder);
|
||||
AiStatistics aiStatistics = new AiStatistics();
|
||||
aiStatistics.setSource(aiOrder.getSource());
|
||||
aiStatistics.setGenerateCount(1L);
|
||||
aiStatistics.setGenerateCount(1l);
|
||||
// 新增生成数量
|
||||
aiStatisticsService.saveOrUpdateData(aiStatistics);
|
||||
}
|
||||
|
|
@ -203,11 +204,6 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
|||
return aiOrderMapper.selectOne(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiOrder getAiOrderByPortalVideoTask(String taskId) {
|
||||
return aiOrderMapper.getAiOrderByPortalVideoTask(taskId);
|
||||
}
|
||||
|
||||
public int getChangerType(String aiType) {
|
||||
switch (aiType) {
|
||||
case "11":
|
||||
|
|
|
|||
|
|
@ -1,24 +1,29 @@
|
|||
package com.ruoyi.ai.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ai.domain.*;
|
||||
import com.ruoyi.ai.mapper.AiRechargeMapper;
|
||||
import com.ruoyi.ai.service.*;
|
||||
import com.ruoyi.common.constant.BalanceChangerConstants;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ai.domain.*;
|
||||
import com.ruoyi.ai.domain.enums.AiConfigEnum;
|
||||
import com.ruoyi.ai.service.*;
|
||||
import com.ruoyi.common.constant.BalanceChangerConstants;
|
||||
import com.ruoyi.common.core.domain.entity.AiUser;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.system.domain.SysConfig;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.ai.mapper.AiRechargeMapper;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 充值管理Service业务层处理
|
||||
*
|
||||
|
|
@ -79,10 +84,6 @@ public class AiRechargeServiceImpl implements IAiRechargeService {
|
|||
public void addRecharge(String orderNo) {
|
||||
// 设置到账时间
|
||||
AiRecharge aiRecharge = this.getAiRechargeByOrderNum(orderNo);
|
||||
if (aiRecharge == null) {
|
||||
log.error("支付回调订单不存在,orderNo: {}", orderNo);
|
||||
return;
|
||||
}
|
||||
Date creditedTime = aiRecharge.getCreditedTime();
|
||||
if (creditedTime != null) {
|
||||
log.error("支付回调重复 {}", orderNo);
|
||||
|
|
@ -106,10 +107,12 @@ public class AiRechargeServiceImpl implements IAiRechargeService {
|
|||
aiRecharge.setGiftAmount(amount);
|
||||
|
||||
|
||||
|
||||
|
||||
// 新增用户余额
|
||||
aiUserService.addUserBalance(orderNo, aiRecharge.getUserId(), amount, BalanceChangerConstants.RECHARGE);
|
||||
aiUserService.addUserBalance(aiRecharge.getUserId(), amount, BalanceChangerConstants.RECHARGE);
|
||||
// 处理返佣 (根据充值金额计算返佣)
|
||||
aiUserService.handleRebate(orderNo, aiRecharge.getUserId(), aiRechargeGiftGear.getRechargeAmount());
|
||||
aiUserService.handleRebate(aiRecharge.getUserId(), aiRechargeGiftGear.getRechargeAmount());
|
||||
// 新增充值统计
|
||||
AiStatistics aiStatistics = new AiStatistics();
|
||||
aiStatistics.setSource(aiRecharge.getSource());
|
||||
|
|
@ -117,14 +120,14 @@ public class AiRechargeServiceImpl implements IAiRechargeService {
|
|||
statisticsService.saveOrUpdateData(aiStatistics);
|
||||
|
||||
|
||||
// 处理赠送金额
|
||||
// 处理赠送金额
|
||||
if (isFristRecharge && aiRecharge.getGearId() != null) {
|
||||
// 查询活动是否过期
|
||||
boolean isAvailable = aiRechargeGiftService.isActivityAvailable(aiRechargeGiftGear.getRechargeId());
|
||||
if (isAvailable) {
|
||||
aiRecharge.setGiveAmount(aiRechargeGiftGear.getGiveAmount());
|
||||
// 新增用户赠送余额
|
||||
aiUserService.addUserBalance(orderNo, aiRecharge.getUserId(), aiRechargeGiftGear.getGiveAmount(), BalanceChangerConstants.RECHARGE_BONUS);
|
||||
aiUserService.addUserBalance(aiRecharge.getUserId(), aiRechargeGiftGear.getGiveAmount(), BalanceChangerConstants.RECHARGE_BONUS);
|
||||
// 新增充值赠送记录
|
||||
AiRechargeGiftRecord aiRechargeGiftRecord = new AiRechargeGiftRecord();
|
||||
aiRechargeGiftRecord.setOrderNum(orderNo);
|
||||
|
|
|
|||
|
|
@ -9,17 +9,20 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|||
import com.ruoyi.ai.domain.*;
|
||||
import com.ruoyi.ai.domain.enums.AiConfigEnum;
|
||||
import com.ruoyi.ai.mapper.AiRechargeMapper;
|
||||
import com.ruoyi.ai.mapper.AiUserMapper;
|
||||
import com.ruoyi.ai.mapper.AiSampleAmountMapper;
|
||||
import com.ruoyi.ai.mapper.AiSampleAmountRecordMapper;
|
||||
import com.ruoyi.ai.service.*;
|
||||
import com.ruoyi.common.constant.BalanceChangerConstants;
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.domain.entity.AiUser;
|
||||
import com.ruoyi.ai.mapper.AiUserMapper;
|
||||
import com.ruoyi.common.core.domain.model.LoginAiUser;
|
||||
import com.ruoyi.common.core.domain.model.LoginAiUserBody;
|
||||
import com.ruoyi.common.core.domain.model.RegisterAiUserBody;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.*;
|
||||
import com.ruoyi.common.utils.ip.IpUtils;
|
||||
import com.ruoyi.common.utils.uuid.UUID;
|
||||
import com.ruoyi.system.domain.SysConfig;
|
||||
import com.ruoyi.system.mapper.SysConfigMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
|
@ -27,6 +30,7 @@ import org.springframework.stereotype.Service;
|
|||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
|
@ -66,6 +70,7 @@ public class AiUserServiceImpl implements IAiUserService {
|
|||
private AiRechargeMapper aiRechargeMapper;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 查询ai-用户信息
|
||||
*
|
||||
|
|
@ -132,19 +137,6 @@ public class AiUserServiceImpl implements IAiUserService {
|
|||
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-用户信息
|
||||
*
|
||||
|
|
@ -216,7 +208,7 @@ public class AiUserServiceImpl implements IAiUserService {
|
|||
// 新增访客统计
|
||||
AiStatistics aiStatistics = new AiStatistics();
|
||||
aiStatistics.setSource(registerAiUserBody.getSource());
|
||||
aiStatistics.setAddUserCount(1L);
|
||||
aiStatistics.setAddUserCount(1l);
|
||||
aiStatisticsService.saveOrUpdateData(aiStatistics);
|
||||
return aiUser;
|
||||
}
|
||||
|
|
@ -360,13 +352,13 @@ public class AiUserServiceImpl implements IAiUserService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void addUserBalance(String orderNo, Long userId, BigDecimal amount, int recharge) {
|
||||
addUserBalance(orderNo, userId, amount, recharge, null);
|
||||
public void addUserBalance(Long userId, BigDecimal amount, int recharge) {
|
||||
addUserBalance(userId, amount, recharge, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void addUserBalance(String orderNo, Long userId, BigDecimal amount, int recharge, String remark) {
|
||||
public void addUserBalance(Long userId, BigDecimal amount, int recharge, String remark) {
|
||||
// 新增余额使用记录
|
||||
AiUser aiUser = aiUserMapper.selectById(userId);
|
||||
AiBalanceChangeRecord balanceChangeRecord = new AiBalanceChangeRecord();
|
||||
|
|
@ -375,7 +367,6 @@ public class AiUserServiceImpl implements IAiUserService {
|
|||
balanceChangeRecord.setChangeAmount(amount);
|
||||
balanceChangeRecord.setResultAmount(NumberUtil.add(amount, aiUser.getBalance()));
|
||||
balanceChangeRecord.setRemark(remark);
|
||||
balanceChangeRecord.setOrderNo(orderNo);
|
||||
balanceChangeRecordService.insertAiBalanceChangeRecord(balanceChangeRecord);
|
||||
// 修改用户余额
|
||||
aiUser.setBalance(balanceChangeRecord.getResultAmount());
|
||||
|
|
@ -405,7 +396,7 @@ public class AiUserServiceImpl implements IAiUserService {
|
|||
public int updatePassword(AiUser aiUser) {
|
||||
aiUser.setPassword(SecurityUtils.encryptPassword(aiUser.getNewPassword()));
|
||||
return aiUserMapper.updateById(aiUser);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
|
|
@ -419,14 +410,14 @@ public class AiUserServiceImpl implements IAiUserService {
|
|||
// if (deductAmount.compareTo(BigDecimal.ZERO) < 0) {
|
||||
// deductAmount = new BigDecimal(0);
|
||||
// }
|
||||
addUserBalance(aiSampleAmountRecord.getOrderNo(), Long.valueOf(userId), NumberUtil.mul(aiSampleAmountRecord.getSampleAmount(), -1), BalanceChangerConstants.EXPERIENCE_GOLD_RECYCLE);
|
||||
addUserBalance(Long.valueOf(userId), NumberUtil.mul(aiSampleAmountRecord.getSampleAmount(), -1), BalanceChangerConstants.EXPERIENCE_GOLD_RECYCLE);
|
||||
// 修改状态已回收
|
||||
aiSampleAmountRecord.setStatus(1);
|
||||
aiSampleAmountRecordService.updateAiSampleAmountRecord(aiSampleAmountRecord);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRebate(String orderNo, Long userId, BigDecimal amount) {
|
||||
public void handleRebate(Long userId, BigDecimal amount) {
|
||||
AiUser aiUser = aiUserMapper.selectById(userId);
|
||||
if (aiUser.getSuperiorId() == null) return;
|
||||
// 查询上级用户
|
||||
|
|
@ -437,7 +428,7 @@ public class AiUserServiceImpl implements IAiUserService {
|
|||
SysConfig sysConfig = sysConfigMapper.checkConfigKeyUnique(AiConfigEnum.SYS_INVITE_COMMISSION.getValue());
|
||||
double decimal = NumberUtil.div(Integer.parseInt(sysConfig.getConfigValue()), 100, 2);
|
||||
BigDecimal rebateAmount = NumberUtil.mul(amount, decimal);
|
||||
addUserBalance(orderNo, superiorUser.getId(), rebateAmount, BalanceChangerConstants.REBATE);
|
||||
addUserBalance(superiorUser.getId(), rebateAmount, BalanceChangerConstants.REBATE);
|
||||
// 新增返佣记录
|
||||
AiRebateRecord rebateRecord = new AiRebateRecord();
|
||||
rebateRecord.setSuperiorId(superiorUser.getId());
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||
import com.ruoyi.ai.domain.ByteBodyReq;
|
||||
import com.ruoyi.ai.domain.ByteBodyRes;
|
||||
import com.ruoyi.ai.service.IByteService;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.http.OkHttpUtils;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.*;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
@ -32,13 +30,6 @@ public class ByteService implements IByteService {
|
|||
@Value("${byteapi.apiKey}")
|
||||
private String apiKey;
|
||||
|
||||
// 火山引擎配置
|
||||
@Value("${volcengine.ark.baseUrl:https://ark.cn-beijing.volces.com}")
|
||||
private String volcBaseUrl;
|
||||
|
||||
@Value("${volcengine.ark.apiKey}")
|
||||
private String volcApiKey;
|
||||
|
||||
@Override
|
||||
public ByteBodyRes promptToImg(ByteBodyReq req) throws Exception {
|
||||
return this.imgToImg(req);
|
||||
|
|
@ -84,148 +75,71 @@ public class ByteService implements IByteService {
|
|||
|
||||
@Override
|
||||
public ByteBodyRes imgToVideo(ByteBodyReq req) throws Exception {
|
||||
return imgToVideo(req, volcApiKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBodyRes imgToVideo(ByteBodyReq req, String arkApiKey) throws Exception {
|
||||
if (req == null) {
|
||||
throw new Exception("imgToVideo error:req is null");
|
||||
}
|
||||
if (StringUtils.isBlank(arkApiKey)) {
|
||||
throw new Exception("imgToVideo error:apiKey is null");
|
||||
}
|
||||
// 1. 验证请求参数(可选,根据业务需求)
|
||||
// if (StringUtils.isBlank(req.getPrompt())) {
|
||||
// throw new Exception("imgToVideo error:prompt is null");
|
||||
// }
|
||||
|
||||
// 2. 构建请求体JSON(基于ByteBodyReq的字段)
|
||||
// 注意:ByteBodyReq需包含与API参数对应的字段(model、prompt等)
|
||||
String jsonBody = objectMapper.writeValueAsString(req);
|
||||
|
||||
// 3. 构建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(volcBaseUrl + "/api/v3/contents/generations/tasks")
|
||||
.url(API_URL + "/contents/generations/tasks")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer " + arkApiKey)
|
||||
.header("Authorization", "Bearer " + apiKey)
|
||||
.post(RequestBody.create(
|
||||
MediaType.parse("application/json; charset=utf-8"),
|
||||
jsonBody
|
||||
))
|
||||
.build();
|
||||
|
||||
// 4. 发送同步请求(因方法需要返回值,使用execute而非enqueue)
|
||||
Response response = OkHttpUtils.newCall(request).execute();
|
||||
|
||||
// 5. 处理响应
|
||||
if (!response.isSuccessful()) {
|
||||
// 非200状态:返回错误信息(假设ByteBodyRes有error字段)
|
||||
String errorMsg = response.body() != null ? response.body().string() : "imgToVideo error";
|
||||
throw new Exception("imgToVideo error:" + errorMsg);
|
||||
}
|
||||
|
||||
// 6. 解析成功响应为ByteBodyRes
|
||||
if (response.body() == null) {
|
||||
throw new Exception("imgToVideo response null");
|
||||
}
|
||||
|
||||
String responseBody = response.body().string();
|
||||
return objectMapper.readValue(responseBody, ByteBodyRes.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBodyRes uploadVideo(String id) throws Exception {
|
||||
return uploadVideo(id, volcApiKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBodyRes uploadVideo(String id, String arkApiKey) throws Exception {
|
||||
// 1. 验证请求参数(可选,根据业务需求)
|
||||
if (StringUtils.isBlank(id)) {
|
||||
throw new Exception("uploadVideo error:id is null");
|
||||
}
|
||||
if (StringUtils.isBlank(arkApiKey)) {
|
||||
throw new Exception("uploadVideo error:apiKey is null");
|
||||
}
|
||||
|
||||
// 2. 构建请求体JSON(基于ByteBodyReq的字段)
|
||||
// 注意:ByteBodyReq需包含与API参数对应的字段(model、prompt等)
|
||||
//String jsonBody = objectMapper.writeValueAsString(req);
|
||||
// 3. 构建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(volcBaseUrl + "/api/v3/contents/generations/tasks/" + id)
|
||||
.url(API_URL + "/contents/generations/tasks/" + id)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer " + arkApiKey)
|
||||
.header("Authorization", "Bearer " + apiKey)
|
||||
.get()
|
||||
.build();
|
||||
|
||||
// 4. 发送同步请求(因方法需要返回值,使用execute而非enqueue)
|
||||
Response response = OkHttpUtils.newCall(request).execute();
|
||||
|
||||
// 5. 处理响应
|
||||
if (!response.isSuccessful()) {
|
||||
// 非200状态:返回错误信息(假设ByteBodyRes有error字段)
|
||||
String errorMsg = response.body() != null ? response.body().string() : "uploadVideo error";
|
||||
throw new Exception("uploadVideo error:" + errorMsg);
|
||||
}
|
||||
|
||||
// 6. 解析成功响应为ByteBodyRes
|
||||
if (response.body() == null) {
|
||||
throw new Exception("uploadVideo response null");
|
||||
}
|
||||
|
||||
String responseBody = response.body().string();
|
||||
return objectMapper.readValue(responseBody, ByteBodyRes.class);
|
||||
}
|
||||
|
||||
@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 error:apiKey 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 error:invalid 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,292 +0,0 @@
|
|||
package com.ruoyi.ai.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.ruoyi.ai.service.IExchangeRateService;
|
||||
import com.ruoyi.common.utils.http.OkHttpUtils;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
/**
|
||||
* 汇率服务实现类
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-XX
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ExchangeRateServiceImpl implements IExchangeRateService {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Value("${exchange-rate.api-url:https://api.currencyfreaks.com/v2.0/rates/latest}")
|
||||
private String apiUrl;
|
||||
|
||||
@Value("${exchange-rate.apikey:}")
|
||||
private String apikey;
|
||||
|
||||
@Value("${exchange-rate.base-currency:USD}")
|
||||
private String baseCurrency;
|
||||
|
||||
@Value("${exchange-rate.enabled:true}")
|
||||
private boolean enabled;
|
||||
|
||||
@Value("${exchange-rate.fallback-rate-jinsha:90}")
|
||||
private BigDecimal fallbackRateJinSha;
|
||||
|
||||
@Value("${exchange-rate.fallback-rate-kada:60}")
|
||||
private BigDecimal fallbackRateKaDa;
|
||||
|
||||
@Value("${exchange-rate.fallback-rate-php:90}")
|
||||
private BigDecimal fallbackRatePHP;
|
||||
|
||||
@Override
|
||||
public BigDecimal getExchangeRate(String fromCurrency, String toCurrency) throws Exception {
|
||||
if (!enabled) {
|
||||
log.warn("汇率服务未启用,使用默认汇率");
|
||||
return getFallbackRate(toCurrency);
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求URL - 使用 CurrencyFreaks Rates Latest Endpoint
|
||||
String url = buildApiUrl();
|
||||
|
||||
// 构建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.build();
|
||||
|
||||
// 发送请求
|
||||
Response response = OkHttpUtils.newCall(request).execute();
|
||||
ResponseBody body = response.body();
|
||||
|
||||
if (response.isSuccessful() && body != null) {
|
||||
String responseBody = body.string();
|
||||
log.debug("汇率API响应: {}", responseBody);
|
||||
|
||||
// 解析响应 - CurrencyFreaks Rates Latest Endpoint 响应格式
|
||||
RatesResponse ratesResponse = objectMapper.readValue(responseBody, RatesResponse.class);
|
||||
|
||||
if (ratesResponse != null && ratesResponse.getRates() != null) {
|
||||
BigDecimal rate = calculateRate(ratesResponse, fromCurrency, toCurrency);
|
||||
if (rate != null && rate.compareTo(BigDecimal.ZERO) > 0) {
|
||||
log.info("成功获取汇率 {} -> {}: {}", fromCurrency, toCurrency, rate);
|
||||
return rate;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("汇率API请求失败,状态码: {}", response.code());
|
||||
if (body != null) {
|
||||
log.warn("响应内容: {}", body.string());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取汇率失败,使用备用汇率: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
// 如果API调用失败,使用备用汇率
|
||||
return getFallbackRate(toCurrency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal convertAmount(BigDecimal amount, String fromCurrency, String toCurrency) throws Exception {
|
||||
if (amount == null) {
|
||||
throw new IllegalArgumentException("金额不能为空");
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
log.warn("汇率服务未启用,使用默认汇率计算");
|
||||
BigDecimal rate = getFallbackRate(toCurrency);
|
||||
// 返回完整精度,不限制小数位
|
||||
return amount.multiply(rate);
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求URL - 使用 CurrencyFreaks Rates Latest Endpoint
|
||||
String url = buildApiUrl();
|
||||
|
||||
// 构建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.build();
|
||||
|
||||
// 发送请求
|
||||
Response response = OkHttpUtils.newCall(request).execute();
|
||||
ResponseBody body = response.body();
|
||||
|
||||
if (response.isSuccessful() && body != null) {
|
||||
String responseBody = body.string();
|
||||
// log.debug("汇率API响应: {}", responseBody);
|
||||
|
||||
// 解析响应 - CurrencyFreaks Rates Latest Endpoint 响应格式
|
||||
RatesResponse ratesResponse = objectMapper.readValue(responseBody, RatesResponse.class);
|
||||
|
||||
if (ratesResponse != null && ratesResponse.getRates() != null) {
|
||||
BigDecimal rate = calculateRate(ratesResponse, fromCurrency, toCurrency);
|
||||
if (rate != null && rate.compareTo(BigDecimal.ZERO) > 0) {
|
||||
BigDecimal convertedAmount = amount.multiply(rate);
|
||||
log.info("成功转换金额 {} {} -> {} {}: {}",
|
||||
amount, fromCurrency, convertedAmount, toCurrency, rate);
|
||||
// 返回完整精度,不限制小数位
|
||||
return convertedAmount;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("汇率API请求失败,状态码: {}", response.code());
|
||||
if (body != null) {
|
||||
log.warn("响应内容: {}", body.string());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("转换金额失败,使用备用汇率计算: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
// 如果API调用失败,使用备用汇率计算(不限制小数位)
|
||||
BigDecimal rate = getFallbackRate(toCurrency);
|
||||
return amount.multiply(rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建API URL - 使用 CurrencyFreaks Rates Latest Endpoint
|
||||
*/
|
||||
private String buildApiUrl() {
|
||||
if (apiUrl.contains("currencyfreaks.com") && apiUrl.contains("/rates/latest")) {
|
||||
// 使用 CurrencyFreaks Rates Latest Endpoint 格式
|
||||
// https://api.currencyfreaks.com/v2.0/rates/latest?apikey=YOUR_APIKEY&base=USD
|
||||
StringBuilder urlBuilder = new StringBuilder(apiUrl);
|
||||
urlBuilder.append("?apikey=").append(apikey);
|
||||
urlBuilder.append("&base=").append(baseCurrency.toUpperCase());
|
||||
return urlBuilder.toString();
|
||||
} else {
|
||||
// 兼容其他API格式(向后兼容)
|
||||
return apiUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据汇率响应计算 fromCurrency 到 toCurrency 的汇率
|
||||
*/
|
||||
private BigDecimal calculateRate(RatesResponse ratesResponse, String fromCurrency, String toCurrency) {
|
||||
String base = ratesResponse.getBase();
|
||||
java.util.Map<String, String> rates = ratesResponse.getRates();
|
||||
|
||||
if (rates == null || rates.isEmpty()) {
|
||||
log.warn("汇率响应中 rates 为空");
|
||||
return null;
|
||||
}
|
||||
|
||||
String fromUpper = fromCurrency.toUpperCase();
|
||||
String toUpper = toCurrency.toUpperCase();
|
||||
String baseUpper = base.toUpperCase();
|
||||
|
||||
// 如果源货币就是基准货币
|
||||
if (baseUpper.equals(fromUpper)) {
|
||||
String toRateStr = rates.get(toUpper);
|
||||
if (toRateStr != null) {
|
||||
try {
|
||||
return new BigDecimal(toRateStr);
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("无法解析目标货币 {} 的汇率: {}", toUpper, toRateStr);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
log.warn("未找到目标货币 {} 的汇率", toUpper);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果目标货币就是基准货币
|
||||
if (baseUpper.equals(toUpper)) {
|
||||
String fromRateStr = rates.get(fromUpper);
|
||||
if (fromRateStr != null) {
|
||||
try {
|
||||
// 如果目标货币是基准货币,汇率是 1 / fromRate
|
||||
BigDecimal fromRate = new BigDecimal(fromRateStr);
|
||||
if (fromRate.compareTo(BigDecimal.ZERO) > 0) {
|
||||
return BigDecimal.ONE.divide(fromRate, 10, RoundingMode.HALF_UP);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("无法解析源货币 {} 的汇率: {}", fromUpper, fromRateStr);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
log.warn("未找到源货币 {} 的汇率", fromUpper);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 两个货币都不是基准货币,需要计算
|
||||
String fromRateStr = rates.get(fromUpper);
|
||||
String toRateStr = rates.get(toUpper);
|
||||
|
||||
if (fromRateStr == null) {
|
||||
log.warn("未找到源货币 {} 的汇率", fromUpper);
|
||||
return null;
|
||||
}
|
||||
if (toRateStr == null) {
|
||||
log.warn("未找到目标货币 {} 的汇率", toUpper);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
BigDecimal fromRate = new BigDecimal(fromRateStr);
|
||||
BigDecimal toRate = new BigDecimal(toRateStr);
|
||||
|
||||
if (fromRate.compareTo(BigDecimal.ZERO) > 0) {
|
||||
// 汇率 = toRate / fromRate
|
||||
// 例如:base=USD, from=CNY, to=PHP
|
||||
// USD->CNY = fromRate, USD->PHP = toRate
|
||||
// CNY->PHP = toRate / fromRate
|
||||
return toRate.divide(fromRate, 10, RoundingMode.HALF_UP);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("无法解析汇率: from={}, to={}", fromRateStr, toRateStr);
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取备用汇率(当API调用失败时使用)
|
||||
*/
|
||||
private BigDecimal getFallbackRate(String toCurrency) {
|
||||
// 根据目标货币返回不同的备用汇率
|
||||
if ("PHP".equalsIgnoreCase(toCurrency)) {
|
||||
// PHP比索,使用配置的PHP备用汇率
|
||||
log.warn("使用PHP备用汇率: {}", fallbackRatePHP);
|
||||
return fallbackRatePHP;
|
||||
}
|
||||
// 其他货币可以扩展,默认使用PHP的备用汇率
|
||||
log.warn("未配置货币 {} 的备用汇率,使用PHP默认汇率 {}", toCurrency, fallbackRatePHP);
|
||||
return fallbackRatePHP;
|
||||
}
|
||||
|
||||
/**
|
||||
* CurrencyFreaks Rates Latest Endpoint 响应对象
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static class RatesResponse {
|
||||
@JsonProperty("date")
|
||||
private String date;
|
||||
|
||||
@JsonProperty("base")
|
||||
private String base;
|
||||
|
||||
@JsonProperty("rates")
|
||||
private java.util.Map<String, String> rates;
|
||||
}
|
||||
}
|
||||
|
|
@ -47,10 +47,6 @@ public class JinShaService implements IJinShaService {
|
|||
private IAiUserService aiUserService;
|
||||
@Autowired
|
||||
private IAiRechargeGiftGearService aiRechargeGiftGearService;
|
||||
@Autowired
|
||||
private IAiRechargeGiftService aiRechargeGiftService;
|
||||
@Autowired
|
||||
private IExchangeRateService exchangeRateService;
|
||||
|
||||
@Override
|
||||
public PayResVO jinShaPay(Long gearId) throws Exception {
|
||||
|
|
@ -61,104 +57,64 @@ public class JinShaService implements IJinShaService {
|
|||
throw new ServiceException("The gear position does not exist.", -1);
|
||||
}
|
||||
BigDecimal amount = aiRechargeGiftGear.getRechargeAmount();
|
||||
// 使用汇率服务转换金额(从CNY转换为目标货币,默认INR)
|
||||
amount = exchangeRateService.convertAmount(amount, "USD", "INR");
|
||||
amount = amount.multiply(new BigDecimal(90));
|
||||
|
||||
// 生成订单号
|
||||
JinShaBodyReq req = new JinShaBodyReq();
|
||||
req.setAppid(appId);
|
||||
req.setReturn_url(returnUrl + "/recharge");
|
||||
req.setNotify_url(notifyUrl + "/api/pay/jinsha-callBack");
|
||||
req.setAmount(amount);
|
||||
// 1. 配置请求参数
|
||||
TreeMap<String, Object> params = new TreeMap<>();
|
||||
params.put("appid", req.getAppid());
|
||||
String uuid = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8);
|
||||
String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||
String orderNo = dateTime + "js" + uuid;
|
||||
|
||||
// 金额转换为number类型(保留两位小数)
|
||||
String amountStr = String.format("%.2f", amount.doubleValue());
|
||||
|
||||
// 构建回调地址和返回地址
|
||||
String notifyUrlFull = notifyUrl + "/api/pay/jinsha-callBack";
|
||||
String returnUrlFull = returnUrl + "/recharge";
|
||||
|
||||
// 1. 配置签名参数(按照key排序:appid, amount, orderno, notify_url, return_url,不包含sign)
|
||||
TreeMap<String, Object> signParams = new TreeMap<>();
|
||||
signParams.put("appid", appId);
|
||||
signParams.put("amount", amountStr);
|
||||
signParams.put("orderno", orderNo);
|
||||
signParams.put("notify_url", notifyUrlFull);
|
||||
signParams.put("return_url", returnUrlFull);
|
||||
|
||||
// 2. 生成签名
|
||||
String sign = this.generateSign(signParams);
|
||||
|
||||
// 3. 构建请求参数(使用FormBody.Builder自动处理URL编码)
|
||||
FormBody.Builder formBuilder = new FormBody.Builder();
|
||||
formBuilder.add("appid", appId);
|
||||
formBuilder.add("amount", amountStr);
|
||||
formBuilder.add("orderno", orderNo);
|
||||
formBuilder.add("notify_url", notifyUrlFull);
|
||||
formBuilder.add("return_url", returnUrlFull);
|
||||
formBuilder.add("sign", sign);
|
||||
RequestBody requestBody = formBuilder.build();
|
||||
|
||||
// 4. 构建 POST 请求
|
||||
params.put("orderno", orderNo);
|
||||
params.put("amount", amount);
|
||||
params.put("notify_url", req.getNotify_url());
|
||||
params.put("return_url", req.getReturn_url());
|
||||
StringBuilder append = this.append(params);
|
||||
String sign = this.sign(append);
|
||||
String formData = append + "&sign=" + sign;
|
||||
// 2. 构建请求体(form-urlencoded 格式)
|
||||
RequestBody requestBody = RequestBody.create(
|
||||
MediaType.get("application/x-www-form-urlencoded;charset=utf-8"),
|
||||
formData
|
||||
);
|
||||
// 3. 构建 POST 请求
|
||||
Request request = new Request.Builder()
|
||||
.url(url + "/api/pay")
|
||||
.header("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
|
||||
// .header("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
|
||||
.post(requestBody)
|
||||
.build();
|
||||
|
||||
// 5. 发送请求并获取响应
|
||||
// 4. 发送请求并获取响应
|
||||
Response response = OkHttpUtils.newCall(request).execute();
|
||||
ResponseBody body = response.body();
|
||||
if (null == body) {
|
||||
log.error("jinsha支付请求的响应异常");
|
||||
throw new Exception("jinshapay responsebody is null");
|
||||
log.error("kada支付请求的响应异常");
|
||||
throw new Exception("kadapay responsebody is null");
|
||||
}
|
||||
String responseBody = body.string();
|
||||
log.info("jinsha支付请求响应: {}", responseBody);
|
||||
JinShaBodyRes jinShaBodyRes = objectMapper.readValue(responseBody, JinShaBodyRes.class);
|
||||
|
||||
// 检查响应code,0表示成功,其他值表示失败
|
||||
Integer code = jinShaBodyRes.getCode();
|
||||
if (code == null || code != 0) {
|
||||
String msg = jinShaBodyRes.getMsg();
|
||||
log.error("jinsha支付请求失败,code: {}, msg: {}", code, msg);
|
||||
throw new ServiceException(msg != null ? msg : "支付请求失败", code != null ? code : -1);
|
||||
}
|
||||
|
||||
// 检查返回的payurl
|
||||
String payUrl = null;
|
||||
if (jinShaBodyRes.getData() != null) {
|
||||
payUrl = jinShaBodyRes.getData().getPayurl();
|
||||
}
|
||||
if (payUrl == null || payUrl.trim().isEmpty()) {
|
||||
log.error("jinsha支付返回的payurl为空,订单号: {}", orderNo);
|
||||
throw new ServiceException("支付返回的支付链接为空", -1);
|
||||
}
|
||||
|
||||
JinShaBodyRes jinShaBodyRes = objectMapper.readValue(body.string(), JinShaBodyRes.class);
|
||||
// 创建充值管理
|
||||
AiUser userInfo = aiUserService.getUserInfo(SecurityUtils.getAiUserId());
|
||||
AiRecharge aiRecharge = new AiRecharge();
|
||||
aiRecharge.setOrderNum(orderNo);
|
||||
aiRecharge.setUserId(SecurityUtils.getAiUserId());
|
||||
aiRecharge.setAmount(amount);
|
||||
aiRecharge.setGearId(gearId);
|
||||
aiRecharge.setGearId(req.getGearId());
|
||||
aiRecharge.setGearAmount(aiRechargeGiftGear.getRechargeAmount());
|
||||
aiRecharge.setSource(userInfo.getSource());
|
||||
AiRechargeGift aiRechargeGift = aiRechargeGiftService.selectAiRechargeGiftById(aiRechargeGiftGear.getRechargeId());
|
||||
aiRecharge.setPayType(aiRechargeGift.getPayType());
|
||||
aiRecharge.setPayUrl(payUrl);
|
||||
aiRechargeService.insertAiRecharge(aiRecharge);
|
||||
|
||||
PayResVO payResVO = new PayResVO();
|
||||
payResVO.setOrderNo(orderNo);
|
||||
payResVO.setPayUrl(payUrl);
|
||||
payResVO.setPayUrl(jinShaBodyRes.getData().getPayurl());
|
||||
return payResVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult jinShaCall(JinShaBodyCall req) {
|
||||
log.info("jinsha支付回调,订单号: {}, 状态: {}", req.getOrderno(), req.getStatus());
|
||||
|
||||
// 验证签名
|
||||
TreeMap<String, Object> params = new TreeMap<>();
|
||||
params.put("status", req.getStatus());
|
||||
params.put("orderno", req.getOrderno());
|
||||
|
|
@ -167,58 +123,14 @@ public class JinShaService implements IJinShaService {
|
|||
StringBuilder append = this.append(params);
|
||||
String sign = this.sign(append);
|
||||
if (!sign.equals(req.getSign())) {
|
||||
log.error("jinsha支付回调签名错误,订单号: {}, 期望签名: {}, 实际签名: {}",
|
||||
req.getOrderno(), sign, req.getSign());
|
||||
return AjaxResult.error("签名验证失败");
|
||||
}
|
||||
|
||||
// 处理订单状态
|
||||
// status:0=>处理中, status:1=>成功, status:2=>失败
|
||||
Integer status = req.getStatus();
|
||||
if (status == null) {
|
||||
log.error("jinsha支付回调状态为空,订单号: {}", req.getOrderno());
|
||||
return AjaxResult.error("状态为空");
|
||||
}
|
||||
|
||||
if (status == 1) {
|
||||
// 充值成功处理
|
||||
log.info("jinsha支付成功,订单号: {}", req.getOrderno());
|
||||
aiRechargeService.addRecharge(req.getOrderno());
|
||||
return AjaxResult.success();
|
||||
} else if (status == 2) {
|
||||
// 支付失败
|
||||
log.warn("jinsha支付失败,订单号: {}", req.getOrderno());
|
||||
// 这里可以添加失败处理逻辑,比如更新订单状态为失败
|
||||
return AjaxResult.success();
|
||||
} else {
|
||||
// 处理中(status:0)
|
||||
log.info("jinsha支付处理中,订单号: {}", req.getOrderno());
|
||||
return AjaxResult.success();
|
||||
log.error("支付回调签名错误 {}", req.getOrderno());
|
||||
return AjaxResult.error();
|
||||
}
|
||||
//充值成功处理
|
||||
aiRechargeService.addRecharge(req.getOrderno());
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
* 按照文档要求:key=value&key=value&...&secret=secret,然后MD5
|
||||
*/
|
||||
private String generateSign(TreeMap<String, Object> sortedParams) {
|
||||
StringBuilder paramSb = new StringBuilder();
|
||||
for (Map.Entry<String, Object> entry : sortedParams.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
// 拼接格式:key=value&
|
||||
paramSb.append(key).append("=").append(value).append("&");
|
||||
}
|
||||
// 最后添加secret
|
||||
paramSb.append("secret=").append(secret);
|
||||
String signSource = paramSb.toString();
|
||||
log.debug("jinsha签名源字符串: {}", signSource);
|
||||
return Md5Utils.hash(signSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 旧的签名方法(回调使用)
|
||||
*/
|
||||
private StringBuilder append(TreeMap<String, Object> sortedParams) {
|
||||
StringBuilder paramSb = new StringBuilder();
|
||||
for (Map.Entry<String, Object> entry : sortedParams.entrySet()) {
|
||||
|
|
|
|||
|
|
@ -54,8 +54,6 @@ public class KaDaService implements IKaDaService {
|
|||
private IAiRechargeService aiRechargeService;
|
||||
@Autowired
|
||||
private IAiRechargeGiftService aiRechargeGiftService;
|
||||
@Autowired
|
||||
private IExchangeRateService exchangeRateService;
|
||||
|
||||
|
||||
@Override
|
||||
|
|
@ -67,8 +65,7 @@ public class KaDaService implements IKaDaService {
|
|||
throw new ServiceException("The gear position does not exist.", -1);
|
||||
}
|
||||
BigDecimal amount = aiRechargeGiftGear.getRechargeAmount();
|
||||
// 使用汇率服务转换金额(从CNY转换为目标货币,默认PHP)
|
||||
amount = exchangeRateService.convertAmount(amount, "USD", "PHP");
|
||||
amount = amount.multiply(new BigDecimal(60.00));
|
||||
|
||||
String uuid = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8);
|
||||
String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||
|
|
|
|||
|
|
@ -1,470 +0,0 @@
|
|||
package com.ruoyi.ai.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ruoyi.ai.domain.*;
|
||||
import com.ruoyi.ai.service.*;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.AiUser;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.http.OkHttpUtils;
|
||||
import com.ruoyi.common.utils.sign.Md5Utils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* VM支付服务实现
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-XX
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class VmService implements IVmService {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
|
||||
@Value("${vm.url:http://payment-api.togame.top}")
|
||||
private String url;
|
||||
|
||||
@Value("${vm.mchNo}")
|
||||
private String mchNo;
|
||||
|
||||
@Value("${vm.appId}")
|
||||
private String appId;
|
||||
|
||||
@Value("${vm.secret}")
|
||||
private String secret;
|
||||
|
||||
@Value("${vm.notifyUrl}")
|
||||
private String notifyUrl;
|
||||
|
||||
@Value("${vm.wayCode:BUZHI_VM}")
|
||||
private String wayCode;
|
||||
|
||||
@Value("${vm.currency:USD}")
|
||||
private String currency;
|
||||
|
||||
@Autowired
|
||||
private IAiRechargeService aiRechargeService;
|
||||
|
||||
@Autowired
|
||||
private IAiUserService aiUserService;
|
||||
|
||||
@Autowired
|
||||
private IAiRechargeGiftGearService aiRechargeGiftGearService;
|
||||
|
||||
@Autowired
|
||||
private IAiRechargeGiftService aiRechargeGiftService;
|
||||
|
||||
@Autowired
|
||||
private IExchangeRateService exchangeRateService;
|
||||
|
||||
@Override
|
||||
public PayResVO vmPay(Long gearId, VmCardInfo vmCardInfo, String clientIp) throws Exception {
|
||||
// 充值挡位查询
|
||||
AiRechargeGiftGear aiRechargeGiftGear = aiRechargeGiftGearService.selectAiRechargeGiftGearById(gearId);
|
||||
if (aiRechargeGiftGear == null) {
|
||||
throw new ServiceException("The gear position does not exist.", -1);
|
||||
}
|
||||
|
||||
BigDecimal amount = aiRechargeGiftGear.getRechargeAmount();
|
||||
// 使用汇率服务转换金额(从USD转换为目标货币,默认USD)
|
||||
// 注意:VM支付使用USD作为基础货币
|
||||
if (!"USD".equalsIgnoreCase(currency)) {
|
||||
amount = exchangeRateService.convertAmount(amount, "USD", currency);
|
||||
}
|
||||
|
||||
// 生成订单号
|
||||
String uuid = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8);
|
||||
String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||
String orderNo = dateTime + "vm" + uuid;
|
||||
|
||||
// 金额转换为分(整数)
|
||||
int amountInCents = amount.multiply(new BigDecimal(100)).intValue();
|
||||
|
||||
// 构建extParam(VM卡信息)
|
||||
String extParam = null;
|
||||
if (vmCardInfo != null) {
|
||||
extParam = objectMapper.writeValueAsString(vmCardInfo);
|
||||
}
|
||||
|
||||
// 构建统一下单请求
|
||||
VmUnifiedOrderReq req = new VmUnifiedOrderReq();
|
||||
req.setMchNo(mchNo);
|
||||
req.setAppId(appId);
|
||||
req.setMchOrderNo(orderNo);
|
||||
req.setWayCode(wayCode);
|
||||
req.setAmount(amountInCents);
|
||||
req.setCurrency(currency.toLowerCase());
|
||||
req.setSubject("游戏充值");
|
||||
req.setBody("游戏充值");
|
||||
req.setNotifyUrl(notifyUrl + "/api/pay/vm-callBack");
|
||||
req.setExpiredTime(7200); // 默认2小时
|
||||
req.setExtParam(extParam);
|
||||
req.setReqTime(System.currentTimeMillis());
|
||||
req.setVersion("1.0");
|
||||
req.setSignType("MD5");
|
||||
// 设置客户端IP
|
||||
if (clientIp != null && !clientIp.trim().isEmpty()) {
|
||||
req.setClientIp(clientIp);
|
||||
log.debug("VM支付设置客户端IP: {}", clientIp);
|
||||
} else {
|
||||
log.warn("VM支付客户端IP为空,订单号: {}", orderNo);
|
||||
}
|
||||
|
||||
// 生成签名
|
||||
String sign = generateSign(req);
|
||||
req.setSign(sign);
|
||||
|
||||
// 构建请求体JSON
|
||||
String jsonBody = objectMapper.writeValueAsString(req);
|
||||
log.info("VM支付请求参数: {}", jsonBody);
|
||||
|
||||
// 构建POST请求
|
||||
Request request = new Request.Builder()
|
||||
.url(url + "/api/pay/unifiedOrder")
|
||||
.header("Content-Type", "application/json")
|
||||
.post(RequestBody.create(MediaType.parse("application/json"), jsonBody))
|
||||
.build();
|
||||
|
||||
// 发送请求
|
||||
Response response = OkHttpUtils.newCall(request).execute();
|
||||
ResponseBody body = response.body();
|
||||
|
||||
if (body == null) {
|
||||
log.error("VM支付请求的响应异常");
|
||||
throw new Exception("vm pay response body is null");
|
||||
}
|
||||
|
||||
String responseBody = body.string();
|
||||
log.info("VM支付请求响应: {}", responseBody);
|
||||
|
||||
VmUnifiedOrderRes res = objectMapper.readValue(responseBody, VmUnifiedOrderRes.class);
|
||||
|
||||
// 检查响应code,0表示成功
|
||||
if (res.getCode() == null || res.getCode() != 0) {
|
||||
String msg = res.getMsg();
|
||||
log.error("VM支付请求失败,code: {}, msg: {}", res.getCode(), msg);
|
||||
throw new ServiceException(msg != null ? msg : "支付请求失败", res.getCode() != null ? res.getCode() : -1);
|
||||
}
|
||||
|
||||
// 检查返回数据
|
||||
if (res.getData() == null) {
|
||||
log.error("VM支付返回的data为空,订单号: {}", orderNo);
|
||||
throw new ServiceException("支付返回的数据为空", -1);
|
||||
}
|
||||
|
||||
VmUnifiedOrderRes.VmUnifiedOrderData data = res.getData();
|
||||
|
||||
// 记录关键字段信息
|
||||
String payOrderId = data.getPayOrderId();
|
||||
String mchOrderNo = data.getMchOrderNo();
|
||||
log.info("VM支付返回数据,商户订单号: {}, 支付订单号: {}, 订单状态: {}, payDataType: {}",
|
||||
mchOrderNo, payOrderId, data.getOrderState(), data.getPayDataType());
|
||||
|
||||
// 检查订单状态
|
||||
Integer orderState = data.getOrderState();
|
||||
if (orderState == null) {
|
||||
log.error("VM支付返回的订单状态为空,订单号: {}", orderNo);
|
||||
throw new ServiceException("支付返回的订单状态为空", -1);
|
||||
}
|
||||
|
||||
// 订单状态说明:
|
||||
// 0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已撤销, 5-已退款, 6-订单关闭
|
||||
String payUrl = null;
|
||||
String payDataType = data.getPayDataType();
|
||||
String payData = data.getPayData();
|
||||
|
||||
// 如果订单状态是支付失败(3),抛出异常
|
||||
if (orderState == 3) {
|
||||
String errCode = data.getErrCode();
|
||||
String errMsg = data.getErrMsg();
|
||||
String errorMessage = "支付失败";
|
||||
if (errMsg != null && !errMsg.trim().isEmpty()) {
|
||||
errorMessage = errMsg;
|
||||
} else if (errCode != null && !errCode.trim().isEmpty()) {
|
||||
errorMessage = String.format("支付失败,错误码: %s", errCode);
|
||||
}
|
||||
log.error("VM支付失败,订单号: {}, 订单状态: {}, 错误码: {}, 错误消息: {}",
|
||||
orderNo, orderState, errCode, errMsg);
|
||||
throw new ServiceException(errorMessage, -1);
|
||||
}
|
||||
|
||||
// 如果订单状态是支付中(1),需要获取支付URL让用户去支付
|
||||
// 如果订单状态是支付成功(2),不需要payUrl(用户说"订单状态只要是支付成功就不用管订单状态")
|
||||
if (orderState == 1) {
|
||||
// 根据payDataType获取支付URL
|
||||
if (payDataType != null && payData != null && !payData.trim().isEmpty()) {
|
||||
// payurl类型(注意:实际返回是小写payurl):跳转链接的方式
|
||||
if ("payurl".equalsIgnoreCase(payDataType) || "payUrl".equalsIgnoreCase(payDataType)) {
|
||||
payUrl = payData;
|
||||
log.info("VM支付使用payData作为支付URL(payDataType={}),订单号: {}", payDataType, orderNo);
|
||||
}
|
||||
// codeUrl类型:二维码地址
|
||||
else if ("codeUrl".equalsIgnoreCase(payDataType)) {
|
||||
payUrl = payData;
|
||||
log.info("VM支付使用payData作为支付URL(payDataType=codeUrl),订单号: {}", orderNo);
|
||||
}
|
||||
// codeImgUrl类型:二维码图片地址
|
||||
else if ("codeImgUrl".equalsIgnoreCase(payDataType)) {
|
||||
payUrl = payData;
|
||||
log.info("VM支付使用payData作为支付URL(payDataType=codeImgUrl),订单号: {}", orderNo);
|
||||
}
|
||||
// 其他类型(form, wxapp, aliapp, ysfapp, none)暂不处理payUrl
|
||||
else if (!"none".equalsIgnoreCase(payDataType)) {
|
||||
log.info("VM支付payDataType为: {},暂不支持作为支付URL,订单号: {}", payDataType, orderNo);
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容旧版本:如果payUrl字段有值,优先使用
|
||||
if ((payUrl == null || payUrl.trim().isEmpty()) &&
|
||||
data.getPayUrl() != null && !data.getPayUrl().trim().isEmpty()) {
|
||||
payUrl = data.getPayUrl();
|
||||
log.info("VM支付使用payUrl字段,订单号: {}", orderNo);
|
||||
}
|
||||
|
||||
// 如果仍然没有payUrl,记录警告
|
||||
if (payUrl == null || payUrl.trim().isEmpty()) {
|
||||
log.warn("VM支付返回的payUrl为空,订单状态为支付中,订单号: {}, payDataType: {}",
|
||||
orderNo, payDataType);
|
||||
payUrl = "";
|
||||
}
|
||||
} else if (orderState == 2) {
|
||||
// 支付成功,不需要payUrl
|
||||
log.info("VM支付订单状态为支付成功,不需要返回payUrl,订单号: {}", orderNo);
|
||||
payUrl = "";
|
||||
} else {
|
||||
// 其他状态(0-订单生成, 4-已撤销, 5-已退款, 6-订单关闭)
|
||||
log.info("VM支付订单状态: {},订单号: {}", orderState, orderNo);
|
||||
payUrl = "";
|
||||
}
|
||||
|
||||
// 创建充值管理
|
||||
AiUser userInfo = aiUserService.getUserInfo(SecurityUtils.getAiUserId());
|
||||
AiRecharge aiRecharge = new AiRecharge();
|
||||
aiRecharge.setOrderNum(orderNo);
|
||||
aiRecharge.setUserId(SecurityUtils.getAiUserId());
|
||||
aiRecharge.setAmount(amount);
|
||||
aiRecharge.setGearId(gearId);
|
||||
aiRecharge.setGearAmount(aiRechargeGiftGear.getRechargeAmount());
|
||||
aiRecharge.setSource(userInfo.getSource());
|
||||
AiRechargeGift aiRechargeGift = aiRechargeGiftService.selectAiRechargeGiftById(aiRechargeGiftGear.getRechargeId());
|
||||
aiRecharge.setPayType(aiRechargeGift.getPayType());
|
||||
aiRecharge.setPayUrl(payUrl);
|
||||
aiRechargeService.insertAiRecharge(aiRecharge);
|
||||
|
||||
PayResVO payResVO = new PayResVO();
|
||||
payResVO.setOrderNo(orderNo);
|
||||
payResVO.setPayUrl(payUrl);
|
||||
return payResVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public String vmCallBack(VmCallBackReq req) {
|
||||
log.info("VM支付回调,订单号: {}, 状态: {}", req.getMchOrderNo(), req.getState());
|
||||
|
||||
log.debug("VM支付回调请求对象: mchNo={}, appId={}, mchOrderNo={}, payOrderId={}, ifCode={}, wayCode={}, amount={}, currency={}, state={}, subject={}, body={}, createdAt={}, reqTime={}, sign={}",
|
||||
req.getMchNo(), req.getAppId(), req.getMchOrderNo(), req.getPayOrderId(), req.getIfCode(), req.getWayCode(),
|
||||
req.getAmount(), req.getCurrency(), req.getState(), req.getSubject(), req.getBody(),
|
||||
req.getCreatedAt(), req.getReqTime(), req.getSign());
|
||||
|
||||
// 验证签名
|
||||
String sign = generateCallBackSign(req);
|
||||
if (!sign.equals(req.getSign())) {
|
||||
log.error("VM支付回调签名错误,订单号: {}, 期望签名: {}, 实际签名: {}",
|
||||
req.getMchOrderNo(), sign, req.getSign());
|
||||
return "fail";
|
||||
}
|
||||
|
||||
// 处理订单状态(文档字段名:state)
|
||||
// 0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已撤销, 5-已退款, 6-订单关闭
|
||||
Integer state = req.getState();
|
||||
if (state == null) {
|
||||
log.warn("VM支付回调state为空,订单号: {},返回 success 等待后续回调", req.getMchOrderNo());
|
||||
return "success";
|
||||
}
|
||||
|
||||
if (state == 2) {
|
||||
log.info("VM支付成功,订单号: {}", req.getMchOrderNo());
|
||||
aiRechargeService.addRecharge(req.getMchOrderNo());
|
||||
return "success";
|
||||
} else if (state == 3) {
|
||||
log.warn("VM支付失败,订单号: {}", req.getMchOrderNo());
|
||||
return "success";
|
||||
} else {
|
||||
log.info("VM支付状态: {}, 订单号: {}", state, req.getMchOrderNo());
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名(统一下单)
|
||||
* 第一步:将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式拼接成字符串stringA
|
||||
* 第二步:在stringA最后拼接上key[即 StringA +"&key=" + 私钥 ] 得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写
|
||||
*/
|
||||
private String generateSign(VmUnifiedOrderReq req) {
|
||||
TreeMap<String, Object> params = new TreeMap<>();
|
||||
|
||||
// 添加所有非空参数(sign不参与签名)
|
||||
if (req.getMchNo() != null && !req.getMchNo().isEmpty()) {
|
||||
params.put("mchNo", req.getMchNo());
|
||||
}
|
||||
if (req.getAppId() != null && !req.getAppId().isEmpty()) {
|
||||
params.put("appId", req.getAppId());
|
||||
}
|
||||
if (req.getMchOrderNo() != null && !req.getMchOrderNo().isEmpty()) {
|
||||
params.put("mchOrderNo", req.getMchOrderNo());
|
||||
}
|
||||
if (req.getWayCode() != null && !req.getWayCode().isEmpty()) {
|
||||
params.put("wayCode", req.getWayCode());
|
||||
}
|
||||
if (req.getAmount() != null) {
|
||||
params.put("amount", req.getAmount());
|
||||
}
|
||||
if (req.getCurrency() != null && !req.getCurrency().isEmpty()) {
|
||||
params.put("currency", req.getCurrency());
|
||||
}
|
||||
if (req.getClientIp() != null && !req.getClientIp().isEmpty()) {
|
||||
params.put("clientIp", req.getClientIp());
|
||||
}
|
||||
if (req.getSubject() != null && !req.getSubject().isEmpty()) {
|
||||
params.put("subject", req.getSubject());
|
||||
}
|
||||
if (req.getBody() != null && !req.getBody().isEmpty()) {
|
||||
params.put("body", req.getBody());
|
||||
}
|
||||
if (req.getNotifyUrl() != null && !req.getNotifyUrl().isEmpty()) {
|
||||
params.put("notifyUrl", req.getNotifyUrl());
|
||||
}
|
||||
if (req.getExpiredTime() != null) {
|
||||
params.put("expiredTime", req.getExpiredTime());
|
||||
}
|
||||
if (req.getChannelExtra() != null && !req.getChannelExtra().isEmpty()) {
|
||||
params.put("channelExtra", req.getChannelExtra());
|
||||
}
|
||||
if (req.getExtParam() != null && !req.getExtParam().isEmpty()) {
|
||||
params.put("extParam", req.getExtParam());
|
||||
}
|
||||
if (req.getReqTime() != null) {
|
||||
params.put("reqTime", req.getReqTime());
|
||||
}
|
||||
if (req.getVersion() != null && !req.getVersion().isEmpty()) {
|
||||
params.put("version", req.getVersion());
|
||||
}
|
||||
if (req.getSignType() != null && !req.getSignType().isEmpty()) {
|
||||
params.put("signType", req.getSignType());
|
||||
}
|
||||
|
||||
// 拼接参数
|
||||
StringBuilder stringA = new StringBuilder();
|
||||
for (Map.Entry<String, Object> entry : params.entrySet()) {
|
||||
if (stringA.length() > 0) {
|
||||
stringA.append("&");
|
||||
}
|
||||
stringA.append(entry.getKey()).append("=").append(entry.getValue());
|
||||
}
|
||||
|
||||
// 拼接key
|
||||
String stringSignTemp = stringA.toString() + "&key=" + secret;
|
||||
log.debug("VM签名源字符串: {}", stringSignTemp);
|
||||
|
||||
// MD5并转大写
|
||||
String sign = Md5Utils.hash(stringSignTemp).toUpperCase();
|
||||
return sign;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成回调签名。按文档字段名:state、reqTime;sign、signType 不参与签名。
|
||||
* 参与签名的非空参数按 key 字典序排序,key=value 用 & 拼接,最后 &key=secret,MD5 后转大写。
|
||||
*/
|
||||
private String generateCallBackSign(VmCallBackReq req) {
|
||||
TreeMap<String, Object> params = new TreeMap<>();
|
||||
|
||||
if (req.getMchNo() != null && !req.getMchNo().isEmpty()) {
|
||||
params.put("mchNo", req.getMchNo());
|
||||
}
|
||||
if (req.getAppId() != null && !req.getAppId().isEmpty()) {
|
||||
params.put("appId", req.getAppId());
|
||||
}
|
||||
if (req.getMchOrderNo() != null && !req.getMchOrderNo().isEmpty()) {
|
||||
params.put("mchOrderNo", req.getMchOrderNo());
|
||||
}
|
||||
if (req.getPayOrderId() != null && !req.getPayOrderId().isEmpty()) {
|
||||
params.put("payOrderId", req.getPayOrderId());
|
||||
}
|
||||
if (req.getIfCode() != null && !req.getIfCode().isEmpty()) {
|
||||
params.put("ifCode", req.getIfCode());
|
||||
}
|
||||
if (req.getWayCode() != null && !req.getWayCode().isEmpty()) {
|
||||
params.put("wayCode", req.getWayCode());
|
||||
}
|
||||
if (req.getAmount() != null) {
|
||||
params.put("amount", req.getAmount());
|
||||
}
|
||||
if (req.getCurrency() != null && !req.getCurrency().isEmpty()) {
|
||||
params.put("currency", req.getCurrency());
|
||||
}
|
||||
if (req.getState() != null) {
|
||||
params.put("state", req.getState());
|
||||
}
|
||||
if (req.getClientIp() != null && !req.getClientIp().isEmpty()) {
|
||||
params.put("clientIp", req.getClientIp());
|
||||
}
|
||||
if (req.getSubject() != null && !req.getSubject().isEmpty()) {
|
||||
params.put("subject", req.getSubject());
|
||||
}
|
||||
if (req.getBody() != null && !req.getBody().isEmpty()) {
|
||||
params.put("body", req.getBody());
|
||||
}
|
||||
if (req.getChannelOrderNo() != null && !req.getChannelOrderNo().isEmpty()) {
|
||||
params.put("channelOrderNo", req.getChannelOrderNo());
|
||||
}
|
||||
if (req.getErrCode() != null && !req.getErrCode().isEmpty()) {
|
||||
params.put("errCode", req.getErrCode());
|
||||
}
|
||||
if (req.getErrMsg() != null && !req.getErrMsg().isEmpty()) {
|
||||
params.put("errMsg", req.getErrMsg());
|
||||
}
|
||||
if (req.getExtParam() != null && !req.getExtParam().isEmpty()) {
|
||||
params.put("extParam", req.getExtParam());
|
||||
}
|
||||
if (req.getCreatedAt() != null) {
|
||||
params.put("createdAt", req.getCreatedAt());
|
||||
}
|
||||
if (req.getSuccessTime() != null) {
|
||||
params.put("successTime", req.getSuccessTime());
|
||||
}
|
||||
if (req.getReqTime() != null) {
|
||||
params.put("reqTime", req.getReqTime());
|
||||
}
|
||||
|
||||
StringBuilder stringA = new StringBuilder();
|
||||
for (Map.Entry<String, Object> entry : params.entrySet()) {
|
||||
if (stringA.length() > 0) {
|
||||
stringA.append("&");
|
||||
}
|
||||
stringA.append(entry.getKey()).append("=").append(entry.getValue());
|
||||
}
|
||||
|
||||
String stringSignTemp = stringA.toString() + "&key=" + secret;
|
||||
log.debug("VM回调签名源字符串: {}", stringSignTemp);
|
||||
|
||||
String sign = Md5Utils.hash(stringSignTemp).toUpperCase();
|
||||
log.debug("VM回调计算出的签名: {}", sign);
|
||||
return sign;
|
||||
}
|
||||
}
|
||||
|
|
@ -60,111 +60,37 @@ public class YuZhouService implements IYuZhouService {
|
|||
String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||
String orderNo = dateTime + "yz" + uuid;
|
||||
|
||||
// 获取用户信息
|
||||
AiUser userInfo = aiUserService.getUserInfo(SecurityUtils.getAiUserId());
|
||||
|
||||
// 处理用户姓名,如果没有则使用默认值
|
||||
String firstName = "John";
|
||||
String lastName = "Doe";
|
||||
if (userInfo.getNickname() != null && !userInfo.getNickname().trim().isEmpty()) {
|
||||
String[] nameParts = userInfo.getNickname().trim().split("\\s+");
|
||||
if (nameParts.length >= 2) {
|
||||
firstName = nameParts[0];
|
||||
lastName = nameParts[nameParts.length - 1];
|
||||
} else if (nameParts.length == 1) {
|
||||
firstName = nameParts[0];
|
||||
}
|
||||
}
|
||||
|
||||
// 从用户表获取邮箱
|
||||
String email = userInfo.getEmail();
|
||||
if (email == null || email.trim().isEmpty()) {
|
||||
log.warn("用户邮箱为空 userId: {}, nickname: {}", userInfo.getId(), userInfo.getNickname());
|
||||
// 如果用户邮箱为空,使用默认邮箱格式
|
||||
email = "user" + userInfo.getId() + "@example.com";
|
||||
} else {
|
||||
email = email.trim();
|
||||
}
|
||||
|
||||
// 处理电话,如果没有则使用默认值
|
||||
String phone = userInfo.getPhone();
|
||||
if (phone == null || phone.trim().isEmpty()) {
|
||||
phone = "1234567890";
|
||||
} else {
|
||||
// 确保电话是10位数字
|
||||
phone = phone.replaceAll("[^0-9]", "");
|
||||
if (phone.length() < 10) {
|
||||
// 如果不足10位,用0补齐
|
||||
phone = String.format("%010d", phone.length() > 0 ? Long.parseLong(phone) : 0);
|
||||
} else if (phone.length() > 10) {
|
||||
// 如果超过10位,取后10位
|
||||
phone = phone.substring(phone.length() - 10);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
Map<String, Object> createMap = new HashMap<>();
|
||||
createMap.put("merchantNo", appId);
|
||||
createMap.put("orderNo", orderNo);
|
||||
createMap.put("money", amount.toString());
|
||||
createMap.put("description", "Recharge");
|
||||
createMap.put("email", email);
|
||||
createMap.put("phone", phone);
|
||||
createMap.put("notifyUrl", callbackUrl + "/api/pay/yuzhou-callBack");
|
||||
createMap.put("description", "test");
|
||||
createMap.put("name", "test");
|
||||
createMap.put("email", "test@gmail.com");
|
||||
createMap.put("callbackUrl", callbackUrl + "/api/pay/yuzhou-callBack");
|
||||
createMap.put("phone", "7383442114");
|
||||
createMap.put("expiredPeriod", "1440");
|
||||
createMap.put("currency", "USD");
|
||||
createMap.put("firstName", firstName);
|
||||
createMap.put("lastName", lastName);
|
||||
createMap.put("street", "123 Main Street");
|
||||
createMap.put("city", "New York");
|
||||
createMap.put("state", "NY");
|
||||
createMap.put("country", userInfo.getCountry() != null && !userInfo.getCountry().trim().isEmpty() ? userInfo.getCountry() : "US");
|
||||
createMap.put("postcode", "10001");
|
||||
|
||||
// 生成签名(注意:签名时不包括signature字段)
|
||||
createMap.put("redirectUrl", redirectUrl + "/recharge");
|
||||
String signedStr = RSAUtils.getSignStr(createMap, secretKey);
|
||||
createMap.put("signature", signedStr);
|
||||
|
||||
String postStr = JSONUtil.toJsonStr(createMap);
|
||||
log.info("yuzhou支付请求参数: {}", postStr);
|
||||
|
||||
// 调用新的接口地址
|
||||
String response = HttpRequest.post(url + "/gateway/US/payIn")
|
||||
.header("Content-Type", "application/json; charset=utf-8")
|
||||
.body(postStr)
|
||||
.execute()
|
||||
.body();
|
||||
|
||||
log.info("yuzhou支付响应: {}", response);
|
||||
|
||||
String response = HttpRequest.post(url + "/gateway/order/US/payIn").header("Content-Type", "application/json")
|
||||
.body(postStr).execute().body();
|
||||
JSONObject returnObj = JSONUtil.parseObj(response);
|
||||
Integer code = returnObj.getInt("code");
|
||||
if (200 != code) {
|
||||
String msg = returnObj.getStr("msg");
|
||||
log.error("yuzhou支付请求的响应异常 code: {}, msg: {}, response: {}", code, msg, returnObj);
|
||||
throw new ServiceException("yuzhoupay request failed: " + (msg != null ? msg : "unknown error"), code);
|
||||
log.error("yuzhou支付请求的响应异常 {}", returnObj);
|
||||
throw new Exception("yuzhoupay responsebody is null");
|
||||
}
|
||||
|
||||
JSONObject data = returnObj.getJSONObject("data");
|
||||
if (data == null) {
|
||||
log.error("yuzhou支付响应数据为空: {}", returnObj);
|
||||
throw new ServiceException("yuzhoupay response data is null", -1);
|
||||
}
|
||||
|
||||
String money = data.getStr("money");
|
||||
String payUrl = data.getStr("url");
|
||||
String platformOrderNo = data.getStr("platformOrderNo");
|
||||
|
||||
if (payUrl == null || payUrl.trim().isEmpty()) {
|
||||
log.error("yuzhou支付URL为空: {}", data);
|
||||
throw new ServiceException("yuzhoupay url is null", -1);
|
||||
}
|
||||
|
||||
// 创建充值管理
|
||||
AiUser userInfo = aiUserService.getUserInfo(SecurityUtils.getAiUserId());
|
||||
AiRecharge aiRecharge = new AiRecharge();
|
||||
aiRecharge.setOrderNum(orderNo);
|
||||
aiRecharge.setUserId(SecurityUtils.getAiUserId());
|
||||
aiRecharge.setAmount(new BigDecimal(money != null ? money : amount.toString()));
|
||||
aiRecharge.setAmount(new BigDecimal(money));
|
||||
aiRecharge.setGearId(gearId);
|
||||
aiRecharge.setSource(userInfo.getSource());
|
||||
aiRecharge.setGearAmount(aiRechargeGiftGear.getRechargeAmount());
|
||||
|
|
@ -172,7 +98,6 @@ public class YuZhouService implements IYuZhouService {
|
|||
aiRecharge.setPayType(aiRechargeGift.getPayType());
|
||||
aiRecharge.setPayUrl(payUrl);
|
||||
aiRechargeService.insertAiRecharge(aiRecharge);
|
||||
|
||||
PayResVO payResVO = new PayResVO();
|
||||
payResVO.setOrderNo(orderNo);
|
||||
payResVO.setPayUrl(payUrl);
|
||||
|
|
@ -181,60 +106,31 @@ public class YuZhouService implements IYuZhouService {
|
|||
|
||||
@Override
|
||||
public String callBack(Map<String, Object> map) throws Exception {
|
||||
log.info("yuzhou支付回调接收参数: {}", map);
|
||||
|
||||
// 验证必要参数
|
||||
if (map == null || map.isEmpty()) {
|
||||
log.error("yuzhou支付回调参数为空");
|
||||
return "FAIL";
|
||||
int code = Integer.parseInt(map.get("status").toString());
|
||||
String orderNo = map.get("orderNo").toString();
|
||||
if (10 != code) {
|
||||
log.error("yuzhou支付失败 {}", orderNo);
|
||||
return null;
|
||||
}
|
||||
|
||||
Object statusObj = map.get("status");
|
||||
Object orderNoObj = map.get("orderNo");
|
||||
|
||||
if (statusObj == null || orderNoObj == null) {
|
||||
log.error("yuzhou支付回调缺少必要参数: status={}, orderNo={}", statusObj, orderNoObj);
|
||||
return "FAIL";
|
||||
}
|
||||
|
||||
int status = Integer.parseInt(statusObj.toString());
|
||||
String orderNo = orderNoObj.toString();
|
||||
|
||||
log.info("yuzhou支付回调 orderNo: {}, status: {}", orderNo, status);
|
||||
|
||||
// 验证签名
|
||||
Object signatureObj = map.get("signature");
|
||||
if (signatureObj == null) {
|
||||
log.error("yuzhou支付回调缺少签名参数 orderNo: {}", orderNo);
|
||||
return "FAIL";
|
||||
}
|
||||
|
||||
// Map<String, Object> createMap = new HashMap<>();
|
||||
// createMap.put("orderNo", orderNo);
|
||||
// createMap.put("platOrderNo", map.get("platOrderNo"));
|
||||
// createMap.put("money", map.get("money"));
|
||||
// createMap.put("fee", map.get("fee"));
|
||||
// createMap.put("status", map.get("status"));
|
||||
// createMap.put("message", map.get("message"));
|
||||
String signature = map.get("signature").toString();
|
||||
JSONObject entries = JSONUtil.parseObj(map);
|
||||
boolean verifyResult = RSAUtils.verifySign(entries, publicKey);
|
||||
if (!verifyResult) {
|
||||
log.error("yuzhou支付回调签名验证失败 orderNo: {}", orderNo);
|
||||
log.error("yuzhou支付回调签名: {}", signatureObj);
|
||||
log.error("yuzhou支付回调完整报文: {}", map);
|
||||
return "FAIL";
|
||||
}
|
||||
|
||||
log.info("yuzhou支付回调签名验证成功 orderNo: {}", orderNo);
|
||||
|
||||
// 判断订单状态,10表示成功
|
||||
if (10 != status) {
|
||||
log.warn("yuzhou支付订单状态非成功 orderNo: {}, status: {}", orderNo, status);
|
||||
return "SUCCESS"; // 即使失败也返回SUCCESS,避免重复回调
|
||||
}
|
||||
|
||||
// 充值成功处理
|
||||
try {
|
||||
aiRechargeService.addRecharge(orderNo);
|
||||
log.info("yuzhou支付回调处理成功 orderNo: {}", orderNo);
|
||||
return "SUCCESS";
|
||||
} catch (Exception e) {
|
||||
log.error("yuzhou支付回调处理失败 orderNo: {}", orderNo, e);
|
||||
return "FAIL";
|
||||
boolean b = RSAUtils.verifySign(entries, publicKey);
|
||||
if (!b) {
|
||||
log.error("yuzhou支付回调签名错误 {}", orderNo);
|
||||
log.error("yuzhou支付回调签名 {}", signature);
|
||||
log.error("yuzhou支付回调报文 {}", map);
|
||||
return null;
|
||||
}
|
||||
//充值成功处理
|
||||
aiRechargeService.addRecharge(orderNo);
|
||||
return "SUCCESS";
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -21,18 +21,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<result property="source" column="source" />
|
||||
<result property="text" column="text" />
|
||||
<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>
|
||||
|
||||
<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, 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
|
||||
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
|
||||
left join ai_user au on au.id = ao.user_id
|
||||
</sql>
|
||||
|
||||
|
|
@ -69,21 +61,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
where result = #{result}
|
||||
</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 COALESCE(sum(amount), 0) from ai_order where user_id = #{userId}
|
||||
</select>
|
||||
|
|
@ -141,7 +118,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="amount != null">amount = #{amount},</if>
|
||||
<if test="result != null">result = #{result},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="source != null">source = #{source},</if>
|
||||
<if test="source != null">status = #{source},</if>
|
||||
<if test="text != null">text = #{text},</if>
|
||||
<if test="isTop != null">is_top = #{isTop},</if>
|
||||
</trim>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<result property="chineseContent" column="chinese_content" />
|
||||
<result property="englishContent" column="english_content" />
|
||||
<result property="imageUrl" column="image_url" />
|
||||
<result property="aiId" column="ai_id" />
|
||||
<result property="status" column="status" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="createTime" column="create_time" />
|
||||
|
|
@ -21,7 +20,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
</resultMap>
|
||||
|
||||
<sql id="selectAiTemplateVo">
|
||||
select id, name, chinese_content, english_content, image_url, ai_id, status, remark, create_time, create_by, update_by, update_time, del_flag from ai_template
|
||||
select id, name, chinese_content, english_content, image_url, status, remark, create_time, create_by, update_by, update_time, del_flag from ai_template
|
||||
</sql>
|
||||
|
||||
<select id="selectAiTemplateList" parameterType="AiTemplate" resultMap="AiTemplateResult">
|
||||
|
|
@ -31,7 +30,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="chineseContent != null and chineseContent != ''"> and chinese_content = #{chineseContent}</if>
|
||||
<if test="englishContent != null and englishContent != ''"> and english_content = #{englishContent}</if>
|
||||
<if test="imageUrl != null and imageUrl != ''"> and image_url = #{imageUrl}</if>
|
||||
<if test="aiId != null and aiId != ''"> and ai_id = #{aiId}</if>
|
||||
<if test="status != null "> and status = #{status}</if>
|
||||
</where>
|
||||
</select>
|
||||
|
|
@ -48,7 +46,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="chineseContent != null">chinese_content,</if>
|
||||
<if test="englishContent != null">english_content,</if>
|
||||
<if test="imageUrl != null">image_url,</if>
|
||||
<if test="aiId != null">ai_id,</if>
|
||||
<if test="status != null">status,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
|
|
@ -62,7 +59,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="chineseContent != null">#{chineseContent},</if>
|
||||
<if test="englishContent != null">#{englishContent},</if>
|
||||
<if test="imageUrl != null">#{imageUrl},</if>
|
||||
<if test="aiId != null">#{aiId},</if>
|
||||
<if test="status != null">#{status},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
|
|
@ -80,7 +76,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="chineseContent != null">chinese_content = #{chineseContent},</if>
|
||||
<if test="englishContent != null">english_content = #{englishContent},</if>
|
||||
<if test="imageUrl != null">image_url = #{imageUrl},</if>
|
||||
<if test="aiId != null">ai_id = #{aiId},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
<if test="createTime != null">create_time = #{createTime},</if>
|
||||
|
|
|
|||
|
|
@ -33,18 +33,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<result property="source" column="source" />
|
||||
<result property="ip" column="ip" />
|
||||
<result property="country" column="country" />
|
||||
<result property="deptId" column="dept_id" />
|
||||
<result property="deptName" column="dept_name" />
|
||||
</resultMap>
|
||||
|
||||
<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, dept_id 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 from ai_user
|
||||
</sql>
|
||||
|
||||
<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, u.dept_id, d.dept_name, 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, au.user_id superiorUuid, au.username superiorName from ai_user u
|
||||
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>
|
||||
<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>
|
||||
|
|
@ -66,25 +63,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="superiorId != null "> and u.superior_id = #{superiorId}</if>
|
||||
<if test="id != null "> and u.id = #{id}</if>
|
||||
<if test="source != null "> and u.source = #{source}</if>
|
||||
<if test="deptId != null "> and u.dept_id = #{deptId}</if>
|
||||
</where>
|
||||
order by u.id desc
|
||||
</select>
|
||||
|
||||
<select id="selectAiUserById" parameterType="Long" 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, u.dept_id, d.dept_name
|
||||
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 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, ip, country, dept_id from ai_user
|
||||
<include refid="selectAiUserVo"/>
|
||||
where id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="countAiUserByDeptId" resultType="int">
|
||||
select count(1) from ai_user where del_flag = '0' and dept_id = #{deptId}
|
||||
<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
|
||||
where id = #{id}
|
||||
</select>
|
||||
<select id="selectPasswordById" resultType="java.lang.String">
|
||||
select password from ai_user where id = #{id}
|
||||
|
|
@ -118,7 +108,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="source != null">source,</if>
|
||||
<if test="ip != null">ip,</if>
|
||||
<if test="country != null">country,</if>
|
||||
<if test="deptId != null">dept_id,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="delFlag != null and delFlag != ''">#{delFlag},</if>
|
||||
|
|
@ -145,7 +134,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="userId != null">#{userId},</if>
|
||||
<if test="source != null">#{source},</if>
|
||||
<if test="country != null">#{country},</if>
|
||||
<if test="deptId != null">#{deptId},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
|
|
@ -175,9 +163,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="superiorId != null">superior_id = #{superiorId},</if>
|
||||
<if test="userId != null">user_id = #{userId},</if>
|
||||
<if test="source != null">source = #{source},</if>
|
||||
<if test="ip != null">ip = #{ip},</if>
|
||||
<if test="ip != null">source = #{ip},</if>
|
||||
<if test="country != null">country = #{country},</if>
|
||||
<if test="deptId != null">dept_id = #{deptId},</if>
|
||||
</trim>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<result property="leader" column="leader" />
|
||||
<result property="phone" column="phone" />
|
||||
<result property="email" column="email" />
|
||||
<result property="byteApiKey" column="byte_api_key" />
|
||||
<result property="status" column="status" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
<result property="parentName" column="parent_name" />
|
||||
|
|
@ -24,7 +23,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
</resultMap>
|
||||
|
||||
<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.byte_api_key, 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.status, d.del_flag, d.create_by, d.create_time
|
||||
from sys_dept d
|
||||
</sql>
|
||||
|
||||
|
|
@ -60,7 +59,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
</select>
|
||||
|
||||
<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.byte_api_key, d.status,
|
||||
select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status,
|
||||
(select dept_name from sys_dept where dept_id = d.parent_id) parent_name
|
||||
from sys_dept d
|
||||
where d.dept_id = #{deptId}
|
||||
|
|
@ -102,7 +101,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="leader != null and leader != ''">leader,</if>
|
||||
<if test="phone != null and phone != ''">phone,</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="createBy != null and createBy != ''">create_by,</if>
|
||||
create_time
|
||||
|
|
@ -115,7 +113,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="leader != null and leader != ''">#{leader},</if>
|
||||
<if test="phone != null and phone != ''">#{phone},</if>
|
||||
<if test="email != null and email != ''">#{email},</if>
|
||||
<if test="byteApiKey != null">#{byteApiKey},</if>
|
||||
<if test="status != null">#{status},</if>
|
||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||
sysdate()
|
||||
|
|
@ -132,7 +129,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="leader != null">leader = #{leader},</if>
|
||||
<if test="phone != null">phone = #{phone},</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="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
|
||||
update_time = sysdate()
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ SET FOREIGN_KEY_CHECKS = 0;
|
|||
DROP TABLE IF EXISTS `ai_balance_change_record`;
|
||||
CREATE TABLE `ai_balance_change_record` (
|
||||
`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代表删除)',
|
||||
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
|
|
@ -34,8 +33,7 @@ CREATE TABLE `ai_balance_change_record` (
|
|||
`type` tinyint(1) NULL DEFAULT NULL COMMENT '操作类型',
|
||||
`change_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '变更金额',
|
||||
`result_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '变更后金额',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_order_no` (`order_no`) USING BTREE
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1159 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '余额使用记录' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
|
|
@ -1451,7 +1449,7 @@ CREATE TABLE `ai_manager` (
|
|||
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_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 '更新时间',
|
||||
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP 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标题',
|
||||
`price` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '价格',
|
||||
|
|
@ -1466,12 +1464,12 @@ CREATE TABLE `ai_manager` (
|
|||
-- ----------------------------
|
||||
-- Records of ai_manager
|
||||
-- ----------------------------
|
||||
INSERT INTO `ai_manager` VALUES (1, '0', 'admin', '2025-11-13 20:05:27', 'admin', '2025-12-23 14:30:07', NULL, '图生图', 10.00, 0, '1girl, solo, exact same character as reference image, (completely nude:1.2), naked, no clothes, breasts fully exposed, {胸部大小}, perfect anatomy, masterpiece, best quality', '11', NULL);
|
||||
INSERT INTO `ai_manager` VALUES (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', 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 {背景}, {服装描述} {胸部大小}, 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', 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, '视频换脸功能', '22', NULL);
|
||||
INSERT INTO `ai_manager` VALUES (1, '0', 'admin', '2025-11-13 20:05:27', 'admin', '2025-12-23 14:30:07', NULL, '图生图1', 10.00, 0, '1girl, solo, exact same character as reference image, identical face eye color hairstyle accessories expression pose background lighting, (completely nude:1.2), naked, no clothes, breasts fully exposed, {胸部大小}, nipples perfectly matching skin tone, {动作}, pose that naturally conceals lower body from front view, legs positioned to avoid frontal genital exposure, side profile or back view emphasis, subtle natural body contours without explicit details, perfect anatomy, exactly two arms and two legs only, perfect hands with exactly 5 clearly separated fingers each no fusion no extra fingers, smooth natural skin texture, realistic proportions, masterpiece, best quality, ultra detailed, 8k, soft lighting, depth of field, high resolution, intricate details, cinematic composition, (if multiple characters: all characters following same rules no frontal exposure:1.1)(negative: clothes, bra, panties, underwear, bikini, swimsuit, any fabric even one pixel, censored, mosaic, bar censor, any censorship, pasties, nipple covers, frontal genital exposure, visible slit, visible pussy, exposed crotch, swollen labia, puffy labia, deformed genital area, dark mismatched crotch skin, any pubic details, extra arms, extra legs, extra hands, extra fingers, third arm, third leg, mutated limbs, more than two arms, more than two legs, fused fingers, deformed hands, bad hands, unnatural poses, violating human anatomy, loli, child, old, realistic photo, lowres:1.8, blurry, artifacts, overexposed, underexposed, pixelated, jpeg artifacts, watermark, text, signature, ugly, deformed, mutated, extra limbs, poorly drawn face, poorly drawn hands, missing limbs, floating limbs, disconnected limbs)', '11', '1,17');
|
||||
INSERT INTO `ai_manager` VALUES (2, '0', 'admin', '2025-11-21 22:24:28', 'admin', '2025-12-14 20:20:56', NULL, '图生图2', 10.00, 0, '生成{主题},{风格}包含{细节},分辨率{技术参数}的图像', '12', NULL);
|
||||
INSERT INTO `ai_manager` VALUES (3, '0', 'admin', '2025-11-13 20:06:02', 'admin', '2025-12-24 15:03:00', NULL, '一键换脸', 10.00, 1, '保持参考图1的内容风格不变,用参考图2的脸部对参考图1的脸部进行替换', '13', '');
|
||||
INSERT INTO `ai_manager` VALUES (4, '0', 'admin', '2025-11-13 20:07:33', 'admin', '2025-12-23 14:47:10', NULL, '快捷生图', 8.00, 0, '1girl, solo, detailed face with {发型} {眼睛颜色} {配饰} {表情} {姿势} in {背景}, {服装描述} {胸部大小} no pubic hair at all, no body hair anywhere from neck to toes, pose that naturally conceals lower body from front view, legs positioned to avoid frontal genital exposure, side profile or back view emphasis, subtle natural body contours without explicit details, perfect anatomy, exactly two arms and two legs only, perfect hands with exactly 5 clearly separated fingers each no fusion no extra fingers, smooth natural skin texture, realistic proportions, masterpiece, best quality, ultra detailed, 8k, soft lighting, depth of field, high resolution, intricate details, cinematic composition, (if multiple characters: {多人描述}, all characters following same rules no frontal exposure:1.1)(negative: everyday clothes, casual outfit, school uniform, regular dress, any non-specified clothing, clothing glitch, fabric clipping, pubic hair, body hair, happy trail, hair on abdomen, hair on stomach, hair on torso, hair around navel, any hair below neck except head hair, frontal genital exposure, visible slit, visible pussy, exposed crotch, swollen labia, puffy labia, deformed genital area, dark mismatched crotch skin, any pubic details, extra arms, extra legs, extra hands, extra fingers, third arm, third leg, mutated limbs, more than two arms, more than two legs, fused fingers, deformed hands, bad hands, unnatural poses, violating human anatomy, bad anatomy, loli, child, old, realistic photo, lowres:1.9, blurry, artifacts, overexposed, underexposed, pixelated, jpeg artifacts, watermark, text, signature, ugly, deformed, mutated, extra limbs, poorly drawn face, poorly drawn hands, missing limbs, floating limbs, disconnected limbs, clothes if nude mode, bra if nude mode, panties if nude mode, underwear if nude mode, bikini if nude mode, swimsuit if nude mode, any fabric even one pixel if nude mode, censored if nude mode, mosaic if nude mode, bar censor if nude mode, any censorship if nude mode, pasties if nude mode, nipple covers if nude mode)\n', '1', '8,5,11,64,66,68,70,72,74');
|
||||
INSERT INTO `ai_manager` VALUES (5, '0', 'admin', '2025-11-13 20:07:47', 'admin', '2026-01-08 14:58:37', '', '快捷生视频', 35.00, 0, '跳舞', '21', '');
|
||||
INSERT INTO `ai_manager` VALUES (7, '0', 'admin', '2025-11-25 19:41:23', 'admin', '2025-11-25 19:41:23', NULL, '视频换脸', 35.00, 1, NULL, '22', NULL);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ai_order
|
||||
|
|
@ -1496,15 +1494,7 @@ CREATE TABLE `ai_order` (
|
|||
`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 '首帧图片',
|
||||
`img2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '第二张图片',
|
||||
`mode` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成模式:text-to-video 或 image-to-video',
|
||||
`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
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1310 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'API订单记录' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
|
|
@ -2802,7 +2792,6 @@ CREATE TABLE `ai_pay_setting` (
|
|||
DROP TABLE IF EXISTS `ai_rebate_record`;
|
||||
CREATE TABLE `ai_rebate_record` (
|
||||
`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代表删除)',
|
||||
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
|
|
@ -2812,8 +2801,7 @@ CREATE TABLE `ai_rebate_record` (
|
|||
`superior_id` bigint NULL DEFAULT NULL COMMENT '上级ID',
|
||||
`subordinate_id` bigint NULL DEFAULT NULL COMMENT '下级ID',
|
||||
`amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '返佣金额',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_order_no` (`order_no`) USING BTREE
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '返佣记录' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
|
|
@ -2878,7 +2866,7 @@ CREATE TABLE `ai_recharge` (
|
|||
`gift_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '到账金额',
|
||||
`give_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '赠送金额',
|
||||
`pay_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付方式',
|
||||
`pay_url` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付链接',
|
||||
`pay_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付链接',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 118 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '充值记录' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
|
|
@ -3115,7 +3103,6 @@ INSERT INTO `ai_sample_amount` VALUES (2, '0', 'admin', '2025-11-14 22:37:01', '
|
|||
DROP TABLE IF EXISTS `ai_sample_amount_record`;
|
||||
CREATE TABLE `ai_sample_amount_record` (
|
||||
`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代表删除)',
|
||||
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
|
|
@ -3126,8 +3113,7 @@ CREATE TABLE `ai_sample_amount_record` (
|
|||
`sample_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '体验金额',
|
||||
`recycle_time` datetime NULL DEFAULT NULL COMMENT '回收时间',
|
||||
`status` tinyint NULL DEFAULT 0 COMMENT '回收状态:0-已发放 1-已回收',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_order_no` (`order_no`) USING BTREE
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '体验金领取记录' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
|
|
@ -3541,7 +3527,6 @@ CREATE TABLE `ai_user` (
|
|||
`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',
|
||||
`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,
|
||||
UNIQUE INDEX `uuid`(`user_id` ASC) USING BTREE,
|
||||
UNIQUE INDEX `invitation_code`(`invitation_code` ASC) USING BTREE,
|
||||
|
|
@ -3552,37 +3537,35 @@ CREATE TABLE `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', '美国', 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', '美国', 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', '美国', 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', '香港', 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', '新加坡', 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', '日本', 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', '美国', 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', '美国', 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', '美国', 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', '香港', 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', '香港', 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', '香港', 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', '香港', 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', '香港', 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', '印尼', 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', '日本', 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', '香港', 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', '香港', 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', '美国', 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', '新加坡', 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', '美国', 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', '英国', 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', '美国', 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', '美国', 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', '美国', 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', '美国', 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', '美国', 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', '美国', NULL);
|
||||
|
||||
-- 已有库升级:ALTER TABLE ai_user ADD COLUMN dept_id bigint NULL DEFAULT NULL COMMENT '归属部门(sys_dept.dept_id)' AFTER country;
|
||||
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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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', '美国');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ai_user_message
|
||||
|
|
@ -4195,7 +4178,6 @@ CREATE TABLE `sys_dept` (
|
|||
`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 '联系电话',
|
||||
`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停用)',
|
||||
`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 '创建者',
|
||||
|
|
@ -4208,23 +4190,21 @@ CREATE TABLE `sys_dept` (
|
|||
-- ----------------------------
|
||||
-- Records of sys_dept
|
||||
-- ----------------------------
|
||||
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', 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', 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', 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', 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', 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', 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', 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', 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', NULL, '0', '0', 'admin', '2025-11-09 01:56:49', '', 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, 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, 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, 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, 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;
|
||||
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 (101, 100, '0,100', '深圳总公司', 1, '若依', '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', '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 (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 (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 (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 (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 (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 (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 (200, 100, '0,100', '胸部', 3, 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 (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 (203, 200, '0,100,200', '小奶', 2, 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);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_dict_data
|
||||
|
|
@ -4692,11 +4672,6 @@ 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 (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 (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 (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, '');
|
||||
|
|
@ -5838,36 +5813,4 @@ INSERT INTO `user_message` VALUES (2, 2);
|
|||
INSERT INTO `user_message` VALUES (2, 3);
|
||||
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;
|
||||
|
|
|
|||
BIN
web-api/接口文档.pdf
BIN
web-api/接口文档.pdf
Binary file not shown.
Loading…
Reference in New Issue