diff --git a/.gitignore b/.gitignore index ed8368a..816ede8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ .gradle /build/ !gradle/wrapper/gradle-wrapper.jar - +*.sql target/ !.mvn/wrapper/maven-wrapper.jar diff --git a/ruoyi-admin/src/main/java/com/ruoyi/api/AiManagerApiController.java b/ruoyi-admin/src/main/java/com/ruoyi/api/AiManagerApiController.java index f698f9f..481372e 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/api/AiManagerApiController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/api/AiManagerApiController.java @@ -51,6 +51,9 @@ 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); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/api/PortalVideoController.java b/ruoyi-admin/src/main/java/com/ruoyi/api/PortalVideoController.java index 282bd1f..a5d26fd 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/api/PortalVideoController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/api/PortalVideoController.java @@ -60,6 +60,17 @@ public class PortalVideoController extends BaseController { 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()); @@ -97,7 +108,7 @@ public class PortalVideoController extends BaseController { /** * 写入订单:提示词、生成模式、图床 URL,以及模型/时长/分辨率/比例与完整 JSON 参数(便于对账与审计)。 */ - private void fillVideoOrderRecord(AiOrder aiOrder, PortalVideoGenRequest req, String mode, ByteBodyReq body) { + private void fillVideoOrderRecord(AiOrder aiOrder, PortalVideoGenRequest req, String mode, ByteBodyReq body, String functionTypeResolved) { aiOrder.setText(req.getText()); aiOrder.setMode(mode); applyOrderImages(aiOrder, req); @@ -119,7 +130,7 @@ public class PortalVideoController extends BaseController { Map snap = new LinkedHashMap<>(); snap.put("generationMode", mode); snap.put("prompt", req.getText()); - snap.put("functionType", StringUtils.isNotEmpty(req.getFunctionType()) ? req.getFunctionType() : "21"); + snap.put("functionType", functionTypeResolved); snap.put("model", body.getModel()); snap.put("duration", body.getDuration()); snap.put("resolution", body.getResolution()); @@ -143,13 +154,13 @@ public class PortalVideoController extends BaseController { } private AjaxResult submitOrderAndCreate(PortalVideoGenRequest req, String mode, ByteBodyReq byteBodyReq) { - AiOrder aiOrder = aiOrderService.getAiOrder( - StringUtils.isNotEmpty(req.getFunctionType()) ? req.getFunctionType() : "21"); + 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); + fillVideoOrderRecord(aiOrder, req, mode, byteBodyReq, functionType); aiOrderService.updateAiOrder(aiOrder); String key = apiKey(); diff --git a/ruoyi-admin/src/main/java/com/ruoyi/config/PortalVideoProperties.java b/ruoyi-admin/src/main/java/com/ruoyi/config/PortalVideoProperties.java index b0ea19c..bccebdd 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/config/PortalVideoProperties.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/config/PortalVideoProperties.java @@ -23,6 +23,19 @@ public class PortalVideoProperties { private List 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; } diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 0d88acd..5ca772c 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -238,6 +238,8 @@ volcengine: # 门户视频生成页:模型 / 比例 / 时长 / 分辨率均由此处维护,前后端不写死业务枚举 portal: video: + # 与库表 ai_manager.type 一致(用于扣费);若报错 functionType does not exist,请插入对应 type 或改此处与库一致 + function-type: "21" defaults: model: ep-20260326165811-dlkth duration: 4 diff --git a/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/AiManagerServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/AiManagerServiceImpl.java index 349124f..287b429 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/AiManagerServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/AiManagerServiceImpl.java @@ -124,6 +124,8 @@ public class AiManagerServiceImpl implements IAiManagerService { public AiManager selectAiManagerByType(String aiType) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("type", aiType); + queryWrapper.eq("del_flag", "0"); + queryWrapper.eq("status", 0); return aiManagerMapper.selectOne(queryWrapper); } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/AiOrderServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/AiOrderServiceImpl.java index 9074639..5db17f2 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/AiOrderServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/AiOrderServiceImpl.java @@ -147,7 +147,9 @@ public class AiOrderServiceImpl implements IAiOrderService { public AiOrder getAiOrder(String aiType) { AiManager aiManager = aiManagerService.selectAiManagerByType(aiType); if (aiManager == null) { - throw new ServiceException("Corresponding functionType does not exist", HttpStatus.BAD_REQUEST); + throw new ServiceException( + "未找到可用的功能类型:请在「AI管理」中新增 type=" + aiType + " 且状态为正常的记录,或执行 sql/seed_ai_manager_type_21.sql 初始化", + HttpStatus.BAD_REQUEST); } // 判断用户余额是否足够 AiUser aiUser = aiUserService.selectAiUserById(SecurityUtils.getAiUserId()); diff --git a/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/ByteDeptApiKeyServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/ByteDeptApiKeyServiceImpl.java index b75457d..5c03a99 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/ByteDeptApiKeyServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/ByteDeptApiKeyServiceImpl.java @@ -16,7 +16,9 @@ import java.util.concurrent.TimeUnit; @Service public class ByteDeptApiKeyServiceImpl implements IByteDeptApiKeyService { - private static final String NO_PERM_MSG = "用户没有权限,请联系管理员分配部门"; + 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 @@ -31,7 +33,7 @@ public class ByteDeptApiKeyServiceImpl implements IByteDeptApiKeyService { @Override public String resolveVolcApiKey(Long aiUserId) { if (aiUserId == null) { - throw new ServiceException(NO_PERM_MSG); + throw new ServiceException(NO_DEPT_MSG); } String cacheKey = aiUserId + "_byte_api_key"; String cached = redisCache.getCacheObject(cacheKey); @@ -40,22 +42,34 @@ public class ByteDeptApiKeyServiceImpl implements IByteDeptApiKeyService { } AiUser aiUser = aiUserService.selectAiUserById(aiUserId); if (aiUser == null || aiUser.getDeptId() == null) { - throw new ServiceException(NO_PERM_MSG); + throw new ServiceException(NO_DEPT_MSG); } SysDept userDept = sysDeptService.selectDeptById(aiUser.getDeptId()); if (userDept == null) { - throw new ServiceException(NO_PERM_MSG); + throw new ServiceException(NO_DEPT_ROW_MSG); } - Long secondLevelDeptId = resolveSecondLevelDeptId(userDept); - SysDept keyDept = sysDeptService.selectDeptById(secondLevelDeptId); - if (keyDept == null || StringUtils.isEmpty(keyDept.getByteApiKey())) { - throw new ServiceException(NO_PERM_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); } - String apiKey = keyDept.getByteApiKey().trim(); redisCache.setCacheObject(cacheKey, apiKey, CACHE_HOURS, TimeUnit.HOURS); return apiKey; } + private static String trimKey(String raw) { + return raw == null ? null : raw.trim(); + } + /** * 二级部门:祖级路径中,紧接在「一级」(ancestors 第二段)之下的部门节点; * 深度不足时退回用户当前部门。