Parcourir la source

update 租户后台 课程新增以及视频封面图片上传企微

ct il y a 6 jours
Parent
commit
e34f7769fd
21 fichiers modifiés avec 700 ajouts et 259 suppressions
  1. 100 73
      fs-admin-saas/src/main/java/com/fs/course/controller/FsUserCourseController.java
  2. 128 92
      fs-admin-saas/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java
  3. 34 4
      fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java
  4. 1 1
      fs-company/src/main/java/com/fs/company/controller/course/FsUserCourseController.java
  5. 26 0
      fs-qw-api/src/main/java/com/fs/app/controller/OpenQwApiController.java
  6. 5 1
      fs-qw-api/src/main/java/com/fs/app/qwTask/qwTask.java
  7. 6 0
      fs-qw-api/src/main/java/com/fs/app/service/OpenQwApiService.java
  8. 147 0
      fs-qw-api/src/main/java/com/fs/app/service/impl/OpenQwApiServiceImpl.java
  9. 17 0
      fs-service/src/main/java/com/fs/course/cache/PublicCourseAppCacheNames.java
  10. 9 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  11. 23 0
      fs-service/src/main/java/com/fs/course/param/FsUserCourseVideoBatchWatchIntegralParam.java
  12. 4 4
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  13. 10 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  14. 85 65
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  15. 24 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  16. 12 0
      fs-service/src/main/java/com/fs/his/utils/RedisCacheUtil.java
  17. 35 17
      fs-service/src/main/java/com/fs/qw/service/impl/AsyncUploadQwCourseImageService.java
  18. 2 1
      fs-service/src/main/java/com/fs/qwApi/config/OpenQwConfig.java
  19. 12 0
      fs-service/src/main/java/com/fs/qwApi/param/QwUploadImageByCourseParam.java
  20. 2 1
      fs-service/src/main/java/com/fs/qwApi/service/impl/QwApiServiceImpl.java
  21. 18 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml

+ 100 - 73
fs-admin-saas/src/main/java/com/fs/course/controller/FsUserCourseController.java

@@ -12,6 +12,7 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.cache.PublicCourseAppCacheNames;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.course.params.FsUserCourseConfigParam;
 import com.fs.course.params.FsUserCourseConfigParam;
@@ -64,9 +65,12 @@ public class FsUserCourseController extends BaseController {
     @GetMapping("/list")
     @GetMapping("/list")
     public TableDataInfo list(FsUserCourse fsUserCourse) {
     public TableDataInfo list(FsUserCourse fsUserCourse) {
         startPage();
         startPage();
-        applyCourseOwnerScope(fsUserCourse);
-        if (fsUserCourse.getIsPrivate() == null) {
-            fsUserCourse.setIsPrivate(1);
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
+            fsUserCourse.setUserId(userId);
         }
         }
         List<FsUserCourseListPVO> list = fsUserCourseService.selectFsUserCourseListPVO(fsUserCourse);
         List<FsUserCourseListPVO> list = fsUserCourseService.selectFsUserCourseListPVO(fsUserCourse);
         return getDataTable(list);
         return getDataTable(list);
@@ -79,30 +83,15 @@ public class FsUserCourseController extends BaseController {
     @GetMapping("/publicList")
     @GetMapping("/publicList")
     public TableDataInfo publicList(FsUserCourse fsUserCourse) {
     public TableDataInfo publicList(FsUserCourse fsUserCourse) {
         startPage();
         startPage();
-        applyCourseOwnerScope(fsUserCourse);
-        if (fsUserCourse.getIsPrivate() == null) {
-            fsUserCourse.setIsPrivate(0);
-        }
-        List<FsUserCourseListPVO> list = fsUserCourseService.selectFsUserCourseListPVO(fsUserCourse);
-        return getDataTable(list);
-    }
-
-    /**
-     * 课程配置开启「绑定创建人」时,按当前登录后台用户过滤课程归属
-     */
-    private void applyCourseOwnerScope(FsUserCourse query) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        if (loginUser == null) {
-            return;
-        }
+        Long userId = loginUser.getUser().getUserId();
         String json = configService.selectConfigByKey("course.config");
         String json = configService.selectConfigByKey("course.config");
-        if (ObjectUtil.isEmpty(json)) {
-            return;
-        }
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
         if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
         if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
-            query.setUserId(loginUser.getUserId());
+            fsUserCourse.setUserId(userId);
         }
         }
+        List<FsUserCourseListPVO> list = fsUserCourseService.selectFsUserCourseListPVO(fsUserCourse);
+        return getDataTable(list);
     }
     }
 
 
     /**
     /**
@@ -112,17 +101,16 @@ public class FsUserCourseController extends BaseController {
     @Log(title = "课程", businessType = BusinessType.EXPORT)
     @Log(title = "课程", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     @GetMapping("/export")
     public AjaxResult export(FsUserCourse fsUserCourse) {
     public AjaxResult export(FsUserCourse fsUserCourse) {
-//        com.fs.framework.security.LoginUser loginUser = (com.fs.framework.security.LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
-//        Long userId = (loginUser.getCompanyUser() != null ? loginUser.getCompanyUser().getUserId() : null);
-//        String json = configService.selectConfigByKey("course.config");
-//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-//        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
-//            fsUserCourse.setUserId(userId);
-//        }
-//        List<FsUserCourse> list = fsUserCourseService.selectFsUserCourseList(fsUserCourse);
-//        ExcelUtil<FsUserCourse> util = new ExcelUtil<FsUserCourse>(FsUserCourse.class);
-//        return util.exportExcel(list, "课程数据");
-        throw new RuntimeException("未实现");
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
+            fsUserCourse.setUserId(userId);
+        }
+        List<FsUserCourse> list = fsUserCourseService.selectFsUserCourseList(fsUserCourse);
+        ExcelUtil<FsUserCourse> util = new ExcelUtil<FsUserCourse>(FsUserCourse.class);
+        return util.exportExcel(list, "课程数据");
     }
     }
 
 
     /**
     /**
@@ -132,17 +120,16 @@ public class FsUserCourseController extends BaseController {
     @Log(title = "课程", businessType = BusinessType.EXPORT)
     @Log(title = "课程", businessType = BusinessType.EXPORT)
     @GetMapping("/publicExport")
     @GetMapping("/publicExport")
     public AjaxResult publicExport(FsUserCourse fsUserCourse) {
     public AjaxResult publicExport(FsUserCourse fsUserCourse) {
-//        com.fs.framework.security.LoginUser loginUser = (com.fs.framework.security.LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
-//        Long userId = (loginUser.getCompanyUser() != null ? loginUser.getCompanyUser().getUserId() : null);
-//        String json = configService.selectConfigByKey("course.config");
-//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-//        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
-//            fsUserCourse.setUserId(userId);
-//        }
-//        List<FsUserCourse> list = fsUserCourseService.selectFsUserCourseList(fsUserCourse);
-//        ExcelUtil<FsUserCourse> util = new ExcelUtil<FsUserCourse>(FsUserCourse.class);
-//        return util.exportExcel(list, "课程数据");
-        throw new RuntimeException("未实现");
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
+            fsUserCourse.setUserId(userId);
+        }
+        List<FsUserCourse> list = fsUserCourseService.selectFsUserCourseList(fsUserCourse);
+        ExcelUtil<FsUserCourse> util = new ExcelUtil<FsUserCourse>(FsUserCourse.class);
+        return util.exportExcel(list, "课程数据");
     }
     }
 
 
     /**
     /**
@@ -170,18 +157,23 @@ public class FsUserCourseController extends BaseController {
     @Log(title = "课程", businessType = BusinessType.INSERT)
     @Log(title = "课程", businessType = BusinessType.INSERT)
     @PostMapping
     @PostMapping
     public AjaxResult add(@RequestBody FsUserCourse fsUserCourse) {
     public AjaxResult add(@RequestBody FsUserCourse fsUserCourse) {
-//        com.fs.framework.security.LoginUser loginUser = (com.fs.framework.security.LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
-//        Long userId = (loginUser.getCompanyUser() != null ? loginUser.getCompanyUser().getUserId() : null);
-//        String json = configService.selectConfigByKey("course.config");
-//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-//        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
-//            fsUserCourse.setUserId(userId);
-//        }
-//        fsUserCourseService.insertFsUserCourse(fsUserCourse);
-//        redisCacheUtil.delRedisKey("getCourseList");
-//
-//        return toAjax(1);
-        throw new RuntimeException("未实现");
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
+            fsUserCourse.setUserId(userId);
+        }
+        if (fsUserCourse.getIsPrivate() == null) {
+            fsUserCourse.setIsPrivate(1);
+        }
+        fsUserCourseService.insertFsUserCourse(fsUserCourse,loginUser.getTenantId());
+        redisCacheUtil.delRedisKey("getCourseList");
+        if (fsUserCourse.getIsPrivate() != null && fsUserCourse.getIsPrivate() == 0) {
+            redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
+        }
+
+        return toAjax(1);
     }
     }
 
 
     /**
     /**
@@ -191,18 +183,22 @@ public class FsUserCourseController extends BaseController {
     @Log(title = "课程", businessType = BusinessType.INSERT)
     @Log(title = "课程", businessType = BusinessType.INSERT)
     @PostMapping("/public")
     @PostMapping("/public")
     public AjaxResult publicAdd(@RequestBody FsUserCourse fsUserCourse) {
     public AjaxResult publicAdd(@RequestBody FsUserCourse fsUserCourse) {
-//        com.fs.framework.security.LoginUser loginUser = (com.fs.framework.security.LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
-//        Long userId = (loginUser.getCompanyUser() != null ? loginUser.getCompanyUser().getUserId() : null);
-//        String json = configService.selectConfigByKey("course.config");
-//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-//        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
-//            fsUserCourse.setUserId(userId);
-//        }
-//        fsUserCourseService.insertFsUserCourse(fsUserCourse);
-//        redisCacheUtil.delRedisKey("getCourseList");
-//
-//        return toAjax(1);
-        throw new RuntimeException("未实现");
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
+            fsUserCourse.setUserId(userId);
+        }
+        if (fsUserCourse.getIsPrivate() == null) {
+            fsUserCourse.setIsPrivate(0);
+        }
+        fsUserCourseService.insertFsUserCourse(fsUserCourse,loginUser.getTenantId());
+        redisCacheUtil.delRedisKey("getCourseList");
+        if (fsUserCourse.getIsPrivate() != null && fsUserCourse.getIsPrivate() == 0) {
+            redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
+        }
+        return toAjax(1);
     }
     }
 
 
     /**
     /**
@@ -212,8 +208,11 @@ public class FsUserCourseController extends BaseController {
     @Log(title = "课程", businessType = BusinessType.UPDATE)
     @Log(title = "课程", businessType = BusinessType.UPDATE)
     @PutMapping
     @PutMapping
     public AjaxResult edit(@RequestBody FsUserCourse fsUserCourse) {
     public AjaxResult edit(@RequestBody FsUserCourse fsUserCourse) {
-        fsUserCourseService.updateFsUserCourse(fsUserCourse);
+        fsUserCourseService.updateFsUserCourse(fsUserCourse,getLoginUser().getTenantId());
         redisCacheUtil.delRedisKey("getCourseList");
         redisCacheUtil.delRedisKey("getCourseList");
+        if (fsUserCourse.getIsPrivate() != null && fsUserCourse.getIsPrivate() == 0) {
+            redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
+        }
         return toAjax(1);
         return toAjax(1);
     }
     }
 
 
@@ -235,8 +234,11 @@ public class FsUserCourseController extends BaseController {
     @Log(title = "课程", businessType = BusinessType.UPDATE)
     @Log(title = "课程", businessType = BusinessType.UPDATE)
     @PutMapping("/public")
     @PutMapping("/public")
     public AjaxResult publicEdit(@RequestBody FsUserCourse fsUserCourse) {
     public AjaxResult publicEdit(@RequestBody FsUserCourse fsUserCourse) {
-        fsUserCourseService.updateFsUserCourse(fsUserCourse);
+        fsUserCourseService.updateFsUserCourse(fsUserCourse,getLoginUser().getTenantId());
         redisCacheUtil.delRedisKey("getCourseList");
         redisCacheUtil.delRedisKey("getCourseList");
+        if (fsUserCourse.getIsPrivate() != null && fsUserCourse.getIsPrivate() == 0) {
+            redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
+        }
         return toAjax(1);
         return toAjax(1);
     }
     }
 
 
@@ -247,7 +249,7 @@ public class FsUserCourseController extends BaseController {
     @Log(title = "课程", businessType = BusinessType.DELETE)
     @Log(title = "课程", businessType = BusinessType.DELETE)
     @GetMapping("/copy/{courseId}")
     @GetMapping("/copy/{courseId}")
     public AjaxResult copy(@PathVariable Long courseId) {
     public AjaxResult copy(@PathVariable Long courseId) {
-        int i = fsUserCourseService.copyFsUserCourse(courseId);
+        int i = fsUserCourseService.copyFsUserCourse(courseId,getLoginUser().getTenantId());
         return toAjax(i);
         return toAjax(i);
     }
     }
 
 
@@ -260,6 +262,8 @@ public class FsUserCourseController extends BaseController {
     public AjaxResult remove(@PathVariable Long[] courseIds) {
     public AjaxResult remove(@PathVariable Long[] courseIds) {
         fsUserCourseService.deleteFsUserCourseByCourseIds(courseIds);
         fsUserCourseService.deleteFsUserCourseByCourseIds(courseIds);
         redisCacheUtil.delRedisKey("getCourseList");
         redisCacheUtil.delRedisKey("getCourseList");
+        redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
+
         return toAjax(1);
         return toAjax(1);
     }
     }
 
 
@@ -272,6 +276,7 @@ public class FsUserCourseController extends BaseController {
     public AjaxResult publicRemove(@PathVariable Long[] courseIds) {
     public AjaxResult publicRemove(@PathVariable Long[] courseIds) {
         fsUserCourseService.deleteFsUserCourseByCourseIds(courseIds);
         fsUserCourseService.deleteFsUserCourseByCourseIds(courseIds);
         redisCacheUtil.delRedisKey("getCourseList");
         redisCacheUtil.delRedisKey("getCourseList");
+        redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
         return toAjax(1);
         return toAjax(1);
     }
     }
 
 
@@ -286,8 +291,11 @@ public class FsUserCourseController extends BaseController {
     @Log(title = "课程上架", businessType = BusinessType.UPDATE)
     @Log(title = "课程上架", businessType = BusinessType.UPDATE)
     @PostMapping("/updateIsShow")
     @PostMapping("/updateIsShow")
     public AjaxResult updateIsShow(@RequestBody FsUserCourse fsUserCourse) {
     public AjaxResult updateIsShow(@RequestBody FsUserCourse fsUserCourse) {
-        fsUserCourseService.updateFsUserCourse(fsUserCourse);
+        fsUserCourseService.updateFsUserCourse(fsUserCourse,getLoginUser().getTenantId());
         redisCacheUtil.delRedisKey("getCourseList");
         redisCacheUtil.delRedisKey("getCourseList");
+        if (fsUserCourse.getIsPrivate() != null && fsUserCourse.getIsPrivate() == 0) {
+            redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
+        }
         return toAjax(1);
         return toAjax(1);
     }
     }
 
 
@@ -295,8 +303,11 @@ public class FsUserCourseController extends BaseController {
     @Log(title = "课程上架", businessType = BusinessType.UPDATE)
     @Log(title = "课程上架", businessType = BusinessType.UPDATE)
     @PostMapping("/publicUpdateIsShow")
     @PostMapping("/publicUpdateIsShow")
     public AjaxResult publicUpdateIsShow(@RequestBody FsUserCourse fsUserCourse) {
     public AjaxResult publicUpdateIsShow(@RequestBody FsUserCourse fsUserCourse) {
-        fsUserCourseService.updateFsUserCourse(fsUserCourse);
+        fsUserCourseService.updateFsUserCourse(fsUserCourse,getLoginUser().getTenantId());
         redisCacheUtil.delRedisKey("getCourseList");
         redisCacheUtil.delRedisKey("getCourseList");
+        if (fsUserCourse.getIsPrivate() != null && fsUserCourse.getIsPrivate() == 0) {
+            redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
+        }
         return toAjax(1);
         return toAjax(1);
     }
     }
 
 
@@ -306,6 +317,9 @@ public class FsUserCourseController extends BaseController {
     public AjaxResult putOn(@PathVariable Long[] courseIds) {
     public AjaxResult putOn(@PathVariable Long[] courseIds) {
         fsUserCourseService.updateFsUserCourseIsShow(courseIds, 1);
         fsUserCourseService.updateFsUserCourseIsShow(courseIds, 1);
         redisCacheUtil.delRedisKey("getCourseList");
         redisCacheUtil.delRedisKey("getCourseList");
+
+        redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
+
         return toAjax(1);
         return toAjax(1);
     }
     }
 
 
@@ -315,6 +329,9 @@ public class FsUserCourseController extends BaseController {
     public AjaxResult publicPutOn(@PathVariable Long[] courseIds) {
     public AjaxResult publicPutOn(@PathVariable Long[] courseIds) {
         fsUserCourseService.updateFsUserCourseIsShow(courseIds, 1);
         fsUserCourseService.updateFsUserCourseIsShow(courseIds, 1);
         redisCacheUtil.delRedisKey("getCourseList");
         redisCacheUtil.delRedisKey("getCourseList");
+
+        redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
+
         return toAjax(1);
         return toAjax(1);
     }
     }
 
 
@@ -324,6 +341,9 @@ public class FsUserCourseController extends BaseController {
     public AjaxResult pullOff(@PathVariable Long[] courseIds) {
     public AjaxResult pullOff(@PathVariable Long[] courseIds) {
         fsUserCourseService.updateFsUserCourseIsShow(courseIds, 0);
         fsUserCourseService.updateFsUserCourseIsShow(courseIds, 0);
         redisCacheUtil.delRedisKey("getCourseList");
         redisCacheUtil.delRedisKey("getCourseList");
+
+        redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
+
         return toAjax(1);
         return toAjax(1);
     }
     }
 
 
@@ -333,6 +353,10 @@ public class FsUserCourseController extends BaseController {
     public AjaxResult publicPutOff(@PathVariable Long[] courseIds) {
     public AjaxResult publicPutOff(@PathVariable Long[] courseIds) {
         fsUserCourseService.updateFsUserCourseIsShow(courseIds, 0);
         fsUserCourseService.updateFsUserCourseIsShow(courseIds, 0);
         redisCacheUtil.delRedisKey("getCourseList");
         redisCacheUtil.delRedisKey("getCourseList");
+
+        redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
+
+
         return toAjax(1);
         return toAjax(1);
     }
     }
 
 
@@ -348,6 +372,9 @@ public class FsUserCourseController extends BaseController {
         redisCacheUtil.delRedisKey("h5user:course:video:list:all");
         redisCacheUtil.delRedisKey("h5user:course:video:list:all");
         redisCacheUtil.delRedisKey("h5user:course:list:all");
         redisCacheUtil.delRedisKey("h5user:course:list:all");
         redisCacheUtil.delRedisKey("cache:video");
         redisCacheUtil.delRedisKey("cache:video");
+
+        redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST);
+
         return R.ok();
         return R.ok();
     }
     }
 
 

+ 128 - 92
fs-admin-saas/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java

@@ -6,6 +6,7 @@ import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.ServletUtils;
@@ -14,10 +15,7 @@ import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.mapper.FsUserCourseVideoMapper;
 import com.fs.course.mapper.FsUserCourseVideoMapper;
-import com.fs.course.param.BatchEditCoverParam;
-import com.fs.course.param.BatchRedUpdate;
-import com.fs.course.param.BatchVideoSvae;
-import com.fs.course.param.CourseVideoUpdates;
+import com.fs.course.param.*;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.FsUserCourseVideoChooseVO;
 import com.fs.course.vo.FsUserCourseVideoChooseVO;
@@ -58,7 +56,6 @@ public class FsUserCourseVideoController extends BaseController
 
 
     @Autowired
     @Autowired
     private ISysConfigService configService;
     private ISysConfigService configService;
-
     /**
     /**
      * 查询课堂视频列表
      * 查询课堂视频列表
      */
      */
@@ -66,17 +63,16 @@ public class FsUserCourseVideoController extends BaseController
     @GetMapping("/list")
     @GetMapping("/list")
     public TableDataInfo list(FsUserCourseVideo fsUserCourseVideo)
     public TableDataInfo list(FsUserCourseVideo fsUserCourseVideo)
     {
     {
-//        com.fs.framework.security.LoginUser loginUser = (com.fs.framework.security.LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
-//        Long userId = loginUser.getCompanyUser().getUserId();
-//        String json = configService.selectConfigByKey("course.config");
-//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-//        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
-//            fsUserCourseVideo.setUserId(userId);
-//        }
-//        startPage();
-//        List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoList(fsUserCourseVideo);
-//        return getDataTable(list);
-        throw new RuntimeException("未实现");
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+            fsUserCourseVideo.setUserId(userId);
+        }
+        startPage();
+        List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoList(fsUserCourseVideo);
+        return getDataTable(list);
     }
     }
 
 
     /**
     /**
@@ -87,17 +83,16 @@ public class FsUserCourseVideoController extends BaseController
     @GetMapping("/export")
     @GetMapping("/export")
     public AjaxResult export(FsUserCourseVideo fsUserCourseVideo)
     public AjaxResult export(FsUserCourseVideo fsUserCourseVideo)
     {
     {
-//        com.fs.framework.security.LoginUser loginUser = (com.fs.framework.security.LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
-//        Long userId = loginUser.getCompanyUser().getUserId();
-//        String json = configService.selectConfigByKey("course.config");
-//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-//        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
-//            fsUserCourseVideo.setUserId(userId);
-//        }
-//        List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoList(fsUserCourseVideo);
-//        ExcelUtil<FsUserCourseVideo> util = new ExcelUtil<FsUserCourseVideo>(FsUserCourseVideo.class);
-//        return util.exportExcel(list, "课堂视频数据");
-        throw new RuntimeException("未实现");
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+            fsUserCourseVideo.setUserId(userId);
+        }
+        List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoList(fsUserCourseVideo);
+        ExcelUtil<FsUserCourseVideo> util = new ExcelUtil<FsUserCourseVideo>(FsUserCourseVideo.class);
+        return util.exportExcel(list, "课堂视频数据");
     }
     }
 
 
     /**
     /**
@@ -107,15 +102,24 @@ public class FsUserCourseVideoController extends BaseController
     @GetMapping(value = "/{videoId}")
     @GetMapping(value = "/{videoId}")
     public AjaxResult getInfo(@PathVariable("videoId") Long videoId)
     public AjaxResult getInfo(@PathVariable("videoId") Long videoId)
     {
     {
-//        com.fs.framework.security.LoginUser loginUser = (com.fs.framework.security.LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
-//        Long userId = loginUser.getCompanyUser().getUserId();
-//        String json = configService.selectConfigByKey("course.config");
-//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-//        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
-//            return AjaxResult.success(fsUserCourseVideoService.selectFsUserCourseVideoByVideoIdVO(videoId,userId));
-//        }
-//        return AjaxResult.success(fsUserCourseVideoService.selectFsUserCourseVideoByVideoIdVO(videoId,null));
-        throw new RuntimeException("未实现");
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+            return AjaxResult.success(fsUserCourseVideoService.selectFsUserCourseVideoByVideoIdVO(videoId,userId));
+        }
+        return AjaxResult.success(fsUserCourseVideoService.selectFsUserCourseVideoByVideoIdVO(videoId,null));
+    }
+
+    /**
+     * 获取课堂视频详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseVideo:query')")
+    @GetMapping(value = "/public/{videoId}")
+    public AjaxResult getPublicInfo(@PathVariable("videoId") Long videoId)
+    {
+        return AjaxResult.success(fsUserCourseVideoService.selectFsUserCourseVideoByVideoIdVO(videoId,null));
     }
     }
 
 
     /**
     /**
@@ -126,24 +130,23 @@ public class FsUserCourseVideoController extends BaseController
     @PostMapping
     @PostMapping
     public AjaxResult add(@RequestBody FsUserCourseVideo fsUserCourseVideo)
     public AjaxResult add(@RequestBody FsUserCourseVideo fsUserCourseVideo)
     {
     {
-//        Long count = fsUserCourseVideoMapper.selectFsUserCourseVideoByCourseSort(fsUserCourseVideo.getCourseId(),fsUserCourseVideo.getCourseSort());
-//        if (count>0){
-//            return AjaxResult.error("课程排序重复");
-//        }
-//        com.fs.framework.security.LoginUser loginUser = (com.fs.framework.security.LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
-//        Long userId = loginUser.getCompanyUser().getUserId();
-//        String json = configService.selectConfigByKey("course.config");
-//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-//        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
-//            fsUserCourseVideo.setUserId(userId);
-//        }
-//        // 设置项目ID
-//        FsUserCourse fsUserCourse = fsUserCourseService.selectFsUserCourseByCourseId(fsUserCourseVideo.getCourseId());
-//        fsUserCourseVideo.setProjectId(fsUserCourse.getProject());
-//        return toAjax(fsUserCourseVideoService.insertFsUserCourseVideo(fsUserCourseVideo));
-        throw new RuntimeException("未实现");
+        Long count = fsUserCourseVideoMapper.selectFsUserCourseVideoByCourseSort(fsUserCourseVideo.getCourseId(),fsUserCourseVideo.getCourseSort());
+        if (count>0){
+            return AjaxResult.error("课程排序重复");
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+            fsUserCourseVideo.setUserId(userId);
+        }
+        // 设置项目ID
+        FsUserCourse fsUserCourse = fsUserCourseService.selectFsUserCourseByCourseId(fsUserCourseVideo.getCourseId());
+        fsUserCourseVideo.setProjectId(fsUserCourse.getProject());
+        int result = fsUserCourseVideoService.insertFsUserCourseVideo(fsUserCourseVideo);
+        return toAjax(result);
     }
     }
-
     /**
     /**
      * 修改课堂视频
      * 修改课堂视频
      */
      */
@@ -155,6 +158,26 @@ public class FsUserCourseVideoController extends BaseController
         return toAjax(fsUserCourseVideoService.updateFsUserCourseVideo(fsUserCourseVideo));
         return toAjax(fsUserCourseVideoService.updateFsUserCourseVideo(fsUserCourseVideo));
     }
     }
 
 
+    /**
+     * 批量修改课节:观看时长(分钟)、积分奖励(限同一课程下勾选的 videoId)
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseVideo:edit')")
+    @Log(title = "课堂视频", businessType = BusinessType.UPDATE)
+    @PostMapping("/batchUpdateWatchIntegral")
+    public AjaxResult batchUpdateWatchIntegral(@RequestBody FsUserCourseVideoBatchWatchIntegralParam param) {
+        if (param == null || param.getCourseId() == null) {
+            return AjaxResult.error("课程ID不能为空");
+        }
+        if (param.getVideoIds() == null || param.getVideoIds().isEmpty()) {
+            return AjaxResult.error("请选择要修改的课节");
+        }
+        if (param.getWatchDurationMinutes() == null || param.getIntegralReward() == null) {
+            return AjaxResult.error("请填写观看时长与积分奖励");
+        }
+        int rows = fsUserCourseVideoService.batchUpdateWatchIntegral(param);
+        return rows > 0 ? AjaxResult.success() : AjaxResult.error("未更新任何数据,请确认课节属于当前课程");
+    }
+
     /**
     /**
      * 删除课堂视频
      * 删除课堂视频
      */
      */
@@ -170,17 +193,24 @@ public class FsUserCourseVideoController extends BaseController
     @GetMapping("/getVideoListByCourseId")
     @GetMapping("/getVideoListByCourseId")
     public TableDataInfo getVideoListByCourseId(FsUserCourseVideo fsUserCourseVideo)
     public TableDataInfo getVideoListByCourseId(FsUserCourseVideo fsUserCourseVideo)
     {
     {
-//        startPage();
-//        com.fs.framework.security.LoginUser loginUser = (com.fs.framework.security.LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
-//        Long userId = loginUser.getCompanyUser().getUserId();
-//        String json = configService.selectConfigByKey("course.config");
-//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-//        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
-//            fsUserCourseVideo.setUserId(userId);
-//        }
-//        List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(fsUserCourseVideo);
-//        return getDataTable(list);
-        throw new RuntimeException("未实现");
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+            fsUserCourseVideo.setUserId(userId);
+        }
+        List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(fsUserCourseVideo);
+        return getDataTable(list);
+    }
+
+    @GetMapping("/getPublicVideoListByCourseId")
+    public TableDataInfo getPublicVideoListByCourseId(FsUserCourseVideo fsUserCourseVideo)
+    {
+        startPage();
+        List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(fsUserCourseVideo);
+        return getDataTable(list);
     }
     }
 
 
     @GetMapping("/getVideoListByCourseIdAll")
     @GetMapping("/getVideoListByCourseIdAll")
@@ -214,19 +244,18 @@ public class FsUserCourseVideoController extends BaseController
     }
     }
     @PostMapping("/batchSaveVideo")
     @PostMapping("/batchSaveVideo")
     public R batchSaveVideo(@RequestBody BatchVideoSvae vo){
     public R batchSaveVideo(@RequestBody BatchVideoSvae vo){
-//        // 设置项目ID
-//        FsUserCourse fsUserCourse = fsUserCourseService.selectFsUserCourseByCourseId(vo.getCourseId());
-//        vo.setProjectId(fsUserCourse.getProject());
-//        com.fs.framework.security.LoginUser loginUser = (com.fs.framework.security.LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
-//        Long userId = loginUser.getCompanyUser().getUserId();
-//        String json = configService.selectConfigByKey("course.config");
-//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-//        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
-//            vo.setUserId(userId);
-//        }
-//        fsUserCourseVideoService.batchSaveVideo(vo);
-//        return R.ok();
-        throw new RuntimeException("未实现");
+        // 设置项目ID
+        FsUserCourse fsUserCourse = fsUserCourseService.selectFsUserCourseByCourseId(vo.getCourseId());
+        vo.setProjectId(fsUserCourse.getProject());
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+            vo.setUserId(userId);
+        }
+        fsUserCourseVideoService.batchSaveVideo(vo);
+        return R.ok();
     }
     }
     @PostMapping("/batchUpdateRed")
     @PostMapping("/batchUpdateRed")
     @Log(title = "按课程批量保存设置红包金额", businessType = BusinessType.UPDATE)
     @Log(title = "按课程批量保存设置红包金额", businessType = BusinessType.UPDATE)
@@ -254,21 +283,20 @@ public class FsUserCourseVideoController extends BaseController
     public R getChooseCourseVideoList(@RequestParam(required = false) Long courseId,
     public R getChooseCourseVideoList(@RequestParam(required = false) Long courseId,
                                       @RequestParam(required = false, defaultValue = "1") Integer pageNum,
                                       @RequestParam(required = false, defaultValue = "1") Integer pageNum,
                                       @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
                                       @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
-//        com.fs.framework.security.LoginUser loginUser = (com.fs.framework.security.LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
-//        Long userId = loginUser.getCompanyUser().getUserId();
-//        String json = configService.selectConfigByKey("course.config");
-//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-//
-//        Map<String,Object> params = new HashMap<>();
-//        params.put("courseId", courseId);
-//        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
-//            params.put("userId", userId);
-//        }
-//
-//        PageHelper.startPage(pageNum, pageSize);
-//        List<FsUserCourseVideoChooseVO> list = fsUserCourseVideoService.getChooseCourseVideoListByMap(params);
-//        return R.ok().put("data", new PageInfo<>(list));
-        throw new RuntimeException("未实现");
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+
+        Map<String,Object> params = new HashMap<>();
+        params.put("courseId", courseId);
+        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+            params.put("userId", userId);
+        }
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsUserCourseVideoChooseVO> list = fsUserCourseVideoService.getChooseCourseVideoListByMap(params);
+        return R.ok().put("data", new PageInfo<>(list));
     }
     }
 
 
     @ApiOperation("视频下架")
     @ApiOperation("视频下架")
@@ -279,6 +307,14 @@ public class FsUserCourseVideoController extends BaseController
         return toAjax(fsUserCourseVideoService.batchDown(videoIds));
         return toAjax(fsUserCourseVideoService.batchDown(videoIds));
     }
     }
 
 
+    @ApiOperation("视频上架")
+    @PreAuthorize("@ss.hasPermi('course:userCourseVideo:batchUp')")
+    @Log(title = "课堂视频", businessType = BusinessType.UPDATE)
+    @PostMapping("/batchUp/{videoIds}")
+    public AjaxResult batchUp(@PathVariable String[] videoIds) {
+        return toAjax(fsUserCourseVideoService.batchUp(videoIds));
+    }
+
     @ApiOperation("批量修改视频封面图")
     @ApiOperation("批量修改视频封面图")
     @PreAuthorize("@ss.hasPermi('course:userCourseVideo:batchEditCover')")
     @PreAuthorize("@ss.hasPermi('course:userCourseVideo:batchEditCover')")
     @Log(title = "课堂视频", businessType = BusinessType.UPDATE)
     @Log(title = "课堂视频", businessType = BusinessType.UPDATE)

+ 34 - 4
fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java

@@ -3,10 +3,7 @@ package com.fs.common.core.redis;
 import java.util.*;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeUnit;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.redis.core.BoundSetOperations;
-import org.springframework.data.redis.core.HashOperations;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.data.redis.core.*;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Component;
 
 
 /**
 /**
@@ -510,4 +507,37 @@ public class RedisCache
     public Set<String> zRangeByScore(String key, double min, double max, long offset, long count) {
     public Set<String> zRangeByScore(String key, double min, double max, long offset, long count) {
         return redisTemplate.opsForZSet().rangeByScore(key, min, max, offset, count);
         return redisTemplate.opsForZSet().rangeByScore(key, min, max, offset, count);
     }
     }
+
+    /**
+     * 使用SCAN命令遍历匹配的key,避免阻塞主线程
+     *
+     * @param pattern 匹配模式(如 "cacheName::*")
+     * @return 匹配的key集合
+     */
+    public Set<String> scan(final String pattern)
+    {
+        return (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
+            Set<String> keys = new HashSet<>();
+            Cursor<byte[]> cursor = connection.keyCommands().scan(
+                    ScanOptions.scanOptions()
+                            .match(pattern)
+                            .count(1000)
+                            .build()
+            );
+
+            try {
+                while (cursor.hasNext()) {
+                    keys.add(new String(cursor.next()));
+                }
+            } finally {
+                try {
+                    cursor.close();
+                } catch (Exception e) {
+                    // 忽略关闭异常
+                }
+            }
+
+            return keys;
+        });
+    }
 }
 }

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/course/FsUserCourseController.java

@@ -70,7 +70,7 @@ public class FsUserCourseController extends BaseController
     @PutMapping
     @PutMapping
     public AjaxResult edit(@RequestBody FsUserCourse fsUserCourse)
     public AjaxResult edit(@RequestBody FsUserCourse fsUserCourse)
     {
     {
-        return toAjax(fsUserCourseService.updateFsUserCourse(fsUserCourse));
+        return toAjax(fsUserCourseService.updateFsUserCourse(fsUserCourse,getLoginUser().getTenantId()));
     }
     }
 
 
 
 

+ 26 - 0
fs-qw-api/src/main/java/com/fs/app/controller/OpenQwApiController.java

@@ -9,6 +9,7 @@ import com.fs.framework.datasource.TenantDataSourceUtil;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.QwExternalContactAddTagParam;
 import com.fs.qw.param.QwExternalContactAddTagParam;
 import com.fs.qw.param.QwExternalContactUpdateNoteParam;
 import com.fs.qw.param.QwExternalContactUpdateNoteParam;
+import com.fs.qwApi.param.QwUploadImageByCourseParam;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
@@ -167,5 +168,30 @@ public class OpenQwApiController extends BaseController {
             return R.error("加密ExternalUserid失败: " + e.getMessage());
             return R.error("加密ExternalUserid失败: " + e.getMessage());
         }
         }
     }
     }
+
+    /**
+     * 上传图片
+     */
+    @PostMapping("/uploadCourseImage")
+    public R uploadCourseImage(@RequestBody QwUploadImageByCourseParam param){
+        Long tenantId = param.getTenantId();
+        try {
+            log.info("[QwFriendWelcome] 上传图片,tenantId={}", tenantId);
+            // 切换到指定租户数据源执行操作(TenantDataSourceUtil 会自动设置 Redis 租户上下文)
+            return tenantDataSourceUtil.executeWithResult(tenantId, () ->
+                    {
+                        try {
+                            return openQwApiService.uploadCourseImage(param);
+                        } catch (Exception e) {
+                            log.error("[QwFriendWelcome] 上传图片,tenantId={}", tenantId, e);
+                            return R.error("上传图片失败: " + e.getMessage());
+                        }
+                    }
+            );
+        } catch (Exception e) {
+            log.error("[QwFriendWelcome] 上传图片,tenantId={}", tenantId, e);
+            return R.error("上传图片失败: " + e.getMessage());
+        }
+    }
 }
 }
 
 

+ 5 - 1
fs-qw-api/src/main/java/com/fs/app/qwTask/qwTask.java

@@ -1,5 +1,6 @@
 package com.fs.app.qwTask;
 package com.fs.app.qwTask;
 
 
+import com.fs.app.service.OpenQwApiService;
 import com.fs.course.service.IFinishCourseStatisticsSyncService;
 import com.fs.course.service.IFinishCourseStatisticsSyncService;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.qw.domain.QwIpadServerLog;
 import com.fs.qw.domain.QwIpadServerLog;
@@ -92,6 +93,9 @@ public class qwTask {
     @Autowired
     @Autowired
     private TenantTaskRunner tenantTaskRunner;
     private TenantTaskRunner tenantTaskRunner;
 
 
+    @Autowired
+    private OpenQwApiService openQwApiService;
+
     @Value("${saas.task.enabled:true}")
     @Value("${saas.task.enabled:true}")
     private boolean saasTaskEnabled;
     private boolean saasTaskEnabled;
 
 
@@ -199,7 +203,7 @@ public class qwTask {
      */
      */
     @Scheduled(cron = "0 0 6 1/2 * ?")
     @Scheduled(cron = "0 0 6 1/2 * ?")
     public void processQwSopCourseMaterialTimer() {
     public void processQwSopCourseMaterialTimer() {
-        runWithSaaSTenant("processQwSopCourseMaterialTimer", () -> iFsUserCourseService.processQwSopCourseMaterialTimer());
+        runWithSaaSTenant("processQwSopCourseMaterialTimer", () -> openQwApiService.processQwSopCourseMaterialTimer());
     }
     }
     /**
     /**
     *  同步待同步客户
     *  同步待同步客户

+ 6 - 0
fs-qw-api/src/main/java/com/fs/app/service/OpenQwApiService.java

@@ -4,6 +4,7 @@ import com.fs.common.core.domain.R;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.QwExternalContactAddTagParam;
 import com.fs.qw.param.QwExternalContactAddTagParam;
 import com.fs.qw.param.QwExternalContactUpdateNoteParam;
 import com.fs.qw.param.QwExternalContactUpdateNoteParam;
+import com.fs.qwApi.param.QwUploadImageByCourseParam;
 
 
 public interface OpenQwApiService {
 public interface OpenQwApiService {
     R getSyncQwUser(Long tenantId, String corpId);
     R getSyncQwUser(Long tenantId, String corpId);
@@ -21,4 +22,9 @@ public interface OpenQwApiService {
     R delTag(QwExternalContactAddTagParam param);
     R delTag(QwExternalContactAddTagParam param);
 
 
     R getOpenExternalUserid(String externalUserid,String corpId,String qwUserId);
     R getOpenExternalUserid(String externalUserid,String corpId,String qwUserId);
+
+    R uploadCourseImage(QwUploadImageByCourseParam param) throws Exception;
+
+    void processQwSopCourseMaterialTimer();
+
 }
 }

+ 147 - 0
fs-qw-api/src/main/java/com/fs/app/service/impl/OpenQwApiServiceImpl.java

@@ -1,10 +1,13 @@
 package com.fs.app.service.impl;
 package com.fs.app.service.impl;
 
 
+import cn.hutool.http.HttpUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
 import com.fs.app.service.OpenQwApiService;
 import com.fs.app.service.OpenQwApiService;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.SecurityUtils;
+import com.fs.course.domain.FsUserCourse;
+import com.fs.course.mapper.FsUserCourseMapper;
 import com.fs.framework.datasource.TenantDataSourceUtil;
 import com.fs.framework.datasource.TenantDataSourceUtil;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.domain.QwDept;
 import com.fs.qw.domain.QwDept;
@@ -21,6 +24,7 @@ import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.vo.QwSopRuleTimeVO;
 import com.fs.qw.vo.QwSopRuleTimeVO;
 import com.fs.qwApi.Result.DeptUserResult;
 import com.fs.qwApi.Result.DeptUserResult;
 import com.fs.qwApi.Result.QwOpenidResult;
 import com.fs.qwApi.Result.QwOpenidResult;
+import com.fs.qwApi.Result.QwUploadResult;
 import com.fs.qwApi.Result.UserResult;
 import com.fs.qwApi.Result.UserResult;
 import com.fs.qwApi.domain.QwDeptResult;
 import com.fs.qwApi.domain.QwDeptResult;
 import com.fs.qwApi.domain.QwExternalContactRemarkResult;
 import com.fs.qwApi.domain.QwExternalContactRemarkResult;
@@ -31,6 +35,7 @@ import com.fs.qwApi.domain.inner.DeptUser;
 import com.fs.qwApi.param.QwEditUserTagParam;
 import com.fs.qwApi.param.QwEditUserTagParam;
 import com.fs.qwApi.param.QwExternalContactRemarkParam;
 import com.fs.qwApi.param.QwExternalContactRemarkParam;
 import com.fs.qwApi.param.QwOpenidByUserParams;
 import com.fs.qwApi.param.QwOpenidByUserParams;
+import com.fs.qwApi.param.QwUploadImageByCourseParam;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.domain.SopUserLogsInfo;
@@ -50,6 +55,11 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
 import java.time.LocalDate;
 import java.time.LocalDate;
 import java.time.LocalTime;
 import java.time.LocalTime;
 import java.time.ZoneId;
 import java.time.ZoneId;
@@ -86,6 +96,8 @@ public class OpenQwApiServiceImpl implements OpenQwApiService {
 
 
     @Autowired
     @Autowired
     private TenantDataSourceUtil tenantDataSourceUtil;
     private TenantDataSourceUtil tenantDataSourceUtil;
+    @Autowired
+    private FsUserCourseMapper fsUserCourseMapper;
 
 
     /** 同步用户线程池 */
     /** 同步用户线程池 */
     private static final ExecutorService SYNC_USER_EXECUTOR = new ThreadPoolExecutor(
     private static final ExecutorService SYNC_USER_EXECUTOR = new ThreadPoolExecutor(
@@ -835,6 +847,141 @@ public class OpenQwApiServiceImpl implements OpenQwApiService {
         }
         }
     }
     }
 
 
+    @Override
+    public R uploadCourseImage(QwUploadImageByCourseParam param) throws Exception {
+        List<String> corpIds = param.getCorpIds();
+        StringBuilder failMsg = new StringBuilder();
+        if (corpIds != null && !corpIds.isEmpty()){
+            for (String corpId : corpIds) {
+                File imageFile = null;
+                try {
+                    // 将图片URL转为本地临时文件
+                    imageFile = urlToFile(param.getImgUrl());
+                    // 调用企业微信API上传图片
+//            QwUploadResult result = qwApiService.upload(imageFile, "image", corpId);
+                    QwUploadResult result = qwApiService.upload(imageFile, "image", corpId);
+
+                    if (result.getErrCode() == 0) {
+                        // 上传成功,缓存mediaId
+                        String cacheKey = String.format("miniprogram:%s:%s", corpId, param.getCourseId());
+                        redisCache.setCacheObject(cacheKey, result.getMediaId());
+                    } else {
+                        failMsg.append(String.format("上传图片失败: errMsg=%s, corpId=%s, courseId=%s",result.getErrMsg(), corpId, param.getCourseId()));
+                        log.error("上传图片失败: errMsg={}, corpId={}, courseId={}",
+                                result.getErrMsg(), corpId, param.getCourseId());
+                    }
+                } finally {
+                    // 清理临时文件
+                    if (imageFile != null && imageFile.exists()) {
+                        imageFile.delete();
+                    }
+                }
+            }
+        }
+        if (!failMsg.isEmpty()){
+            return R.error(failMsg.toString());
+        } else {
+            return R.ok();
+        }
+
+    }
+
+    /**
+     * 定时任务 - 处理企业微信SOP课程素材
+     * 每2天执行一次,将课程封面图片上传到企业微信素材库并缓存mediaId
+     */
+    @Override
+    public void processQwSopCourseMaterialTimer() {
+        // 获取所有需要处理的课程列表
+        List<FsUserCourse> fsUserCourses = fsUserCourseMapper.selectFsUserCourseAllCourseByQw();
+        // 获取所有企业微信配置
+        List<QwCompany> companies = iQwCompanyService.selectQwCompanyList(new QwCompany());
+
+
+        // 遍历每个课程,上传图片到对应企业的素材库
+        for (FsUserCourse course : fsUserCourses) {
+            try {
+                // 获取所有企业微信配置
+                if (companies != null && !companies.isEmpty()) {
+                    List<String> corpIds = companies.stream().map(QwCompany::getCorpId).filter(Objects::nonNull).toList();
+                    if (!corpIds.isEmpty()) {
+                        try {
+
+                            QwUploadImageByCourseParam param = new QwUploadImageByCourseParam();
+                            BeanUtils.copyProperties(course, param);
+                            param.setCorpIds(corpIds);
+                            R result = uploadCourseImage(param);
+                            log.info("上传图片到对应企业的素材库: result={}", result);
+                        } catch (Exception e) {
+                            log.error("处理课程图片失败: courseId={}, corpIds={}, error={}", course.getCourseId(), corpIds, e.getMessage());
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                log.error("处理课程图片失败: courseId={}, error={}",
+                        course.getCourseId(), e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * 将URL指向的文件下载为临时文件
+     *
+     * @param fileUrl 文件URL地址
+     * @return 下载的临时文件
+     * @throws Exception URL解析或IO异常
+     */
+    private static File urlToFile(String fileUrl) throws Exception {
+        URL url = new URL(fileUrl);
+        // 生成临时文件名(原文件名+时间戳)
+        String fileName = extractFileName(url) + System.currentTimeMillis();
+        String extension = getFileExtension(new File(fileUrl).getName());
+
+        // 创建临时文件
+        File tempFile = StringUtil.strIsNullOrEmpty(extension) ?
+                File.createTempFile(fileName, null) :
+                File.createTempFile(fileName, "." + extension);
+
+        // 下载文件内容
+        try (BufferedInputStream in = new BufferedInputStream(url.openStream());
+             FileOutputStream out = new FileOutputStream(tempFile)) {
+            byte[] buffer = new byte[8192];
+            int len;
+            while ((len = in.read(buffer)) != -1) {
+                out.write(buffer, 0, len);
+            }
+            return tempFile;
+        } catch (IOException e) {
+            tempFile.delete();
+            throw new IOException("下载文件失败: " + fileUrl, e);
+        }
+    }
+
+    /**
+     * 从URL中提取文件名(不含扩展名)
+     *
+     * @param url 文件URL
+     * @return 文件名
+     */
+    private static String extractFileName(URL url) {
+        String path = url.getPath();
+        String fileName = path.substring(path.lastIndexOf('/') + 1);
+        int dotIndex = fileName.lastIndexOf('.');
+        return dotIndex > 0 ? fileName.substring(0, dotIndex) : fileName;
+    }
+
+    /**
+     * 获取文件扩展名
+     *
+     * @param fileName 完整文件名
+     * @return 扩展名(不含点号),如果没有扩展名则返回空字符串
+     */
+    private static String getFileExtension(String fileName) {
+        int lastIndex = fileName.lastIndexOf('.');
+        return (lastIndex != -1 && lastIndex + 1 < fileName.length()) ?
+                fileName.substring(lastIndex + 1) : "";
+    }
+
     @Autowired
     @Autowired
     private QwSopMapper qwSopMapper;
     private QwSopMapper qwSopMapper;
 
 

+ 17 - 0
fs-service/src/main/java/com/fs/course/cache/PublicCourseAppCacheNames.java

@@ -0,0 +1,17 @@
+package com.fs.course.cache;
+
+/**
+ * 用户端(小程序等)公域课程/分类查询 Spring Cache 名称,与 Redis 中
+ * "cacheName::key" 前缀一致,管理端需按名批量删除时与此常量对齐。
+ */
+public final class PublicCourseAppCacheNames {
+
+    /** 公域/小程序课程分类 App 列表 */
+    public static final String CATEGORY_APP_LIST = "publicCourseCategoryApp";
+
+    /** 公域课程 App 列表 */
+    public static final String COURSE_PUBLIC_APP_LIST = "publicCoursePublicApp";
+
+    private PublicCourseAppCacheNames() {
+    }
+}

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

@@ -306,4 +306,13 @@ public interface FsUserCourseVideoMapper extends BaseMapper<FsUserCourseVideo> {
             "      AND course_id = #{courseId}")
             "      AND course_id = #{courseId}")
     List<OptionsVO> selectVideoOptionsByCourseId(@Param("courseId") Long courseId);
     List<OptionsVO> selectVideoOptionsByCourseId(@Param("courseId") Long courseId);
 
 
+    int batchUpdateWatchIntegralByVideoIds(@Param("courseId") Long courseId,
+                                           @Param("videoIds") List<Long> videoIds,
+                                           @Param("watchDurationMinutes") Integer watchDurationMinutes,
+                                           @Param("integralReward") Integer integralReward);
+
+    /**
+     * 上架
+     */
+    int batchUp(String[] videoIds);
 }
 }

+ 23 - 0
fs-service/src/main/java/com/fs/course/param/FsUserCourseVideoBatchWatchIntegralParam.java

@@ -0,0 +1,23 @@
+package com.fs.course.param;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 批量更新课节:观看时长(分钟)、积分奖励
+ */
+@Data
+public class FsUserCourseVideoBatchWatchIntegralParam implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Long courseId;
+
+    private List<Long> videoIds;
+
+    private Integer watchDurationMinutes;
+
+    private Integer integralReward;
+}

+ 4 - 4
fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java

@@ -43,7 +43,7 @@ public interface IFsUserCourseService {
      * @param fsUserCourse 课程
      * @param fsUserCourse 课程
      * @return 结果
      * @return 结果
      */
      */
-    public int insertFsUserCourse(FsUserCourse fsUserCourse);
+    public int insertFsUserCourse(FsUserCourse fsUserCourse,Long tenantId);
 
 
     /**
     /**
      * 修改课程
      * 修改课程
@@ -51,7 +51,7 @@ public interface IFsUserCourseService {
      * @param fsUserCourse 课程
      * @param fsUserCourse 课程
      * @return 结果
      * @return 结果
      */
      */
-    public int updateFsUserCourse(FsUserCourse fsUserCourse);
+    public int updateFsUserCourse(FsUserCourse fsUserCourse,Long tenantId);
 
 
     /**
     /**
      * 批量删除课程
      * 批量删除课程
@@ -107,7 +107,7 @@ public interface IFsUserCourseService {
 
 
     List<FsUserCourseListVO> getFsUserCourseList(FsUserCourseListParam param);
     List<FsUserCourseListVO> getFsUserCourseList(FsUserCourseListParam param);
 
 
-    void processQwSopCourseMaterialTimer();
+//    void processQwSopCourseMaterialTimer();
 
 
     List<FsCourseListBySidebarVO> getFsCourseListBySidebar(FsCourseListBySidebarParam param);
     List<FsCourseListBySidebarVO> getFsCourseListBySidebar(FsCourseListBySidebarParam param);
 
 
@@ -123,7 +123,7 @@ public interface IFsUserCourseService {
 
 
     String createUserImageQR(@NotNull(message = "链接不能为空") String realLink, String backgroundImagePath, InputStream inputStream, String png, @NotNull(message = "销售id不能为空") Long companyUserId) throws Exception;
     String createUserImageQR(@NotNull(message = "链接不能为空") String realLink, String backgroundImagePath, InputStream inputStream, String png, @NotNull(message = "销售id不能为空") Long companyUserId) throws Exception;
 
 
-    int copyFsUserCourse(Long courseId);
+    int copyFsUserCourse(Long courseId, Long tenantId);
 
 
     List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoApplet();
     List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoApplet();
 
 

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

@@ -262,4 +262,14 @@ public interface IFsUserCourseVideoService extends IService<FsUserCourseVideo> {
      * @return list
      * @return list
      */
      */
     List<OptionsVO> selectVideoOptionsByCourseId(Long courseId);
     List<OptionsVO> selectVideoOptionsByCourseId(Long courseId);
+
+    /**
+     * 批量更新指定课节:观看时长(分钟)、积分奖励(需 courseId+videoIds 与库一致)
+     */
+    int batchUpdateWatchIntegral(FsUserCourseVideoBatchWatchIntegralParam param);
+
+    /**
+     * 视频上架
+     */
+    int batchUp(String[] videoIds);
 }
 }

+ 85 - 65
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -45,6 +45,7 @@ import com.fs.qw.domain.QwCompany;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.impl.AsyncUploadQwCourseImageService;
 import com.fs.qw.service.impl.AsyncUploadQwCourseImageService;
 import com.fs.qwApi.Result.QwUploadResult;
 import com.fs.qwApi.Result.QwUploadResult;
+import com.fs.qwApi.param.QwUploadImageByCourseParam;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.system.mapper.SysDictDataMapper;
 import com.fs.system.mapper.SysDictDataMapper;
 import com.fs.system.service.ISysConfigService;
 import com.fs.system.service.ISysConfigService;
@@ -172,11 +173,20 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
      * @return 结果
      * @return 结果
      */
      */
     @Override
     @Override
-    public int insertFsUserCourse(FsUserCourse fsUserCourse)
+    public int insertFsUserCourse(FsUserCourse fsUserCourse,Long tenantId)
     {
     {
 
 
         if (fsUserCourse.getIsPrivate()==1){
         if (fsUserCourse.getIsPrivate()==1){
-            asyncUploadQwCourseImageService.uploadQwCourseImage(fsUserCourse);
+            QwUploadImageByCourseParam param = new QwUploadImageByCourseParam();
+            BeanUtils.copyProperties(fsUserCourse, param);
+            param.setTenantId(tenantId);
+            // 获取所有企业微信配置
+            List<QwCompany> companies = iQwCompanyService.selectQwCompanyList(new QwCompany());
+            if (companies != null && !companies.isEmpty()){
+                List<String> corpIds = companies.stream().map(QwCompany::getCorpId).filter(Objects::nonNull).toList();
+                param.setCorpIds(corpIds);
+                asyncUploadQwCourseImageService.uploadQwCourseImage(param);
+            }
         }
         }
 
 
 
 
@@ -191,10 +201,19 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
      * @return 结果
      * @return 结果
      */
      */
     @Override
     @Override
-    public int updateFsUserCourse(FsUserCourse fsUserCourse)
+    public int updateFsUserCourse(FsUserCourse fsUserCourse,Long tenantId)
     {
     {
         if (fsUserCourse.getIsPrivate()==1) {
         if (fsUserCourse.getIsPrivate()==1) {
-            asyncUploadQwCourseImageService.uploadQwCourseImage(fsUserCourse);
+            QwUploadImageByCourseParam param = new QwUploadImageByCourseParam();
+            BeanUtils.copyProperties(fsUserCourse, param);
+            param.setTenantId(tenantId);
+            // 获取所有企业微信配置
+            List<QwCompany> companies = iQwCompanyService.selectQwCompanyList(new QwCompany());
+            if (companies != null && !companies.isEmpty()) {
+                List<String> corpIds = companies.stream().map(QwCompany::getCorpId).filter(Objects::nonNull).toList();
+                param.setCorpIds(corpIds);
+                asyncUploadQwCourseImageService.uploadQwCourseImage(param);
+            }
         }
         }
 
 
         fsUserCourse.setUpdateTime(DateUtils.getNowDate());
         fsUserCourse.setUpdateTime(DateUtils.getNowDate());
@@ -488,35 +507,35 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
         return fsUserCourseMapper.getFsUserCourseList(param);
         return fsUserCourseMapper.getFsUserCourseList(param);
     }
     }
 
 
-    /**
-     * 定时任务 - 处理企业微信SOP课程素材
-     * 每2天执行一次,将课程封面图片上传到企业微信素材库并缓存mediaId
-     */
-    @Override
-    public void processQwSopCourseMaterialTimer() {
-        // 获取所有需要处理的课程列表
-        List<FsUserCourse> fsUserCourses = fsUserCourseMapper.selectFsUserCourseAllCourseByQw();
-        // 获取所有企业微信配置
-        List<QwCompany> companies = iQwCompanyService.selectQwCompanyList(new QwCompany());
-
-        // 遍历每个企业微信配置
-        for (QwCompany company : companies) {
-            String corpId = company.getCorpId();
-            if (corpId == null) {
-                continue;
-            }
-
-            // 遍历每个课程,上传图片到对应企业的素材库
-            for (FsUserCourse course : fsUserCourses) {
-                try {
-                    uploadCourseImage(course, corpId);
-                } catch (Exception e) {
-                    log.error("处理课程图片失败: courseId={}, corpId={}, error={}",
-                            course.getCourseId(), corpId, e.getMessage());
-                }
-            }
-        }
-    }
+//    /**
+//     * 定时任务 - 处理企业微信SOP课程素材
+//     * 每2天执行一次,将课程封面图片上传到企业微信素材库并缓存mediaId
+//     */
+//    @Override
+//    public void processQwSopCourseMaterialTimer() {
+//        // 获取所有需要处理的课程列表
+//        List<FsUserCourse> fsUserCourses = fsUserCourseMapper.selectFsUserCourseAllCourseByQw();
+//        // 获取所有企业微信配置
+//        List<QwCompany> companies = iQwCompanyService.selectQwCompanyList(new QwCompany());
+//
+//        // 遍历每个企业微信配置
+//        for (QwCompany company : companies) {
+//            String corpId = company.getCorpId();
+//            if (corpId == null) {
+//                continue;
+//            }
+//
+//            // 遍历每个课程,上传图片到对应企业的素材库
+//            for (FsUserCourse course : fsUserCourses) {
+//                try {
+//                    uploadCourseImage(course, corpId);
+//                } catch (Exception e) {
+//                    log.error("处理课程图片失败: courseId={}, corpId={}, error={}",
+//                            course.getCourseId(), corpId, e.getMessage());
+//                }
+//            }
+//        }
+//    }
 
 
     @Override
     @Override
     public List<FsCourseListBySidebarVO> getFsCourseListBySidebar(FsCourseListBySidebarParam param) {
     public List<FsCourseListBySidebarVO> getFsCourseListBySidebar(FsCourseListBySidebarParam param) {
@@ -699,12 +718,12 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
 
 
     @Override
     @Override
     @Transactional(rollbackFor = Exception.class) // 显式声明事务
     @Transactional(rollbackFor = Exception.class) // 显式声明事务
-    public int copyFsUserCourse(Long courseId) {
+    public int copyFsUserCourse(Long courseId, Long tenantId) {
         FsUserCourse fsUserCourse = fsUserCourseService.selectFsUserCourseByCourseId(courseId);
         FsUserCourse fsUserCourse = fsUserCourseService.selectFsUserCourseByCourseId(courseId);
         try {
         try {
             if(fsUserCourse != null){
             if(fsUserCourse != null){
                 fsUserCourse.setCourseId(null);
                 fsUserCourse.setCourseId(null);
-                fsUserCourseService.insertFsUserCourse(fsUserCourse);
+                fsUserCourseService.insertFsUserCourse(fsUserCourse,tenantId);
                 Long newCourseId = fsUserCourse.getCourseId();
                 Long newCourseId = fsUserCourse.getCourseId();
 
 
                 if (newCourseId == null) {
                 if (newCourseId == null) {
@@ -887,36 +906,37 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
 
 
         return calendar;
         return calendar;
     }
     }
-    /**
-     * 上传课程图片到企业微信并缓存
-     *
-     * @param course 课程信息
-     * @param corpId 企业ID
-     * @throws Exception 上传过程中的异常
-     */
-    public void uploadCourseImage(FsUserCourse course, String corpId) throws Exception {
-        File imageFile = null;
-        try {
-            // 将图片URL转为本地临时文件
-            imageFile = urlToFile(course.getImgUrl());
-            // 调用企业微信API上传图片
-            QwUploadResult result = qwApiService.upload(imageFile, "image", corpId);
-
-            if (result.getErrCode() == 0) {
-                // 上传成功,缓存mediaId
-                String cacheKey = String.format("miniprogram:%s:%s", corpId, course.getCourseId());
-                redisCache.setCacheObject(cacheKey, result.getMediaId());
-            } else {
-                log.error("上传图片失败: errMsg={}, corpId={}, courseId={}",
-                        result.getErrMsg(), corpId, course.getCourseId());
-            }
-        } finally {
-            // 清理临时文件
-            if (imageFile != null && imageFile.exists()) {
-                imageFile.delete();
-            }
-        }
-    }
+//    /**
+//     * 上传课程图片到企业微信并缓存
+//     *
+//     * @param course 课程信息
+//     * @param corpId 企业ID
+//     * @throws Exception 上传过程中的异常
+//     */
+//    public void uploadCourseImage(FsUserCourse course, String corpId) throws Exception {
+//        File imageFile = null;
+//        try {
+//            // 将图片URL转为本地临时文件
+//            imageFile = urlToFile(course.getImgUrl());
+//            // 调用企业微信API上传图片
+////            QwUploadResult result = qwApiService.upload(imageFile, "image", corpId);
+//            QwUploadResult result = qwApiService.upload(imageFile, "image", corpId);
+//
+//            if (result.getErrCode() == 0) {
+//                // 上传成功,缓存mediaId
+//                String cacheKey = String.format("miniprogram:%s:%s", corpId, course.getCourseId());
+//                redisCache.setCacheObject(cacheKey, result.getMediaId());
+//            } else {
+//                log.error("上传图片失败: errMsg={}, corpId={}, courseId={}",
+//                        result.getErrMsg(), corpId, course.getCourseId());
+//            }
+//        } finally {
+//            // 清理临时文件
+//            if (imageFile != null && imageFile.exists()) {
+//                imageFile.delete();
+//            }
+//        }
+//    }
 
 
     /**
     /**
      * 将URL指向的文件下载为临时文件
      * 将URL指向的文件下载为临时文件

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

@@ -4499,5 +4499,29 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
     public List<OptionsVO> selectVideoOptionsByCourseId(Long courseId) {
     public List<OptionsVO> selectVideoOptionsByCourseId(Long courseId) {
         return fsUserCourseVideoMapper.selectVideoOptionsByCourseId(courseId);
         return fsUserCourseVideoMapper.selectVideoOptionsByCourseId(courseId);
     }
     }
+
+    @Override
+    public int batchUpdateWatchIntegral(FsUserCourseVideoBatchWatchIntegralParam param) {
+        if (param == null || param.getCourseId() == null
+                || param.getVideoIds() == null || param.getVideoIds().isEmpty()) {
+            return 0;
+        }
+        if (param.getWatchDurationMinutes() == null || param.getIntegralReward() == null) {
+            return 0;
+        }
+        return fsUserCourseVideoMapper.batchUpdateWatchIntegralByVideoIds(
+                param.getCourseId(),
+                param.getVideoIds(),
+                param.getWatchDurationMinutes(),
+                param.getIntegralReward());
+    }
+
+    /**
+     * 视频上架
+     */
+    @Override
+    public int batchUp(String[] videoIds) {
+        return baseMapper.batchUp(videoIds);
+    }
 }
 }
 
 

+ 12 - 0
fs-service/src/main/java/com/fs/his/utils/RedisCacheUtil.java

@@ -9,6 +9,8 @@ import javax.crypto.Cipher;
 import javax.crypto.spec.SecretKeySpec;
 import javax.crypto.spec.SecretKeySpec;
 import java.util.Base64;
 import java.util.Base64;
 import java.util.Collection;
 import java.util.Collection;
+import java.util.Set;
+
 @Service
 @Service
 public class RedisCacheUtil {
 public class RedisCacheUtil {
     @Autowired
     @Autowired
@@ -30,4 +32,14 @@ public class RedisCacheUtil {
         String key = cacheName + "::" + cacheKey;
         String key = cacheName + "::" + cacheKey;
         redisCache.deleteObject(key);
         redisCache.deleteObject(key);
     }
     }
+
+    /**
+     * 使用SCAN扫描并清除某个 Spring {@link org.springframework.cache.annotation.Cacheable} 名称下的全部 Redis 项(同前缀 "cacheName::")
+     */
+    public void delSpringCacheAllByName(String cacheName) {
+        Set<String> keys = redisCache.scan(cacheName + "::*");
+        if (keys != null && !keys.isEmpty()) {
+            redisCache.deleteObject(keys);
+        }
+    }
 }
 }

+ 35 - 17
fs-service/src/main/java/com/fs/qw/service/impl/AsyncUploadQwCourseImageService.java

@@ -1,5 +1,7 @@
 package com.fs.qw.service.impl;
 package com.fs.qw.service.impl;
 
 
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson.JSON;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.PubFun;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.course.service.impl.FsUserCourseServiceImpl;
 import com.fs.course.service.impl.FsUserCourseServiceImpl;
@@ -9,6 +11,8 @@ import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.result.QwFilterSopCustomersResult;
 import com.fs.qw.result.QwFilterSopCustomersResult;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.vo.QwSopRuleTimeVO;
 import com.fs.qw.vo.QwSopRuleTimeVO;
+import com.fs.qwApi.config.OpenQwConfig;
+import com.fs.qwApi.param.QwUploadImageByCourseParam;
 import com.fs.sop.domain.*;
 import com.fs.sop.domain.*;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.QwSopMapper;
 import com.fs.sop.mapper.QwSopMapper;
@@ -47,29 +51,43 @@ public class AsyncUploadQwCourseImageService {
     private FsUserCourseServiceImpl fsUserCourseService;
     private FsUserCourseServiceImpl fsUserCourseService;
 
 
     /**
     /**
-     * 异步处理销售删除客户
+     * 异步处上传图片到对应企业的素材库
      */
      */
     @Async("scheduledExecutorService")
     @Async("scheduledExecutorService")
-    public void uploadQwCourseImage(FsUserCourse fsUserCourse) {
-
-        // 获取所有企业微信配置
-        List<QwCompany> companies = iQwCompanyService.selectQwCompanyList(new QwCompany());
-
-        // 遍历每个企业微信配置
-        for (QwCompany company : companies) {
-            String corpId = company.getCorpId();
-            if (corpId == null) {
-                continue;
-            }
-
-            //上传图片到对应企业的素材库
+    public void uploadQwCourseImage(QwUploadImageByCourseParam param) {
+        List<String> corpIds = param.getCorpIds();
+        if (!corpIds.isEmpty()){
             try {
             try {
-                fsUserCourseService.uploadCourseImage(fsUserCourse, corpId);
+
+                String url = OpenQwConfig.baseApi + "/uploadCourseImage";
+                String result = HttpUtil.createPost(url)
+                        .body(JSON.toJSONString(param))
+                        .execute()
+                        .body();
+               log.info("上传图片到对应企业的素材库: result={}", result);
             } catch (Exception e) {
             } catch (Exception e) {
-                log.error("处理课程图片失败: courseId={}, corpId={}, error={}", fsUserCourse.getCourseId(), corpId, e.getMessage());
+                log.error("处理课程图片失败: courseId={}, corpIds={}, error={}", param.getCourseId(), corpIds, e.getMessage());
             }
             }
-
         }
         }
 
 
+//        // 获取所有企业微信配置
+//        List<QwCompany> companies = iQwCompanyService.selectQwCompanyList(new QwCompany());
+//
+//        // 遍历每个企业微信配置
+//        for (QwCompany company : companies) {
+//            String corpId = company.getCorpId();
+//            if (corpId == null) {
+//                continue;
+//            }
+//
+//            //上传图片到对应企业的素材库
+//            try {
+//                fsUserCourseService.uploadCourseImage(fsUserCourse, corpId);
+//            } catch (Exception e) {
+//                log.error("处理课程图片失败: courseId={}, corpId={}, error={}", fsUserCourse.getCourseId(), corpId, e.getMessage());
+//            }
+//
+//        }
+
     }
     }
 }
 }

+ 2 - 1
fs-service/src/main/java/com/fs/qwApi/config/OpenQwConfig.java

@@ -1,7 +1,8 @@
 package com.fs.qwApi.config;
 package com.fs.qwApi.config;
 
 
 public interface OpenQwConfig {
 public interface OpenQwConfig {
-    String baseApi ="http://saasqwapi.ylrzcloud.com/open/qwapi";
+//    String baseApi ="http://saasqwapi.ylrzcloud.com/open/qwapi";
+    String baseApi ="http://127.0.0.1:8007/open/qwapi";
     String api ="http://saasqwapi.ylrzcloud.com";
     String api ="http://saasqwapi.ylrzcloud.com";
     String taskApi ="192.168.0.64:7006";
     String taskApi ="192.168.0.64:7006";
 }
 }

+ 12 - 0
fs-service/src/main/java/com/fs/qwApi/param/QwUploadImageByCourseParam.java

@@ -0,0 +1,12 @@
+package com.fs.qwApi.param;
+
+import com.fs.course.domain.FsUserCourse;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class QwUploadImageByCourseParam extends FsUserCourse {
+    private List<String> corpIds;
+    private Long tenantId;
+}

+ 2 - 1
fs-service/src/main/java/com/fs/qwApi/service/impl/QwApiServiceImpl.java

@@ -703,7 +703,8 @@ public class QwApiServiceImpl implements QwApiService {
 
 
         QwCompany qwCompany = iQwCompanyService.selectQwCompanyByCorpId(corpId);
         QwCompany qwCompany = iQwCompanyService.selectQwCompanyByCorpId(corpId);
 
 
-        String openSecret = qwCompany.getOpenSecret();
+//        String openSecret = qwCompany.getOpenSecret();
+        String openSecret = qwCompany.getPermanentCode();
 
 
         String token = getToken(corpId, openSecret);
         String token = getToken(corpId, openSecret);
 
 

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

@@ -525,4 +525,22 @@
             #{videoId}
             #{videoId}
         </foreach>
         </foreach>
     </update>
     </update>
+    <update id="batchUpdateWatchIntegralByVideoIds">
+        update fs_user_course_video
+        set watch_duration_minutes = #{watchDurationMinutes},
+        integral_reward = #{integralReward},
+        update_time = now()
+        where is_del = 0
+        and course_id = #{courseId}
+        and video_id in
+        <foreach collection="videoIds" item="vid" open="(" separator="," close=")">
+            #{vid}
+        </foreach>
+    </update>
+    <update id="batchUp" parameterType="String">
+        update fs_user_course_video set is_on_put = 0 where video_id in
+        <foreach item="videoId" collection="array" open="(" separator="," close=")">
+            #{videoId}
+        </foreach>
+    </update>
 </mapper>
 </mapper>