wyj0522 2 hari lalu
induk
melakukan
4c102e2ee5

+ 1 - 1
eco-common/common-core/src/main/java/org/eco/common/core/utils/file/MimeTypeUtils.java

@@ -31,7 +31,7 @@ public class MimeTypeUtils {
         // word excel powerpoint
         "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
         // 压缩文件
-        "rar", "zip", "gz", "bz2",
+        "rar", "zip", "gz", "bz2", "wav",
         // 视频格式
         "mp4", "avi", "rmvb",
         // pdf

+ 35 - 0
eco-modules/system/src/main/java/org/eco/system/controller/video/TaskController.java

@@ -0,0 +1,35 @@
+package org.eco.system.controller.video;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaCheckRole;
+import jakarta.annotation.Resource;
+import lombok.RequiredArgsConstructor;
+import org.eco.common.core.constant.TenantConstants;
+import org.eco.common.core.core.domain.CommonResult;
+import org.eco.common.web.core.BaseController;
+import org.eco.system.domain.po.VideoPo;
+import org.eco.system.service.ITask;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/video/VideoTask")
+public class TaskController extends BaseController {
+
+    @Resource
+    private ITask iTaskV;
+
+
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenantPackage:list")
+    @GetMapping("/list")
+    public CommonResult<List<VideoPo>> list(VideoPo po) {
+        return CommonResult.success(iTaskV.selectListByUser(po));
+    }
+}

+ 41 - 0
eco-modules/system/src/main/java/org/eco/system/controller/video/VideofileController.java

@@ -0,0 +1,41 @@
+package org.eco.system.controller.video;
+
+import cn.dev33.satoken.annotation.SaCheckRole;
+import jakarta.annotation.Resource;
+import lombok.RequiredArgsConstructor;
+import org.eco.common.core.constant.TenantConstants;
+import org.eco.common.log.annotation.Log;
+import org.eco.common.log.enums.BusinessType;
+import org.eco.common.web.annotation.RepeatSubmit;
+import org.eco.common.web.core.BaseController;
+import org.eco.system.domain.po.VideoPo;
+import org.eco.system.domain.vo.VideoVO;
+import org.eco.system.service.IVideofile;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.IOException;
+
+
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/video/Videofile")
+public class VideofileController extends BaseController {
+
+
+    @Resource
+    private IVideofile videofileService;
+
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @Log(title = "创建数字人视频生成任务", businessType = BusinessType.INSERT)
+    @RepeatSubmit
+    @PostMapping("/createVideo")
+    public VideoPo createVideo(@RequestBody VideoVO vo) throws IOException {
+        VideoPo video = videofileService.createVideo(vo);
+        return video;
+    }
+}

+ 31 - 0
eco-modules/system/src/main/java/org/eco/system/domain/BasePO.java

@@ -0,0 +1,31 @@
+package org.eco.system.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+public class BasePO implements Serializable {
+    /**
+     * 创建者
+     */
+    private String createBy;
+
+    /**
+     * 创建者
+     */
+    private String updateBy;
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /**
+     * 更新时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+}

+ 35 - 0
eco-modules/system/src/main/java/org/eco/system/domain/po/VideoPo.java

@@ -0,0 +1,35 @@
+package org.eco.system.domain.po;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.Table;
+import lombok.Data;
+import org.eco.common.orm.core.domain.BaseEntity;
+import org.eco.system.domain.BasePO;
+
+import java.util.Date;
+
+@Data
+@Table(value = "video_generation_task")
+public class VideoPo extends BasePO {
+
+    @Id
+    private String id;          // 任务ID
+
+    private String userId;      // 创建任务的用户ID
+
+    private String taskType;    // 生成任务类型(text/record/audio)
+
+    private String videoName;   // 视频名称
+
+    private String textContent; // 文本内容
+
+    private String errorMsg;      // 音色
+
+    private String status;      // 任务状态(pending/processing/success/failed)
+
+    private String videoUrl;    // 视频播放链接
+
+    private Date createTime;    // 创建时间
+
+    private Date updateTime;    // 更新时间
+}

+ 40 - 0
eco-modules/system/src/main/java/org/eco/system/domain/vo/VideoVO.java

@@ -0,0 +1,40 @@
+package org.eco.system.domain.vo;
+
+import com.mybatisflex.core.BaseMapper;
+import lombok.Data;
+import org.eco.common.orm.core.domain.BaseEntity;
+import org.eco.system.domain.BasePO;
+import org.eco.system.mapper.VideoMapper;
+import org.w3c.dom.Text;
+
+import java.io.File;
+
+@Data
+public class VideoVO extends BasePO {
+    /**
+     * 生成任务类型
+     */
+    private String current;
+    /**
+     * 生成任务名称
+     */
+    private String videoName;
+
+    /**
+     * 任务形象文件
+     */
+    private String imagefile;
+    /**
+     * 音频文件
+     */
+    private String audioFile;
+    /**
+     * 文本内容
+     */
+    private String txtCount;
+    /**
+     * 音色
+     */
+    private String timbre;
+
+}

+ 9 - 0
eco-modules/system/src/main/java/org/eco/system/mapper/TaskMapper.java

@@ -0,0 +1,9 @@
+package org.eco.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.eco.system.domain.po.VideoPo;
+
+@Mapper
+public interface TaskMapper extends BaseMapper<VideoPo> {
+}

+ 9 - 0
eco-modules/system/src/main/java/org/eco/system/mapper/VideoMapper.java

@@ -0,0 +1,9 @@
+package org.eco.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.eco.system.domain.vo.VideoVO;
+
+@Mapper
+public interface VideoMapper  extends BaseMapper<VideoVO> {
+}

+ 12 - 0
eco-modules/system/src/main/java/org/eco/system/service/ICommonService.java

@@ -0,0 +1,12 @@
+package org.eco.system.service;
+
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Map;
+/**
+ * 上传下载处理
+ *
+ */
+public interface ICommonService {
+    Map<String, String> uploadFile(MultipartFile file);
+}

+ 12 - 0
eco-modules/system/src/main/java/org/eco/system/service/ITask.java

@@ -0,0 +1,12 @@
+package org.eco.system.service;
+
+import org.eco.common.orm.core.service.IBaseService;
+import org.eco.system.domain.po.VideoPo;
+
+import java.util.List;
+
+public interface ITask extends IBaseService<VideoPo> {
+
+
+    List<VideoPo> selectListByUser(VideoPo po);
+}

+ 16 - 0
eco-modules/system/src/main/java/org/eco/system/service/IVideofile.java

@@ -0,0 +1,16 @@
+package org.eco.system.service;
+
+import org.eco.common.orm.core.service.IBaseService;
+import org.eco.system.domain.po.VideoPo;
+import org.eco.system.domain.vo.VideoVO;
+
+import java.io.IOException;
+
+/**
+ * 属于自认视频生成接口
+ */
+public interface IVideofile extends IBaseService<VideoVO> {
+
+
+    VideoPo createVideo(VideoVO vo) throws IOException;
+}

+ 75 - 0
eco-modules/system/src/main/java/org/eco/system/service/impl/CommonService.java

@@ -0,0 +1,75 @@
+package org.eco.system.service.impl;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eco.common.core.config.EcoConfig;
+import org.eco.common.core.constant.Constants;
+import org.eco.common.core.exception.BusinessException;
+import org.eco.common.core.utils.StringUtils;
+import org.eco.common.core.utils.file.FileUploadUtils;
+import org.eco.system.service.ICommonService;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class CommonService implements ICommonService {
+
+    @Value("${server.port:8080}")
+    private Long port;
+
+    @Override
+    public Map<String, String> uploadFile(MultipartFile file) {
+        try {
+
+            // 上传文件路径
+            String filePath = EcoConfig.getUploadPath();
+            // 上传并返回新文件名称
+            String fileName = FileUploadUtils.upload(filePath, file);
+            String url = Constants.HTTP + getIpAddress() + ":" + port + fileName;
+            String originalFilename = file.getOriginalFilename();
+            assert originalFilename != null;
+            String suffix = StringUtils.substring(originalFilename, originalFilename.lastIndexOf("."), originalFilename.length());
+            Map<String, String> map = new HashMap<>();
+            map.put("url", url);
+            map.put("originalFilename", originalFilename);
+            map.put("suffix", suffix);
+            map.put("fileName", fileName);
+            return map;
+        } catch (Exception e) {
+            throw new BusinessException(e.getMessage());
+        }
+    }
+
+    public static String getIpAddress() {
+        try {
+            //从网卡中获取IP
+            Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
+            InetAddress ip;
+            while (allNetInterfaces.hasMoreElements()) {
+                NetworkInterface netInterface = allNetInterfaces.nextElement();
+                //用于排除回送接口,非虚拟网卡,未在使用中的网络接口
+                if (!netInterface.isLoopback() && !netInterface.isVirtual() && netInterface.isUp()) {
+                    //返回和网络接口绑定的所有IP地址
+                    Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
+                    while (addresses.hasMoreElements()) {
+                        ip = addresses.nextElement();
+                        if (ip instanceof Inet4Address) {
+                            return ip.getHostAddress();
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.info("IP地址获取失败{}", e.getMessage());
+        }
+        return "127.0.0.1";
+    }
+}

+ 23 - 0
eco-modules/system/src/main/java/org/eco/system/service/impl/ITaskImpl.java

@@ -0,0 +1,23 @@
+package org.eco.system.service.impl;
+
+import com.mybatisflex.core.query.QueryWrapper;
+import org.eco.common.orm.core.service.impl.BaseServiceImpl;
+import org.eco.common.security.utils.LoginHelper;
+import org.eco.system.domain.po.VideoPo;
+import org.eco.system.mapper.TaskMapper;
+import org.eco.system.service.ITask;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+import static org.eco.system.domain.po.table.VideoPoTableDef.VIDEO_PO;
+
+
+@Service
+public class ITaskImpl extends BaseServiceImpl<TaskMapper, VideoPo> implements ITask {
+    @Override
+    public List<VideoPo> selectListByUser(VideoPo po) {
+        QueryWrapper wrapper = QueryWrapper.create().and(VIDEO_PO.USER_ID.eq(LoginHelper.getUserId())).orderBy(VIDEO_PO.CREATE_TIME,false);
+        return  this.listAs(wrapper,VideoPo.class);
+    }
+}

+ 13 - 2
eco-modules/system/src/main/java/org/eco/system/service/impl/SysOssServiceImpl.java

@@ -7,6 +7,7 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import com.mybatisflex.core.paginate.Page;
 import com.mybatisflex.core.query.QueryWrapper;
+import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -32,6 +33,7 @@ import org.eco.system.domain.SysOss;
 import org.eco.system.domain.bo.SysOssBo;
 import org.eco.system.domain.vo.SysOssVo;
 import org.eco.system.mapper.SysOssMapper;
+import org.eco.system.service.ICommonService;
 import org.eco.system.service.ISysOssService;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.http.MediaType;
@@ -44,6 +46,7 @@ import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 import static org.eco.system.domain.table.SysOssTableDef.SYS_OSS;
 
@@ -59,6 +62,8 @@ import static org.eco.system.domain.table.SysOssTableDef.SYS_OSS;
 public class SysOssServiceImpl extends BaseServiceImpl<SysOssMapper, SysOss> implements ISysOssService, OssService {
 
     private final SysOssMapper ossMapper;
+    @Resource
+    private ICommonService commonService;
 
 
     @Override
@@ -153,8 +158,14 @@ public class SysOssServiceImpl extends BaseServiceImpl<SysOssMapper, SysOss> imp
         OssClient storage = OssFactory.instance();
         UploadResult uploadResult;
         try {
-            uploadResult = storage.uploadSuffix(file.getBytes(), suffix);
-        } catch (IOException e) {
+            if (StrUtil.equals(storage.getConfigKey(), "local")) {
+                Map<String, String> map = commonService.uploadFile(file);
+                uploadResult = UploadResult.builder().url(map.get("url")).filename(map.get("fileName")).build();
+            } else {
+                uploadResult = storage.uploadSuffix(file.getBytes(), suffix);
+            }
+        }
+        catch (IOException e) {
             throw new BusinessException(e.getMessage());
         }
         // 保存文件信息

+ 198 - 0
eco-modules/system/src/main/java/org/eco/system/service/impl/VideofileServiceImpl.java

@@ -0,0 +1,198 @@
+package org.eco.system.service.impl;
+
+import com.alibaba.fastjson2.JSON;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.annotation.Resource;
+import org.eco.common.orm.core.service.impl.BaseServiceImpl;
+import org.eco.common.security.utils.LoginHelper;
+import org.eco.common.websocket.utils.WebSocketUtils;
+import org.eco.system.domain.po.VideoPo;
+import org.eco.system.domain.vo.VideoVO;
+import org.eco.system.mapper.TaskMapper;
+import org.eco.system.mapper.VideoMapper;
+import org.eco.system.service.IVideofile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class VideofileServiceImpl extends BaseServiceImpl<VideoMapper, VideoVO> implements IVideofile, org.springframework.beans.factory.DisposableBean {
+
+    private static final Logger logger = LoggerFactory.getLogger(VideofileServiceImpl.class);
+
+    private final ExecutorService executorService;
+
+    @Value("${model.httpurl}")
+    private String modelPath;
+
+    @Value("${video.thread.pool.size:5}")
+    private int threadPoolSize;
+
+    @Resource
+    private TaskMapper taskMapper;
+
+
+    public VideofileServiceImpl(@Value("${video.thread.pool.size:5}") int threadPoolSize) {
+        this.executorService = Executors.newFixedThreadPool(threadPoolSize);
+    }
+
+    /**
+     * 创建视频生成任务
+     * @param vo
+     */
+    @Override
+    public VideoPo createVideo(VideoVO vo) throws IOException {
+        VideoPo po = new VideoPo();
+        po.setUserId(LoginHelper.getUserId().toString());
+        po.setVideoName(vo.getVideoName());
+        po.setStatus("pending");
+        int insert = taskMapper.insert(po);
+
+        // 准备API请求参数
+        Map<String, Object> httpMap = prepareHttpParams(vo);
+        String jsonRequest = JSON.toJSONString(httpMap);
+        executorService.submit(() -> processVideoGeneration(po, jsonRequest));
+        return po;
+    }
+
+    private Map<String, Object> prepareHttpParams(VideoVO vo) {
+        Map<String, Object> httpMap = new HashMap<>();
+        if("text".equals(vo.getCurrent())){
+            httpMap.put("persona_template", vo.getImagefile());
+            httpMap.put("audio_text", vo.getTxtCount());
+            httpMap.put("voice_type", vo.getTimbre());
+        } else {
+            httpMap.put("persona_template", vo.getImagefile());
+            httpMap.put("persona_audio", vo.getAudioFile());
+        }
+        return httpMap;
+    }
+
+    @Transactional
+    protected void processVideoGeneration(VideoPo po, String jsonRequest) {
+        try {
+            Map<String, Object> responseMap = sendPostRequest(modelPath, jsonRequest);
+
+            if (responseMap != null && responseMap.containsKey("url")) {
+                po.setVideoUrl(responseMap.get("url").toString());
+                po.setStatus("completed");
+
+                // 延迟15秒后执行数据库更新
+                CompletableFuture.runAsync(() -> {
+                    try {
+                        Thread.sleep(15000); // 15秒延时
+
+                        // 使用新的事务执行更新操作
+                        updateVideoStatusInNewTransaction(po);
+
+                        // 通过WebSocket发送通知
+                        WebSocketUtils.sendMessage(Long.valueOf(po.getId()), "视频生成完成");
+
+                        logger.info("视频生成任务完成,ID: {}", po.getId());
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                        logger.error("延时被中断", e);
+                        handleFailedTask(po, "系统异常: 任务延时被中断");
+                    } catch (Exception e) {
+                        logger.error("视频生成任务处理失败", e);
+                        handleFailedTask(po, "系统异常: " + e.getMessage());
+                    }
+                });
+            } else {
+                handleFailedTask(po, "API响应格式异常");
+            }
+        } catch (Exception e) {
+            logger.error("视频生成任务处理失败", e);
+            handleFailedTask(po, "系统异常: " + e.getMessage());
+        }
+    }
+    // 使用@Transactional注解创建新事务执行更新
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    protected void updateVideoStatusInNewTransaction(VideoPo po) {
+        taskMapper.update(po);
+    }
+
+    private void handleFailedTask(VideoPo po, String errorMsg) {
+        po.setStatus("failed");
+        po.setErrorMsg(errorMsg);
+        taskMapper.update(po);
+    }
+
+    /**
+     * 发送POST请求
+     */
+    public static Map<String, Object> sendPostRequest(String apiUrl, String jsonBody) throws IOException {
+        URL url = new URL(apiUrl);
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.setRequestMethod("POST");
+        connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
+        connection.setDoOutput(true);
+
+        byte[] requestBody = jsonBody.getBytes(StandardCharsets.UTF_8);
+        connection.setRequestProperty("Content-Length", String.valueOf(requestBody.length));
+
+        try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
+            outputStream.write(requestBody);
+            outputStream.flush();
+        }
+
+        int responseCode = connection.getResponseCode();
+        StringBuilder response = new StringBuilder();
+        try (BufferedReader reader = new BufferedReader(
+            new InputStreamReader(
+                responseCode >= 200 && responseCode < 300 ?
+                    connection.getInputStream() : connection.getErrorStream(),
+                StandardCharsets.UTF_8
+            )
+        )) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                response.append(line);
+            }
+        }
+
+        // 记录完整响应信息
+        logger.info("API 请求响应: 状态码={}, 响应数据={}", responseCode, response);
+
+        if (responseCode >= 200 && responseCode < 300) {
+            ObjectMapper objectMapper = new ObjectMapper();
+            return objectMapper.readValue(response.toString(), Map.class);
+        } else {
+            logger.error("API请求失败,状态码: {}", responseCode);
+            throw new IOException("API请求失败,状态码: " + responseCode);
+        }
+    }
+
+    /**
+     * 优雅关闭线程池
+     */
+    @Override
+    public void destroy() throws Exception {
+        executorService.shutdown();
+        try {
+            if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
+                executorService.shutdownNow();
+            }
+        } catch (InterruptedException e) {
+            executorService.shutdownNow();
+        }
+        logger.info("视频处理线程池已关闭");
+    }
+}

+ 6 - 6
eco-start/pom.xml

@@ -18,12 +18,12 @@
 
     <dependencies>
 
-        <!-- spring-boot-devtools -->
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-devtools</artifactId>
-            <optional>true</optional> <!-- 表示依赖不会传递 -->
-        </dependency>
+<!--        &lt;!&ndash; spring-boot-devtools &ndash;&gt;-->
+<!--        <dependency>-->
+<!--            <groupId>org.springframework.boot</groupId>-->
+<!--            <artifactId>spring-boot-devtools</artifactId>-->
+<!--            <optional>true</optional> &lt;!&ndash; 表示依赖不会传递 &ndash;&gt;-->
+<!--        </dependency>-->
 
         <!-- Mysql驱动包 -->
         <dependency>

+ 6 - 1
eco-start/src/main/resources/application.yml

@@ -111,7 +111,7 @@ spring:
   devtools:
     restart:
       # 热部署开关
-      enabled: true
+      enabled: false
 
 # PageHelper分页插件
 pagehelper:
@@ -286,6 +286,8 @@ security:
     - /**/*.css
     - /**/*.js
     - /profile/**
+    - /resource/oss/upload
+    - /common/upload
     # 公共路径
     - /favicon.ico
     - /error
@@ -322,3 +324,6 @@ websocket:
   # 设置访问源地址
   allowedOrigins: '*'
 
+model:
+  httpurl: http://0.0.0.0:10001/test
+