Kaynağa Gözat

火山云代码提交、处理销售展示分页异常问题、导出优化、营期统计没数据问题处理

yjwang 4 hafta önce
ebeveyn
işleme
4cbd709d2b
34 değiştirilmiş dosya ile 1033 ekleme ve 42 silme
  1. 28 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseLinkController.java
  2. 44 3
      fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java
  3. 32 2
      fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java
  4. 114 4
      fs-admin/src/main/java/com/fs/qw/qwTask/qwTask.java
  5. 13 2
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseRedPacketLogController.java
  6. 6 0
      fs-service/pom.xml
  7. 8 0
      fs-service/src/main/java/com/fs/config/cloud/CloudHostProper.java
  8. 53 0
      fs-service/src/main/java/com/fs/core/config/VolcEngineConfiguration.java
  9. 30 0
      fs-service/src/main/java/com/fs/core/service/impl/STSService.java
  10. 4 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java
  11. 5 0
      fs-service/src/main/java/com/fs/course/domain/FsVideoResource.java
  12. 17 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  13. 21 0
      fs-service/src/main/java/com/fs/course/param/FsCourseLinkRoomNewParam.java
  14. 58 0
      fs-service/src/main/java/com/fs/course/service/HsyAssumeRoleService.java
  15. 22 0
      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. 329 18
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  18. 121 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserVideoServiceImpl.java
  19. 47 0
      fs-service/src/main/java/com/fs/course/service/impl/VodUploadMediaProcessListener.java
  20. 1 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  21. 2 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java
  22. 5 2
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java
  23. 5 5
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  24. 8 2
      fs-service/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java
  25. 13 0
      fs-service/src/main/resources/application-common.yml
  26. 2 0
      fs-service/src/main/resources/application-config-dev-yjb.yml
  27. 2 0
      fs-service/src/main/resources/application-config-druid-bnkc.yml
  28. 2 0
      fs-service/src/main/resources/application-config-druid-yjb.yml
  29. 1 1
      fs-service/src/main/resources/application-druid-bnkc-test.yml
  30. 6 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  31. 0 1
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  32. 11 0
      fs-service/src/main/resources/mapper/qw/QwUserMapper.xml
  33. 2 2
      fs-service/src/main/resources/mapper/qw/QwWatchLogMapper.xml
  34. 15 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java

+ 28 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseLinkController.java

@@ -11,7 +11,10 @@ import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.course.domain.FsCourseLink;
 import com.fs.course.param.FsCourseLinkCreateParam;
+import com.fs.course.param.FsCourseLinkRoomNewParam;
 import com.fs.course.service.IFsCourseLinkService;
+import com.fs.course.service.IFsUserCourseVideoService;
+import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -31,6 +34,9 @@ public class FsCourseLinkController extends BaseController
     @Autowired
     private IFsCourseLinkService fsCourseLinkService;
 
+    @Autowired
+    private IFsUserCourseVideoService fsUserCourseVideoService;
+
     /**
      * 查询短链列表
      */
@@ -109,4 +115,26 @@ public class FsCourseLinkController extends BaseController
         return fsCourseLinkService.createLinkUrl(param);
     }
 
+    /**
+     * 创建课程看课链接
+     * @param param
+     * @return
+     */
+    //@PreAuthorize("@ss.hasPermi('course:courseLink:createRoomLink')")
+    @PostMapping("/createRoomLink")
+    @ApiOperation("创建群发链接")
+    public R createRoomLink(@RequestBody FsCourseLinkRoomNewParam param) {
+
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+        if (param.getVideoId()==null){
+            return R.error("视频id不能为空");
+        }
+        if (param.getQwUserId()==null){
+            return R.error("销售企微不能为空");
+        }
+
+        return fsUserCourseVideoService.createRoomMiniLinkByCourse(param);
+    }
 }

+ 44 - 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,17 @@ 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 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 +61,11 @@ public class FsUserVideoController extends BaseController
     @Autowired
     private IFsUserCourseVideoService fsUserCourseVideoService;
 
+    @Autowired
+    private RedisTemplate redisTemplate;
+    @Autowired
+    private HsyAssumeRoleService hsyAssumeRoleService;
+
     /**
      * 查询课堂视频列表
      */
@@ -389,4 +395,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);
+    }
 }

+ 32 - 2
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,10 @@ public class FsVideoResourceController extends BaseController {
 
     @Autowired
     private ISysConfigService configService;
+    @Autowired
+    private IFsUserVideoService fsUserVideoService;
+    @Autowired
+    private IFsUserCourseVideoService fsUserCourseVideoService;
 
     /**
      * 查询视频素材库列表
@@ -98,7 +107,15 @@ public class FsVideoResourceController extends BaseController {
             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();
     }
 
@@ -184,7 +201,20 @@ public class FsVideoResourceController extends BaseController {
                 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();
     }
 }

+ 114 - 4
fs-admin/src/main/java/com/fs/qw/qwTask/qwTask.java

@@ -1,21 +1,29 @@
 package com.fs.qw.qwTask;
 
 import com.fs.course.service.IFsUserCourseService;
-import com.fs.qw.service.IQwExternalContactService;
-import com.fs.qw.service.IQwGroupMsgService;
-import com.fs.qw.service.IQwMaterialService;
-import com.fs.qw.service.IQwUserService;
+import com.fs.qw.domain.QwIpadServerLog;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.service.*;
 import com.fs.sop.service.impl.QwSopLogsServiceImpl;
 import com.fs.sop.service.impl.QwSopServiceImpl;
 import com.fs.sop.service.ISopUserLogsService;
 import com.fs.statis.IFsStatisQwWatchService;
 import com.fs.statis.service.FsStatisSalerWatchService;
+import com.fs.wxwork.dto.WxWorkGetQrCodeDTO;
+import com.fs.wxwork.service.WxWorkService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import java.time.Duration;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
 
 @Component("qwTask")
 public class qwTask {
@@ -49,7 +57,20 @@ public class qwTask {
 
     @Autowired
     private IQwMaterialService iQwMaterialService;
+    @Autowired
+    private QwUserMapper qwUserMapper;
+
+    @Autowired
+    private IQwIpadServerService ipadServerService;
+
+    @Autowired
+    private IQwIpadServerLogService qwIpadServerLogService;
+
+    @Autowired
+    private IQwIpadServerUserService qwIpadServerUserService;
 
+    @Autowired
+    private WxWorkService wxWorkService;
 
     //正在使用
     public void qwExternalContact()
@@ -199,4 +220,93 @@ public class qwTask {
     public void updateMaterialByTwoDays(){
         iQwMaterialService.updateQwMaterialByQw();
     }
+
+    /**
+     * 定时清除 占着茅坑不拉屎的ipad 账号
+     */
+    public void selectQwUserByUnbindIpad(){
+
+        //查询所有状态为 绑定了AI主机的
+        List<QwUser> list = qwUserMapper.selectQwUserByTest();
+        for (QwUser qwUser : list) {
+            try {
+
+                Long serverId = qwUser.getServerId();
+
+                if (serverId==null){
+                    System.out.println("serverId不存在");
+                }else {
+                    //没绑定销售 或者 已经离职
+                    if (qwUser.getStatus()==0 || qwUser.getIsDel()==2){
+
+                        updateIpadStatus(qwUser,serverId);
+                    }
+
+                    //绑定了销售-也绑定了ipad,但是长时间离线的(离线状态,无操作超过2天的,也自动解绑)
+                    if(qwUser.getCreateTime()!=null){
+                        Date createTime = qwUser.getCreateTime();
+                        Integer serverStatus = qwUser.getServerStatus();
+                        Integer ipadStatus = qwUser.getIpadStatus();
+
+                        boolean result = isCreateTimeMoreThanDaysWithOptional(createTime, 2);
+                        //大于2天 ,绑定了ipad,离线
+                        if(result && serverStatus==1 && ipadStatus==0){
+                            updateIpadStatus(qwUser,serverId);
+
+                        }
+                    }
+
+
+                }
+
+
+            } catch (Exception e) {
+                System.out.println("解绑ipad报错"+e);
+
+            }
+        }
+    }
+
+    public void updateIpadStatus(QwUser qwUser,Long serverId){
+        QwUser u = new QwUser();
+        u.setId(qwUser.getId());
+        u.setServerId(null);
+        u.setServerStatus(0);
+        qwUserMapper.updateQwUser(u);
+        ipadServerService.addServer(serverId);
+        QwIpadServerLog qwIpadServerLog = new QwIpadServerLog();
+        qwIpadServerLog.setType(2);
+        qwIpadServerLog.setTilie("解绑");
+        qwIpadServerLog.setServerId(serverId);
+        qwIpadServerLog.setQwUserId(qwUser.getId());
+        qwIpadServerLog.setCompanyUserId(qwUser.getCompanyUserId());
+        qwIpadServerLog.setCompanyId(qwUser.getCompanyId());
+        qwIpadServerLog.setCreateTime(new Date());
+        qwIpadServerLogService.insertQwIpadServerLog(qwIpadServerLog);
+        qwIpadServerUserService.deleteQwIpadServerUserByQwUserId(qwUser.getId());
+        WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
+        wxWorkGetQrCodeDTO.setUuid(qwUser.getUid());
+        wxWorkService.LoginOut(wxWorkGetQrCodeDTO,qwUser.getServerId());
+        updateIpadStatus(qwUser.getId(),0);
+    }
+
+    public static boolean isCreateTimeMoreThanDaysWithOptional(Date createTime, int days) {
+        return Optional.ofNullable(createTime)
+                .map(time -> {
+                    LocalDateTime createDateTime = time.toInstant()
+                            .atZone(ZoneId.systemDefault())
+                            .toLocalDateTime();
+                    LocalDateTime now = LocalDateTime.now();
+                    Duration duration = Duration.between(createDateTime, now);
+                    return duration.toDays() > days;
+                })
+                .orElse(false); // 为null时返回false,可根据需求调整
+    }
+
+    void updateIpadStatus(Long id ,Integer status){
+        QwUser u = new QwUser();
+        u.setId(id);
+        u.setIpadStatus(status);
+        qwUserMapper.updateQwUser(u);
+    }
 }

+ 13 - 2
fs-company/src/main/java/com/fs/company/controller/course/FsCourseRedPacketLogController.java

@@ -9,6 +9,7 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.course.config.CourseConfig;
@@ -128,9 +129,19 @@ public class FsCourseRedPacketLogController extends BaseController
     @GetMapping("/export")
     public AjaxResult export(FsCourseRedPacketLogParam fsCourseRedPacketLog)
     {
-//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-//        fsCourseRedPacketLog.setCompanyId( loginUser.getCompany().getCompanyId());
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        fsCourseRedPacketLog.setCompanyId( loginUser.getCompany().getCompanyId());
+
+        if(fsCourseRedPacketLog.getSTime() == null){
+            throw new ServiceException("操作失败,请选择开始时间!");
+        }
+        if(fsCourseRedPacketLog.getETime() == null){
+            throw new ServiceException("操作失败,请选择结束时间!");
+        }
 
+        if(fsCourseRedPacketLog.getCourseId() == null){
+            throw new ServiceException("操作失败,请选择课程!");
+        }
         if (fsCourseRedPacketLog.getPhoneMk()!=null&&fsCourseRedPacketLog.getPhoneMk()!=""){
             fsCourseRedPacketLog.setPhone(encryptPhone(fsCourseRedPacketLog.getPhoneMk()));
         }

+ 6 - 0
fs-service/pom.xml

@@ -305,6 +305,12 @@
             <version>1.0.2</version>
         </dependency>
 
+        <!--火山云sdk-->
+        <dependency>
+            <groupId>com.volcengine</groupId>
+            <artifactId>volc-sdk-java</artifactId>
+            <version>1.0.250</version>
+        </dependency>
     </dependencies>
 
 </project>

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

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

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

@@ -0,0 +1,30 @@
+package com.fs.core.service.impl;
+
+import com.fs.config.cloud.CloudHostProper;
+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;
+//    }
+
+}

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

@@ -113,4 +113,8 @@ 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;
 }

+ 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;
@@ -239,6 +240,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);
 
@@ -255,4 +262,14 @@ public interface FsUserCourseVideoMapper
     List<FsUserCourseVideoAppletVO> getFsUserCourseVideoAppletVOListByIds(@Param("videoIds") List<Long> videoIds);
 
     FsUserCourseVO selectFsUserCourseVideoVoByVideoIdAndCourdeId(@Param("videoId") Long videoId,@Param("courseId") Long courseId);
+
+    @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();
 }

+ 21 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseLinkRoomNewParam.java

@@ -0,0 +1,21 @@
+package com.fs.course.param;
+
+import lombok.Data;
+
+/**
+ * 这个是弘珍的功能,创建课程群发链接
+ */
+@Data
+public class FsCourseLinkRoomNewParam {
+
+    private Long videoId;
+
+    private Long qwUserId;
+
+    private String corpId;
+
+    private Long courseId;
+
+    private String title;//视频标题
+
+}

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

+ 22 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java

@@ -208,4 +208,26 @@ public interface IFsUserCourseVideoService
      * 查询选择使用的视频列表
      */
     List<FsUserCourseVideoChooseVO> getChooseCourseVideoListByMap(Map<String, Object> params);
+
+    /**
+     * 上传视频到火山云通过URL(把华为云的视频传到火山去并且存储JOBID)
+     * @return
+     */
+    R uploadVideoToHuoShanByUrl();
+
+    /**
+     * 通过jobId拿vid
+     * @return
+     */
+    R getVidByJob();
+
+    /**
+     * 通过vid获取视频路径并且组装地址存到替换线路二
+     * @return
+     */
+    R getVideoInfoByVid();
+
+    R createRoomMiniLinkByCourse(FsCourseLinkRoomNewParam param);
+
+    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);
 }

+ 329 - 18
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -15,10 +15,8 @@ import com.fs.common.core.domain.entity.SysDictData;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.enums.BizResponseEnum;
 import com.fs.common.exception.CustomException;
-import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.DateUtils;
-import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
 import com.fs.company.constant.CompanyTrafficConstants;
@@ -37,10 +35,7 @@ import com.fs.course.dto.CoursePackageDTO;
 import com.fs.course.mapper.*;
 import com.fs.course.param.*;
 import com.fs.course.param.newfs.*;
-import com.fs.course.service.IFsUserCompanyBindService;
-import com.fs.course.service.IFsUserCompanyUserService;
-import com.fs.course.service.IFsUserCourseVideoService;
-import com.fs.course.service.IFsVideoResourceService;
+import com.fs.course.service.*;
 import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.*;
 import com.fs.his.domain.FsUser;
@@ -62,36 +57,38 @@ import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.param.FsUserCourseRedPageParam;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwExternalContactService;
-import com.fs.qw.vo.SortDayVo;
 import com.fs.qwApi.Result.QwAddContactWayResult;
-import com.fs.qwApi.Result.QwGroupChatDetailsResult;
 import com.fs.qwApi.param.QwAddContactWayParam;
 import com.fs.qwApi.service.QwApiService;
-import com.fs.sop.domain.QwSopTempDay;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import com.fs.sop.service.ISopUserLogsInfoService;
-import com.fs.system.domain.SysConfig;
 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.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.apache.rocketmq.spring.core.RocketMQTemplate;
-import org.jetbrains.annotations.NotNull;
 import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
-import org.redisson.client.RedisClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.annotation.Autowired;
 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;
 
@@ -100,10 +97,9 @@ import java.math.RoundingMode;
 import java.text.SimpleDateFormat;
 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;
 
@@ -257,7 +253,8 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     @Autowired
     private BalanceRollbackErrorMapper balanceRollbackErrorMapper;
 
-
+    @Autowired
+    private IFsCourseLinkService linkService;
 
 
     /**
@@ -3317,5 +3314,319 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         return fsUserCourseVideoMapper.getChooseCourseVideoListByMap(params);
     }
 
+    @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();
+    }
+
+    @Override
+    public R createRoomMiniLinkByCourse(FsCourseLinkRoomNewParam param) {
+
+        QwUser qwUser = qwUserMapper.selectQwUserById(param.getQwUserId());
+
+        if (qwUser == null || qwUser.getCompanyId() == null || qwUser.getCompanyUserId() == null) {
+            return R.error("员工未绑定 销售公司 或 销售 请先绑定");
+        }
+
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(param.getCorpId());
+
+        if (qwCompany == null) {
+            return R.error().put("msg", "企业不存在,请联系管理员");
+        }
+        if (qwCompany.getMiniAppId() == null) {
+            return R.error().put("msg", "当前企业未绑定小程序,请联系管理员");
+        }
+
+        //生成小程序链接
+        String linkByMiniApp = createLinkByMiniApp(new Date(), param.getCourseId(), param.getVideoId(), qwUser, null, 2, null, 1);
+
+        if(StringUtils.isNotEmpty(linkByMiniApp)){
+            linkByMiniApp = linkByMiniApp.replaceAll(".html", "");
+        }
+        String link = linkService.getGotoWxAppLink(linkByMiniApp, qwCompany.getMiniAppId());
+
+        System.out.println("生成课程通链: " + link + " :" + param);
+
+        return R.ok().put("link", link);
+    }
+
+    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 {
+                if (StringUtils.isEmpty(resp.getResult().getData().getMediaInfoList(0).getVid())){
+                    return;
+                }
+                FsVideoResource video = new FsVideoResource();
+                video.setId(videoResource.getId());
+                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 url = cloudHostProper.volcengineUrl+"/"+resp.getResult().getMediaInfoList(0).getSourceInfo().getStoreUri();
+
+                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());
+                }
+            }
+        }
+    }
+
 }
 

+ 121 - 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,109 @@ 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 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java

@@ -442,4 +442,5 @@ public interface QwUserMapper extends BaseMapper<QwUser>
 
     List<QwOptionsVO> selectQwCompanyListOptionsVOBySys();
 
+    List<QwUser> selectQwUserByTest();
 }

+ 2 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java

@@ -203,6 +203,8 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
             String replace = param.getIds().replace("(", "").replace(")", "");
             param.setIdsList(Arrays.asList(replace.split(",")));
         }
+        param.setPageNum(null);
+        param.setPageSize(null);
         Long total = qwWatchLogMapper.selectQwExtCountByDayAndCount(param);
         rspData.setRows(vos);
         rspData.setTotal(total);

+ 5 - 2
fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java

@@ -389,6 +389,9 @@ List<SopUserLogsVO> selectSopUserLogsGroupListByParam(@Param("maps") SopUserLogs
     void replaceUser(@Param("vo") ReplaceUserDto vo);
 
     @DataSource(DataSourceType.SOP)
-    @Select("SELECT * FROM sop_user_logs WHERE sop_id = #{sopId} AND qw_user_id = #{qwUserId}")
-    SopUserLogs queryUserBySopId(@Param("qwUserId")String qwUserId,@Param("sopId") String sopId);
+    @Select("<script>" +"SELECT * FROM sop_user_logs WHERE sop_id = #{sopId} AND qw_user_id = #{qwUserId} " +
+            "<if test=\"chatId != null and chatId != ''\"> and chat_id = #{chatId} </if>" +
+            " limit 1 " +
+            "</script>")
+    SopUserLogs queryUserBySopId(@Param("qwUserId")String qwUserId,@Param("sopId") String sopId,@Param("chatId") String chatId);
 }

+ 5 - 5
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -558,7 +558,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     sopLogs.setQwUserKey(qwUser.getId());
 
                     // 设置实际发送人
-                    updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId());
+                    updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId(),groupUser.getChatId());
                     //域名
                     String companyUserId = qwUser.getCompanyUserId().toString();
                     String domainName = companyUserMapper.selectDomainByUserId(Long.parseLong(companyUserId));
@@ -696,7 +696,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     sopLogs.setExternalUserName(groupChat.getName());
                     sopLogs.setQwUserKey(qwUser.getId());
                     // 设置实际发送人
-                    updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId());
+                    updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId(),groupChat.getChatId());
 
                     QwSopCourseFinishTempSetting setting = new QwSopCourseFinishTempSetting();
 
@@ -1011,7 +1011,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 }
                 sopLogs.setContentJson(JSON.toJSONString(setting));
                 // 设置实际发送人
-                updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId());
+                updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId(),null);
 
                 sopLogsList.add(sopLogs);
             });
@@ -1025,9 +1025,9 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return R.ok();
     }
 
-    private void updateQwUserKey(QwSopLogs sopLogs, String qwUserId, String sopId) {
+    private void updateQwUserKey(QwSopLogs sopLogs, String qwUserId, String sopId,String chatId) {
         // 设置实际发送人
-        SopUserLogs qwSopLogs = sopUserLogsMapper.queryUserBySopId(qwUserId,sopId);
+        SopUserLogs qwSopLogs = sopUserLogsMapper.queryUserBySopId(qwUserId,sopId,chatId);
         if (qwSopLogs != null && ObjectUtil.isNotEmpty(qwSopLogs.getActualQwId())){
             // sopLogs.setQwUserid(qwUser.getQwUserId());
             sopLogs.setQwUserKey(qwSopLogs.getActualQwId());

+ 8 - 2
fs-service/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java

@@ -120,8 +120,14 @@ public class WxWorkServiceImpl implements WxWorkService {
     @Override
     public WxWorkResponseDTO<WxwLoginOutRespDTO> LoginOut(WxWorkGetQrCodeDTO param,Long serverId) {
         String url = getUrl(serverId) + "/LoginOut";
-        return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<WxwLoginOutRespDTO>>() {
-        });
+        try {
+            return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<WxwLoginOutRespDTO>>() {
+            });
+        }catch (Exception e){
+            log.error("LoginOut error",e);
+            return null;
+        }
+
     }
 
     @Override

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

@@ -140,3 +140,16 @@ image:
   storage:
     local-path: C:\logoFile\logo.jpg
     server-path: C:\logoFile\logo.jpg
+
+# application.properties
+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-yjb.yml

@@ -107,6 +107,8 @@ tmp_secret_config:
 cloud_host:
   company_name: 医健宝
   projectCode: YJB
+  spaceName: bjyjb-2114522511
+  volcengineUrl: https://bjyjbvolcengine.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-bnkc.yml

@@ -85,6 +85,8 @@ tmp_secret_config:
 cloud_host:
   company_name: 百年康成
   projectCode: BNKC
+  spaceName: bnkc-2114522511
+  volcengineUrl: https://bjbnkcvolcengine.ylrztop.com
 headerImg:
   imgUrl: https://bnkc-1363824368.cos.ap-chongqing.myqcloud.com/fs/logo/bnkc.png
 ipad:

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

@@ -86,6 +86,8 @@ tmp_secret_config:
 cloud_host:
   company_name: 医健宝
   projectCode: YJB
+  spaceName: bjyjb-2114522511
+  volcengineUrl: https://bjyjbvolcengine.ylrztop.com
 headerImg:
   imgUrl: https://drk-1363981074.cos.ap-chongqing.myqcloud.com/fs/logo/30d7a0d1ec31e5ac16c6e96d5ca76ad.png
 ipad:

+ 1 - 1
fs-service/src/main/resources/application-druid-bnkc-test.yml

@@ -39,7 +39,7 @@ spring:
             druid:
                 # 主库数据源
                 master:
-                  url: jdbc:mysql://nj-cdb-n80v6uox.sql.tencentcdb.com:22935/fs_his_test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                  url: jdbc:mysql://nj-cdb-n80v6uox.sql.tencentcdb.com:22935/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                   username: root
                   password: Ylrz_1q2w3e4r5t6y
                 # 从库数据源

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

+ 0 - 1
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -382,7 +382,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 AND ucu.project_id = #{projectId}
             </if>
         </where>
-        limit ${(pageNum-1)*pageSize},${pageSize}
     </select>
 
     <select id="selectFsUserPageListCount" resultType="java.lang.Long">

+ 11 - 0
fs-service/src/main/resources/mapper/qw/QwUserMapper.xml

@@ -290,4 +290,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </if>
     </select>
 
+    <select id="selectQwUserByTest" resultMap="QwUserResult">
+        <include refid="selectQwUserVo"/>
+        where  server_status = 1
+    </select>
+    <select id="selectQwUserByServerIds" resultType="com.fs.qw.domain.QwUser">
+        <include refid="selectQwUserVo"/>
+        where  server_id in
+        <foreach collection="serverIds" item="serverId" open="(" close=")" separator=",">
+            #{serverId}
+        </foreach>
+    </select>
 </mapper>

+ 2 - 2
fs-service/src/main/resources/mapper/qw/QwWatchLogMapper.xml

@@ -212,8 +212,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         JOIN
         qw_user qu ON qec.qw_user_id = qu.id
         WHERE
-        DATE(qec.create_time) &gt;= DATE(#{sTime})
-        AND DATE(qec.create_time) &lt;= DATE(#{eTime})
+        qec.create_time &gt;= #{sTime}
+        AND qec.create_time &lt; DATE_ADD(#{eTime}, INTERVAL 1 DAY)
         AND qec.company_id = #{companyId}
         <if test='nickName != null and nickName != ""'>
             AND qu.qw_user_name LIKE CONCAT(#{nickName}, '%')

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

@@ -410,6 +410,21 @@ public class CourseQwController extends AppBaseController {
         return courseVideoService.updateVideo();
     }
 
+    @GetMapping("/testJob")
+    public R testJob() {
+        return courseVideoService.uploadVideoToHuoShanByUrl();
+    }
+
+    @GetMapping("/testVid")
+    public R testVid() {
+        return courseVideoService.getVidByJob();
+    }
+
+    @GetMapping("/testUpdateLineTwo")
+    public R testUpdateLineTwo() {
+        return courseVideoService.getVideoInfoByVid();
+    }
+
     @ApiOperation("查询课程")
     @GetMapping("/getCourseByCourseId")
     public R getCourseByCourseId(@RequestParam Long courseId)