瀏覽代碼

红德堂-火山云代码同步

Long 1 周之前
父節點
當前提交
06b9dab11f
共有 31 個文件被更改,包括 997 次插入98 次删除
  1. 46 3
      fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java
  2. 37 6
      fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java
  3. 8 8
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  4. 11 8
      fs-service/pom.xml
  5. 13 13
      fs-service/src/main/java/com/cloud/host/CloudHostConfig.java
  6. 8 0
      fs-service/src/main/java/com/fs/config/cloud/CloudHostProper.java
  7. 30 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. 3 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java
  10. 5 0
      fs-service/src/main/java/com/fs/course/domain/FsVideoResource.java
  11. 18 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java
  12. 17 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  13. 58 0
      fs-service/src/main/java/com/fs/course/service/HsyAssumeRoleService.java
  14. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseTrafficLogService.java
  15. 21 1
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  16. 6 0
      fs-service/src/main/java/com/fs/course/service/IFsUserVideoService.java
  17. 3 2
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseLinkServiceImpl.java
  18. 82 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java
  19. 298 3
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  20. 122 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserVideoServiceImpl.java
  21. 47 0
      fs-service/src/main/java/com/fs/course/service/impl/VodUploadMediaProcessListener.java
  22. 6 6
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  23. 48 48
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  24. 8 0
      fs-service/src/main/resources/application-common.yml
  25. 2 0
      fs-service/src/main/resources/application-config-dev.yml
  26. 2 0
      fs-service/src/main/resources/application-config-druid-hdt.yml
  27. 2 0
      fs-service/src/main/resources/application-config-myhk.yml
  28. 27 0
      fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml
  29. 6 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  30. 2 0
      fs-user-app/src/main/java/com/fs/app/controller/WxPayController.java
  31. 23 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java

+ 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);
+    }
 }

+ 37 - 6
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;
@@ -13,12 +14,15 @@ import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
 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 +34,7 @@ import java.util.stream.Collectors;
 /**
  * 资源库管理
  */
+@Slf4j
 @RestController
 @RequestMapping("/course/videoResource")
 @AllArgsConstructor
@@ -42,6 +47,11 @@ public class FsVideoResourceController extends BaseController {
 
     @Autowired
     private ISysConfigService configService;
+    @Autowired
+    private IFsUserVideoService fsUserVideoService;
+    @Autowired
+    private IFsUserCourseVideoService fsUserCourseVideoService;
+
 
     /**
      * 查询视频素材库列表
@@ -88,17 +98,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();
     }
 
@@ -183,8 +201,21 @@ public class FsVideoResourceController extends BaseController {
             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();
     }
 }

+ 8 - 8
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -912,14 +912,14 @@ public class QwUserController extends BaseController
         return R.ok();
     }
 
-    /**
-     * 重启云主机
-     * @return
-     */
-    @PutMapping("/restartHost")
-    public R restartCloudHost(@RequestParam String serverIp) {
-        return qwUserService.restartCloudHost(serverIp);
-    }
+//    /**
+//     * 重启云主机
+//     * @return
+//     */
+//    @PutMapping("/restartHost")
+//    public R restartCloudHost(@RequestParam String serverIp) {
+//        return qwUserService.restartCloudHost(serverIp);
+//    }
     @PostMapping("/updateSendType")
     public R updateSendType(@RequestBody UpdateSendTypeVo vo) {
         return qwUserService.updateSendType(vo);

+ 11 - 8
fs-service/pom.xml

@@ -285,17 +285,20 @@
         </dependency>
 
         <!-- 移动云ECS SDK -->
+<!--        <dependency>-->
+<!--            <groupId>com.ecloud.sdk</groupId>-->
+<!--            <artifactId>ecloud-sdk-ecs</artifactId>-->
+<!--            <version>1.1.26</version>-->
+<!--        </dependency>-->
+
+        <!--火山云sdk-->
         <dependency>
-            <groupId>com.ecloud.sdk</groupId>
-            <artifactId>ecloud-sdk-ecs</artifactId>
-            <version>1.1.26</version>
-        </dependency>
-        <dependency>
-            <groupId>commons-io</groupId>
-            <artifactId>commons-io</artifactId>
-            <version>2.11.0</version>
+            <groupId>com.volcengine</groupId>
+            <artifactId>volc-sdk-java</artifactId>
+            <version>1.0.250</version>
         </dependency>
 
+
     </dependencies>
 
 </project>

+ 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);
+//    }
 
 }

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

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

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

@@ -0,0 +1,30 @@
+package com.fs.core.config;
+
+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;
+    }
+}
+

+ 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;
+//    }
+
+}

+ 3 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java

@@ -112,4 +112,7 @@ public class FsUserCourseVideo extends BaseEntity
 
     private Long listingEndTime;//商品结束售卖时间
 
+    private String jobId;
+
+    private String vid;
 }

+ 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;
 }

+ 18 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java

@@ -195,4 +195,22 @@ public interface FsCourseTrafficLogMapper
     @Select("SELECT SUM(T.internet_traffic)/1024 AS totalInternetTraffic FROM fs_course_traffic_log T " +
             "WHERE DATE(T.create_time) = DATE(CURDATE() - INTERVAL 1 DAY) AND T.company_id = #{companyId} GROUP BY T.company_id ")
     Long sumTrafficByCompanyYesterday(Long companyId);
+
+    /**
+     * 查询过期的流量记录ID列表(分页)
+     */
+    List<Long> selectExpireLinkIds(@Param("createTime") Date createTime,
+                                   @Param("offset") int offset,
+                                   @Param("limit") int limit);
+
+    /**
+     * 批量删除ID列表
+     * @return 删除的行数
+     */
+    int batchDeleteByIds(@Param("ids") List<Long> ids);
+
+    /**
+     * 查询过期记录总数
+     */
+    Long countExpireLink(@Param("createTime") Date createTime);
 }

+ 17 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java

@@ -1,6 +1,7 @@
 package com.fs.course.mapper;
 
 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;
@@ -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")
+    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));
+
+    }
+}

+ 2 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseTrafficLogService.java

@@ -80,4 +80,6 @@ public interface IFsCourseTrafficLogService
      * 定时统计流量总数
      */
     void sumTrafficlog();
+
+    void batchDelTraffic();
 }

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

@@ -17,7 +17,7 @@ import com.fs.course.vo.newfs.FsUserVideoListVO;
 import com.fs.his.domain.FsUser;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.param.FsUserCourseRedPageParam;
-import com.fs.sop.domain.QwSopTempDay;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.util.List;
 import java.util.Map;
@@ -209,4 +209,24 @@ public interface IFsUserCourseVideoService
     R sendAppReward(FsCourseSendRewardUParam param);
 
     R isSaveKf(FsUserCourseVideoAddKfUParam 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);
 }

+ 3 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsCourseLinkServiceImpl.java

@@ -773,7 +773,8 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
         CloseableHttpClient client = null;
         try {
             client = HttpClients.createDefault();
-            String[] split = linkStr.split("\\?");
+            String[] split = linkStr.split("\\?.*?=");
+            String key = linkStr.replaceAll(".*\\?(.*?)=.*", "$1");
             if (split.length == 2 && split[0].length() > 0 && split[1].length() > 0) {
                 //处理页面路径
                 String pageUrl = split[0];
@@ -782,7 +783,7 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
                 }
                 //处理参数
                 String query = split[1];
-                query = URLEncoder.encode(query, StandardCharsets.UTF_8.toString());
+                query = key + "=" + URLEncoder.encode(query, StandardCharsets.UTF_8.toString());
 //                String json = configService.selectConfigByKey("course.config");
 //                CourseConfig config = JSON.parseObject(json, CourseConfig.class);
 //                String miniprogramAppid = config.getMiniprogramAppid();

+ 82 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java

@@ -1,6 +1,8 @@
 package com.fs.course.service.impl;
 
 import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.ZoneId;
 import java.util.*;
 
 import com.alibaba.fastjson.JSONObject;
@@ -402,4 +404,84 @@ public class FsCourseTrafficLogServiceImpl implements IFsCourseTrafficLogService
                 minutes % 60,
                 seconds % 60);
     }
+
+    @Override
+    public void batchDelTraffic() {
+        // 设置删除的时间条件(2025-09-01之前)
+        LocalDate targetLocalDate = LocalDate.of(2025, 10, 1);
+        Date targetDate = Date.from(targetLocalDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+
+        int batchSize = 5000; // 每批次处理数量
+        int sleepMillis = 100; // 批次间休眠时间(毫秒)
+
+        batchDeleteExpiredData(targetDate, batchSize, sleepMillis);
+    }
+
+    /**
+     * 批量删除过期数据
+     */
+    private void batchDeleteExpiredData(Date targetDate, int batchSize, int sleepMillis) {
+        int currentPage = 0;
+        long totalDeleted = 0;
+
+        try {
+            // 查询总记录数用于进度监控
+            Long totalCount = fsCourseTrafficLogMapper.countExpireLink(targetDate);
+            log.info("开始批量删除过期流量记录,目标时间: {}, 总记录数: {}", targetDate, totalCount);
+
+            if (totalCount == null || totalCount == 0) {
+                log.info("没有需要删除的过期记录");
+                return;
+            }
+
+            long startTime = System.currentTimeMillis();
+
+            while (true) {
+                // 分页查询过期记录的log_id
+                int offset = currentPage * batchSize;
+                List<Long> logIds = fsCourseTrafficLogMapper.selectExpireLinkIds(targetDate, offset, batchSize);
+
+                if (logIds == null || logIds.isEmpty()) {
+                    log.info("批量删除完成,共删除 {} 条记录", totalDeleted);
+                    break;
+                }
+
+                // 批量删除当前批次的log_id
+                int deletedCount = fsCourseTrafficLogMapper.batchDeleteByIds(logIds);
+                totalDeleted += deletedCount;
+
+                // 每5批次或最后一批输出日志
+                if (currentPage % 5 == 0 || logIds.size() < batchSize) {
+                    double progress = (double) totalDeleted / totalCount * 100;
+                    long currentTime = System.currentTimeMillis();
+                    long elapsedSeconds = (currentTime - startTime) / 1000;
+
+                    log.info("批次 {}: 删除 {} 条,进度: {}/{} ({:.2f}%),耗时: {}秒",
+                            currentPage + 1, deletedCount, totalDeleted, totalCount,
+                            progress, elapsedSeconds);
+                }
+
+                currentPage++;
+
+                // 批次间短暂休眠,避免数据库压力过大
+                if (sleepMillis > 0 && logIds.size() == batchSize) {
+                    try {
+                        Thread.sleep(sleepMillis);
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                        log.warn("删除任务被中断");
+                        break;
+                    }
+                }
+            }
+
+            long endTime = System.currentTimeMillis();
+            long totalSeconds = (endTime - startTime) / 1000;
+            log.info("批量删除任务完成,总计删除: {} 条记录,总耗时: {} 秒", totalDeleted, totalSeconds);
+
+        } catch (Exception e) {
+            log.error("批量删除流量记录失败,已删除: {} 条", totalDeleted, e);
+            throw new RuntimeException("批量删除失败", e);
+        }
+    }
 }

+ 298 - 3
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -74,6 +74,16 @@ 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.volcengine.service.vod.IVodService;
+import com.volcengine.service.vod.model.business.VodUrlUploadURLSet;
+import com.volcengine.service.vod.model.request.VodGetMediaInfosRequest;
+import com.volcengine.service.vod.model.request.VodQueryUploadTaskInfoRequest;
+import com.volcengine.service.vod.model.request.VodUpdateMediaPublishStatusRequest;
+import com.volcengine.service.vod.model.request.VodUrlUploadRequest;
+import com.volcengine.service.vod.model.response.VodGetMediaInfosResponse;
+import com.volcengine.service.vod.model.response.VodQueryUploadTaskInfoResponse;
+import com.volcengine.service.vod.model.response.VodUpdateMediaPublishStatusResponse;
+import com.volcengine.service.vod.model.response.VodUrlUploadResponse;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
 import org.redisson.api.RLock;
@@ -92,8 +102,8 @@ import java.math.RoundingMode;
 import java.time.*;
 import java.time.format.DateTimeFormatter;
 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.function.Function;
 import java.util.stream.Collectors;
@@ -290,7 +300,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService {
         }
         String videoRedisKey1 = "h5user:video:duration:" + fsUserCourseVideo.getVideoId();
         redisCache.setCacheObject(videoRedisKey1, fsUserCourseVideo.getDuration());
-        String videoRedisKey2 = "h5wxuser:video:duration" + fsUserCourseVideo.getVideoId();
+        String videoRedisKey2 = "h5wxuser:video:duration:" + fsUserCourseVideo.getVideoId();
         redisCache.setCacheObject(videoRedisKey2, fsUserCourseVideo.getDuration());
         return fsUserCourseVideoMapper.updateFsUserCourseVideo(fsUserCourseVideo);
     }
@@ -3462,6 +3472,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;
+        }
+    }
+}

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

@@ -167,12 +167,12 @@ public interface IQwUserService
     List<Long> selectQwUserListByCuDeptIdList(QwSop qwSop);
 
 
-    /**
-     * 重启云主机
-     * @param IP 服务器ip
-     * @return
-     */
-    R restartCloudHost(String IP);
+//    /**
+//     * 重启云主机
+//     * @param IP 服务器ip
+//     * @return
+//     */
+//    R restartCloudHost(String IP);
 
     /**
      * 获取企微用户信息

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

@@ -6,12 +6,12 @@ import cn.hutool.http.HttpRequest;
 import com.alibaba.fastjson.JSON;
 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.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;
@@ -1375,48 +1375,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) {

+ 8 - 0
fs-service/src/main/resources/application-common.yml

@@ -143,3 +143,11 @@ wechat:
   api:
     base-url: https://api.weixin.qq.com
     upload-shipping-info: /wxa/sec/order/upload_shipping_info
+hsy:
+  access_key: AKLTZTc4YTE4ZjI2OWViNDNjZGI2NjhiYTI5Njc5ZjA1Mzk
+  secret_key: WXpjelpUYzFOakF5TUdObE5EZGtNR0ZsWXpKaU1tTmtZakk1WXpObE4yRQ==
+  region: cn-north-1
+  role_access_key: AKLTNmMwNjJkNDFhYTVjNDIzYzhhNzEyZmZmZTlmYzBhNGM
+  role_secret_key: T0RaaFl6UmhZV1V4WXpKbU5EWTBNMkZpT0RNNU9UY3daak0wTjJFd09XUQ==
+  role_trn: trn:iam::2114522511:role/hylj
+

+ 2 - 0
fs-service/src/main/resources/application-config-dev.yml

@@ -107,6 +107,8 @@ tmp_secret_config:
 cloud_host:
   company_name: 金康健
   projectCode: DEV
+  spaceName: bdhdt-2114522511
+  volcengineUrl: https://bdhdtvolcengine.ylrztop.com
 headerImg:
   imgUrl: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png
 ipad:

+ 2 - 0
fs-service/src/main/resources/application-config-druid-hdt.yml

@@ -86,6 +86,8 @@ tencent_cloud_config:
 cloud_host:
   company_name: 弘德堂
   projectCode: HDT
+  spaceName: bdhdt-2114522511
+  volcengineUrl: https://bdhdtvolcengine.ylrztop.com
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png

+ 2 - 0
fs-service/src/main/resources/application-config-myhk.yml

@@ -87,6 +87,8 @@ tencent_cloud_config:
 cloud_host:
   company_name: 木易华康
   projectCode: MYHK
+  spaceName: myhk-2114522511
+  volcengineUrl: https://myhkvolcengine.ylrztop.com
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://fs-1346741853.cos.ap-chengdu.myqcloud.com/fs/20250323/6189704f2e134b84ad9c9e7c9999f103.jpg

+ 27 - 0
fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml

@@ -348,4 +348,31 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         GROUP BY l.company_id, DATE_FORMAT(l.create_time, '%Y-%m')
     </select>
 
+
+    <select id="selectExpireLinkIds" resultType="java.lang.Long">
+        <![CDATA[
+        SELECT log_id
+        FROM fs_course_traffic_log
+        WHERE create_time < #{createTime}
+        ORDER BY log_id ASC
+            LIMIT #{limit} OFFSET #{offset}
+        ]]>
+    </select>
+
+    <delete id="batchDeleteByIds">
+        DELETE FROM fs_course_traffic_log
+        WHERE log_id IN
+        <foreach collection="ids" item="id" open="(" close=")" separator=",">
+            #{id}
+        </foreach>
+    </delete>
+
+    <select id="countExpireLink" resultType="java.lang.Long">
+        <![CDATA[
+        SELECT COUNT(*)
+        FROM fs_course_traffic_log
+        WHERE create_time < #{createTime}
+        ]]>
+    </select>
+
 </mapper>

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

@@ -108,6 +108,8 @@
             <if test="listingEndTime != null">listing_end_time,</if>
             <if test="userId != null">user_id,</if>
             <if test="isFirst != null">is_first,</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 +148,8 @@
             <if test="projectId != null">#{projectId},</if>
             <if test="userId != null">#{userId},</if>
             <if test="isFirst != null">#{isFirst},</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 +237,8 @@
             <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="jobId != null">job_id = #{jobId},</if>
+            <if test="vid != null">vid = #{vid},</if>
         </trim>
         where video_id = #{videoId}
     </update>

+ 2 - 0
fs-user-app/src/main/java/com/fs/app/controller/WxPayController.java

@@ -124,6 +124,8 @@ public class WxPayController {
                         break;
                     case "product":
                         courseProductOrderService.payConfirm("",orderId[1],tradeNo,"",1,tradeNo,null);
+                    case "payment":
+                        paymentService.payConfirm(orderId[1], outtradeno,tradeNo,null);
                 }
                 return WxPayNotifyResponse.success("处理成功!");
             }else{

+ 23 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java

@@ -360,6 +360,29 @@ public class CourseQwController extends AppBaseController {
 //        courseVideoService.updateVideoUrl();
     }
 
+    @GetMapping("/testJob")
+    public R testJob() {
+        return courseVideoService.uploadVideoToHuoShanByUrl();
+    }
+
+    @GetMapping("/testVid")
+    public R testVid() {
+        return courseVideoService.getVidByJob();
+    }
+
+    @GetMapping("/testUpdateLineTwo")
+    public R testUpdateLineTwo() {
+        return courseVideoService.getVideoInfoByVid();
+    }
+
+    @Autowired
+    private IFsCourseTrafficLogService courseTrafficLogService;
+
+    @GetMapping("/test4")
+    public void test4() {
+        courseTrafficLogService.batchDelTraffic();
+    }
+
     @Login
     @ApiOperation("保存评论数据")
     @PostMapping("/saveMsg")