소스 검색

Merge remote-tracking branch 'origin/master'

jzp 1 주 전
부모
커밋
ad413bf0ee
40개의 변경된 파일2171개의 추가작업 그리고 54개의 파일을 삭제
  1. 14 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java
  2. 14 0
      fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java
  3. 2 2
      fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java
  4. 258 26
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/AsyncCourseWatchFinishService.java
  5. 83 0
      fs-service/src/main/java/com/fs/course/domain/FsBlackTalent.java
  6. 25 0
      fs-service/src/main/java/com/fs/course/domain/FsUserTalent.java
  7. 76 0
      fs-service/src/main/java/com/fs/course/mapper/FsBlackTalentMapper.java
  8. 11 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserTalentFollowMapper.java
  9. 5 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserTalentMapper.java
  10. 25 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserVideoMapper.java
  11. 3 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserVideoTagsMapper.java
  12. 14 0
      fs-service/src/main/java/com/fs/course/param/FsBlackTalentAuditParam.java
  13. 10 0
      fs-service/src/main/java/com/fs/course/param/FsUserTalentFansParam.java
  14. 77 0
      fs-service/src/main/java/com/fs/course/service/IFsBlackTalentService.java
  15. 11 0
      fs-service/src/main/java/com/fs/course/service/IFsUserTalentFollowService.java
  16. 10 0
      fs-service/src/main/java/com/fs/course/service/IFsUserTalentService.java
  17. 12 0
      fs-service/src/main/java/com/fs/course/service/IFsUserVideoService.java
  18. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsUserVideoTagsService.java
  19. 126 0
      fs-service/src/main/java/com/fs/course/service/impl/FsBlackTalentServiceImpl.java
  20. 102 16
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseFinishTempServiceImpl.java
  21. 7 1
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  22. 24 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserTalentFollowServiceImpl.java
  23. 117 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserTalentServiceImpl.java
  24. 93 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserVideoServiceImpl.java
  25. 5 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserVideoTagsServiceImpl.java
  26. 44 0
      fs-service/src/main/java/com/fs/course/vo/FsBlackTalentPVO.java
  27. 14 0
      fs-service/src/main/java/com/fs/course/vo/FsUserTalentFansVo.java
  28. 14 0
      fs-service/src/main/java/com/fs/course/vo/FsUserTalentFollowVo.java
  29. 3 0
      fs-service/src/main/java/com/fs/course/vo/FsUserVideoPVO.java
  30. 14 0
      fs-service/src/main/java/com/fs/course/vo/FsUserVideoTagsVo.java
  31. 87 0
      fs-service/src/main/java/com/fs/his/utils/TalentTreeUtil.java
  32. 105 0
      fs-service/src/main/java/com/fs/utils/VideoUtil.java
  33. 94 0
      fs-service/src/main/resources/application-config-druid-hat.yml
  34. 176 0
      fs-service/src/main/resources/application-druid-hat.yml
  35. 171 0
      fs-service/src/main/resources/mapper/course/FsBlackTalentMapper.xml
  36. 35 1
      fs-service/src/main/resources/mapper/course/FsUserTalentFollowMapper.xml
  37. 42 1
      fs-service/src/main/resources/mapper/course/FsUserTalentMapper.xml
  38. 4 0
      fs-service/src/main/resources/mapper/course/FsUserVideoMapper.xml
  39. 221 7
      fs-user-app/src/main/java/com/fs/app/controller/TalentController.java
  40. 21 0
      fs-user-app/src/main/java/com/fs/app/controller/VideoController.java

+ 14 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java

@@ -5,8 +5,11 @@ import java.util.List;
 
 import com.fs.common.constant.HttpStatus;
 import com.fs.common.exception.CustomException;
+import com.fs.common.utils.ServletUtils;
+import com.fs.course.param.FsCourseOverParam;
 import com.fs.course.param.FsCourseWatchLogListParam;
 import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
+import com.fs.course.vo.FsCourseOverVO;
 import com.fs.course.vo.FsCourseWatchLogListVO;
 import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
@@ -164,4 +167,15 @@ public class FsCourseWatchLogController extends BaseController
     {
         return toAjax(fsCourseWatchLogService.deleteFsCourseWatchLogByLogIds(logIds));
     }
+
+    @GetMapping("/watchLogStatistics")
+    public TableDataInfo watchLogStatistics(FsCourseOverParam param)
+    {
+        startPage();
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        List<FsCourseOverVO> list = fsCourseWatchLogService.selectFsCourseWatchLogOverStatisticsListVO(param);
+        return getDataTable(list);
+    }
 }

+ 14 - 0
fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java

@@ -6,12 +6,15 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.exception.CustomException;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.param.FsCourseOverParam;
 import com.fs.course.param.FsCourseWatchLogListParam;
 import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
 import com.fs.course.param.PeriodStatisticCountParam;
 import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.vo.FsCourseOverVO;
 import com.fs.course.vo.FsCourseWatchLogListVO;
 import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
@@ -150,4 +153,15 @@ public class QwFsCourseWatchLogController extends BaseController
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectListBytrainingCampId(param);
         return getDataTable(list);
     }
+
+    @GetMapping("/watchLogStatistics")
+    public TableDataInfo watchLogStatistics(FsCourseOverParam param)
+    {
+        startPage();
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        List<FsCourseOverVO> list = fsCourseWatchLogService.selectFsCourseWatchLogOverStatisticsListVO(param);
+        return getDataTable(list);
+    }
 }

+ 2 - 2
fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java

@@ -92,8 +92,8 @@ public class SendMsg {
 
     private Map<String, FsCoursePlaySourceConfig> getMiniMap() {
         List<FsCoursePlaySourceConfig> list = fsCoursePlaySourceConfigService.list(new QueryWrapper<FsCoursePlaySourceConfig>().ne("type", 2).eq("is_del", 0));
-        log.info("获取到的小程序配置:{}", JSON.toJSONString(list));
-        log.info("获取到的小程序配置:{}", JSON.toJSONString(list));
+//        log.info("获取到的小程序配置:{}", JSON.toJSONString(list));
+//        log.info("获取到的小程序配置:{}", JSON.toJSONString(list));
         return PubFun.listToMapByGroupObject(list, FsCoursePlaySourceConfig::getAppid);
     }
 

+ 258 - 26
fs-qw-task/src/main/java/com/fs/app/taskService/impl/AsyncCourseWatchFinishService.java

@@ -10,14 +10,22 @@ import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.impl.QwExternalContactServiceImpl;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+
+import org.apache.rocketmq.client.exception.MQClientException;
 import org.apache.rocketmq.client.producer.SendCallback;
 import org.apache.rocketmq.client.producer.SendResult;
+import org.apache.rocketmq.common.message.MessageConst;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import org.apache.rocketmq.spring.support.RocketMQHeaders;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.messaging.support.MessageBuilder;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
 import java.util.Optional;
+import java.util.concurrent.*;
 
 @Slf4j
 @Service
@@ -36,37 +44,51 @@ public class AsyncCourseWatchFinishService {
     @Autowired
     RedisCache redisCache;
 
+    // 重试队列和调度器
+    private final BlockingQueue<RetryMessage> retryQueue = new LinkedBlockingQueue<>(10000);
+    private final ScheduledExecutorService retryExecutor = Executors.newSingleThreadScheduledExecutor();
+
+    // 主题映射配置
+    private static final String TOPIC = "course-finish-notes";
+
+    @PostConstruct
+    public void init() {
+        // 启动重试任务,每5秒处理一次重试队列
+        retryExecutor.scheduleWithFixedDelay(this::processRetryQueue, 10, 5, TimeUnit.SECONDS);
+        log.info("AsyncCourseWatchFinishService 重试队列处理器已启动");
+    }
+
     /**
     * 异步处理完课打备注的
     */
     @Async("scheduledExecutorService")
     public void executeCourseWatchFinish(FsCourseWatchLog finishLog) {
-
-        FsCourseWatchLog watchLog = new FsCourseWatchLog();
-        watchLog.setQwExternalContactId(finishLog.getQwExternalContactId());
-        watchLog.setFinishTime(finishLog.getFinishTime());
-        watchLog.setQwUserId(finishLog.getQwUserId());
-
-
-        QwUser qwUserByRedis = qwExternalContactService.getQwUserByRedisForId(String.valueOf(finishLog.getQwUserId()));
-        if (qwUserByRedis == null) {
-            log.error("无企微员工信息 {} 跳过处理。", finishLog.getQwUserId());
-            return;
-        }
-
-        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(qwUserByRedis.getCorpId());
-
-        if (qwCompany == null) {
-            log.error("企业微信主体为空 {} 跳过处理。{} ", qwUserByRedis.getCorpId(),watchLog);
-            return;
-        }
-
-        rocketMQTemplate.asyncSend("course-finish-notes", JSON.toJSONString(finishLog),     new SendCallback() {
-            @Override public void onSuccess(SendResult sendResult) {
-                log.info("推送完课打备注成功1:{},{}",JSON.toJSONString(finishLog),sendResult.getMsgId());
-            }  // 空实现
-            @Override public void onException(Throwable e) {log.error("推送完课打备注失败1:{},{}",JSON.toJSONString(finishLog),e.getMessage());}          // 空实现
-        });
+//        原代码
+//        FsCourseWatchLog watchLog = new FsCourseWatchLog();
+//        watchLog.setQwExternalContactId(finishLog.getQwExternalContactId());
+//        watchLog.setFinishTime(finishLog.getFinishTime());
+//        watchLog.setQwUserId(finishLog.getQwUserId());
+//
+//
+//        QwUser qwUserByRedis = qwExternalContactService.getQwUserByRedisForId(String.valueOf(finishLog.getQwUserId()));
+//        if (qwUserByRedis == null) {
+//            log.error("无企微员工信息 {} 跳过处理。", finishLog.getQwUserId());
+//            return;
+//        }
+//
+//        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(qwUserByRedis.getCorpId());
+//
+//        if (qwCompany == null) {
+//            log.error("企业微信主体为空 {} 跳过处理。{} ", qwUserByRedis.getCorpId(),watchLog);
+//            return;
+//        }
+//
+//        rocketMQTemplate.asyncSend("course-finish-notes", JSON.toJSONString(finishLog),     new SendCallback() {
+//            @Override public void onSuccess(SendResult sendResult) {
+//                log.info("推送完课打备注成功1:{},{}",JSON.toJSONString(finishLog),sendResult.getMsgId());
+//            }  // 空实现
+//            @Override public void onException(Throwable e) {log.error("推送完课打备注失败1:{},{}",JSON.toJSONString(finishLog),e.getMessage());}          // 空实现
+//        });
 
 
 //        // 定义默认值
@@ -102,6 +124,216 @@ public class AsyncCourseWatchFinishService {
 //        }
 
 
+        // 1. 数据验证和准备
+        ValidationResult validationResult = validateAndPrepareData(finishLog);
+        if (!validationResult.isValid()) {
+            return;
+        }
+
+
+        //  2. 发送消息(使用Tag区分)
+        sendWithFlowControl(finishLog, validationResult, 0);
+
+    }
+
+    /**
+     * 数据验证和准备
+     */
+    private ValidationResult validateAndPrepareData(FsCourseWatchLog finishLog) {
+        // 准备日志对象
+        FsCourseWatchLog watchLog = new FsCourseWatchLog();
+        watchLog.setQwExternalContactId(finishLog.getQwExternalContactId());
+        watchLog.setFinishTime(finishLog.getFinishTime());
+        watchLog.setQwUserId(finishLog.getQwUserId());
+
+        // 验证企微用户信息
+        QwUser qwUserByRedis = qwExternalContactService.getQwUserByRedisForId(String.valueOf(finishLog.getQwUserId()));
+        if (qwUserByRedis == null) {
+            log.error("无企微员工信息 {} 跳过处理。", finishLog.getQwUserId());
+            return ValidationResult.invalid();
+        }
+
+        // 验证企业主体
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(qwUserByRedis.getCorpId());
+        if (qwCompany == null) {
+            log.error("企业微信主体为空 {} 跳过处理。{} ", qwUserByRedis.getCorpId(), watchLog);
+            return ValidationResult.invalid();
+        }
+
+        return ValidationResult.valid(watchLog, qwUserByRedis, qwCompany);
+    }
+
+
+    /**
+     * 带流控处理的消息发送
+     */
+    private void sendWithFlowControl(FsCourseWatchLog finishLog,
+                                     ValidationResult validationResult, int retryCount) {
+        if (retryCount >= 3) {
+            log.warn("消息重试超过最大次数,转入重试队列: topic={}, qwUserId={}",
+                    TOPIC, finishLog.getQwUserId());
+            offerToRetryQueue(finishLog, validationResult);
+            return;
+        }
+
+        rocketMQTemplate.asyncSend(TOPIC, JSON.toJSONString(finishLog), new SendCallback() {
+            @Override
+            public void onSuccess(SendResult sendResult) {
+                log.info("推送完课打备注成功1:{},{}",JSON.toJSONString(finishLog),sendResult.getMsgId());
+            }
+
+            @Override
+            public void onException(Throwable e) {
+                if (isFlowControlException(e)) {
+                    // 流控异常处理
+                    handleFlowControlRetry(TOPIC, finishLog, validationResult, retryCount, e);
+                    log.error("推送完课打备注失败1流控异常:finishLog={},e={}",JSON.toJSONString(finishLog),e.getMessage());
+                } else {
+                    // 其他异常
+                    log.error("推送完课打备注失败1:{},{}",JSON.toJSONString(finishLog),e.getMessage());
+                }
+            }
+        });
+    }
+
+    /**
+     * 放入重试队列
+     */
+    private void offerToRetryQueue(FsCourseWatchLog finishLog,
+                                   ValidationResult validationResult) {
+        RetryMessage retryMessage = new RetryMessage(finishLog, validationResult);
+        boolean offered = retryQueue.offer(retryMessage);
+        if (offered) {
+            log.info("消息已加入重试队列: topic={}, qwUserId={}", TOPIC, finishLog.getQwUserId());
+        } else {
+            log.error("重试队列已满,消息可能丢失: topic={}, qwUserId={}", TOPIC, finishLog.getQwUserId());
+            // 这里可以接入告警系统
+        }
+    }
+
+    /**
+     * 处理重试队列
+     */
+    private void processRetryQueue() {
+        try {
+            int processedCount = 0;
+            RetryMessage retryMessage;
+
+            while (processedCount < 100 && (retryMessage = retryQueue.poll()) != null) {
+                try {
+                    // 重新发送消息
+                    sendWithFlowControl(retryMessage.getFinishLog(),
+                            retryMessage.getValidationResult(), 0);
+                    processedCount++;
+
+                    Thread.sleep(10);
+                } catch (Exception e) {
+                    log.error("重试队列处理失败: {}", e.getMessage());
+                    offerToRetryQueue(retryMessage.getFinishLog(), retryMessage.getValidationResult());
+                }
+            }
+
+            if (processedCount > 0) {
+                log.debug("重试队列处理完成,本次处理数量: {}", processedCount);
+            }
+        } catch (Exception e) {
+            log.error("处理重试队列异常: {}", e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 判断是否为流控异常
+     */
+    private boolean isFlowControlException(Throwable e) {
+        if (e instanceof MQClientException) {
+            return ((MQClientException) e).getResponseCode() == 215;
+        }
+        // 检查异常链
+        Throwable cause = e.getCause();
+        if (cause instanceof MQClientException) {
+            return ((MQClientException) cause).getResponseCode() == 215;
+        }
+        return false;
+    }
+
+    /**
+     * 流控重试处理
+     */
+    private void handleFlowControlRetry(String topic, FsCourseWatchLog finishLog,
+                                        ValidationResult validationResult, int retryCount, Throwable e) {
+        long backoffTime = calculateBackoffTime(retryCount);
+        log.warn("流控触发,{}ms后第{}次重试: topic={}, qwUserId={}",
+                backoffTime, retryCount + 1, topic, finishLog.getQwUserId());
+
+        // 使用 ScheduledExecutorService 进行延迟执行
+        retryExecutor.schedule(() -> {
+            try {
+                sendWithFlowControl(finishLog, validationResult, retryCount + 1);
+            } catch (Exception ex) {
+                log.error("延迟重试执行异常: {}", ex.getMessage(), ex);
+            }
+        }, backoffTime, TimeUnit.MILLISECONDS);
+    }
+    /**
+     * 计算退避时间(指数退避)
+     */
+    private long calculateBackoffTime(int retryCount) {
+        return Math.min(1000 * (long) Math.pow(2, retryCount), 10000); // 最大10秒
+    }
+
+    @PreDestroy
+    public void destroy() {
+        retryExecutor.shutdown();
+        try {
+            if (!retryExecutor.awaitTermination(10, TimeUnit.SECONDS)) {
+                retryExecutor.shutdownNow();
+            }
+        } catch (InterruptedException e) {
+            retryExecutor.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+        log.info("AsyncCourseWatchFinishService 已关闭");
+    }
+
+    // 内部辅助类
+    private static class ValidationResult {
+        private final boolean valid;
+        private final FsCourseWatchLog watchLog;
+        private final QwUser qwUser;
+        private final QwCompany qwCompany;
+
+        public ValidationResult(boolean valid, FsCourseWatchLog watchLog, QwUser qwUser, QwCompany qwCompany) {
+            this.valid = valid;
+            this.watchLog = watchLog;
+            this.qwUser = qwUser;
+            this.qwCompany = qwCompany;
+        }
+
+        public static ValidationResult valid(FsCourseWatchLog watchLog, QwUser qwUser, QwCompany qwCompany) {
+            return new ValidationResult(true, watchLog, qwUser, qwCompany);
+        }
+
+        public static ValidationResult invalid() {
+            return new ValidationResult(false, null, null, null);
+        }
+
+        public boolean isValid() { return valid; }
+        public FsCourseWatchLog getWatchLog() { return watchLog; }
+        public QwUser getQwUser() { return qwUser; }
+        public QwCompany getQwCompany() { return qwCompany; }
+    }
+
+    private static class RetryMessage {
+        private final FsCourseWatchLog finishLog;
+        private final ValidationResult validationResult;
+
+        public RetryMessage(FsCourseWatchLog finishLog, ValidationResult validationResult) {
+            this.finishLog = finishLog;
+            this.validationResult = validationResult;
+        }
+
+        public FsCourseWatchLog getFinishLog() { return finishLog; }
+        public ValidationResult getValidationResult() { return validationResult; }
     }
 
 }

+ 83 - 0
fs-service/src/main/java/com/fs/course/domain/FsBlackTalent.java

@@ -0,0 +1,83 @@
+package com.fs.course.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 达人或视频举报拉黑功能对象 fs_black_talent
+ *
+ * @author fs
+ * @date 2025-08-17
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsBlackTalent extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 达人id */
+    @Excel(name = "达人id")
+    private Long talentId;
+
+    /** 类型1:拉黑,2:举报 */
+    @Excel(name = "类型1:拉黑,2:举报")
+    private String type;
+
+    /** 举报说明(拉黑为空) */
+    @Excel(name = "举报说明", readConverterExp = "拉=黑为空")
+    private String reportDesc;
+
+    /** 是否审核-1:驳回,0:待审核,1:通过(拉黑不需要审核) */
+    @Excel(name = "是否审核-1:驳回,0:待审核,1:通过", readConverterExp = "拉=黑不需要审核")
+    private String isAudit;
+
+    /** 审核时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "审核时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date auditTime;
+
+    /** 审核人 */
+    @Excel(name = "审核人")
+    private Long auditUser;
+
+    /** 审核说明 */
+    @Excel(name = "审核说明")
+    private String auditDesc;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date creatTime;
+
+    /** 短视频id */
+    @Excel(name = "短视频id")
+    private Long videoId;
+
+    /** 1:达人,2:短视频 */
+    @Excel(name = "1:达人,2:短视频")
+    private String style;
+
+    @Excel(name = "联系方式")
+    private String phone;
+
+    /** 图片地址 */
+    @Excel(name = "图片地址")
+    private String urls;
+
+    @Excel(name = "投诉模板id")
+    private Long templateId;
+    //交易截图
+    private String tradeImage;
+
+
+}

+ 25 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserTalent.java

@@ -111,4 +111,29 @@ public class FsUserTalent extends BaseEntity
     @Excel(name = "已提现佣金")
     private BigDecimal extractMoney;
 
+    /** 达人类别1:普通达人2:带货达人 */
+    @Excel(name = "达人类别1:普通达人2:带货达人")
+    private String talentType;
+
+    @Excel(name = "状态1正常状态,2禁用状态")
+    private Long status;
+
+    @Excel(name = "生日")
+    private String birthDay;
+
+    @Excel(name = "背景")
+    private String backGround;
+
+    /** 收货人所在省 */
+    @Excel(name = "收货人所在省")
+    private String province;
+
+    /** 收货人所在市 */
+    @Excel(name = "收货人所在市")
+    private String city;
+
+    /** 收货人所在区 */
+    @Excel(name = "收货人所在区")
+    private String district;
+
 }

+ 76 - 0
fs-service/src/main/java/com/fs/course/mapper/FsBlackTalentMapper.java

@@ -0,0 +1,76 @@
+package com.fs.course.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsBlackTalent;
+import com.fs.course.param.FsBlackTalentAuditParam;
+import com.fs.course.vo.FsBlackTalentPVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 达人或视频举报拉黑功能Mapper接口
+ * 
+ * @author fs
+ * @date 2025-08-17
+ */
+public interface FsBlackTalentMapper extends BaseMapper<FsBlackTalent>{
+    /**
+     * 查询达人或视频举报拉黑功能
+     * 
+     * @param id 达人或视频举报拉黑功能主键
+     * @return 达人或视频举报拉黑功能
+     */
+    FsBlackTalent selectFsBlackTalentById(Long id);
+
+    /**
+     * 查询达人或视频举报拉黑功能列表
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 达人或视频举报拉黑功能集合
+     */
+    List<FsBlackTalent> selectFsBlackTalentList(FsBlackTalent fsBlackTalent);
+
+    List<FsBlackTalentPVO> selectFsBlackTalentPVOList(FsBlackTalent fsBlackTalent);
+    /**
+     * 新增达人或视频举报拉黑功能
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 结果
+     */
+    int insertFsBlackTalent(FsBlackTalent fsBlackTalent);
+
+    /**
+     * 修改达人或视频举报拉黑功能
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 结果
+     */
+    int updateFsBlackTalent(FsBlackTalent fsBlackTalent);
+
+    /**
+     * 删除达人或视频举报拉黑功能
+     * 
+     * @param id 达人或视频举报拉黑功能主键
+     * @return 结果
+     */
+    int deleteFsBlackTalentById(Long id);
+
+    /**
+     * 批量删除达人或视频举报拉黑功能
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsBlackTalentByIds(Long[] ids);
+
+    int deleteFsBlackVideo(@Param("userId")String userId, @Param("videoId") String videoId);
+
+    int deleteFsBlackTalent(@Param("userId")String userId,@Param("talentId") String talentId);
+
+    int audit(@Param("map") FsBlackTalentAuditParam fsBlackTalentAuditParam);
+
+    List<FsBlackTalent> selectBlackAndReportVideoIdsByUserId(@Param("userId") Long userId);
+
+    int selectBlackTalent(@Param("talentId") Long talentId,@Param("loginUserId") Long loginUserId);
+}

+ 11 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserTalentFollowMapper.java

@@ -2,6 +2,9 @@ package com.fs.course.mapper;
 
 import java.util.List;
 import com.fs.course.domain.FsUserTalentFollow;
+import com.fs.course.param.FsUserTalentFansParam;
+import com.fs.course.vo.FsUserTalentFansVo;
+import com.fs.course.vo.FsUserTalentFollowVo;
 import org.apache.ibatis.annotations.Delete;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -68,4 +71,12 @@ public interface FsUserTalentFollowMapper
 
     @Delete("delete from fs_user_talent_follow  where talent_id =#{talentId} and user_id=#{userId} ")
     int deleteFollow(@Param("talentId") Long talentId,@Param("userId")long userId);
+
+    Integer queryFansCount(Long talentId);
+
+    Integer queryIdolCount(Long userId);
+
+    List<FsUserTalentFansVo> selectFsUserTalentFansVoList(@Param("maps") FsUserTalentFansParam param);
+
+    List<FsUserTalentFollowVo> selectFsUserFollowVoList(@Param("maps")FsUserTalentFansParam param);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserTalentMapper.java

@@ -66,4 +66,9 @@ public interface FsUserTalentMapper
 
     @Update("update fs_user_talent set fans=fans-1 where talent_id=#{talentId}")
     int minusFans(Long talentId);
+
+    //根据userID查询达人数据
+    FsUserTalent queryTalentByUserId(Long userId);
+
+    int updateFsUserTalentByUser(FsUserTalent fsUserTalent);
 }

+ 25 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserVideoMapper.java

@@ -138,6 +138,7 @@ public interface FsUserVideoMapper
             "left join fs_user_talent t on t.talent_id = v.talent_id " +
             " left join fs_package p on p.package_id = v.product_id " +
             "where v.is_del = 0 and v.status = 1  " +
+            " and v.is_audit = 1 " +
             "<if test = ' maps.keyword!=null and maps.keyword != \"\" '> " +
             "and v.title like CONCAT('%',#{maps.keyword},'%') " +
             "</if>" +
@@ -242,5 +243,29 @@ public interface FsUserVideoMapper
 
     @Select("select * from fs_user_video where url like CONCAT('%','https://obs.jy.cc','%') ")
     List<FsUserVideo> selectVideo();
+
+    List<FsUserVideo> selectVideoByTalentId(Long talentId);
+
+    @Select("SELECT count(1) from fs_user_video_favorite f LEFT JOIN " +
+            "fs_user_video v ON v.video_id = f.video_id  " +
+            "where v.is_del = 0 and  v.status = 1 and f.user_id = #{userId}")
+    int countFavoriteVideos(@Param("userId") Long userId);
+
+    @Select({"<script> " +
+            "select v.video_id as id,v.title,v.description as msg,t.nick_name as username,t.avatar as headImg, " +
+            "v.thumbnail as cover,v.url as src,v.likes as likeNum,v.comments as smsNum,v.favorite_num," +
+            "v.create_time,v.views as playNumber,v.product_id,p.img_url,p.package_name,v.upload_type,v.shares,v.add_num,v.is_audit,v.fail_reason,v.status from fs_user_video v " +
+            "left join fs_user_talent t on t.talent_id = v.talent_id " +
+            " left join fs_package p on p.package_id = v.product_id " +
+            "where v.is_del = 0 and (" +
+            "(#{oneSelf} = true and (v.is_audit = -1 or v.is_audit = 0 or v.is_audit = 1)) or " +
+            "(#{oneSelf} = false and v.is_audit = 1 and v.status = 1)" +
+            ") " +
+            "<if test = ' talentId!=null and talentId != \"\" '> " +
+            "and v.talent_id = #{talentId}" +
+            " order by v.create_time" +
+            "</if>" +
+            "</script>"})
+    List<FsUserVideoListUVO> selectFsUserVideoListUVOByUser(@Param("talentId") Long talentId, @Param("oneSelf") boolean oneSelf);
 }
 

+ 3 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserVideoTagsMapper.java

@@ -64,4 +64,7 @@ public interface FsUserVideoTagsMapper
 
     @Select("select * from fs_user_video_tags where pid !=0 and is_del = 0 ")
     List<FsUserVideoTagsPVO> selectFsUserVideoTagsSubList();
+
+    @Select("select * from fs_user_video_tags where is_del = 0")
+    List<FsUserVideoTags> selectTagList();
 }

+ 14 - 0
fs-service/src/main/java/com/fs/course/param/FsBlackTalentAuditParam.java

@@ -0,0 +1,14 @@
+package com.fs.course.param;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class FsBlackTalentAuditParam {
+    private Long auditUser;
+    private String isAudit;
+    private Long id;
+    private Date auditTime;
+    private String auditDesc;
+}

+ 10 - 0
fs-service/src/main/java/com/fs/course/param/FsUserTalentFansParam.java

@@ -0,0 +1,10 @@
+package com.fs.course.param;
+
+import com.fs.watch.param.BaseQueryParam;
+import lombok.Data;
+
+@Data
+public class FsUserTalentFansParam extends BaseQueryParam {
+    private Long talentId;
+    private Long userId;
+}

+ 77 - 0
fs-service/src/main/java/com/fs/course/service/IFsBlackTalentService.java

@@ -0,0 +1,77 @@
+package com.fs.course.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.course.domain.FsBlackTalent;
+import com.fs.course.param.FsBlackTalentAuditParam;
+import com.fs.course.vo.FsBlackTalentPVO;
+
+import java.util.List;
+
+/**
+ * 达人或视频举报拉黑功能Service接口
+ * 
+ * @author fs
+ * @date 2025-08-17
+ */
+public interface IFsBlackTalentService extends IService<FsBlackTalent>{
+    /**
+     * 查询达人或视频举报拉黑功能
+     * 
+     * @param id 达人或视频举报拉黑功能主键
+     * @return 达人或视频举报拉黑功能
+     */
+    FsBlackTalent selectFsBlackTalentById(Long id);
+
+    /**
+     * 查询达人或视频举报拉黑功能列表
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 达人或视频举报拉黑功能集合
+     */
+    List<FsBlackTalentPVO> selectFsBlackTalentList(FsBlackTalent fsBlackTalent);
+
+    /**
+     * 新增达人或视频举报拉黑功能
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 结果
+     */
+    int insertFsBlackTalent(FsBlackTalent fsBlackTalent);
+
+    /**
+     * 修改达人或视频举报拉黑功能
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 结果
+     */
+    int updateFsBlackTalent(FsBlackTalent fsBlackTalent);
+
+    /**
+     * 批量删除达人或视频举报拉黑功能
+     * 
+     * @param ids 需要删除的达人或视频举报拉黑功能主键集合
+     * @return 结果
+     */
+    int deleteFsBlackTalentByIds(Long[] ids);
+
+    /**
+     * 删除达人或视频举报拉黑功能信息
+     * 
+     * @param id 达人或视频举报拉黑功能主键
+     * @return 结果
+     */
+    int deleteFsBlackTalentById(Long id);
+
+    int addBlack(FsBlackTalent blackTalent);
+
+    int deleteFsBlackVideo(String userId,String videoId);
+
+    int deleteFsBlackTalent(String userId,String talentId);
+
+    int audit(FsBlackTalentAuditParam fsBlackTalentAuditParam);
+
+    List<FsBlackTalent> selectBlackAndReportVideoIdsByUserId(Long userId);
+
+    int selectBlackTalent(Long talentId,Long loginUserId);
+
+}

+ 11 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserTalentFollowService.java

@@ -4,6 +4,9 @@ import java.util.List;
 
 import com.fs.common.core.domain.R;
 import com.fs.course.domain.FsUserTalentFollow;
+import com.fs.course.param.FsUserTalentFansParam;
+import com.fs.course.vo.FsUserTalentFansVo;
+import com.fs.course.vo.FsUserTalentFollowVo;
 
 /**
  * 达人关注Service接口
@@ -64,4 +67,12 @@ public interface IFsUserTalentFollowService
     R checkFollow(Long talentId, long userId);
 
     int deleteFollow(Long talentId, long userId);
+
+    Integer queryFansCount(Long talentId);
+
+    Integer queryIdolCount(Long userId);
+
+    List<FsUserTalentFansVo> selectFsUserTalentFansVoList(FsUserTalentFansParam param);
+
+    List<FsUserTalentFollowVo> selectFsUserFollowVoList(FsUserTalentFansParam param);
 }

+ 10 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserTalentService.java

@@ -1,6 +1,8 @@
 package com.fs.course.service;
 
 import java.util.List;
+
+import com.fs.common.core.domain.R;
 import com.fs.course.domain.FsUserTalent;
 
 /**
@@ -61,4 +63,12 @@ public interface IFsUserTalentService
 
 
     int updateFans(Long talentId, Integer type);
+
+    FsUserTalent queryTalentByUserId(Long userId);
+
+    R getTalentDetail(Long userId, Long loginUser);
+
+    int addFsUserTalent(Long userId);
+
+    int updateFsUserTalentByUser(FsUserTalent fsUserTalent);
 }

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

@@ -92,4 +92,16 @@ public interface IFsUserVideoService {
     void updateVideoUrl();
 
     List<FsUserVideoListUVO> addNum(List<FsUserVideoListUVO> oldList);
+
+    List<FsUserVideo> selectVideoByTalentId(Long talentId);
+
+    int countFavoriteVideos(Long userId);
+
+    R addUserVideoByTalent(FsUserVideoAddParam param);
+
+    List<FsUserVideoListUVO> selectFsUserVideoListUVOByUser(Long talentId, boolean oneSelf, Long userId);
+
+    R deleteFsUserVideoByVideoIdWithVerify(Long videoId, Long userId);
+
+    R updateVideoStatusWithVerify(FsUserVideo fsUserVideo, Long userId);
 }

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

@@ -61,4 +61,6 @@ public interface IFsUserVideoTagsService
     public int deleteFsUserVideoTagsByTagId(Long tagId);
 
     List<FsUserVideoTagsPVO> selectFsUserVideoTagsSubList();
+
+    List<FsUserVideoTags> selectTagList();
 }

+ 126 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsBlackTalentServiceImpl.java

@@ -0,0 +1,126 @@
+package com.fs.course.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.course.domain.FsBlackTalent;
+import com.fs.course.mapper.FsBlackTalentMapper;
+import com.fs.course.param.FsBlackTalentAuditParam;
+import com.fs.course.service.IFsBlackTalentService;
+import com.fs.course.vo.FsBlackTalentPVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 达人或视频举报拉黑功能Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-08-17
+ */
+@Service
+public class FsBlackTalentServiceImpl extends ServiceImpl<FsBlackTalentMapper, FsBlackTalent> implements IFsBlackTalentService {
+    @Autowired
+    private FsBlackTalentMapper blackTalentMapper;
+    /**
+     * 查询达人或视频举报拉黑功能
+     * 
+     * @param id 达人或视频举报拉黑功能主键
+     * @return 达人或视频举报拉黑功能
+     */
+    @Override
+    public FsBlackTalent selectFsBlackTalentById(Long id)
+    {
+        return baseMapper.selectFsBlackTalentById(id);
+    }
+
+    /**
+     * 查询达人或视频举报拉黑功能列表
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 达人或视频举报拉黑功能
+     */
+    @Override
+    public List<FsBlackTalentPVO> selectFsBlackTalentList(FsBlackTalent fsBlackTalent)
+    {
+        return blackTalentMapper.selectFsBlackTalentPVOList(fsBlackTalent);
+    }
+
+    /**
+     * 新增达人或视频举报拉黑功能
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 结果
+     */
+    @Override
+    public int insertFsBlackTalent(FsBlackTalent fsBlackTalent)
+    {
+        return baseMapper.insertFsBlackTalent(fsBlackTalent);
+    }
+
+    /**
+     * 修改达人或视频举报拉黑功能
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 结果
+     */
+    @Override
+    public int updateFsBlackTalent(FsBlackTalent fsBlackTalent)
+    {
+        return baseMapper.updateFsBlackTalent(fsBlackTalent);
+    }
+
+    /**
+     * 批量删除达人或视频举报拉黑功能
+     * 
+     * @param ids 需要删除的达人或视频举报拉黑功能主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsBlackTalentByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsBlackTalentByIds(ids);
+    }
+
+    /**
+     * 删除达人或视频举报拉黑功能信息
+     * 
+     * @param id 达人或视频举报拉黑功能主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsBlackTalentById(Long id)
+    {
+        return baseMapper.deleteFsBlackTalentById(id);
+    }
+
+    @Override
+    public int addBlack(FsBlackTalent blackTalent) {
+        return blackTalentMapper.insertFsBlackTalent(blackTalent);
+    }
+
+    @Override
+    public int deleteFsBlackVideo(String userId,String videoId) {
+        return blackTalentMapper.deleteFsBlackVideo(userId,videoId);
+    }
+
+    @Override
+    public int deleteFsBlackTalent(String userId,String talentId) {
+        return blackTalentMapper.deleteFsBlackTalent(userId,talentId);
+    }
+
+    @Override
+    public int audit(FsBlackTalentAuditParam fsBlackTalentAuditParam) {
+        return blackTalentMapper.audit(fsBlackTalentAuditParam);
+    }
+
+    @Override
+    public List<FsBlackTalent> selectBlackAndReportVideoIdsByUserId(Long userId) {
+        return blackTalentMapper.selectBlackAndReportVideoIdsByUserId(userId);
+    }
+
+    @Override
+    public int selectBlackTalent(Long talentId, Long loginUserId) {
+
+        return blackTalentMapper.selectBlackTalent(talentId,loginUserId);
+    }
+}

+ 102 - 16
fs-service/src/main/java/com/fs/course/service/impl/FsCourseFinishTempServiceImpl.java

@@ -28,6 +28,7 @@ import com.fs.qwApi.service.QwApiService;
 import com.fs.voice.utils.StringUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDate;
@@ -209,9 +210,10 @@ public class FsCourseFinishTempServiceImpl implements IFsCourseFinishTempService
     /**
      * 完课用户 打备注
      */
+    @Async
     @Override
     public void finishCourseExtContactIdByRemark(FsCourseWatchLog watchLog) {
-
+        addRandomDelay();
         Long qwExternalContactId = watchLog.getQwExternalContactId();
 
         Date finishTime = watchLog.getFinishTime();
@@ -300,7 +302,8 @@ public class FsCourseFinishTempServiceImpl implements IFsCourseFinishTempService
             remarkParam.setUserid(externalContact.getUserId());
             remarkParam.setExternal_userid(externalContact.getExternalUserId());
 
-            for (int attempt = 1; attempt <= 2; attempt++) {
+//            for (int attempt = 1; attempt <= 2; attempt++) {
+            for (int attempt = 1; attempt <= 3; attempt++) { // 增加到3次重试
                 try {
                     QwExternalContactRemarkResult qwResult = qwApiService.externalcontactRemark(remarkParam, externalContact.getCorpId());
                     if (qwResult.getErrcode() == 0) {
@@ -314,23 +317,46 @@ public class FsCourseFinishTempServiceImpl implements IFsCourseFinishTempService
 
                         break;
                     } else {
-                        if (attempt==2 && (qwResult.getErrcode() == 45033 || qwResult.getErrcode()== -1 || qwResult.getErrcode()== 60020 )) {
-                            QwCourseFinishRemarkRty remarkRty=new QwCourseFinishRemarkRty();
-                            remarkRty.setQwUserId(externalContact.getUserId());
-                            remarkRty.setCorpId(externalContact.getCorpId());
-                            remarkRty.setExternalUserId(externalContact.getExternalUserId());
-                            remarkRty.setExternalId(externalContact.getId());
-                            remarkRty.setRemark(newRemark);
-                            remarkRty.setCreateTime(new Date());
-                            finishRemarkRtyService.insertOrUpdateQwCourseFinishRemarkRty(remarkRty);
-
+//                        if (attempt==2 && (qwResult.getErrcode() == 45033 || qwResult.getErrcode()== -1 || qwResult.getErrcode()== 60020 )) {
+//                            QwCourseFinishRemarkRty remarkRty=new QwCourseFinishRemarkRty();
+//                            remarkRty.setQwUserId(externalContact.getUserId());
+//                            remarkRty.setCorpId(externalContact.getCorpId());
+//                            remarkRty.setExternalUserId(externalContact.getExternalUserId());
+//                            remarkRty.setExternalId(externalContact.getId());
+//                            remarkRty.setRemark(newRemark);
+//                            remarkRty.setCreateTime(new Date());
+//                            finishRemarkRtyService.insertOrUpdateQwCourseFinishRemarkRty(remarkRty);
+//
+//                        }
+//
+//                        log.error("完课加备注失败:" + externalContact.getName() + "|" + externalContact.getExternalUserId() + "|" + externalContact.getCorpId() + "|" + externalContact.getUserId() + "|" + newRemark + "|原因" + qwResult.getErrmsg());
+
+                        // 根据错误码智能处理
+                        if (isRateLimitError(qwResult.getErrcode())) {
+                            // 保存到重试表
+                            saveToRetryTable(externalContact, newRemark);
+
+                            // 智能延迟
+                            if (attempt < 3) {
+                                smartDelayByErrorCode(qwResult.getErrcode(), attempt);
+                                continue; // 继续重试
+                            }
                         }
-
-                        log.error("完课加备注失败:" + externalContact.getName() + "|" + externalContact.getExternalUserId() + "|" + externalContact.getCorpId() + "|" + externalContact.getUserId() + "|" + newRemark + "|原因" + qwResult.getErrmsg());
-
+                        log.error("完课加备注失败:{}|{}|{}|{}|{}|原因{}",
+                                externalContact.getName(), externalContact.getExternalUserId(),
+                                externalContact.getCorpId(), externalContact.getUserId(),
+                                newRemark, qwResult.getErrmsg());
                     }
                 } catch (Exception e) {
-                    log.error("添加备注异常 [尝试第 " + attempt + " 次]:" + externalContact.getName() + "|" + externalContact.getExternalUserId() + "|" + externalContact.getCorpId() + "|" + externalContact.getUserId() + "|" + newRemark + "|" + e.getMessage());
+//                    log.error("添加备注异常 [尝试第 " + attempt + " 次]:" + externalContact.getName() + "|" + externalContact.getExternalUserId() + "|" + externalContact.getCorpId() + "|" + externalContact.getUserId() + "|" + newRemark + "|" + e.getMessage());
+                    log.error("添加备注异常 [尝试第 {} 次]:{}|{}|{}|{}|{}|{}",
+                            attempt, externalContact.getName(), externalContact.getExternalUserId(),
+                            externalContact.getCorpId(), externalContact.getUserId(),
+                            newRemark, e.getMessage());
+
+                    if (attempt < 3) {
+                        smartDelayByErrorCode(-1, attempt);
+                    }
                 }
 
                 // 若不是最后一次尝试,则等待3秒再试
@@ -411,4 +437,64 @@ public class FsCourseFinishTempServiceImpl implements IFsCourseFinishTempService
     public List<FsCourseFinishTempListVO> selectFsCourseFinishTempListVO(FsCourseFinishTemp fsCourseFinishTemp) {
         return fsCourseFinishTempMapper.selectFsCourseFinishTempListVO(fsCourseFinishTemp);
     }
+
+    /**
+     * 添加随机延迟,分散请求峰值
+     */
+    private void addRandomDelay() {
+        try {
+            // 随机延迟100-500ms,避免同时大量请求
+            Thread.sleep(100 + (long)(Math.random() * 400));
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.warn("延迟被中断", e);
+        }
+    }
+
+    /**
+     * 根据错误码智能延迟
+     */
+    private void smartDelayByErrorCode(Integer errcode, int attempt) {
+        try {
+            long delayMs;
+            if (errcode == 45033) { // 并发限制
+                delayMs = 5000 + attempt * 2000L; // 递增延迟
+            } else if (errcode == 60020) { // 频率限制
+                delayMs = 3000 + attempt * 1000L;
+            } else if (errcode == -1) { // 系统繁忙
+                delayMs = 2000;
+            } else {
+                delayMs = 1000; // 默认延迟
+            }
+            log.info("因错误码 {} 延迟 {}ms", errcode, delayMs);
+            Thread.sleep(delayMs);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    /**
+     * 判断是否为频率限制类错误
+     */
+    private boolean isRateLimitError(Integer errcode) {
+        return errcode == 45033 || errcode == 60020 || errcode == -1;
+    }
+
+    /**
+     * 保存到重试表
+     */
+    private void saveToRetryTable(QwExternalContact externalContact, String remark) {
+        try {
+            QwCourseFinishRemarkRty remarkRty=new QwCourseFinishRemarkRty();
+            remarkRty.setQwUserId(externalContact.getUserId());
+            remarkRty.setCorpId(externalContact.getCorpId());
+            remarkRty.setExternalUserId(externalContact.getExternalUserId());
+            remarkRty.setExternalId(externalContact.getId());
+            remarkRty.setRemark(remark);
+            remarkRty.setCreateTime(new Date());
+            finishRemarkRtyService.insertOrUpdateQwCourseFinishRemarkRty(remarkRty);
+        } catch (Exception e) {
+            log.error("保存重试记录失败", e);
+        }
+    }
 }

+ 7 - 1
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -383,7 +383,13 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             watchLog.setDuration(duration);
 
             //取对应视频的时长
-            Long videoDuration = getFsUserVideoDuration(videoId);
+            Long videoDuration = 0L;
+            try {
+                videoDuration = getFsUserVideoDuration(videoId);
+            }catch (Exception e){
+                log.error("视频时长识别错误:{}", key);
+                continue;
+            }
             if (videoDuration != null && videoDuration != 0) {
                 //判断是否完课
                 long percentage = (duration * 100 / videoDuration);

+ 24 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserTalentFollowServiceImpl.java

@@ -1,9 +1,13 @@
 package com.fs.course.service.impl;
 
+import java.util.Collections;
 import java.util.List;
 
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
+import com.fs.course.param.FsUserTalentFansParam;
+import com.fs.course.vo.FsUserTalentFansVo;
+import com.fs.course.vo.FsUserTalentFollowVo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.course.mapper.FsUserTalentFollowMapper;
@@ -107,4 +111,24 @@ public class FsUserTalentFollowServiceImpl implements IFsUserTalentFollowService
         return fsUserTalentFollowMapper.deleteFollow(talentId,userId);
     }
 
+    @Override
+    public Integer queryFansCount(Long talentId) {
+        return fsUserTalentFollowMapper.queryFansCount(talentId);
+    }
+
+    @Override
+    public Integer queryIdolCount(Long userId) {
+        return fsUserTalentFollowMapper.queryIdolCount(userId);
+    }
+
+    @Override
+    public List<FsUserTalentFansVo> selectFsUserTalentFansVoList(FsUserTalentFansParam param) {
+        return fsUserTalentFollowMapper.selectFsUserTalentFansVoList(param);
+    }
+
+    @Override
+    public List<FsUserTalentFollowVo> selectFsUserFollowVoList(FsUserTalentFansParam param) {
+        return fsUserTalentFollowMapper.selectFsUserFollowVoList(param);
+    }
+
 }

+ 117 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserTalentServiceImpl.java

@@ -1,12 +1,26 @@
 package com.fs.course.service.impl;
 
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+
+import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
+import com.fs.course.domain.FsUserVideo;
+import com.fs.course.service.IFsBlackTalentService;
+import com.fs.course.service.IFsUserTalentFollowService;
+import com.fs.course.service.IFsUserVideoService;
+import com.fs.his.domain.FsUser;
+import com.fs.his.service.IFsUserService;
+import com.fs.his.utils.PhoneUtil;
+import com.fs.huifuPay.sdk.opps.core.utils.StringUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.course.mapper.FsUserTalentMapper;
 import com.fs.course.domain.FsUserTalent;
 import com.fs.course.service.IFsUserTalentService;
+import org.springframework.transaction.annotation.Transactional;
 
 /**
  * 达人Service业务层处理
@@ -19,6 +33,14 @@ public class FsUserTalentServiceImpl implements IFsUserTalentService
 {
     @Autowired
     private FsUserTalentMapper fsUserTalentMapper;
+    @Autowired
+    private IFsUserTalentFollowService userTalentFollowService;
+    @Autowired
+    private IFsBlackTalentService blackTalentService;
+    @Autowired
+    private IFsUserVideoService fsUserVideoService;
+    @Autowired
+    private IFsUserService fsUserService;
 
     /**
      * 查询达人
@@ -101,4 +123,99 @@ public class FsUserTalentServiceImpl implements IFsUserTalentService
             return fsUserTalentMapper.minusFans(talentId);
         }
     }
+
+    @Override
+    public FsUserTalent queryTalentByUserId(Long userId){
+        return fsUserTalentMapper.queryTalentByUserId(userId);
+    }
+
+    @Override
+    @Transactional
+    public int addFsUserTalent(Long userId) {
+        //根据userID查询达人表中是否存在数据
+        FsUserTalent fsUserTalent = queryTalentByUserId(userId);
+        if (null!=fsUserTalent){
+            return 0;
+        }
+        FsUser fsUser1 = fsUserService.selectFsUserByUserId(userId);
+        fsUserTalent = new FsUserTalent();
+        if (!StringUtil.isEmpty(fsUser1.getAvatar())){
+            fsUserTalent.setAvatar(fsUser1.getAvatar());
+        }
+        fsUserTalent.setUserId(fsUser1.getUserId());
+        if (!StringUtil.isEmpty(fsUser1.getPhone())){
+            fsUserTalent.setNickName(fsUser1.getNickName());
+        }
+        if (!StringUtil.isEmpty(fsUser1.getPhone())){
+            String phone = PhoneUtil.decryptPhone(fsUser1.getPhone());
+            fsUserTalent.setPhone(phone);
+
+        }
+        fsUserTalent.setCreateTime(new Date());
+        if (!StringUtil.isEmpty(fsUser1.getSex())){
+            fsUserTalent.setSex(fsUser1.getSex().longValue());
+        }
+        fsUserTalent.setLevel(1l);
+        fsUserTalent.setTalentType("1");
+        fsUserTalent.setIsAudit(1l);
+        fsUserTalent.setAuditTime(new Date());
+        fsUserTalent.setStatus(1l);
+        fsUserTalent.setIsDel(1l);
+        fsUserTalentMapper.insertFsUserTalent(fsUserTalent);
+        return 1;
+    }
+
+    @Override
+    public int updateFsUserTalentByUser(FsUserTalent fsUserTalent)
+    {
+        return fsUserTalentMapper.updateFsUserTalentByUser(fsUserTalent);
+    }
+
+    @Override
+    public R getTalentDetail(Long userId, Long loginUser) {
+
+        HashMap<String, Object> map = new HashMap<>();
+        FsUserTalent fsUserTalent = queryTalentByUserId(userId);
+        if (null==fsUserTalent){
+            return R.error("该用户暂未注册达人身份");
+        }
+
+        //查询达人粉丝数
+        Integer fansCount = userTalentFollowService.queryFansCount(fsUserTalent.getTalentId());
+        //查询关注的达人数量
+        Integer idolCount = userTalentFollowService.queryIdolCount(userId);
+        //查询视频列表
+        List<FsUserVideo> fsUserVideos = fsUserVideoService.selectVideoByTalentId(fsUserTalent.getTalentId());
+        //所有视频的收藏量
+//        Long favoriteNum=0l;
+        Long likeNum = 0l;
+        //拿到用户的所有视频id
+        ArrayList<Long> videoIds = new ArrayList<>();
+        for (FsUserVideo fsUserVideo : fsUserVideos) {
+//            favoriteNum+=fsUserVideo.getFavoriteNum();
+            likeNum+=fsUserVideo.getLikes();
+            //videoIds.add(fsUserVideo.getVideoId());
+        }
+        //查询我的——> 我的视频  视频收藏数量
+        int favoriteNum = fsUserVideoService.countFavoriteVideos(userId);
+        //查询视频收藏数量
+        /*if (fsUserVideos.size()>0){
+            fsUserVideoFavoriteService.queryFavoriteCount(fsUserTalent.getTalentId());
+        }*/
+        //查询当前达人是否被查询的用户拉黑或举报
+        if (loginUser>0){
+            if (blackTalentService.selectBlackTalent(fsUserTalent.getTalentId(),loginUser)>0){
+                map.put("isBlack",1);
+            }
+        }
+        map.put("fsUserTalent",fsUserTalent);
+        map.put("fansCount",fansCount);
+        map.put("idolCount",idolCount);
+        //map.put("fsUserVideos",fsUserVideos);
+        map.put("favoriteNum",favoriteNum);
+        map.put("likeNum",likeNum);
+
+        return R.ok().put("data",map);
+    }
+
 }

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

@@ -338,6 +338,99 @@ public class FsUserVideoServiceImpl implements IFsUserVideoService {
         return oldList;
     }
 
+    @Override
+    public List<FsUserVideo> selectVideoByTalentId(Long talentId) {
+        return fsUserVideoMapper.selectVideoByTalentId(talentId);
+    }
+
+    @Override
+    public int countFavoriteVideos(Long userId) {
+        return fsUserVideoMapper.countFavoriteVideos(userId);
+    }
+
+    @Override
+    public R addUserVideoByTalent(FsUserVideoAddParam param) {
+        FsUserVideo fsUserVideo = new FsUserVideo();
+        BeanUtils.copyProperties(param, fsUserVideo);
+        fsUserVideo.setCreateTime(DateUtils.getNowDate());
+        fsUserVideo.setIsAudit(0);
+        fsUserVideo.setSource(2);
+        fsUserVideo.setStatus(1);
+        //fsUserVideo.setCateId(param.getCateId());
+//        fsUserVideo.setComments(0L);
+        fsUserVideoMapper.insertFsUserVideo(fsUserVideo);
+        return R.ok();
+    }
+
+    @Override
+    public List<FsUserVideoListUVO> selectFsUserVideoListUVOByUser(Long talentId, boolean oneSelf, Long userId) {
+        List<FsUserVideoListUVO> list = fsUserVideoMapper.selectFsUserVideoListUVOByUser(talentId, oneSelf);
+        /*if (param != null && param.getUserId() != null) {
+            Long userId = param.getUserId();
+            list = selectLikesAndFavorites(userId, list);
+        }*/
+        // 当前视频是否被自己喜欢或收藏
+        if (list.size() > 0) {
+            selectLikesAndFavoritesByMyself(list,userId);
+        }
+
+
+        return list;
+    }
+
+    /**
+     * 删除达人视频信息
+     *
+     * @param videoId 课堂视频id
+     * @param userId 用户id
+     * @return 结果
+     */
+    @Override
+    public R deleteFsUserVideoByVideoIdWithVerify(Long videoId, Long userId) {
+        if (videoId == null) {
+            return R.error("请选择要删除的视频");
+        }
+        FsUserVideoPVO entity = fsUserVideoMapper.selectFsUserVideoPVO(videoId);
+        if (entity == null) {
+            return R.error("视频不存在");
+        } else if (!Objects.equals(entity.getUserId(), userId)) {
+            return R.error("您没有权限删除此视频");
+        }
+        this.deleteFsUserVideoByVideoId(videoId.toString().trim());
+        return R.ok();
+    }
+
+    @Override
+    public R updateVideoStatusWithVerify(FsUserVideo fsUserVideo, Long userId) {
+        if (fsUserVideo.getVideoId() == null) {
+            return R.error("请选择要操作的视频");
+        }
+        FsUserVideoPVO entity = fsUserVideoMapper.selectFsUserVideoPVO(fsUserVideo.getVideoId());
+        if (entity == null) {
+            return R.error("视频不存在");
+        } else if (!Objects.equals(entity.getUserId(), userId)) {
+            return R.error("您没有权限操作此视频");
+        }
+        this.updateFsUserVideoIsShow(new Long[]{fsUserVideo.getVideoId()}, fsUserVideo.getStatus());
+        return R.ok();
+    }
+
+    private void selectLikesAndFavoritesByMyself(List<FsUserVideoListUVO> list, long userId) {
+        List<Long> videoIds = list.stream().map(vo -> Long.parseLong(vo.getId())).collect(Collectors.toList());
+        Map<Long, VideoLikeStatusDTO> likeMaps = fsUserVideoLikeMapper.checkLikes(videoIds, userId);
+        Map<Long, VideoFavoriteStatusDTO> FavoriteMaps = fsUserVideoFavoriteMapper.checkFavorites(videoIds, userId);
+        long videoId;
+        for (FsUserVideoListUVO entity : list) {
+            videoId = Long.parseLong(entity.getId());
+            if (likeMaps.containsKey(videoId)) {
+                entity.setLike(1);
+            }
+            if (FavoriteMaps.containsKey(videoId)) {
+                entity.setFavorite(1);
+            }
+        }
+    }
+
     public static String updateUrlPrefix(String url) {
         final String oldPrefix = "https://obs.ylrztop.com";
         final String newPrefix = "https://rtobs.ylrztop.com";

+ 5 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserVideoTagsServiceImpl.java

@@ -100,4 +100,9 @@ public class FsUserVideoTagsServiceImpl implements IFsUserVideoTagsService
     public List<FsUserVideoTagsPVO> selectFsUserVideoTagsSubList() {
         return fsUserVideoTagsMapper.selectFsUserVideoTagsSubList();
     }
+
+    @Override
+    public List<FsUserVideoTags> selectTagList() {
+        return fsUserVideoTagsMapper.selectTagList();
+    }
 }

+ 44 - 0
fs-service/src/main/java/com/fs/course/vo/FsBlackTalentPVO.java

@@ -0,0 +1,44 @@
+package com.fs.course.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class FsBlackTalentPVO {
+    /** $column.columnComment */
+    private Long id;
+
+    private Long userId;
+
+    private Long talentId;
+
+    private String type;
+
+    private String reportDesc;
+
+    private String isAudit;
+
+    /** 审核时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date auditTime;
+
+    /** 审核人 */
+    private Long auditUser;
+
+    /** 审核说明 */
+    private String auditDesc;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date creatTime;
+
+    /** 短视频id */
+    private Long videoId;
+
+    /** 1:达人,2:短视频 */
+    private String style;
+    private String auditUserName;
+    private String talentName;
+}

+ 14 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserTalentFansVo.java

@@ -0,0 +1,14 @@
+package com.fs.course.vo;
+
+import lombok.Data;
+
+@Data
+public class FsUserTalentFansVo {
+    private Long userId; //粉丝id
+    private Long talentId; //粉丝达人id
+    private String nickName; //粉丝名字
+    private String avatar; //粉丝头像
+    private Long fans; //粉丝的粉丝数
+    private Long videoNum; //粉丝发布视频数
+    private Integer isFollow; //是否关注这个粉丝
+}

+ 14 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserTalentFollowVo.java

@@ -0,0 +1,14 @@
+package com.fs.course.vo;
+
+import lombok.Data;
+
+@Data
+public class FsUserTalentFollowVo {
+
+    private Long talentId; //关注达人id
+    private String nickName; //关注达人名字
+    private String avatar; //关注达人头像
+    private String remark; //关注达人个签
+    private Long userId; //关注达人的
+
+}

+ 3 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserVideoPVO.java

@@ -94,5 +94,8 @@ public class FsUserVideoPVO extends BaseEntity
     /** 收藏数 */
     private Long favoriteNum;
 
+    /** 视频发布者对应的用户id */
+    private Long userId;
+
     private String auditByName;
 }

+ 14 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserVideoTagsVo.java

@@ -0,0 +1,14 @@
+package com.fs.course.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class FsUserVideoTagsVo {
+    private static final long serialVersionUID = 1L;
+    private Long value;
+    private String label;
+    private Long pid;
+    private List<FsUserVideoTagsVo> children; //子集
+}

+ 87 - 0
fs-service/src/main/java/com/fs/his/utils/TalentTreeUtil.java

@@ -0,0 +1,87 @@
+package com.fs.his.utils;
+import com.fs.course.vo.FsUserVideoTagsVo;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @ClassName 树形工具类
+ **/
+public class TalentTreeUtil {
+    /**
+     * 获得指定节点下所有归档
+     *
+     * @param list
+     * @param parentId
+     * @return
+     */
+    public static List<FsUserVideoTagsVo> list2TreeConverter(List<FsUserVideoTagsVo> list, int parentId) {
+        List<FsUserVideoTagsVo> returnList = new ArrayList<>();
+
+        for (FsUserVideoTagsVo res : list) {
+            //判断对象是否为根节点
+
+            if (res.getPid() == parentId) {
+                //该节点为根节点,开始递归
+                //通过递归为节点设置childList
+                recursionFn(list, res);
+                returnList.add(res);
+            }
+        }
+
+        return returnList;
+    }
+
+    /**
+     * 递归列表
+     * 通过递归,给指定t节点设置childList
+     *
+     * @param list
+     * @param t
+     */
+    public static void recursionFn(List<FsUserVideoTagsVo> list, FsUserVideoTagsVo t) {
+        //只能获取当前t节点的子节点集,并不是所有子节点集
+        List<FsUserVideoTagsVo> childsList = getChildList(list, t);
+
+        if(childsList!=null&&childsList.size()>0){
+            //设置他的子集对象集
+            t.setChildren(childsList);
+        }
+
+
+
+        //迭代子集对象集
+
+        //遍历完,则退出递归
+        for (FsUserVideoTagsVo nextChild : childsList) {
+
+            //判断子集对象是否还有子节点
+            if (!CollectionUtils.isEmpty(childsList)) {
+                //有下一个子节点,继续递归
+                recursionFn(list, nextChild);
+            }
+        }
+    }
+
+    /**
+     * 获得指定节点下的所有子节点
+     *
+     * @param list
+     * @param t
+     * @return
+     */
+    public static List<FsUserVideoTagsVo> getChildList(List<FsUserVideoTagsVo> list, FsUserVideoTagsVo t) {
+        List<FsUserVideoTagsVo> childsList = new ArrayList<>();
+        //遍历集合元素,如果元素的Parentid==指定元素的id,则说明是该元素的子节点
+        for (FsUserVideoTagsVo t1 : list) {
+            if (t1.getPid().equals(t.getValue())) {
+                childsList.add(t1);
+            }
+        }
+
+        return childsList;
+    }
+
+
+}

+ 105 - 0
fs-service/src/main/java/com/fs/utils/VideoUtil.java

@@ -0,0 +1,105 @@
+package com.fs.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Slf4j
+public class VideoUtil {
+    /**
+     * 获取视频元信息(宽高、大小、时长等)
+     */
+    public static Map<String, Object> getVideoInfo(File videoFile) throws IOException, InterruptedException {
+        Map<String, Object> videoInfo = new HashMap<>();
+        String videoPath = videoFile.getAbsolutePath();
+        String[] command = {
+                "ffmpeg",
+                "-i", videoPath
+        };
+
+        ProcessBuilder processBuilder = new ProcessBuilder(command);
+        processBuilder.redirectErrorStream(true);
+        Process process = processBuilder.start();
+
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (line.trim().startsWith("Duration:")) {
+                    String durationStr = line.split(",")[0].replace("Duration:","").trim();
+                    videoInfo.put("duration", parseDurationToSeconds(durationStr));
+                }
+                if (line.contains("Video:") && line.contains("fps")) {
+                    String[] parts = line.split(",");
+                    for (String part : parts) {
+                        Pattern pattern = Pattern.compile("(\\d{2,5})x(\\d{2,5})");
+                        Matcher matcher = pattern.matcher(part.trim());
+                        if (matcher.find()) {
+                            videoInfo.put("width", Integer.parseInt(matcher.group(1)));
+                            videoInfo.put("height", Integer.parseInt(matcher.group(2)));
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        int exitCode = process.waitFor();
+        if (exitCode != 0 && exitCode != 1) {
+            throw new RuntimeException("FFmpeg 获取视频信息失败,退出代码:" + exitCode);
+        }
+
+        videoInfo.put("size", videoFile.length());
+
+        return videoInfo;
+    }
+
+    /**
+     * 将时长字符串转换为秒
+     */
+    public static Integer parseDurationToSeconds(String durationStr) {
+        String[] parts = durationStr.split(":");
+        int hours = (int) Math.round(Double.parseDouble(parts[0]));
+        int minutes = (int) Math.round(Double.parseDouble(parts[1]));
+        int seconds = (int) Math.round(Double.parseDouble(parts[2]));
+        return hours * 3600 + minutes * 60 + seconds;
+    }
+
+    /**
+     * 提取缩略图
+     */
+    public static void extractFirstFrame(String videoPath, String outputImagePath) throws IOException, InterruptedException {
+        String[] command = {
+                "ffmpeg",
+                "-ss", "00:00:01.000",  // 精准定位到第1秒
+                "-i", videoPath,        // 输入视频路径
+                "-vframes", "1",        // 只提取1帧
+                "-q:v", "2",            // 质量控制(2=高质量,范围1-31)
+                "-y",                   // 覆盖输出文件
+                outputImagePath         // 输出图片路径(如 cover.jpg)
+        };
+        ProcessBuilder processBuilder = new ProcessBuilder(command);
+        processBuilder.redirectErrorStream(true); // 将标准错误和标准输出合并
+        Process process = processBuilder.start();
+//        process.waitFor();
+
+        // 处理进程的标准输出和错误流
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                log.info(line); // 输出到控制台或日志
+            }
+        }
+
+        int exitCode = process.waitFor();
+        if (exitCode != 0 && exitCode != 1) {
+            throw new RuntimeException("FFmpeg 执行失败,退出代码:" + exitCode);
+        }
+    }
+}

+ 94 - 0
fs-service/src/main/resources/application-config-druid-hat.yml

@@ -0,0 +1,94 @@
+baidu:
+  token: 12313231232
+  back-domain: https://www.xxxx.com
+#配置
+logging:
+  level:
+    org.springframework.web: INFO
+    com.github.binarywang.demo.wx.cp: DEBUG
+    me.chanjar.weixin: DEBUG
+wx:
+  miniapp:
+    configs:
+      - appid: wx41   #
+        secret: 58910ae743005c396012b029c7def579
+        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
+        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+        msgDataFormat: JSON
+      - appid: wxe
+        secret: 928d2961c81610d8f64b019597212fcd
+        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
+        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+        msgDataFormat: JSON
+  cp:
+    corpId: wwb2a10
+    appConfigs:
+      - agentId: 100005
+        secret: ec7okROXJqkN
+        token: PPKOdAloMO
+        aesKey: PKvaxtpSvNGpfTDm7VUHIK8Wok2ESyYX24qpXJAdMP
+  pay:
+    appId: wx73f85f8d6119 #微信公众号或者小程序等的appid
+    mchId: 1611045 #微信支付商户号
+    mchKey: 8cab128997a3547c10898b877f38 #微信支付商户密钥
+    subAppId:  #服务商模式下的子商户公众账号ID
+    subMchId:  #服务商模式下的子商户号
+    keyPath: c:\\cert\\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
+    notifyUrl: https://usepp.his.runtzh.com/app/wxpay/wxPayNotify
+  mp:
+    useRedis: false
+    redisConfig:
+      host: 127.0.0.1
+      port: 6379
+      timeout: 2000
+    configs:
+      - appId: wxe4bb68ede29c94b2 # 第一个公众号的appid
+        secret: 5a0c530a497c855559ee3958f8f9443d # 公众号的appsecret
+        token: PPKOdAlCoMO # 接口配置里的Token值
+        aesKey: Eswa6VjwtVcw03qZy6Wllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+  # 开放平台app微信授权配置
+  open:
+    app-id: wx9746858bdb5e0643
+    secret: 32dfaa2b2dcad9229935ff089c65d372
+aifabu:  #爱链接
+  appKey: 7b471be905ab17ef358c610dd117601d008
+watch:
+  watchUrl: watch.ylrzcloud.com/prod-api
+#  account: tcloud
+#  password: mdf-m2h_6yw2$hq
+  account1: ccif #866655060138751
+  password1: cp-t5or_6xw7$mt
+  account2: tcloud #rt500台
+  password2: mdf-m2h_6yw2$hq
+  account3: whr
+  password3: v9xsKuqn_$d2y
+
+fs :
+  commonApi: http://172.16.0.45:8010
+  h5CommonApi: http://172.16.0.45:8010
+nuonuo:
+  key: 10924508
+  secret: A2EB20764D304D16
+# 存储捅配置
+tencent_cloud_config:
+  secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
+  secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
+  bucket: hat-1323137866
+  app_id: 1323137866
+  region: ap-chongqing
+  proxy: hat
+cloud_host:
+  company_name: 恒安图
+  projectCode: HAT
+#看课授权时显示的头像
+headerImg:
+  imgUrl: https://hat-1323137866.cos.ap-chongqing.myqcloud.com/fs/20250928/hatlogo.png
+ipad:
+  ipadUrl: http://ipad.****.cn
+  aiApi: http://62:3000/api
+  voiceApi:
+  commonApi:
+wx_miniapp_temp:
+  pay_order_temp_id:
+  inquiry_temp_id:
+

+ 176 - 0
fs-service/src/main/resources/application-druid-hat.yml

@@ -0,0 +1,176 @@
+# 数据源配置
+spring:
+    profiles:
+        include: config-druid-hat,common
+    # redis 配置
+    redis:
+        # 地址
+        host: 172.16.0.82
+        # 端口,默认为6379
+        port: 6379
+        # 数据库索引
+        database: 0
+        # 密码
+        password: YlrztekHat250218!3@.
+        # 连接超时时间
+        timeout: 20s
+        lettuce:
+            pool:
+                # 连接池中的最小空闲连接
+                min-idle: 0
+                # 连接池中的最大空闲连接
+                max-idle: 8
+                # 连接池的最大数据库连接数
+                max-active: 100
+                # #连接池最大阻塞等待时间(使用负值表示没有限制)
+                max-wait: -1ms
+    datasource:
+        #        clickhouse:
+        #            type: com.alibaba.druid.pool.DruidDataSource
+        #            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
+        #            url: jdbc:clickhouse://cc-2vc8zzo26w0l7m2l6.public.clickhouse.ads.aliyuncs.com/sop?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
+        #            username: rt_2024
+        #            password: Yzx_19860213
+        #            initialSize: 10
+        #            maxActive: 100
+        #            minIdle: 10
+        #            maxWait: 6000
+        mysql:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://172.16.0.74:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek250928hat!3@.
+                # 从库数据源
+                slave:
+                    # 从数据源开关/默认关闭
+                    enabled: false
+                    url:
+                    username:
+                    password:
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 200
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: false
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+        sop:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://172.16.0.74:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek250928hat!3@.
+                read:
+                    url: jdbc:mysql://172.16.0.74:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek250928hat!3@.
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 200
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: false
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+rocketmq:
+    name-server: rmq-1243b25nj.rocketmq.gz.public.tencenttdmq.com:8080 # RocketMQ NameServer 地址
+    producer:
+        group: my-producer-group
+        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
+        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+    consumer:
+        group: test-group
+        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
+        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+custom:
+    token: "1o62d3YxvdHd4LEU7sK"
+    encoding-aes-key: "UJfTQ5qKTKlegjkXzJzxeHlUKvq5GyFbERN1iU"
+    corp-id: "ww51717e2b71d3"
+    secret: "6ODAmw-8W4t6h9mdzHh2Z4Apwj8mnsyRnjEDZOHdA7k"
+    private-key-path: "privatekey.pem"
+    webhook-url: "https://your-server.com/wecom/archive"
+# token配置
+token:
+    # 令牌自定义标识
+    header: Authorization
+    # 令牌密钥
+    secret: abcdefghijklmnopqrstuvwxyz
+    # 令牌有效期(默认30分钟)
+    expireTime: 180
+openIM:
+    secret: open
+    userID: imAd
+#是否为新商户,新商户不走mpOpenId
+isNewWxMerchant: false
+
+enableRedPackAccount: 1

+ 171 - 0
fs-service/src/main/resources/mapper/course/FsBlackTalentMapper.xml

@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.course.mapper.FsBlackTalentMapper">
+    
+    <resultMap type="FsBlackTalent" id="FsBlackTalentResult">
+        <result property="id"    column="id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="talentId"    column="talent_id"    />
+        <result property="type"    column="type"    />
+        <result property="reportDesc"    column="report_desc"    />
+        <result property="isAudit"    column="is_audit"    />
+        <result property="auditTime"    column="audit_time"    />
+        <result property="auditUser"    column="audit_user"    />
+        <result property="auditDesc"    column="audit_desc"    />
+        <result property="creatTime"    column="creat_time"    />
+        <result property="videoId"    column="video_id"    />
+        <result property="style"    column="style"    />
+        <result property="phone"    column="phone"    />
+        <result property="urls"    column="urls"    />
+        <result property="templateId"    column="template_id"    />
+        <result property="tradeImage"    column="trade_image"    />
+    </resultMap>
+
+    <sql id="selectFsBlackTalentVo">
+        select id, user_id, talent_id, type, report_desc, is_audit, audit_time, audit_user, audit_desc, creat_time, video_id, style,template_id,urls,phone,trade_image from fs_black_talent
+    </sql>
+
+    <select id="selectFsBlackTalentList" parameterType="FsBlackTalent" resultMap="FsBlackTalentResult">
+        <include refid="selectFsBlackTalentVo"/>
+        <where>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="talentId != null "> and talent_id = #{talentId}</if>
+            <if test="type != null  and type != ''"> and type = #{type}</if>
+            <if test="reportDesc != null  and reportDesc != ''"> and report_desc = #{reportDesc}</if>
+            <if test="isAudit != null  and isAudit != ''"> and is_audit = #{isAudit}</if>
+            <if test="auditTime != null "> and audit_time = #{auditTime}</if>
+            <if test="auditUser != null "> and audit_user = #{auditUser}</if>
+            <if test="auditDesc != null  and auditDesc != ''"> and audit_desc = #{auditDesc}</if>
+            <if test="creatTime != null "> and creat_time = #{creatTime}</if>
+            <if test="videoId != null "> and video_id = #{videoId}</if>
+            <if test="style != null  and style != ''"> and style = #{style}</if>
+            <if test="phone != null  and phone != ''"> and phone = #{phone}</if>
+            <if test="urls != null  and urls != ''"> and urls = #{urls}</if>
+            <if test="templateId != null  and templateId != ''"> and template_id = #{templateId}</if>
+            <if test="tradeImage != null  and tradeImage != ''"> and trade_image = #{tradeImage}</if>
+        </where>
+    </select>
+
+    <select id="selectFsBlackTalentPVOList" parameterType="FsBlackTalent" resultType="com.fs.course.vo.FsBlackTalentPVO">
+        select fbt.*,u.nick_name as audit_user_name,t.nick_name as talent_name from fs_black_talent fbt
+        left join sys_user u on u.user_id = fbt.audit_user
+        left join fs_user_talent t on t.talent_id = fbt.talent_id
+        <where>
+            <if test="userId != null "> and fbt.user_id = #{userId}</if>
+            <if test="talentId != null "> and fbt.talent_id = #{talentId}</if>
+            <if test="type != null  and type != ''"> and fbt.type = #{type}</if>
+            <if test="reportDesc != null  and reportDesc != ''"> and fbt.eport_desc = #{reportDesc}</if>
+            <if test="isAudit != null  and isAudit != ''"> and fbt.is_audit = #{isAudit}</if>
+            <if test="auditTime != null "> and fbt.audit_time = #{auditTime}</if>
+            <if test="auditUser != null "> and fbt.audit_user = #{auditUser}</if>
+            <if test="auditDesc != null  and auditDesc != ''"> and fbt.audit_desc = #{auditDesc}</if>
+            <if test="creatTime != null "> and fbt.creat_time = #{creatTime}</if>
+            <if test="videoId != null "> and fbt.video_id = #{videoId}</if>
+            <if test="style != null  and style != ''"> and fbt.style = #{style}</if>
+            <if test="phone != null  and phone != ''"> and fbt.phone = #{phone}</if>
+            <if test="urls != null  and urls != ''"> and fbt.urls = #{urls}</if>
+            <if test="templateId != null  and templateId != ''"> and fbt.template_id = #{templateId}</if>
+            <if test="tradeImage != null  and tradeImage != ''"> and fbt.trade_image = #{tradeImage}</if>
+        </where>
+    </select>
+    
+    <select id="selectFsBlackTalentById" parameterType="Long" resultMap="FsBlackTalentResult">
+        <include refid="selectFsBlackTalentVo"/>
+        where id = #{id}
+    </select>
+    <select id="selectBlackAndReportVideoIdsByUserId" resultMap="FsBlackTalentResult">
+        select video_id , talent_id from fs_black_talent where user_id = #{userId}
+    </select>
+    <select id="selectBlackTalent" resultType="java.lang.Integer">
+        select count(id) from fs_black_talent where user_id = #{loginUserId} and talent_id = #{talentId} and type = 1
+    </select>
+
+    <insert id="insertFsBlackTalent" parameterType="FsBlackTalent" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_black_talent
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="talentId != null">talent_id,</if>
+            <if test="type != null">type,</if>
+            <if test="reportDesc != null">report_desc,</if>
+            <if test="isAudit != null">is_audit,</if>
+            <if test="auditTime != null">audit_time,</if>
+            <if test="auditUser != null">audit_user,</if>
+            <if test="auditDesc != null">audit_desc,</if>
+            <if test="creatTime != null">creat_time,</if>
+            <if test="videoId != null">video_id,</if>
+            <if test="style != null">style,</if>
+            <if test="phone != null">phone,</if>
+            <if test="urls != null">urls,</if>
+            <if test="templateId != null">template_id,</if>
+            <if test="tradeImage != null">trade_image,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="talentId != null">#{talentId},</if>
+            <if test="type != null">#{type},</if>
+            <if test="reportDesc != null">#{reportDesc},</if>
+            <if test="isAudit != null">#{isAudit},</if>
+            <if test="auditTime != null">#{auditTime},</if>
+            <if test="auditUser != null">#{auditUser},</if>
+            <if test="auditDesc != null">#{auditDesc},</if>
+            <if test="creatTime != null">#{creatTime},</if>
+            <if test="videoId != null">#{videoId},</if>
+            <if test="style != null">#{style},</if>
+            <if test="phone != null">#{phone},</if>
+            <if test="urls != null">#{urls},</if>
+            <if test="templateId != null">#{templateId},</if>
+            <if test="tradeImage != null">#{tradeImage},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsBlackTalent" parameterType="FsBlackTalent">
+        update fs_black_talent
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="talentId != null">talent_id = #{talentId},</if>
+            <if test="type != null">type = #{type},</if>
+            <if test="reportDesc != null">report_desc = #{reportDesc},</if>
+            <if test="isAudit != null">is_audit = #{isAudit},</if>
+            <if test="auditTime != null">audit_time = #{auditTime},</if>
+            <if test="auditUser != null">audit_user = #{auditUser},</if>
+            <if test="auditDesc != null">audit_desc = #{auditDesc},</if>
+            <if test="creatTime != null">creat_time = #{creatTime},</if>
+            <if test="videoId != null">video_id = #{videoId},</if>
+            <if test="style != null">style = #{style},</if>
+            <if test="phone != null">phone = #{phone},</if>
+            <if test="urls != null">urls = #{urls},</if>
+            <if test="templateId != null">template_id = #{templateId},</if>
+            <if test="tradeImage != null">trade_image = #{tradeImage},</if>
+        </trim>
+        where id = #{id}
+    </update>
+    <update id="audit">
+        update fs_black_talent
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="map.isAudit != null">is_audit = #{map.isAudit},</if>
+            <if test="map.auditTime != null">audit_time = #{map.auditTime},</if>
+            <if test="map.auditUser != null">audit_user = #{map.auditUser},</if>
+            <if test="map.auditDesc != null">audit_desc = #{map.auditDesc},</if>
+        </trim>
+        where id = #{map.id}
+    </update>
+
+    <delete id="deleteFsBlackTalentById" parameterType="Long">
+        delete from fs_black_talent where id = #{id}
+    </delete>
+
+    <delete id="deleteFsBlackTalentByIds" parameterType="String">
+        delete from fs_black_talent where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+    <delete id="deleteFsBlackVideo">
+        delete from fs_black_talent where user_id = #{userId} and video_id = #{videoId} and type = '1'
+    </delete>
+    <delete id="deleteFsBlackTalent">
+        delete from fs_black_talent where user_id = #{userId} and talent_id = #{talentId} and type = '1'
+    </delete>
+</mapper>

+ 35 - 1
fs-service/src/main/resources/mapper/course/FsUserTalentFollowMapper.xml

@@ -28,7 +28,41 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <include refid="selectFsUserTalentFollowVo"/>
         where id = #{id}
     </select>
-        
+    <select id="queryFansCount" resultType="java.lang.Integer">
+        select count(user_id) from fs_user_talent_follow where talent_id = #{talentId}
+    </select>
+    <select id="queryIdolCount" resultType="java.lang.Integer">
+        select count(talent_id) from fs_user_talent_follow where user_id = #{userId}
+    </select>
+    <select id="selectFsUserTalentFansVoList" resultType="com.fs.course.vo.FsUserTalentFansVo">
+        SELECT f.user_id,t.talent_id,t.nick_name,t.avatar,COUNT(f2.id) fans,count(v.video_id) video_num,
+               CASE
+                   WHEN EXISTS (
+                       SELECT 1
+                       FROM fs_user_talent_follow f2
+                       WHERE f2.talent_id = t.talent_id
+                         AND f2.user_id = t2.user_id
+                   ) THEN 1
+                   ELSE 0
+                   END AS is_follow
+        FROM `fs_user_talent_follow` f
+                 LEFT JOIN fs_user_talent t ON f.user_id = t.user_id
+                 LEFT JOIN fs_user_talent t2 ON f.talent_id = t2.talent_id
+                 LEFT JOIN fs_user_video v ON t.talent_id = v.talent_id
+                 LEFT JOIN fs_user_talent_follow f2 ON t.talent_id = f2.talent_id
+        WHERE f.talent_id = #{maps.talentId}  and t.is_del = 0
+        GROUP BY
+            f.user_id, t.talent_id, t.nick_name, t.avatar, t.fans
+    </select>
+    <select id="selectFsUserFollowVoList" resultType="com.fs.course.vo.FsUserTalentFollowVo">
+        SELECT t.talent_id,t.nick_name,t.avatar,t.title as remark,t.user_id
+        FROM `fs_user_talent_follow` f
+        LEFT JOIN fs_user_talent t ON f.talent_id = t.talent_id
+        WHERE f.user_id = #{maps.userId} and t.is_del = 0
+        <if test="maps.keyword != null and maps.keyword != ''">
+            and t.nick_name like concat('%',#{maps.keyword},'%')
+        </if>
+    </select>
     <insert id="insertFsUserTalentFollow" parameterType="FsUserTalentFollow" useGeneratedKeys="true" keyProperty="id">
         insert into fs_user_talent_follow
         <trim prefix="(" suffix=")" suffixOverrides=",">

+ 42 - 1
fs-service/src/main/resources/mapper/course/FsUserTalentMapper.xml

@@ -30,10 +30,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="totalMoney"    column="total_money"    />
         <result property="freezeMoney"    column="freeze_money"    />
         <result property="extractMoney"    column="extract_money"    />
+        <result property="talentType"    column="talent_ype"    />
+        <result property="birthDay"    column="birth_day"    />
+        <result property="backGround"    column="back_ground"    />
+        <result property="province"    column="province"    />
+        <result property="city"    column="city"    />
+        <result property="district"    column="district"    />
     </resultMap>
 
     <sql id="selectFsUserTalentVo">
-        select talent_id, user_id, create_time, update_time, nick_name, avatar, phone, title, sex, tags, address, level, fans, likes, is_del, tiktok_link, kwai_link, is_audit, audit_time, certificate_code, certificate_images, balance, total_money, freeze_money, extract_money from fs_user_talent
+        select talent_id, user_id,back_ground,province,city,district,birth_day, create_time,talent_type,status, update_time, nick_name, avatar, phone, title, sex, tags, address, level, fans, likes, is_del, tiktok_link, kwai_link, is_audit, audit_time, certificate_code, certificate_images, balance, total_money, freeze_money, extract_money from fs_user_talent
     </sql>
 
     <select id="selectFsUserTalentList" parameterType="FsUserTalent" resultMap="FsUserTalentResult">
@@ -62,6 +68,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="totalMoney != null "> and total_money = #{totalMoney}</if>
             <if test="freezeMoney != null "> and freeze_money = #{freezeMoney}</if>
             <if test="extractMoney != null "> and extract_money = #{extractMoney}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="talentType != null "> and talent_type = #{talentType}</if>
+            <if test="birthDay != null "> and birth_day = #{birthDay}</if>
         </where>
     </select>
 
@@ -69,6 +78,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <include refid="selectFsUserTalentVo"/>
         where talent_id = #{talentId}
     </select>
+    <select id="queryTalentByUserId" resultType="com.fs.course.domain.FsUserTalent">
+        <include refid="selectFsUserTalentVo"/>
+        where user_id = #{userId}
+    </select>
 
     <insert id="insertFsUserTalent" parameterType="FsUserTalent" useGeneratedKeys="true" keyProperty="talentId">
         insert into fs_user_talent
@@ -97,6 +110,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="totalMoney != null">total_money,</if>
             <if test="freezeMoney != null">freeze_money,</if>
             <if test="extractMoney != null">extract_money,</if>
+            <if test="status != null">status,</if>
+            <if test="talentType != null">talent_type,</if>
+            <if test="birthDay != null">birth_day,</if>
+            <if test="province != null">province,</if>
+            <if test="city != null">city,</if>
+            <if test="district != null">district,</if>
+            <if test="backGround != null">back_ground,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="userId != null">#{userId},</if>
@@ -123,6 +143,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="totalMoney != null">#{totalMoney},</if>
             <if test="freezeMoney != null">#{freezeMoney},</if>
             <if test="extractMoney != null">#{extractMoney},</if>
+            <if test="status != null">#{status},</if>
+            <if test="talentType != null">#{talentType},</if>
+            <if test="birthDay != null">#{birthDay},</if>
+            <if test="province != null">#{province},</if>
+            <if test="city != null">#{city},</if>
+            <if test="district != null">#{district},</if>
+            <if test="backGround != null">#{backGround},</if>
         </trim>
     </insert>
 
@@ -153,9 +180,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="totalMoney != null">total_money = #{totalMoney},</if>
             <if test="freezeMoney != null">freeze_money = #{freezeMoney},</if>
             <if test="extractMoney != null">extract_money = #{extractMoney},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="talentType != null">talent_type = #{talentType},</if>
+            <if test="birthDay != null">birth_day = #{birthDay},</if>
+            <if test="backGround != null">back_ground = #{backGround},</if>
+            <if test="province != null">province = #{province},</if>
+            <if test="city != null">city = #{city},</if>
+            <if test="district != null">district = #{district},</if>
         </trim>
         where talent_id = #{talentId}
     </update>
+    <update id="updateFsUserTalentByUser" parameterType="FsUserTalent">
+        update fs_user_talent set avatar = #{avatar},nick_name = #{nickName},title = #{title},sex = #{sex},birth_day = #{birthDay},phone = #{phone},address = #{address},update_time = #{updateTime},
+        province = #{province},city = #{city},district = #{district},back_ground = #{backGround}
+        <if test="tiktokLink != null">,tiktok_link = #{tiktokLink}</if>
+        <if test="kwaiLink != null">,kwai_link = #{kwaiLink}</if>
+        where user_id = #{userId}
+    </update>
 
     <delete id="deleteFsUserTalentByTalentId" parameterType="Long">
         delete from fs_user_talent where talent_id = #{talentId}

+ 4 - 0
fs-service/src/main/resources/mapper/course/FsUserVideoMapper.xml

@@ -79,6 +79,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <include refid="selectFsUserVideoVo"/>
         where video_id = #{videoId}
     </select>
+    <select id="selectVideoByTalentId" resultMap="FsUserVideoResult">
+        <include refid="selectFsUserVideoVo"/>
+        where talent_id = #{talentId}
+    </select>
 
     <insert id="insertFsUserVideo" parameterType="FsUserVideo" useGeneratedKeys="true" keyProperty="videoId">
         insert into fs_user_video

+ 221 - 7
fs-user-app/src/main/java/com/fs/app/controller/TalentController.java

@@ -2,22 +2,51 @@ package com.fs.app.controller;
 
 
 import com.fs.app.annotation.Login;
+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.exception.file.OssException;
 import com.fs.common.utils.ServletUtils;
+import com.fs.course.domain.FsUserTalent;
 import com.fs.course.domain.FsUserTalentFollow;
+import com.fs.course.domain.FsUserVideo;
+import com.fs.course.param.FsUserTalentFansParam;
 import com.fs.course.param.FsUserTalentFollowParam;
+import com.fs.course.param.FsUserVideoAddParam;
+import com.fs.course.param.FsUserVideoListUParam;
 import com.fs.course.service.IFsUserTalentFollowService;
 import com.fs.course.service.IFsUserTalentService;
+import com.fs.course.service.IFsUserVideoService;
+import com.fs.course.service.IFsUserVideoTagsService;
+import com.fs.course.vo.FsUserVideoListUVO;
+import com.fs.course.vo.FsUserVideoTagsPVO;
+import com.fs.his.domain.FsUser;
+import com.fs.his.service.IFsUserService;
+import com.fs.system.oss.CloudStorageService;
+import com.fs.system.oss.OSSFactory;
+import com.fs.utils.VideoUtil;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import io.jsonwebtoken.Claims;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.Synchronized;
+import org.apache.commons.io.FilenameUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
 
 @Api("达人接口")
 @RestController
@@ -27,14 +56,55 @@ public class TalentController extends  AppBaseController{
     private IFsUserTalentFollowService userTalentFollowService;
     @Autowired
     private IFsUserTalentService userTalentService;
+    @Autowired
+    private IFsUserService fsUserService;
+    @Autowired
+    private IFsUserVideoService fsUserVideoService;
+    @Autowired
+    private IFsUserVideoTagsService fsUserVideoTagsService;
 
+    private static final String VIDEO_UPLOAD_DIR = "C:\\fs\\uploadPath\\talent\\video";  // 上传目录
+    private static final String FRAME_OUTPUT_DIR = "C:\\fs\\uploadPath\\talent\\frame";  // 输出帧的目录
 
-    @ApiOperation("获取达人详情")
-    @GetMapping("/getTalentById")
-    private R getTalentById(@RequestParam("talentId")Long talentId){
 
-        return R.ok();
+    @ApiOperation("获取指定达人详情")
+    @GetMapping("/getTalentByUserId")
+    @Login
+    private R getTalentById(@RequestParam("userId")Long userId){
+        return userTalentService.getTalentDetail(userId,Long.parseLong(getUserId()));
     }
+    @Login
+    @ApiOperation("获当前登录用户达人详情")
+    @GetMapping("/getTalentByToken")
+    private R getTalentByToken(){
+        //点击进入达人页面入口时,有可能当前用户还不是达人,需要自动注册成为达人
+        //查询基本信息
+        String userId = getUserId();
+        FsUserTalent fsUserTalent = userTalentService.queryTalentByUserId(Long.parseLong(userId));
+        if (null==fsUserTalent){
+            FsUser fsUser = fsUserService.selectFsUserByUserId(Long.parseLong(userId));
+            //新增达人
+            userTalentService.addFsUserTalent(fsUser.getUserId());
+        }
+        return userTalentService.getTalentDetail(Long.parseLong(userId),0l);
+    }
+
+    @ApiOperation("获取达人粉丝列表")
+    @GetMapping("/getTalentFansByUserId")
+    private TableDataInfo getFansByUserId(FsUserTalentFansParam param){
+        startPage();
+        param.setUserId(null);
+        return getDataTable(userTalentFollowService.selectFsUserTalentFansVoList(param));
+    }
+
+    @ApiOperation("获取达人关注列表")
+    @GetMapping("/getTalentFollowByUserId")
+    private TableDataInfo getFollowByUserId(FsUserTalentFansParam param){
+        startPage();
+        param.setTalentId(null);
+        return getDataTable(userTalentFollowService.selectFsUserFollowVoList(param));
+    }
+
 
     @Login
     @ApiOperation("获取是否关注")
@@ -60,14 +130,14 @@ public class TalentController extends  AppBaseController{
     @Login
     @ApiOperation("关注")
     @PostMapping("/doFollow")
-    @Transactional
+    //@Transactional
     @Synchronized
     public R doFollow(@RequestBody FsUserTalentFollowParam param, HttpServletRequest request){
         R r=userTalentFollowService.checkFollow(param.getTalentId(),Long.parseLong(getUserId()));
         if(r.get("code").equals(200)){
             //取消关注
             userTalentFollowService.deleteFollow(param.getTalentId(),Long.parseLong(getUserId()));
-            userTalentService.updateFans(param.getTalentId(),2);
+            userTalentService.updateFans(param.getTalentId(),1);
             return R.ok("已取消关注");
         }
         else{
@@ -77,8 +147,152 @@ public class TalentController extends  AppBaseController{
             map.setUserId(Long.parseLong(getUserId()));
             map.setCreateTime(new Date());
             userTalentFollowService.insertFsUserTalentFollow(map);
-            userTalentService.updateFans(param.getTalentId(),1);
+            userTalentService.updateFans(param.getTalentId(),-1);
             return R.ok("已关注");
         }
+
+    }
+
+    /**
+     * 达人上传视频
+     * @param file
+     * @return
+     * @throws Exception
+     */
+    //@Login
+    @PostMapping("/uploadOSSTalent")
+    public R uploadFile(@RequestParam("file") MultipartFile file) throws Exception {
+        //校验文件是否为空
+        if (file.isEmpty()) {
+            throw new OssException("上传文件不能为空");
+        }
+
+        //获取文件基本信息
+        String originalFilename = file.getOriginalFilename();
+        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
+        String fileType = file.getContentType();
+
+        //如果是视频文件且需要缩略图
+        if (fileType != null && fileType.startsWith("video/")) {
+            // 3.1 校验视频格式(示例仅允许MP4)
+            if (!fileType.equals("video/mp4")) {
+                return R.error("仅支持MP4视频格式");
+            }
+
+            //保存临时视频文件
+            String videoFileName = System.currentTimeMillis() + "_" + originalFilename;
+            File videoFile = new File(VIDEO_UPLOAD_DIR, videoFileName);
+            file.transferTo(videoFile);
+
+            //获取视频元信息(宽高、大小、时长等)
+            Map<String, Object> videoInfo = VideoUtil.getVideoInfo(videoFile);
+
+            //提取第一帧作为缩略图
+            String frameFileName = FilenameUtils.removeExtension(videoFileName) + "_frame.jpg";
+            File frameFile = new File(FRAME_OUTPUT_DIR, frameFileName);
+            VideoUtil.extractFirstFrame(videoFile.getAbsolutePath(), frameFile.getAbsolutePath());
+
+            //上传缩略图到OSS
+            CloudStorageService storage = OSSFactory.build();
+            UUID uuid = UUID.randomUUID();
+            //上传原始视频到OSS
+            byte[] fileBytes = Files.readAllBytes(videoFile.toPath()); // 从临时文件读取
+            String dateStr = new SimpleDateFormat("yyyyMMdd").format(new Date());
+            String path = "fs/talent/" + dateStr + "/" + uuid + ".mp4";
+            String videoUrl = storage.upload(fileBytes, path);
+            //上传缩略图到OSS
+            String thumbnailPath = "fs/talent/" + dateStr + "/" + uuid + ".jpg"; // 改为.jpg
+            String thumbnailUrl;
+            try (InputStream inputStream = new FileInputStream(frameFile)) {
+                thumbnailUrl = storage.upload(inputStream, thumbnailPath); // 使用相同路径规则
+            }
+            //清理临时文件
+            videoFile.delete();
+            frameFile.delete();
+
+            return R.ok()
+                    .put("url", videoUrl)          // 视频URL
+                    .put("thumbnailUrl", thumbnailUrl) // 缩略图URL
+                    .put("videoInfo", videoInfo);     // 视频元信息
+        }
+
+        //普通文件上传(图片/文档等)
+        CloudStorageService storage = OSSFactory.build();
+        String fileUrl = storage.uploadSuffix(file.getBytes(), suffix);
+
+        //返回普通文件URL
+        return R.ok()
+                .put("url", fileUrl)
+                .put("fileType", fileType);
+    }
+    @Login
+    @ApiOperation("达人提交上上传视频后返回的数据")
+    @PostMapping("/talentVideo")
+    private R talentVideo(@RequestBody FsUserVideoAddParam param){
+        String userId = getUserId();
+        FsUserTalent fsUserTalent = userTalentService.queryTalentByUserId(Long.parseLong(userId));
+        if (null==fsUserTalent){
+            return R.error("请先注册达人身份");
+        }
+        param.setTalentId(fsUserTalent.getTalentId());
+        return fsUserVideoService.addUserVideoByTalent(param);
+    }
+    @Login
+    @ApiOperation("视频列表")
+    @GetMapping("/getVideoList")
+    public R getVideoList(FsUserVideoListUParam param)
+    {
+        long userId = Long.parseLong(getUserId());
+        if (null==param.getUserId()||param.getUserId()<=0){
+            param.setUserId(userId);
+        }
+        FsUserTalent fsUserTalent = userTalentService.queryTalentByUserId(param.getUserId());
+        if (null==fsUserTalent){
+            return R.error("您选择的用户还未注册达人");
+        }
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        boolean oneSelf = userId == param.getUserId();
+        List<FsUserVideoListUVO> list = fsUserVideoService.selectFsUserVideoListUVOByUser(fsUserTalent.getTalentId(),oneSelf,userId);
+
+        PageInfo<FsUserVideoListUVO> listPageInfo=new PageInfo<>(list);
+        return R.ok().put("data",listPageInfo);
+    }
+
+    @Login
+    @ApiOperation("修改达人信息")
+    @PostMapping("/updateTalent")
+    public R updateTalent(@RequestBody FsUserTalent fsUserTalent)
+    {
+        String userId = getUserId();
+        fsUserTalent.setUserId(Long.parseLong(userId));
+        fsUserTalent.setUpdateTime(new Date());
+        userTalentService.updateFsUserTalentByUser(fsUserTalent);
+        return R.ok();
+    }
+
+
+    @Login
+    @ApiOperation("达人视频删除")
+    @PostMapping("/deleteVideo")
+    public R deleteVideo(@RequestBody FsUserVideo fsUserVideo)
+    {
+        Long userId = Long.parseLong(getUserId());
+        return fsUserVideoService.deleteFsUserVideoByVideoIdWithVerify(fsUserVideo.getVideoId(), userId);
+    }
+
+    @Login
+    @ApiOperation("更新达人视频状态")
+    @PostMapping("/updateVideoStatus")
+    public R updateVideoStatus(@RequestBody FsUserVideo fsUserVideo)
+    {
+        Long userId = Long.parseLong(getUserId());
+        return fsUserVideoService.updateVideoStatusWithVerify(fsUserVideo, userId);
+    }
+
+    @GetMapping("/subList")
+    public AjaxResult subList()
+    {
+        List<FsUserVideoTagsPVO> list = fsUserVideoTagsService.selectFsUserVideoTagsSubList();
+        return AjaxResult.success(list);
     }
 }

+ 21 - 0
fs-user-app/src/main/java/com/fs/app/controller/VideoController.java

@@ -9,8 +9,11 @@ import com.fs.course.param.*;
 import com.fs.course.service.*;
 import com.fs.course.vo.FsUserVideoCommentListUVO;
 import com.fs.course.vo.FsUserVideoListUVO;
+import com.fs.course.vo.FsUserVideoTagsVo;
+import com.fs.his.utils.TalentTreeUtil;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
+import com.google.common.collect.Lists;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -39,6 +42,8 @@ public class VideoController extends  AppBaseController{
 
     @Autowired
     private IRecommendationService recommendationService;
+    @Autowired
+    private IFsUserVideoTagsService fsUserVideoTagsService;
 
     @ApiOperation("视频列表")
     @GetMapping("/getVideoList")
@@ -218,4 +223,20 @@ public class VideoController extends  AppBaseController{
         return R.ok("未登录");
     }
 
+    @ApiOperation("获取短视频标签")
+    @GetMapping("/getTag")
+    public R getTag()
+    {
+        List<FsUserVideoTags> list=fsUserVideoTagsService.selectTagList();
+        List<FsUserVideoTagsVo> tagsVos = Lists.newArrayList();
+        for (FsUserVideoTags tag : list){
+            FsUserVideoTagsVo tagsVo = new FsUserVideoTagsVo();
+            tagsVo.setValue(tag.getTagId());
+            tagsVo.setLabel(tag.getTagName());
+            tagsVo.setPid(tag.getPid());
+            tagsVos.add(tagsVo);
+        }
+        return R.ok().put("data", TalentTreeUtil.list2TreeConverter(tagsVos, 0));
+    }
+
 }