Jelajahi Sumber

file-service 添加视频转码功能, 添加一个 JobDetail model 表示任务详情

reghao 1 tahun lalu
induk
melakukan
a43a6cc4b7

+ 5 - 0
file/file-service/pom.xml

@@ -43,6 +43,11 @@
             <artifactId>file-api</artifactId>
             <version>1.0.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>cn.reghao.tnb.content</groupId>
+            <artifactId>content-api</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
 
         <dependency>
             <groupId>org.springframework.boot</groupId>

+ 1 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/config/SpringLifecycle.java

@@ -2,7 +2,7 @@ package cn.reghao.tnb.file.app.config;
 
 import cn.reghao.tnb.file.app.service.AliyunOss;
 import cn.reghao.tnb.file.app.service.AliyunService;
-import cn.reghao.tnb.file.app.mapper.StoreConfigMapper;
+import cn.reghao.tnb.file.app.db.mapper.StoreConfigMapper;
 import cn.reghao.tnb.file.app.model.constant.OssType;
 import cn.reghao.tnb.file.app.model.po.StoreConfig;
 import lombok.extern.slf4j.Slf4j;

+ 19 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/db/mapper/JobDetailMapper.java

@@ -0,0 +1,19 @@
+package cn.reghao.tnb.file.app.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.tnb.file.app.model.po.JobDetail;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author reghao
+ * @date 2024-11-25 09:11:20
+ */
+@Mapper
+public interface JobDetailMapper extends BaseMapper<JobDetail> {
+    void updateSetEnd(@Param("jobId") long jobId,
+                      @Param("status") String status,
+                      @Param("endAt") LocalDateTime endAt);
+}

+ 1 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/mapper/StoreConfigMapper.java → file/file-service/src/main/java/cn/reghao/tnb/file/app/db/mapper/StoreConfigMapper.java

@@ -1,4 +1,4 @@
-package cn.reghao.tnb.file.app.mapper;
+package cn.reghao.tnb.file.app.db.mapper;
 
 import cn.reghao.jutil.jdk.db.BaseMapper;
 import cn.reghao.tnb.file.app.model.po.StoreConfig;

+ 31 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/model/po/JobDetail.java

@@ -0,0 +1,31 @@
+package cn.reghao.tnb.file.app.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import cn.reghao.tnb.file.app.model.vo.ConvertJob;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author reghao
+ * @date 2024-11-25 09:11:07
+ */
+@NoArgsConstructor
+@Setter
+@Getter
+public class JobDetail extends BaseObject<Integer> {
+    private Long jobId;
+    private String jobName;
+    private String status;
+    private LocalDateTime startAt;
+    private LocalDateTime endAt;
+
+    public JobDetail(long jobId, ConvertJob convertJob) {
+        this.jobId = jobId;
+        this.jobName = convertJob.getJobName();
+        this.status = "running";
+        this.startAt = LocalDateTime.now();
+    }
+}

+ 28 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/model/vo/ConvertJob.java

@@ -0,0 +1,28 @@
+package cn.reghao.tnb.file.app.model.vo;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author reghao
+ * @date 2024-11-25 09:21:25
+ */
+@NoArgsConstructor
+@Getter
+public class ConvertJob {
+    private long jobId;
+    private String jobName;
+    private String videoFileId;
+    private int channelCode;
+    private String srcPath;
+    private String destPath;
+
+    public ConvertJob(long jobId, String videoFileId, int channelCode, String srcPath, String destPath) {
+        this.jobId = jobId;
+        this.jobName = "视频转码任务";
+        this.videoFileId = videoFileId;
+        this.channelCode = channelCode;
+        this.srcPath = srcPath;
+        this.destPath = destPath;
+    }
+}

+ 41 - 3
file/file-service/src/main/java/cn/reghao/tnb/file/app/rpc/AvatarServiceImpl.java → file/file-service/src/main/java/cn/reghao/tnb/file/app/rpc/FileServiceImpl.java

@@ -1,6 +1,9 @@
 package cn.reghao.tnb.file.app.rpc;
 
+import cn.reghao.file.api.dto.JobInfo;
 import cn.reghao.file.api.iface.FileService;
+import cn.reghao.jutil.jdk.db.PageList;
+import cn.reghao.jutil.jdk.result.Result;
 import cn.reghao.oss.sdk.model.dto.ServerInfo;
 import cn.reghao.oss.sdk.model.dto.media.ImageInfo;
 import cn.reghao.oss.sdk.model.rest.UploadFileRet;
@@ -8,6 +11,9 @@ import cn.reghao.oss.sdk.OssConsoleClient;
 import cn.reghao.oss.sdk.OssStoreClient;
 import cn.reghao.tnb.file.app.config.OssConsoleClientFactory;
 import cn.reghao.tnb.file.app.config.OssProperties;
+import cn.reghao.tnb.file.app.db.mapper.JobDetailMapper;
+import cn.reghao.tnb.file.app.model.po.JobDetail;
+import cn.reghao.tnb.file.app.service.ConvertService;
 import cn.reghao.tnb.file.app.util.ImageUtil;
 import cn.reghao.tnb.file.app.model.constant.OssType;
 import org.apache.commons.io.FileUtils;
@@ -16,6 +22,9 @@ import org.springframework.stereotype.Service;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * @author reghao
@@ -23,14 +32,18 @@ import java.io.File;
  */
 @DubboService
 @Service
-@Deprecated
-public class AvatarServiceImpl implements FileService {
+public class FileServiceImpl implements FileService {
     private final OssConsoleClientFactory ossConsoleClientFactory;
     private final int ossType;
+    private final ConvertService convertService;
+    private JobDetailMapper jobDetailMapper;
 
-    private AvatarServiceImpl(OssProperties ossProperties, OssConsoleClientFactory ossConsoleClientFactory) {
+    private FileServiceImpl(OssProperties ossProperties, OssConsoleClientFactory ossConsoleClientFactory,
+                            ConvertService convertService, JobDetailMapper jobDetailMapper) {
         this.ossConsoleClientFactory = ossConsoleClientFactory;
         this.ossType = ossProperties.getOssType();
+        this.convertService = convertService;
+        this.jobDetailMapper = jobDetailMapper;
     }
 
     public String getAccountAvatar(long userId) {
@@ -78,4 +91,29 @@ public class AvatarServiceImpl implements FileService {
 
         return null;
     }
+
+    @Override
+    public Result convertVideo(String videoFileId, int channelCode) {
+        try {
+            convertService.convert(videoFileId, channelCode);
+            return Result.success();
+        } catch (Exception e) {
+            return Result.fail(e.getMessage());
+        }
+    }
+
+    @Override
+    public PageList<JobInfo> getConvertJobs(int pn, int ps) {
+        List<JobDetail> list = jobDetailMapper.findAll();
+        List<JobInfo> jobList = list.stream().map(jobDetail -> {
+            long jobId = jobDetail.getJobId();
+            String jobName = jobDetail.getJobName();
+            String status = jobDetail.getStatus();
+            LocalDateTime startAt = jobDetail.getStartAt();
+            LocalDateTime endAt = jobDetail.getEndAt();
+            return new JobInfo(jobId, jobName, status, startAt, endAt);
+        }).collect(Collectors.toList());
+
+        return PageList.pageList(pn, ps, jobList.size(), jobList);
+    }
 }

+ 1 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/rpc/StoreConfigServiceImpl.java

@@ -2,7 +2,7 @@ package cn.reghao.tnb.file.app.rpc;
 
 import cn.reghao.file.api.dto.StoreConfigInfo;
 import cn.reghao.file.api.iface.StoreConfigService;
-import cn.reghao.tnb.file.app.mapper.StoreConfigMapper;
+import cn.reghao.tnb.file.app.db.mapper.StoreConfigMapper;
 import cn.reghao.tnb.file.app.model.constant.OssType;
 import cn.reghao.tnb.file.app.model.po.StoreConfig;
 import org.apache.dubbo.config.annotation.DubboService;

+ 71 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/service/ConvertService.java

@@ -0,0 +1,71 @@
+package cn.reghao.tnb.file.app.service;
+
+import cn.reghao.jutil.jdk.thread.ThreadPoolWrapper;
+import cn.reghao.jutil.tool.id.SnowFlake;
+import cn.reghao.oss.sdk.OssConsoleClient;
+import cn.reghao.oss.sdk.model.dto.ObjectInfo;
+import cn.reghao.tnb.content.api.iface.AdminVideoService;
+import cn.reghao.tnb.file.app.config.OssConsoleClientFactory;
+import cn.reghao.tnb.file.app.db.mapper.JobDetailMapper;
+import cn.reghao.tnb.file.app.model.po.JobDetail;
+import cn.reghao.tnb.file.app.model.vo.ConvertJob;
+import cn.reghao.tnb.file.app.task.ConvertTask;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+/**
+ * @author reghao
+ * @date 2024-11-25 08:52:52
+ */
+@Service
+public class ConvertService {
+    @DubboReference(check = false, timeout = 60_000)
+    private AdminVideoService adminVideoService;
+    private final ExecutorService threadPool = ThreadPoolWrapper.threadPool("convert-pool", 20);
+    private final OssConsoleClientFactory ossConsoleClientFactory;
+    private final JobDetailMapper jobDetailMapper;
+    private final String baseDir;
+    private final Map<Long, Future<?>> futureMap = new HashMap<>();
+    private final SnowFlake idGenerator;
+
+    public ConvertService(OssConsoleClientFactory ossConsoleClientFactory, JobDetailMapper jobDetailMapper,
+                          ServerProperties serverProperties) {
+        this.ossConsoleClientFactory = ossConsoleClientFactory;
+        this.jobDetailMapper = jobDetailMapper;
+        this.baseDir = serverProperties.getTomcat().getBasedir().getAbsolutePath();
+        this.idGenerator = new SnowFlake(1, 1);
+    }
+
+    public void convert(String videoFileId, int channelCode) throws Exception {
+        OssConsoleClient ossConsoleClient = ossConsoleClientFactory.getOssConsoleClient();
+        ObjectInfo objectInfo = ossConsoleClient.getObjectInfo(channelCode, videoFileId);
+        if (objectInfo == null) {
+            throw new Exception("not found ObjectInfo by videoFileId " + videoFileId);
+        }
+
+        String objectName = objectInfo.getObjectName();
+        String localPath = ossConsoleClient.getObject(objectName, channelCode, baseDir);
+        String destPath = String.format("%s/%s", baseDir, UUID.randomUUID());
+
+        long jobId = idGenerator.nextId();
+        ConvertJob convertJob = new ConvertJob(jobId, videoFileId, channelCode, localPath, destPath);
+        JobDetail jobDetail = new JobDetail(jobId, convertJob);
+        jobDetailMapper.save(jobDetail);
+        Future<?> future = threadPool.submit(new ConvertTask(convertJob, jobDetailMapper, ossConsoleClient, adminVideoService));
+        futureMap.put(jobId, future);
+    }
+
+    public void cancel(long jobId) {
+        Future<?> future = futureMap.get(jobId);
+        if (future != null && !future.isCancelled()) {
+            future.cancel(true);
+        }
+    }
+}

+ 1 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/service/StoreConfigService.java

@@ -1,6 +1,6 @@
 package cn.reghao.tnb.file.app.service;
 
-import cn.reghao.tnb.file.app.mapper.StoreConfigMapper;
+import cn.reghao.tnb.file.app.db.mapper.StoreConfigMapper;
 import cn.reghao.tnb.file.app.model.po.StoreConfig;
 import org.springframework.stereotype.Service;
 

+ 65 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/task/ConvertTask.java

@@ -0,0 +1,65 @@
+package cn.reghao.tnb.file.app.task;
+
+import cn.reghao.jutil.media.FFmpegWrapper;
+import cn.reghao.oss.sdk.OssConsoleClient;
+import cn.reghao.oss.sdk.model.rest.UploadFileRet;
+import cn.reghao.tnb.content.api.dto.VideoConvertedDto;
+import cn.reghao.tnb.content.api.iface.AdminVideoService;
+import cn.reghao.tnb.file.app.db.mapper.JobDetailMapper;
+import cn.reghao.tnb.file.app.model.vo.ConvertJob;
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.time.LocalDateTime;
+
+/**
+ * @author reghao
+ * @date 2024-11-25 09:01:04
+ */
+public class ConvertTask implements Runnable {
+    private final ConvertJob convertJob;
+    private final JobDetailMapper jobDetailMapper;
+    private final OssConsoleClient ossConsoleClient;
+    private final AdminVideoService adminVideoService;
+
+    public ConvertTask(ConvertJob convertJob, JobDetailMapper jobDetailMapper,
+                       OssConsoleClient ossConsoleClient, AdminVideoService adminVideoService) {
+        this.convertJob = convertJob;
+        this.jobDetailMapper = jobDetailMapper;
+        this.ossConsoleClient = ossConsoleClient;
+        this.adminVideoService = adminVideoService;
+    }
+
+    @Override
+    public void run() {
+        String videoFileId = convertJob.getVideoFileId();
+        String srcPath = convertJob.getSrcPath();
+        String destPath = convertJob.getDestPath();
+        String format = "mp4";
+        try {
+            String status = "terminated";
+            int ret = FFmpegWrapper.formatCovert(srcPath, destPath, format);
+            if (ret == 0) {
+                int channelCode = convertJob.getChannelCode();
+                File destFile = new File(destPath);
+                UploadFileRet uploadFileRet = ossConsoleClient.postObject(destFile, channelCode);
+                if (uploadFileRet != null) {
+                    String uploadId = uploadFileRet.getUploadId();
+                    VideoConvertedDto videoConvertedDto = new VideoConvertedDto(videoFileId, channelCode, uploadId, format);
+                    adminVideoService.addConvertedVideo(videoConvertedDto);
+                }
+            } else {
+                status = "failed";
+            }
+
+            FileUtils.deleteQuietly(new File(srcPath));
+            FileUtils.deleteQuietly(new File(destPath));
+
+            long jobId = convertJob.getJobId();
+            LocalDateTime current = LocalDateTime.now();
+            jobDetailMapper.updateSetEnd(jobId, status, current);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 1 - 0
file/file-service/src/main/resources/application.yml

@@ -9,6 +9,7 @@ dubbo:
 server:
   port: 6004
   tomcat:
+    basedir: /opt/tmp/tomcat
     max-http-form-post-size: 4MB
 spring:
   servlet:

+ 30 - 0
file/file-service/src/main/resources/mapper/JobDetailMapper.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="cn.reghao.tnb.file.app.db.mapper.JobDetailMapper">
+    <insert id="save">
+        insert into oss_job_detail
+        (`job_id`,`job_name`,`status`,`start_at`,`end_at`)
+        values
+        (#{jobId},#{jobName},#{status},#{startAt},#{endAt})
+    </insert>
+    <insert id="saveAll" useGeneratedKeys="true" keyProperty="id">
+        insert into oss_job_detail
+        (`job_id`,`job_name`,`status`,`start_at`,`end_at`)
+        values
+        <foreach collection="list" item="item" index="index" separator=",">
+            (#{item.jobId},#{item.jobName},#{item.status},#{item.startAt},#{item.endAt})
+        </foreach>
+    </insert>
+
+    <update id="updateSetEnd">
+        update oss_job_detail
+        set `status`=#{status},end_at=#{endAt}
+        where job_id=#{jobId}
+    </update>
+
+    <select id="findAll" resultType="cn.reghao.tnb.file.app.model.po.JobDetail">
+        select *
+        from oss_job_detail
+    </select>
+</mapper>

+ 1 - 1
file/file-service/src/main/resources/mapper/StoreConfigMapper.xml

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
-<mapper namespace="cn.reghao.tnb.file.app.mapper.StoreConfigMapper">
+<mapper namespace="cn.reghao.tnb.file.app.db.mapper.StoreConfigMapper">
     <insert id="save">
         insert into oss_store_config
         (`oss_type`,`endpoint`,`access_key_id`,`access_key_secret`,`region`,`bucket_name`,`role_arn`)

+ 1 - 1
file/file-service/src/test/java/FileTest.java

@@ -1,5 +1,5 @@
 import cn.reghao.tnb.file.app.FileApplication;
-import cn.reghao.tnb.file.app.mapper.StoreConfigMapper;
+import cn.reghao.tnb.file.app.db.mapper.StoreConfigMapper;
 import cn.reghao.tnb.file.app.model.po.StoreConfig;
 import lombok.extern.slf4j.Slf4j;
 import org.junit.Test;