xgb 2 settimane fa
parent
commit
f3c9b82e7d
21 ha cambiato i file con 957 aggiunte e 112 eliminazioni
  1. 41 0
      fs-admin/src/main/java/com/fs/course/business/FsVideoResourceBusinessService.java
  2. 46 3
      fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java
  3. 59 23
      fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java
  4. 3 3
      fs-service/pom.xml
  5. 13 13
      fs-service/src/main/java/com/cloud/host/CloudHostConfig.java
  6. 17 0
      fs-service/src/main/java/com/fs/config/cloud/CloudHostProper.java
  7. 53 0
      fs-service/src/main/java/com/fs/core/config/VolcEngineConfiguration.java
  8. 36 0
      fs-service/src/main/java/com/fs/core/service/impl/STSService.java
  9. 49 0
      fs-service/src/main/java/com/fs/course/domain/FsProjectAddressConfig.java
  10. 5 0
      fs-service/src/main/java/com/fs/course/domain/FsVideoResource.java
  11. 19 2
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  12. 58 0
      fs-service/src/main/java/com/fs/course/service/HsyAssumeRoleService.java
  13. 21 2
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  14. 6 0
      fs-service/src/main/java/com/fs/course/service/IFsUserVideoService.java
  15. 304 7
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  16. 122 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserVideoServiceImpl.java
  17. 47 0
      fs-service/src/main/java/com/fs/course/service/impl/VodUploadMediaProcessListener.java
  18. 1 1
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  19. 42 46
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  20. 10 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  21. 5 12
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

+ 41 - 0
fs-admin/src/main/java/com/fs/course/business/FsVideoResourceBusinessService.java

@@ -0,0 +1,41 @@
+package com.fs.course.business;
+
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.domain.FsVideoResource;
+import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.service.IFsVideoResourceService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class FsVideoResourceBusinessService {
+
+    private final IFsVideoResourceService fsVideoResourceService;
+
+    private final IFsUserCourseVideoService fsUserCourseVideoService;
+
+    public FsVideoResourceBusinessService(IFsVideoResourceService fsVideoResourceService,
+                                          IFsUserCourseVideoService fsUserCourseVideoService) {
+        this.fsVideoResourceService = fsVideoResourceService;
+        this.fsUserCourseVideoService = fsUserCourseVideoService;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void edit(FsVideoResource fsVideoResource) {
+        FsVideoResource oldResource = fsVideoResourceService.getById(fsVideoResource.getId());
+        fsVideoResourceService.updateById(fsVideoResource);
+        fsUserCourseVideoService.update(new UpdateWrapper<FsUserCourseVideo>()
+                .eq("video_url", oldResource.getVideoUrl())
+                .set("video_url", fsVideoResource.getVideoUrl())
+                .set("line_one", fsVideoResource.getLine1())
+                .set("line_two", fsVideoResource.getLine2())
+                .set("line_three", fsVideoResource.getLine3())
+                .set("duration", fsVideoResource.getDuration())
+                .set("file_size", fsVideoResource.getFileSize())
+                .set("file_key", fsVideoResource.getFileKey())
+                .set("file_name", fsVideoResource.getFileName())
+                .set("thumbnail", fsVideoResource.getThumbnail())
+        );
+    }
+}

+ 46 - 3
fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java

@@ -13,6 +13,7 @@ import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.domain.FsUserVideo;
 import com.fs.course.domain.FsVideoResource;
 import com.fs.course.param.*;
+import com.fs.course.service.HsyAssumeRoleService;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.service.IFsUserVideoService;
 import com.fs.course.service.IFsVideoResourceService;
@@ -23,17 +24,18 @@ import com.fs.his.service.IFsPackageService;
 import com.fs.his.vo.FsPackageVO;
 import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
+import com.volcengine.model.response.AssumeRoleResponse;
+import com.volcengine.service.vod.IVodService;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.io.FilenameUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.*;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
+import java.util.*;
 
 /**
  * 课堂视频Controller
@@ -60,6 +62,12 @@ public class FsUserVideoController extends BaseController
     @Autowired
     private IFsUserCourseVideoService fsUserCourseVideoService;
 
+
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+    @Autowired
+    private HsyAssumeRoleService hsyAssumeRoleService;
     /**
      * 查询课堂视频列表
      */
@@ -389,4 +397,39 @@ public class FsUserVideoController extends BaseController
             return R.error("服务器内部错误");
         }
     }
+
+
+    @PreAuthorize("@ss.hasPermi('course:userVideo:uploadUserVideo')")
+    @Log(title = "课堂视频", businessType = BusinessType.INSERT)
+    @PostMapping("/uploadUserVideo")
+    public AjaxResult upload(@RequestParam("file") MultipartFile file, @RequestParam("uploadId")String uploadId) {
+        String vid = fsUserVideoService.uploadVideo(file, uploadId);
+
+        Map<String, Object> res = new HashMap<>();
+        res.put("uploadId", uploadId);
+        res.put("vid", vid);
+        return new AjaxResult(200, "上传成功", res);
+    }
+
+
+    @GetMapping("/uploadProgress")
+    public AjaxResult getUploadProgress(@RequestParam String uploadId) {
+        Object percentObj = redisTemplate.opsForValue()
+                .get("vod:upload:" + uploadId);
+
+        int percent = 0;
+        if (percentObj != null) {
+            percent = Integer.parseInt(percentObj.toString());
+        }
+
+        Map<String, Object> res = new HashMap<>();
+        res.put("percent", percent);
+        return AjaxResult.success(res);
+    }
+
+    @GetMapping("/HsyAssumeRoleService")
+    public AjaxResult HsyAssumeRoleService() throws Exception {
+        AssumeRoleResponse roleResponse = hsyAssumeRoleService.getRoleResponse();
+        return AjaxResult.success(roleResponse);
+    }
 }

+ 59 - 23
fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java

@@ -3,6 +3,7 @@ package com.fs.course.controller;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
@@ -11,14 +12,19 @@ import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
+import com.fs.config.cloud.CloudHostProper;
+import com.fs.course.business.FsVideoResourceBusinessService;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsVideoResource;
+import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.service.IFsUserVideoService;
 import com.fs.course.service.IFsVideoResourceService;
 import com.fs.course.vo.FsVideoResourceVO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.system.service.ISysConfigService;
 import com.github.pagehelper.PageHelper;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -30,6 +36,7 @@ import java.util.stream.Collectors;
 /**
  * 资源库管理
  */
+@Slf4j
 @RestController
 @RequestMapping("/course/videoResource")
 @AllArgsConstructor
@@ -37,11 +44,17 @@ public class FsVideoResourceController extends BaseController {
 
     private final IFsVideoResourceService fsVideoResourceService;
 
-    @Autowired
-    private TokenService tokenService;
+    private final TokenService tokenService;
+
+    private final ISysConfigService configService;
+
+    private final CloudHostProper cloudHostProper;
 
+    private final FsVideoResourceBusinessService videoResourceBusinessService;
     @Autowired
-    private ISysConfigService configService;
+    private IFsUserVideoService fsUserVideoService;
+    @Autowired
+    private IFsUserCourseVideoService fsUserCourseVideoService;
 
     /**
      * 查询视频素材库列表
@@ -53,8 +66,7 @@ public class FsVideoResourceController extends BaseController {
                               @RequestParam(required = false) Integer typeId,
                               @RequestParam(required = false) Integer typeSubId,
                               @RequestParam(required = false, defaultValue = "1") Integer pageNum,
-                              @RequestParam(required = false, defaultValue = "10") Integer pageSize)
-    {
+                              @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
         Map<String, Object> params = new HashMap<>();
         params.put("resourceName", resourceName);
         params.put("fileName", fileName);
@@ -63,7 +75,7 @@ public class FsVideoResourceController extends BaseController {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
             params.put("userId", loginUser.getUser().getUserId());
         }
         PageHelper.startPage(pageNum, pageSize);
@@ -77,8 +89,7 @@ public class FsVideoResourceController extends BaseController {
      */
     @PreAuthorize("@ss.hasPermi('course:videoResource:query')")
     @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
         return AjaxResult.success(fsVideoResourceService.getById(id));
     }
 
@@ -88,17 +99,25 @@ public class FsVideoResourceController extends BaseController {
     @PreAuthorize("@ss.hasPermi('course:videoResource:add')")
     @Log(title = "视频素材库", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody FsVideoResource fsVideoResource)
-    {
+    public AjaxResult add(@RequestBody FsVideoResource fsVideoResource) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         Long userId = loginUser.getUser().getUserId();
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
             fsVideoResource.setUserId(userId);
         }
+
         fsVideoResource.setCreateTime(LocalDateTime.now());
-        fsVideoResourceService.save(fsVideoResource);
+        boolean save = fsVideoResourceService.save(fsVideoResource);
+        if (save&&StringUtils.isNotEmpty(fsVideoResource.getHsyVid())){
+            try {
+                fsUserCourseVideoService.updateMediaPublishStatus(fsVideoResource.getHsyVid());
+                log.info("更新视频发布状态成功,hsyVid: {}", fsVideoResource.getHsyVid());
+            } catch (Exception e) {
+                log.error("更新视频发布状态失败,hsyVid: {}, 错误: {}", fsVideoResource.getHsyVid(), e.getMessage());
+            }
+        }
         return AjaxResult.success();
     }
 
@@ -108,8 +127,12 @@ public class FsVideoResourceController extends BaseController {
     @PreAuthorize("@ss.hasPermi('course:videoResource:edit')")
     @Log(title = "视频素材库", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody FsVideoResource fsVideoResource)
-    {
+    public AjaxResult edit(@RequestBody FsVideoResource fsVideoResource) {
+        if (("今正科技".equals(cloudHostProper.getCompanyName()))) {
+            // 同步资源到课程
+            videoResourceBusinessService.edit(fsVideoResource);
+            return AjaxResult.success();
+        }
         fsVideoResourceService.updateById(fsVideoResource);
         return AjaxResult.success();
     }
@@ -120,8 +143,7 @@ public class FsVideoResourceController extends BaseController {
     @PreAuthorize("@ss.hasPermi('course:videoResource:remove')")
     @Log(title = "视频素材库", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids)
-    {
+    public AjaxResult remove(@PathVariable Long[] ids) {
         Wrapper<FsVideoResource> updateWrapper = Wrappers.<FsVideoResource>lambdaUpdate()
                 .set(FsVideoResource::getIsDel, 1)
                 .in(FsVideoResource::getId, Arrays.asList(ids));
@@ -131,6 +153,7 @@ public class FsVideoResourceController extends BaseController {
 
     /**
      * 批量修改视频分类
+     *
      * @param typeId
      * @param typeSubId
      * @param ids
@@ -142,13 +165,13 @@ public class FsVideoResourceController extends BaseController {
     public AjaxResult batchUpdateClass(@RequestParam("typeId") Long typeId,
                                        @RequestParam("typeSubId") Long typeSubId,
                                        @RequestParam("ids") String ids) {
-        if(typeId == null || typeId <= 0){
+        if (typeId == null || typeId <= 0) {
             return AjaxResult.error("请选择分类");
         }
-        if(typeSubId == null || typeSubId <= 0){
+        if (typeSubId == null || typeSubId <= 0) {
             return AjaxResult.error("请选择子分类");
         }
-        if(ids == null || ids.isEmpty()){
+        if (ids == null || ids.isEmpty()) {
             return AjaxResult.error("请选择要修改的分类");
         }
 
@@ -178,13 +201,26 @@ public class FsVideoResourceController extends BaseController {
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
 
-        list.forEach(v ->{
+        list.forEach(v -> {
             v.setCreateTime(LocalDateTime.now());
-            if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+            if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
                 v.setUserId(userId);
             }
-        } );
-        fsVideoResourceService.saveBatch(list);
+        });
+        boolean saveStatus = fsVideoResourceService.saveBatch(list);
+        if (saveStatus) {
+            list.forEach(fsVideoResource -> {
+                // 检查hsyVid是否存在且不为空
+                if (ObjectUtil.isNotEmpty(fsVideoResource.getHsyVid())) {
+                    try {
+                        fsUserCourseVideoService.updateMediaPublishStatus(fsVideoResource.getHsyVid());
+                        log.info("更新视频发布状态成功,hsyVid: {}", fsVideoResource.getHsyVid());
+                    } catch (Exception e) {
+                        log.error("更新视频发布状态失败,hsyVid: {}, 错误: {}", fsVideoResource.getHsyVid(), e.getMessage());
+                    }
+                }
+            });
+        }
         return AjaxResult.success();
     }
 }

+ 3 - 3
fs-service/pom.xml

@@ -286,9 +286,9 @@
 
         <!-- 移动云ECS SDK -->
         <dependency>
-            <groupId>com.ecloud.sdk</groupId>
-            <artifactId>ecloud-sdk-ecs</artifactId>
-            <version>1.1.26</version>
+            <groupId>com.volcengine</groupId>
+            <artifactId>volc-sdk-java</artifactId>
+            <version>1.0.250</version>
         </dependency>
 
     </dependencies>

+ 13 - 13
fs-service/src/main/java/com/cloud/host/CloudHostConfig.java

@@ -1,7 +1,7 @@
 package com.cloud.host;
 
-import com.ecloud.sdk.config.Config;
-import com.ecloud.sdk.ecs.v1.Client;
+//import com.ecloud.sdk.config.Config;
+//import com.ecloud.sdk.ecs.v1.Client;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
@@ -14,16 +14,16 @@ public class CloudHostConfig {
      * @param poolId
      * @return Client
      */
-    public static Client createClient(String accessKey, String secretKey, String poolId) {
-        Config config = new Config();
-        config.setAccessKey(accessKey);
-        config.setSecretKey(secretKey);
-        config.setPoolId(poolId);
-        // 默认连接超时时间为60秒
-        config.setConnectTimeout(60);
-        // 默认响应超时时间为120秒
-        config.setReadTimeout(120);
-        return new Client(config);
-    }
+//    public static Client createClient(String accessKey, String secretKey, String poolId) {
+//        Config config = new Config();
+//        config.setAccessKey(accessKey);
+//        config.setSecretKey(secretKey);
+//        config.setPoolId(poolId);
+//        // 默认连接超时时间为60秒
+//        config.setConnectTimeout(60);
+//        // 默认响应超时时间为120秒
+//        config.setReadTimeout(120);
+//        return new Client(config);
+//    }
 
 }

+ 17 - 0
fs-service/src/main/java/com/fs/config/cloud/CloudHostProper.java

@@ -15,4 +15,21 @@ public class CloudHostProper {
 
     @Value("${cloud_host.projectCode}")
     private String projectCode;
+
+    //火山云空间名称
+    @Value("${cloud_host.spaceName}")
+    public String spaceName;
+
+    //火山云空间绑定域名
+    @Value("${cloud_host.volcengineUrl}")
+    public String volcengineUrl;
+
+//    @Value("${cloud_host.accessKey}")
+//    public String accessKey;
+//
+//    @Value("${cloud_host.secretKey}")
+//    public String secretKey;
+//
+//    @Value("${cloud_host.region}")
+//    public String region;
 }

+ 53 - 0
fs-service/src/main/java/com/fs/core/config/VolcEngineConfiguration.java

@@ -0,0 +1,53 @@
+package com.fs.core.config;
+
+import com.hc.openapi.tool.fastjson.JSON;
+import com.volcengine.model.request.AssumeRoleRequest;
+import com.volcengine.model.response.AssumeRoleResponse;
+import com.volcengine.service.sts.ISTSService;
+import com.volcengine.service.sts.impl.STSServiceImpl;
+import com.volcengine.service.vod.IVodService;
+import com.volcengine.service.vod.impl.VodServiceImpl;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class VolcEngineConfiguration {
+    @Value("${hsy.access_key:''}")
+    private String access_key;
+
+    @Value("${hsy.secret_key:''}")
+    private String secret_key;
+    @Value("${hsy.region:''}")
+    private String region;
+    @Bean
+    public IVodService vodService() throws Exception {
+        // 根据区域获取火山云点播服务实例
+        IVodService vodService = VodServiceImpl.getInstance(region);
+
+        // 设置 AccessKey 和 SecretKey
+        vodService.setAccessKey(access_key);
+        vodService.setSecretKey(secret_key);
+
+        return vodService;
+    }
+
+
+
+    public static void main(String[] args) throws Exception {
+        ISTSService stsService = STSServiceImpl.getInstance();
+
+        stsService.setAccessKey("AKLTNmMwNjJkNDFhYTVjNDIzYzhhNzEyZmZmZTlmYzBhNGM");
+        stsService.setSecretKey("T0RaaFl6UmhZV1V4WXpKbU5EWTBNMkZpT0RNNU9UY3daak0wTjJFd09XUQ==");
+
+        AssumeRoleRequest request = new AssumeRoleRequest();
+        request.setRoleSessionName("just_for_test");
+        request.setDurationSeconds(900);
+        request.setRoleTrn("trn:iam::2114522511:role/hylj");
+
+        AssumeRoleResponse resp = stsService.assumeRole(request);
+        System.out.println(JSON.toJSONString(resp));
+
+    }
+}
+

+ 36 - 0
fs-service/src/main/java/com/fs/core/service/impl/STSService.java

@@ -0,0 +1,36 @@
+package com.fs.core.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.config.cloud.CloudHostProper;
+import com.volcengine.model.request.AssumeRoleRequest;
+import com.volcengine.model.response.AssumeRoleResponse;
+import com.volcengine.service.sts.ISTSService;
+import com.volcengine.service.sts.impl.STSServiceImpl;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+@Service
+public class STSService {
+
+    private final CloudHostProper cloudHostProper;
+
+    public STSService(CloudHostProper cloudHostProper) {
+        this.cloudHostProper = cloudHostProper;
+    }
+
+//    public AssumeRoleResponse geSTSToken() throws Exception {
+//        ISTSService stsService = STSServiceImpl.getInstance();
+//
+//        stsService.setAccessKey(cloudHostProper.accessKey);
+//        stsService.setSecretKey(cloudHostProper.secretKey);
+//
+//        AssumeRoleRequest request = new AssumeRoleRequest();
+//        request.setRoleSessionName("just_for_test");
+//        request.setDurationSeconds(900);
+//        request.setRoleTrn("trn:iam::yourAccountID:role/yourRoleName");
+//
+//        AssumeRoleResponse resp = stsService.assumeRole(request);
+//        return resp;
+//    }
+
+}

+ 49 - 0
fs-service/src/main/java/com/fs/course/domain/FsProjectAddressConfig.java

@@ -0,0 +1,49 @@
+package com.fs.course.domain;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class FsProjectAddressConfig {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 项目名称
+     */
+    private String name;
+
+    /**
+     * 唯一编码
+     */
+    private String code;
+
+    /**
+     * api接口地址
+     */
+    private String addressUrl;
+
+    /**
+     * 存储桶地址
+     */
+    private String bucketPath;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 修改时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 发送类型 1个微 2企微
+     */
+    private Integer sendType;
+}

+ 5 - 0
fs-service/src/main/java/com/fs/course/domain/FsVideoResource.java

@@ -99,4 +99,9 @@ public class FsVideoResource {
     private Integer sort;
 
     private Long userId;
+
+    //火山云vid
+    private String hsyVid;
+
+    private String jobId;
 }

+ 19 - 2
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java

@@ -1,6 +1,8 @@
 package com.fs.course.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.domain.FsVideoResource;
 import com.fs.course.param.CourseVideoUpdates;
 import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.course.param.FsUserCourseVideoListUParam;
@@ -25,8 +27,7 @@ import java.util.Map;
  * @author fs
  * @date 2024-05-17
  */
-public interface FsUserCourseVideoMapper
-{
+public interface FsUserCourseVideoMapper extends BaseMapper<FsUserCourseVideo> {
     /**
      * 查询课堂视频
      *
@@ -240,6 +241,12 @@ public interface FsUserCourseVideoMapper
             "WHERE file_key = #{fileKey}")
     void updateFsUserCourseVideoByFileKey(FsUserCourseVideo courseVideo);
 
+    @Update("UPDATE fs_user_course_video " +
+            "SET line_two = #{lineTwo}, " +
+            "    update_time = NOW() " +  // 添加更新时间
+            "WHERE file_key = #{fileKey}")
+    void updateFsUserCourseVideoByFileKeyForHsy(FsUserCourseVideo courseVideo);
+
     @Select("select title from fs_user_course_video WHERE video_id=#{videoId}")
     String selectFsUserCourseVideoByVideoForTitle(@Param("videoId") Long videoId);
 
@@ -260,4 +267,14 @@ public interface FsUserCourseVideoMapper
     @Select("select video_id,is_first,course_sort,tg_id,watching_tg_id,watched_tg_id,watching_tag_id,watched_tag_id,tag_group_id from fs_user_course_video")
     @MapKey("videoId")
     Map<Long, FsUserCourseVideo> selectAllMap();
+
+    @Select("select * from fs_video_resource where line2 is not null and (job_id is null or job_id='')")
+    List<FsVideoResource> selectVideoByHuaWei();
+
+    @Select("select * from fs_video_resource where job_id is not null and  (hsy_vid is null or hsy_vid='')")
+    List<FsVideoResource> selectVideoByJobId();
+
+
+    @Select("select * from fs_video_resource where hsy_vid is not null")
+    List<FsVideoResource> selectVideoByVid();
 }

+ 58 - 0
fs-service/src/main/java/com/fs/course/service/HsyAssumeRoleService.java

@@ -0,0 +1,58 @@
+package com.fs.course.service;
+
+import com.fs.config.cloud.CloudHostProper;
+import com.hc.openapi.tool.fastjson.JSON;
+import com.volcengine.model.request.AssumeRoleRequest;
+import com.volcengine.model.response.AssumeRoleResponse;
+import com.volcengine.service.sts.ISTSService;
+import com.volcengine.service.sts.impl.STSServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+@Service
+public class HsyAssumeRoleService {
+    @Autowired
+    private CloudHostProper cloudHostProper;
+
+    @Value("${hsy.role_access_key:''}")
+    private String role_access_key;
+
+    @Value("${hsy.role_secret_key:''}")
+    private String role_secret_key;
+
+    @Value("${hsy.role_trn:''}")
+    private String role_trn;
+    public AssumeRoleResponse getRoleResponse() throws Exception {
+        ISTSService stsService = STSServiceImpl.getInstance();
+
+        stsService.setAccessKey(role_access_key);
+        stsService.setSecretKey(role_secret_key);
+
+//        stsService.setAccessKey("AKLTZTc4YTE4ZjI2OWViNDNjZGI2NjhiYTI5Njc5ZjA1Mzk");
+//        stsService.setSecretKey("WXpjelpUYzFOakF5TUdObE5EZGtNR0ZsWXpKaU1tTmtZakk1WXpObE4yRQ==");
+
+        AssumeRoleRequest request = new AssumeRoleRequest();
+        request.setRoleSessionName(cloudHostProper.spaceName);
+        request.setDurationSeconds(900);
+        request.setRoleTrn(role_trn);
+
+        AssumeRoleResponse resp = stsService.assumeRole(request);
+        return resp;
+    }
+
+    public static void main(String[] args) throws Exception {
+        ISTSService stsService = STSServiceImpl.getInstance();
+
+        stsService.setAccessKey("AKLTNmMwNjJkNDFhYTVjNDIzYzhhNzEyZmZmZTlmYzBhNGM");
+        stsService.setSecretKey("T0RaaFl6UmhZV1V4WXpKbU5EWTBNMkZpT0RNNU9UY3daak0wTjJFd09XUQ==");
+
+        AssumeRoleRequest request = new AssumeRoleRequest();
+        request.setRoleSessionName("just_for_test");
+        request.setDurationSeconds(900);
+        request.setRoleTrn("trn:iam::2114522511:role/hylj");
+        AssumeRoleResponse resp = stsService.assumeRole(request);
+        System.out.println(JSON.toJSONString(resp));
+
+    }
+}

+ 21 - 2
fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java

@@ -28,8 +28,7 @@ import java.util.Map;
  * @author fs
  * @date 2024-05-17
  */
-public interface IFsUserCourseVideoService
-{
+public interface IFsUserCourseVideoService extends IService<FsUserCourseVideo> {
     /**
      * 查询课堂视频
      *
@@ -210,4 +209,24 @@ public interface IFsUserCourseVideoService
     List<FsUserCourseVideoChooseVO> getChooseCourseVideoListByMap(Map<String, Object> params);
 
     R sendAppReward(FsCourseSendRewardUParam param);
+
+    /**
+     * 上传视频到火山云通过URL(把华为云的视频传到火山去并且存储JOBID)
+     * @return
+     */
+    R uploadVideoToHuoShanByUrl();
+
+    /**
+     * 通过jobId拿vid
+     * @return
+     */
+    R getVidByJob();
+
+    /**
+     * 通过vid获取视频路径并且组装地址存到替换线路二
+     * @return
+     */
+    R getVideoInfoByVid();
+
+    void updateMediaPublishStatus(String vid);
 }

+ 6 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserVideoService.java

@@ -9,6 +9,7 @@ import com.fs.course.vo.FsUserVideoListPVO;
 import com.fs.course.vo.FsUserVideoPVO;
 import com.fs.course.param.FsUserVideoParam;
 import com.fs.course.vo.FsUserVideoListUVO;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * 课堂视频Service接口
@@ -104,4 +105,9 @@ public interface IFsUserVideoService {
     R deleteFsUserVideoByVideoIdWithVerify(Long videoId, Long userId);
 
     R updateVideoStatusWithVerify(FsUserVideo fsUserVideo, Long userId);
+
+    String getVideoInfoByVid(String vid);
+
+
+    String uploadVideo(MultipartFile file, String uploadId);
 }

+ 304 - 7
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.BeanCopyUtils;
 import com.fs.common.constant.FsConstants;
 import com.fs.common.core.domain.R;
@@ -74,7 +75,12 @@ import com.fs.system.mapper.SysDictDataMapper;
 import com.fs.system.service.ISysConfigService;
 import com.fs.voice.utils.StringUtil;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
-import com.google.common.collect.Sets;
+import com.volcengine.helper.VodUploadProgressListener;
+import com.volcengine.model.beans.Functions;
+import com.volcengine.service.vod.IVodService;
+import com.volcengine.service.vod.model.business.VodUrlUploadURLSet;
+import com.volcengine.service.vod.model.request.*;
+import com.volcengine.service.vod.model.response.*;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
@@ -91,7 +97,9 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
 
+import java.io.File;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.text.SimpleDateFormat;
@@ -99,8 +107,8 @@ import java.time.*;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.stream.Collectors;
 
@@ -114,7 +122,7 @@ import static com.fs.course.utils.LinkUtil.generateRandomStringWithLock;
  */
 @Slf4j
 @Service
-public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
+public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoMapper, FsUserCourseVideo> implements IFsUserCourseVideoService
 {
     @Value("${cloud_host.company_name}")
     private String signProjectName;
@@ -255,7 +263,8 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     @Autowired
     private IFsUserIntegralLogsService iFsUserIntegralLogsService;
     @Autowired
-    private QwTagGroupMapper qwTagGroupMapper;
+    private RedisTemplate redisTemplate;
+
     @Autowired
     private QwTagMapper qwTagMapper;
 
@@ -314,8 +323,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             fsUserCourseVideo.setListingStartTime(null);
             fsUserCourseVideo.setListingEndTime(null);
         }
-        String videoRedisKey = "h5user:video:duration:" + fsUserCourseVideo.getVideoId();
-        redisCache.setCacheObject(videoRedisKey, fsUserCourseVideo.getDuration());
+        String videoRedisKey1 = "h5user:video:duration:" + fsUserCourseVideo.getVideoId();
+        redisCache.setCacheObject(videoRedisKey1, fsUserCourseVideo.getDuration());
+        String videoRedisKey2 = "h5wxuser:video:duration:" + fsUserCourseVideo.getVideoId();
+        redisCache.setCacheObject(videoRedisKey2, fsUserCourseVideo.getDuration());
         return fsUserCourseVideoMapper.updateFsUserCourseVideo(fsUserCourseVideo);
     }
 
@@ -3444,5 +3455,291 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         return R.ok("奖励发放成功");
     }
 
+    @Override
+    public R uploadVideoToHuoShanByUrl() {
+        List <FsVideoResource> videos = fsUserCourseVideoMapper.selectVideoByHuaWei();
+        for (FsVideoResource video : videos){
+            uploadVideoByUrl(video);
+        }
+        return R.ok();
+    }
+
+    @Autowired
+    private IVodService vodService;
+
+    //通过Url上传视频
+    public void uploadVideoByUrl(FsVideoResource videoResource) {
+
+        try {
+            VodUrlUploadRequest.Builder reqBuilder = VodUrlUploadRequest.newBuilder();
+            //空间名称
+            reqBuilder.setSpaceName(cloudHostProper.getSpaceName());
+            VodUrlUploadURLSet.Builder uRLSetsBuilder = VodUrlUploadURLSet.newBuilder();
+            //源文件 URL
+            uRLSetsBuilder.setSourceUrl(videoResource.getLine2());//华为云
+            //存储类型。默认为 1。取值如下:
+            //1:标准存储。
+            //2:归档存储。
+            //3:低频存储。
+            uRLSetsBuilder.setStorageClass(1);
+            //文件后缀,即点播存储中文件的类型。
+            uRLSetsBuilder.setFileExtension(".mp4");
+            //用户额外信息,最大长度 512 字节。
+            uRLSetsBuilder.setCallbackArgs("");
+            // 火山云存储路径(文件上传后在火山云的路径)
+//            String datePath = new SimpleDateFormat("yyyyMMdd").format(new Date());
+//            String fileName = System.currentTimeMillis() + ".mp4";
+//            String remoteFileName = "fs/" + datePath + "/" + fileName;
+
+            uRLSetsBuilder.setFileName(videoResource.getFileKey());
+            reqBuilder.addURLSets(uRLSetsBuilder);
+
+            VodUrlUploadResponse resp = vodService.uploadMediaByUrl(reqBuilder.build());
+
+            if (resp.getResponseMetadata().hasError()) {
+                log.info("上传返回异常:{}",resp.getResponseMetadata().getError());
+                System.exit(-1);
+            }else {
+                FsVideoResource video = new FsVideoResource();
+                video.setId(videoResource.getId());
+                video.setJobId(resp.getResult().getData(0).getJobId());
+                //更新JobId
+                fsVideoResourceMapper.updateById(video);
+            }
+            log.info("上传返回参数:{}",resp);
+        } catch (Exception e) {
+            throw new RuntimeException("视频上传失败: " + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public R getVidByJob() {
+        // 查询有JobId的视频
+        List<FsVideoResource> list = fsUserCourseVideoMapper.selectVideoByJobId();
+        if (list.isEmpty()) {
+            log.info("没有待上传的视频任务");
+            return R.error();
+        }
+        // 按五百一批切割
+        List<List<FsVideoResource>> batches = splitList(list, 500);
+        log.info("总任务 {} 条,分成 {} 批", list.size(), batches.size());
+
+        int batchIndex = 1;
+
+        // 批次顺序执行,每批内部多线程并发执行
+        for (List<FsVideoResource> batch : batches) {
+
+            log.info("开始执行批次 {}/{},本批任务 {} 条", batchIndex, batches.size(), batch.size());
+
+            CountDownLatch latch = new CountDownLatch(batch.size());
+
+            for (FsVideoResource video : batch) {
+                uploadExecutor.submit(() -> {
+                    try {
+                        uploadSingleTaskWithRetry(video,1);
+                    } finally {
+                        latch.countDown();
+                    }
+                });
+            }
+
+            // 等待这一批全部完成
+            try {
+                latch.await();
+            } catch (InterruptedException e) {
+                log.error("批次等待异常", e);
+            }
+
+            log.info("批次 {}/{} 执行完成", batchIndex, batches.size());
+            batchIndex++;
+        }
+
+        log.info("全部批次执行完成");
+        return R.ok();
+    }
+
+    @Override
+    public R getVideoInfoByVid() {
+        // 查询有JobId的视频
+        List<FsVideoResource> list = fsUserCourseVideoMapper.selectVideoByVid();
+        if (list.isEmpty()) {
+            log.info("没有包含vid的视频");
+            return R.error();
+        }
+        // 按五百一批切割
+        List<List<FsVideoResource>> batches = splitList(list, 500);
+        log.info("总任务 {} 条,分成 {} 批", list.size(), batches.size());
+
+        int batchIndex = 1;
+
+        // 批次顺序执行,每批内部多线程并发执行
+        for (List<FsVideoResource> batch : batches) {
+
+            log.info("开始执行批次 {}/{},本批任务 {} 条", batchIndex, batches.size(), batch.size());
+
+            CountDownLatch latch = new CountDownLatch(batch.size());
+
+            for (FsVideoResource video : batch) {
+                uploadExecutor.submit(() -> {
+                    try {
+                        uploadSingleTaskWithRetry(video,2);
+                    } finally {
+                        latch.countDown();
+                    }
+                });
+            }
+
+            // 等待这一批全部完成
+            try {
+                latch.await();
+            } catch (InterruptedException e) {
+                log.error("批次等待异常", e);
+            }
+
+            log.info("批次 {}/{} 执行完成", batchIndex, batches.size());
+            batchIndex++;
+        }
+
+        log.info("全部批次执行完成");
+        return R.ok();
+    }
+
+    private final ExecutorService uploadExecutor = new ThreadPoolExecutor(
+            8,  // core
+            16, // max
+            60L, TimeUnit.SECONDS,
+            new LinkedBlockingQueue<>(2000),
+            new ThreadFactory() {
+                private final AtomicInteger index = new AtomicInteger(1);
+
+                @Override
+                public Thread newThread(Runnable r) {
+                    return new Thread(r, "video-upload-" + index.getAndIncrement());
+                }
+            },
+            new ThreadPoolExecutor.CallerRunsPolicy()
+    );
+
+    public <T> List<List<T>> splitList(List<T> list, int batchSize) {
+        List<List<T>> result = new ArrayList<>();
+        int total = list.size();
+        for (int i = 0; i < total; i += batchSize) {
+            result.add(list.subList(i, Math.min(total, i + batchSize)));
+        }
+        return result;
+    }
+
+    //根据jobid查询上传视频的vid
+    public void getVidByJobId(FsVideoResource videoResource){
+        try {
+            VodQueryUploadTaskInfoRequest.Builder reqBuilder = VodQueryUploadTaskInfoRequest.newBuilder();
+            reqBuilder.setJobIds(videoResource.getJobId());
+
+            VodQueryUploadTaskInfoResponse resp = vodService.queryUploadTaskInfo(reqBuilder.build());
+            if (resp.getResponseMetadata().hasError()) {
+                System.out.println(resp.getResponseMetadata().getError());
+                System.exit(-1);
+            }else {
+                FsVideoResource video = new FsVideoResource();
+                video.setId(videoResource.getId());
+                //视频上传失败,清空jobid
+                if (StringUtils.isEmpty(resp.getResult().getData().getMediaInfoList(0).getVid())){
+                    video.setJobId("");
+                }else {
+                    video.setHsyVid(resp.getResult().getData().getMediaInfoList(0).getVid());
+                }
+                fsVideoResourceMapper.updateById(video);
+            }
+            System.out.println(resp);
+        } catch (Exception e) {
+            throw new RuntimeException("查询 URL 批量上传任务状态: " + e.getMessage(), e);
+        }
+    }
+
+
+    //根据vid获取视频详情
+    public void getVideoInfoByVid(FsVideoResource videoResource) {
+        try {
+            VodGetMediaInfosRequest.Builder reqBuilder = VodGetMediaInfosRequest.newBuilder();
+            reqBuilder.setVids(videoResource.getHsyVid());
+
+            VodGetMediaInfosResponse resp = vodService.getMediaInfos20230701(reqBuilder.build());
+            if (resp.getResponseMetadata().hasError()) {
+                System.out.println(resp.getResponseMetadata().getError());
+                System.exit(-1);
+            }else {
+                //如果路径是空的直接返回
+                if (StringUtils.isEmpty(resp.getResult().getMediaInfoList(0).getSourceInfo().getStoreUri())){
+                    return;
+                }
+                //如果是未发布状态修改发布状态
+                if (resp.getResult().getMediaInfoList(0).getBasicInfo().getPublishStatus().equals("Unpublished")){
+                    updateMediaPublishStatus(videoResource.getHsyVid());
+                }
+                //更新视频资源
+//                String storeUri = resp.getResult().getMediaInfoList(0).getSourceInfo().getStoreUri();
+//                String uri = storeUri.substring(storeUri.indexOf("/") + 1);
+                String url = cloudHostProper.volcengineUrl+"/"+resp.getResult().getMediaInfoList(0).getSourceInfo().getFileName();
+
+                FsVideoResource fsVideoResource = new FsVideoResource();
+                fsVideoResource.setId(videoResource.getId());
+                fsVideoResource.setLine2(url);
+                fsVideoResourceMapper.updateById(fsVideoResource);
+
+                //更新小节
+                FsUserCourseVideo courseVideo = new FsUserCourseVideo();
+                courseVideo.setFileKey(videoResource.getFileKey());
+                courseVideo.setLineTwo(url);
+                fsUserCourseVideoMapper.updateFsUserCourseVideoByFileKeyForHsy(courseVideo);
+
+            }
+            System.out.println(resp);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    //修改发布状态
+    public void updateMediaPublishStatus(String vid){
+        String statusPublished = "Published";
+
+        try {
+            // publish
+            VodUpdateMediaPublishStatusRequest.Builder req = VodUpdateMediaPublishStatusRequest.newBuilder();
+            req.setVid(vid);
+            req.setStatus(statusPublished);
+
+            VodUpdateMediaPublishStatusResponse resp = vodService.updateMediaPublishStatus(req.build());
+            System.out.println(resp);
+
+            Thread.sleep(1000);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    public void uploadSingleTaskWithRetry(FsVideoResource videoResource,Integer type) {
+        int maxRetry = 3;
+        for (int i = 1; i <= maxRetry; i++) {
+            try {
+                if (type == 1){
+                    //获取上传成功的视频vid,同步到数据库
+                    getVidByJobId(videoResource);
+                }else if (type == 2){
+                    //获取视频地址同步到线路二
+                    getVideoInfoByVid(videoResource);
+                }
+                return;
+            } catch (Exception e) {
+                log.error("视频 {} 上传失败,第 {} 次重试,原因:{}",
+                        videoResource.getId(), i, e.getMessage());
+                if (i == maxRetry) {
+                    log.error("视频 {} 上传最终失败!", videoResource.getId());
+                }
+            }
+        }
+    }
 }
 

+ 122 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserVideoServiceImpl.java

@@ -1,10 +1,14 @@
 package com.fs.course.service.impl;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.text.SimpleDateFormat;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
+import com.alibaba.fastjson.JSON;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
@@ -20,11 +24,20 @@ import com.fs.course.vo.FsUserVideoPVO;
 import com.fs.course.param.FsUserVideoParam;
 import com.fs.course.vo.FsUserVideoListUVO;
 
+import com.volcengine.helper.VodUploadProgressListener;
+import com.volcengine.model.beans.Functions;
+import com.volcengine.service.vod.IVodService;
+import com.volcengine.service.vod.model.request.VodGetMediaInfosRequest;
+import com.volcengine.service.vod.model.request.VodUploadMediaRequest;
+import com.volcengine.service.vod.model.response.VodCommitUploadInfoResponse;
+import com.volcengine.service.vod.model.response.VodGetMediaInfosResponse;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 import com.fs.course.domain.FsUserVideo;
 import com.fs.course.service.IFsUserVideoService;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * 课堂视频Service业务层处理
@@ -32,6 +45,7 @@ import com.fs.course.service.IFsUserVideoService;
  * @author fs
  * @date 2024-05-15
  */
+@Slf4j
 @Service
 public class FsUserVideoServiceImpl implements IFsUserVideoService {
     @Autowired
@@ -46,6 +60,9 @@ public class FsUserVideoServiceImpl implements IFsUserVideoService {
     private IRecommendationService recommendationService;
     @Autowired
     private RedisTemplate redisTemplate;
+
+    @Autowired
+    private IVodService vodService;
     private static final String LIKE_KEY_PREFIX = "like:video:";
     private static final String FAVORITE_KEY_PREFIX = "favorite:video:";
 
@@ -444,5 +461,110 @@ public class FsUserVideoServiceImpl implements IFsUserVideoService {
         return url;
     }
 
+    //本地文件视频上传
+    @Override
+    public String uploadVideo(MultipartFile file, String uploadId) {
+        log.info("上传视频开始",uploadId);
+        File tempFile = null;
+        try {
+            // 将 MultipartFile 转换为临时文件
+            String originalFilename = file.getOriginalFilename();
+            String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
+            tempFile = File.createTempFile("upload_", suffix);
+            file.transferTo(tempFile);
+
+            // 点播空间名称
+            String space = "rt-huoshan";
+            // 本地临时文件路径(用于上传)
+            String localFilePath = tempFile.getAbsolutePath();
+
+            // 火山云存储路径(文件上传后在火山云的路径)
+            String datePath = new SimpleDateFormat("yyyyMMdd").format(new Date());
+            String fileName = System.currentTimeMillis() + ".mp4";
+            String remoteFileName = "course/" + datePath + "/" + fileName;
+
+            // 上传功能函数,可用于实现截图、设置媒资信息、触发工作流进行转码等功能。详见[上传功能函数说明](https://www.volcengine.com/docs/4/1185644)
+            List<Functions> functionsList = new ArrayList<>();
+            Functions getMetaFunc = Functions.GetMetaFunction();
+            functionsList.add(getMetaFunc);
+            Functions snapShotFunc = Functions.SnapShotFunction(2.3);
+            functionsList.add(snapShotFunc);
+            Functions addOptionInfo = Functions.AddOptionInfoFunction(fileName, "test", "用户上传视频", 0, true);
+            functionsList.add(addOptionInfo);
+
+            // 使用工作流进行转码
+
+            List<String> impTemplateIds = new ArrayList<>();
+//            impTemplateIds.add("a4a7066c968344e9bf7a56ec0891fbb8");
+//            FunctionsWorkflowTemplate impTemplate = new FunctionsWorkflowTemplate(impTemplateIds, "imp");
+//
+//            List<String> transcodeTemplateIds = new ArrayList<>();
+//            transcodeTemplateIds.add("a4a7066c968344e9bf7a56ec0891fbb8");
+//            FunctionsWorkflowTemplate transcodeTemplate = new FunctionsWorkflowTemplate(transcodeTemplateIds,
+//                    "transcode");
+
+//            List<FunctionsWorkflowTemplate> templates = new ArrayList<>();
+//            templates.add(impTemplate);
+//            templates.add(transcodeTemplate);
+
+//            Functions workflowFunc = Functions.StartWorkFlowFunction(templates);
+//            functionsList.add(workflowFunc);
+
+            VodUploadMediaRequest vodUploadMediaRequest = VodUploadMediaRequest.newBuilder()
+                    .setSpaceName(space)
+                    .setFilePath(localFilePath)  // 使用本地临时文件路径
+                    .setFileName(remoteFileName)  // 火山云存储路径
+                    .setFunctions(JSON.toJSONString(functionsList))
+                    .setStorageClass(1)
+                    .build();
+
+            // 需要进度条功能时添加相应 listener,如无需求,传 null 值即可
+            VodUploadProgressListener listener = new VodUploadMediaProcessListener(uploadId, redisTemplate);
+
+            VodCommitUploadInfoResponse vodCommitUploadInfoResponse = vodService.uploadMedia(vodUploadMediaRequest, listener);
+
+            redisTemplate.opsForValue()
+                    .set("vod:upload:" + uploadId, 100, 10, TimeUnit.MINUTES);
+
+            if (vodCommitUploadInfoResponse.getResponseMetadata().hasError()) {
+                throw new RuntimeException("火山云上传失败: " + vodCommitUploadInfoResponse.getResponseMetadata().getError());
+            }
+
+            // 返回视频 VID
+            String vid = vodCommitUploadInfoResponse.getResult().getData().getVid();
+            System.out.println("视频上传成功,VID: " + vid);
+            System.out.println("RequestId: " + vodCommitUploadInfoResponse.getResponseMetadata().getRequestId());
+
+            return vid;
+        } catch (Exception e) {
+            throw new RuntimeException("视频上传失败: " + e.getMessage(), e);
+        } finally {
+            // 删除临时文件
+            if (tempFile != null && tempFile.exists()) {
+                tempFile.delete();
+            }
+        }
+    }
+
+    //根据vid查询视频信息
+    @Override
+    public String getVideoInfoByVid(String vid) {
+        try {
+            VodGetMediaInfosRequest.Builder reqBuilder = VodGetMediaInfosRequest.newBuilder();
+            reqBuilder.setVids("vid");
+
+            VodGetMediaInfosResponse resp = vodService.getMediaInfos20230701(reqBuilder.build());
+            if (resp.getResponseMetadata().hasError()) {
+                System.out.println(resp.getResponseMetadata().getError());
+                System.exit(-1);
+            }else {
+                return resp.getResult().getMediaInfoList(0).getSourceInfo().getStoreUri();
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
 
 }

+ 47 - 0
fs-service/src/main/java/com/fs/course/service/impl/VodUploadMediaProcessListener.java

@@ -0,0 +1,47 @@
+package com.fs.course.service.impl;
+
+
+import com.volcengine.helper.VodUploadProgressEvent;
+import com.volcengine.helper.VodUploadProgressListener;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class VodUploadMediaProcessListener implements VodUploadProgressListener {
+
+    private long bytesUploaded = 0;
+    private long fileSize = -1;
+
+    private final String uploadId;
+    private final RedisTemplate<String, Object> redisTemplate;
+
+    public VodUploadMediaProcessListener(String uploadId,
+                                         RedisTemplate redisTemplate) {
+        this.uploadId = uploadId;
+        this.redisTemplate = redisTemplate;
+    }
+
+    @Override
+    public void progressChanged(VodUploadProgressEvent event) {
+        switch (event.getEventType()) {
+            case FILE_SIZE_EVENT:
+                fileSize = event.getByteSize();
+                break;
+
+            case UPLOAD_BYTES_EVENT:
+                bytesUploaded += event.getByteSize();
+                if (fileSize > 0) {
+                    int percent = Math.min(
+                            (int) (bytesUploaded * 100.0 / fileSize),
+                            100
+                    );
+                    redisTemplate.opsForValue()
+                            .set("vod:upload:" + uploadId, percent, 10, TimeUnit.MINUTES);
+                    log.info("uploadId={} progress={}%", uploadId, percent);
+                }
+                break;
+        }
+    }
+}

+ 1 - 1
fs-service/src/main/java/com/fs/qw/service/IQwUserService.java

@@ -172,7 +172,7 @@ public interface IQwUserService
      * @param IP 服务器ip
      * @return
      */
-    R restartCloudHost(String IP);
+//    R restartCloudHost(String IP);
 
     /**
      * 获取企微用户信息

+ 42 - 46
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

@@ -8,10 +8,6 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.cloud.host.CloudHostConfig;
 import com.cloud.host.PoolInfoEnum;
-import com.ecloud.sdk.ecs.v1.Client;
-import com.ecloud.sdk.ecs.v1.model.VmRebootPath;
-import com.ecloud.sdk.ecs.v1.model.VmRebootRequest;
-import com.ecloud.sdk.ecs.v1.model.VmRebootResponse;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.CloudHostUtils;
@@ -1373,48 +1369,48 @@ public class QwUserServiceImpl implements IQwUserService
         return R.ok(status.toString());
     }
 
-    @Override
-    public R restartCloudHost(String IP) {
-        // 调用SDK必须要的参数
-        String serverId = "";
-        String poolId = "";
-        String accessKey= "";
-        String secretKey ="";
-        try {
-            String bodyData = HttpRequest.get("http://watch.ylrzcloud.com/prod-api/server/getServerId?ipAddress="+IP)
-                    .execute().body();
-            QwFsServerBindResult qwFsServerBindResult = JSON.parseObject(bodyData, QwFsServerBindResult.class);
-            if(qwFsServerBindResult.getData() != null){
-                QwFsServerBindResult.Data data = qwFsServerBindResult.getData();
-                serverId = data.getServerId();
-                poolId = PoolInfoEnum.getPoolIdByCityName(data.getCity() != null ? data.getCity().substring(0, data.getCity().length() - 1):"");
-                accessKey = data.getAccesskey();
-                secretKey = data.getSecretKey();
-            }
-        } catch (Exception e){
-            e.printStackTrace();
-            logger.error("获取主机信息异常,服务器IP:{}", IP);
-        }
-
-        if(StringUtils.isNotEmpty(serverId) && StringUtils.isNotEmpty(poolId) && StringUtils.isNotEmpty(accessKey) && StringUtils.isNotEmpty(secretKey)){
-            Client client = CloudHostConfig.createClient(accessKey, secretKey, poolId);
-            VmRebootRequest.Builder requestBuilder = VmRebootRequest.builder();
-            VmRebootPath vmRebootPath = VmRebootPath.builder().serverId(serverId).build();
-            requestBuilder.vmRebootPath(vmRebootPath);
-            VmRebootRequest request = requestBuilder.build();
-            VmRebootResponse result = client.vmReboot(request);
-            System.out.println(result);
-
-            if(!VmRebootResponse.StateEnum.OK.equals(result.getState())){
-                return R.error(501,"重启主机异常," + result.getErrorMessage()).put("errMsg", result);
-            }
-
-            return R.ok();
-        } else {
-            logger.error("重启云主机异常,获取主机信息异常,serverId:{},poolId:{},accessKey:{},secretKey:{}", serverId, poolId, accessKey, secretKey);
-            return R.error(400,"重启云主机异常,获取主机信息异常");
-        }
-    }
+//    @Override
+//    public R restartCloudHost(String IP) {
+//        // 调用SDK必须要的参数
+//        String serverId = "";
+//        String poolId = "";
+//        String accessKey= "";
+//        String secretKey ="";
+//        try {
+//            String bodyData = HttpRequest.get("http://watch.ylrzcloud.com/prod-api/server/getServerId?ipAddress="+IP)
+//                    .execute().body();
+//            QwFsServerBindResult qwFsServerBindResult = JSON.parseObject(bodyData, QwFsServerBindResult.class);
+//            if(qwFsServerBindResult.getData() != null){
+//                QwFsServerBindResult.Data data = qwFsServerBindResult.getData();
+//                serverId = data.getServerId();
+//                poolId = PoolInfoEnum.getPoolIdByCityName(data.getCity() != null ? data.getCity().substring(0, data.getCity().length() - 1):"");
+//                accessKey = data.getAccesskey();
+//                secretKey = data.getSecretKey();
+//            }
+//        } catch (Exception e){
+//            e.printStackTrace();
+//            logger.error("获取主机信息异常,服务器IP:{}", IP);
+//        }
+//
+//        if(StringUtils.isNotEmpty(serverId) && StringUtils.isNotEmpty(poolId) && StringUtils.isNotEmpty(accessKey) && StringUtils.isNotEmpty(secretKey)){
+//            Client client = CloudHostConfig.createClient(accessKey, secretKey, poolId);
+//            VmRebootRequest.Builder requestBuilder = VmRebootRequest.builder();
+//            VmRebootPath vmRebootPath = VmRebootPath.builder().serverId(serverId).build();
+//            requestBuilder.vmRebootPath(vmRebootPath);
+//            VmRebootRequest request = requestBuilder.build();
+//            VmRebootResponse result = client.vmReboot(request);
+//            System.out.println(result);
+//
+//            if(!VmRebootResponse.StateEnum.OK.equals(result.getState())){
+//                return R.error(501,"重启主机异常," + result.getErrorMessage()).put("errMsg", result);
+//            }
+//
+//            return R.ok();
+//        } else {
+//            logger.error("重启云主机异常,获取主机信息异常,serverId:{},poolId:{},accessKey:{},secretKey:{}", serverId, poolId, accessKey, secretKey);
+//            return R.error(400,"重启云主机异常,获取主机信息异常");
+//        }
+//    }
 
     @Override
     public List<QwUser> getQwUserInfo(QwFsUserParam param) {

+ 10 - 0
fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml

@@ -108,6 +108,9 @@
             <if test="listingEndTime != null">listing_end_time,</if>
             <if test="userId != null">user_id,</if>
             <if test="isFirst != null">is_first,</if>
+            <if test="isSpeed != null">is_speed,</if>
+            <if test="jobId != null">job_id,</if>
+            <if test="vid != null">vid,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="fileId != null">#{fileId},</if>
@@ -146,6 +149,9 @@
             <if test="projectId != null">#{projectId},</if>
             <if test="userId != null">#{userId},</if>
             <if test="isFirst != null">#{isFirst},</if>
+            <if test="isSpeed != null">#{isSpeed},</if>
+            <if test="jobId != null">#{jobId},</if>
+            <if test="vid != null">#{vid},</if>
         </trim>
     </insert>
     <insert id="insertBatchFsUserCourseVideo" parameterType="FsUserCourseVideo" useGeneratedKeys="true" keyProperty="videoId">
@@ -233,6 +239,10 @@
             <if test="listingEndTime != null">listing_end_time = #{listingEndTime},</if>
             <if test="projectId != null">project_id = #{projectId},</if>
             <if test="isFirst != null">is_first = #{isFirst},</if>
+            <if test="isSpeed != null">is_speed = #{isSpeed},</if>
+            <if test="isOnPut != null">is_on_put = #{isOnPut},</if>
+            <if test="jobId != null">job_id = #{jobId},</if>
+            <if test="vid != null">vid = #{vid},</if>
         </trim>
         where video_id = #{videoId}
     </update>

+ 5 - 12
fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

@@ -1,37 +1,31 @@
 package com.fs.app.controller.course;
 
 
-
 import cn.hutool.core.util.ObjectUtil;
-import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fs.app.annotation.Login;
 import com.fs.app.annotation.UserOperationLog;
 import com.fs.app.controller.AppBaseController;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
-import com.fs.app.annotation.Login;
-import com.fs.common.core.domain.model.LoginUser;
-import com.fs.common.utils.SecurityUtils;
-import com.fs.course.dto.BatchSendCourseDTO;
-import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.param.FsCourseQuestionAnswerUParam;
 import com.fs.course.param.FsCourseSendRewardUParam;
 import com.fs.course.param.FsUserCourseVideoFinishUParam;
 import com.fs.course.param.newfs.FsUserCourseAddCompanyUserParam;
 import com.fs.course.param.newfs.FsUserCourseVideoLinkParam;
 import com.fs.course.param.newfs.FsUserCourseVideoUParam;
-import com.fs.course.service.*;
+import com.fs.course.service.IFsCourseLinkService;
+import com.fs.course.service.IFsCourseQuestionBankService;
+import com.fs.course.service.IFsUserCourseService;
+import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.FsUserCourseVideoH5VO;
 import com.fs.course.vo.newfs.FsUserCourseVideoLinkDetailsVO;
 import com.fs.his.domain.FsUser;
 import com.fs.his.enums.FsUserOperationEnum;
-import com.fs.im.dto.OpenImResponseDTO;
-import com.fs.im.service.OpenIMService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
@@ -57,7 +51,6 @@ public class CourseFsUserController extends AppBaseController {
     private IFsCourseQuestionBankService questionBankService;
 
 
-
     @Login
     @ApiOperation("判断是否添加客服(是否关联销售)")
     @PostMapping("/isAddKf")