Kaynağa Gözat

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_scrm_java

caoliqin 2 hafta önce
ebeveyn
işleme
2647ceaad3
85 değiştirilmiş dosya ile 1512 ekleme ve 210 silme
  1. 97 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseFinishTempParentController.java
  2. 1 1
      fs-admin/src/main/java/com/fs/store/controller/FsUserController.java
  3. 12 0
      fs-admin/src/main/java/com/fs/web/controller/system/SysDictTypeController.java
  4. 4 2
      fs-admin/src/main/resources/application.yml
  5. 1 1
      fs-company/src/main/java/com/fs/course/controller/FsCourseFinishTempController.java
  6. 105 0
      fs-company/src/main/java/com/fs/course/controller/FsCourseFinishTempParentController.java
  7. 1 1
      fs-company/src/main/java/com/fs/users/controller/FsUserController.java
  8. 2 0
      fs-company/src/main/resources/application.yml
  9. 6 1
      fs-qw-api-msg/pom.xml
  10. 27 3
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  11. 1 17
      fs-qw-api-msg/src/main/java/com/fs/app/controller/testTask.java
  12. BIN
      fs-qw-api-msg/src/main/resources/jniLibs/WeWorkFinanceSdk.dll
  13. BIN
      fs-qw-api-msg/src/main/resources/jniLibs/libcrypto-3-x64.dll
  14. BIN
      fs-qw-api-msg/src/main/resources/jniLibs/libcurl-x64.dll
  15. 17 0
      fs-qw-api/pom.xml
  16. 304 0
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  17. 3 3
      fs-qw-api/src/main/java/com/fs/core/aspectj/DataScopeAspect.java
  18. 2 2
      fs-qw-api/src/main/java/com/fs/core/aspectj/DataSourceAspect.java
  19. 5 5
      fs-qw-api/src/main/java/com/fs/core/aspectj/LogAspect.java
  20. 1 1
      fs-qw-api/src/main/java/com/fs/core/aspectj/RateLimiterAspect.java
  21. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/ApplicationConfig.java
  22. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/ArrayStringTypeHandler.java
  23. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/CaptchaConfig.java
  24. 2 2
      fs-qw-api/src/main/java/com/fs/core/config/DataSourceConfig.java
  25. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/FastJson2JsonRedisSerializer.java
  26. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/FilterConfig.java
  27. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/KaptchaTextCreator.java
  28. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/MyBatisConfig.java
  29. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/RedisConfig.java
  30. 2 2
      fs-qw-api/src/main/java/com/fs/core/config/ResourcesConfig.java
  31. 4 4
      fs-qw-api/src/main/java/com/fs/core/config/SecurityConfig.java
  32. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/ServerConfig.java
  33. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/SwaggerConfig.java
  34. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/ThreadPoolConfig.java
  35. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/properties/DruidProperties.java
  36. 1 1
      fs-qw-api/src/main/java/com/fs/core/datasource/DynamicDataSource.java
  37. 1 1
      fs-qw-api/src/main/java/com/fs/core/datasource/DynamicDataSourceContextHolder.java
  38. 1 1
      fs-qw-api/src/main/java/com/fs/core/exception/GlobalExceptionHandler.java
  39. 1 1
      fs-qw-api/src/main/java/com/fs/core/interceptor/RepeatSubmitInterceptor.java
  40. 2 2
      fs-qw-api/src/main/java/com/fs/core/interceptor/impl/SameUrlDataInterceptor.java
  41. 1 1
      fs-qw-api/src/main/java/com/fs/core/manager/AsyncManager.java
  42. 1 1
      fs-qw-api/src/main/java/com/fs/core/manager/ShutdownManager.java
  43. 1 1
      fs-qw-api/src/main/java/com/fs/core/manager/factory/AsyncFactory.java
  44. 1 1
      fs-qw-api/src/main/java/com/fs/core/security/LoginBody.java
  45. 1 1
      fs-qw-api/src/main/java/com/fs/core/security/LoginUser.java
  46. 1 1
      fs-qw-api/src/main/java/com/fs/core/security/SecurityUtils.java
  47. 4 4
      fs-qw-api/src/main/java/com/fs/core/security/filter/JwtAuthenticationTokenFilter.java
  48. 1 1
      fs-qw-api/src/main/java/com/fs/core/security/handle/AuthenticationEntryPointImpl.java
  49. 5 5
      fs-qw-api/src/main/java/com/fs/core/security/handle/LogoutSuccessHandlerImpl.java
  50. 4 4
      fs-qw-api/src/main/java/com/fs/core/service/CompanyLoginService.java
  51. 1 1
      fs-qw-api/src/main/java/com/fs/core/service/CompanyPermissionService.java
  52. 2 2
      fs-qw-api/src/main/java/com/fs/core/service/PermissionService.java
  53. 2 2
      fs-qw-api/src/main/java/com/fs/core/service/TokenService.java
  54. 2 2
      fs-qw-api/src/main/java/com/fs/core/service/UserDetailsServiceImpl.java
  55. 128 0
      fs-qw-api/src/main/java/com/tencent/wework/Finance.java
  56. BIN
      fs-qw-api/src/main/resources/jniLibs/WeWorkFinanceSdk.dll
  57. BIN
      fs-qw-api/src/main/resources/jniLibs/libcrypto-3-x64.dll
  58. BIN
      fs-qw-api/src/main/resources/jniLibs/libcurl-x64.dll
  59. 1 1
      fs-qw-api/src/main/resources/mybatis/mybatis-config.xml
  60. 1 0
      fs-service-system/src/main/java/com/fs/course/domain/FsCourseFinishTemp.java
  61. 48 0
      fs-service-system/src/main/java/com/fs/course/domain/FsCourseFinishTempParent.java
  62. 20 23
      fs-service-system/src/main/java/com/fs/course/mapper/FsCourseFinishTempMapper.java
  63. 62 0
      fs-service-system/src/main/java/com/fs/course/mapper/FsCourseFinishTempParentMapper.java
  64. 62 0
      fs-service-system/src/main/java/com/fs/course/service/IFsCourseFinishTempParentService.java
  65. 4 1
      fs-service-system/src/main/java/com/fs/course/service/IFsCourseFinishTempService.java
  66. 123 0
      fs-service-system/src/main/java/com/fs/course/service/impl/FsCourseFinishTempParentServiceImpl.java
  67. 12 16
      fs-service-system/src/main/java/com/fs/course/service/impl/FsCourseFinishTempServiceImpl.java
  68. 18 3
      fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java
  69. 2 2
      fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  70. 16 1
      fs-service-system/src/main/java/com/fs/fastGpt/service/AiHookService.java
  71. 31 3
      fs-service-system/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  72. 2 1
      fs-service-system/src/main/java/com/fs/qw/param/QwMsgSendParam.java
  73. 88 47
      fs-service-system/src/main/java/com/fs/qw/service/impl/QwMsgServiceImpl.java
  74. 6 0
      fs-service-system/src/main/java/com/fs/system/service/ISysDictTypeService.java
  75. 22 6
      fs-service-system/src/main/java/com/fs/system/service/impl/SysDictTypeServiceImpl.java
  76. 5 0
      fs-service-system/src/main/java/com/fs/wx/mp/config/WxMpConfiguration.java
  77. 15 0
      fs-service-system/src/main/java/com/fs/wxwork/dto/WxCdnUploadImgLinkDTO.java
  78. 30 0
      fs-service-system/src/main/java/com/fs/wxwork/dto/WxCdnUploadImgLinkResp.java
  79. 31 0
      fs-service-system/src/main/java/com/fs/wxwork/dto/WxwDownloadWeChatFileDTO.java
  80. 16 0
      fs-service-system/src/main/java/com/fs/wxwork/service/WxWorkService.java
  81. 25 0
      fs-service-system/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java
  82. 1 1
      fs-service-system/src/main/resources/application-config-dev.yml
  83. 1 1
      fs-service-system/src/main/resources/application-config-zkzh.yml
  84. 17 14
      fs-service-system/src/main/resources/mapper/course/FsCourseFinishTempMapper.xml
  85. 83 0
      fs-service-system/src/main/resources/mapper/course/FsCourseFinishTempParentMapper.xml

+ 97 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseFinishTempParentController.java

@@ -0,0 +1,97 @@
+package com.fs.course.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.domain.FsCourseFinishTempParent;
+import com.fs.course.service.IFsCourseFinishTempParentService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 完课模板Controller
+ * 
+ * @author 吴树波
+ * @date 2025-05-22
+ */
+@RestController
+@RequestMapping("/course/courseFinishTempParent")
+public class FsCourseFinishTempParentController extends BaseController
+{
+    @Autowired
+    private IFsCourseFinishTempParentService fsCourseFinishTempParentService;
+
+    /**
+     * 查询完课模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        startPage();
+        List<FsCourseFinishTempParent> list = fsCourseFinishTempParentService.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出完课模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:export')")
+    @Log(title = "完课模板", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        List<FsCourseFinishTempParent> list = fsCourseFinishTempParentService.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
+        ExcelUtil<FsCourseFinishTempParent> util = new ExcelUtil<FsCourseFinishTempParent>(FsCourseFinishTempParent.class);
+        return util.exportExcel(list, "完课模板数据");
+    }
+
+    /**
+     * 获取完课模板详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsCourseFinishTempParentService.selectFsCourseFinishTempParentById(id));
+    }
+
+    /**
+     * 新增完课模板
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:add')")
+    @Log(title = "完课模板", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsCourseFinishTempParent fsCourseFinishTempParent){
+
+        return toAjax(fsCourseFinishTempParentService.insertFsCourseFinishTempParent(fsCourseFinishTempParent));
+    }
+
+    /**
+     * 修改完课模板
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:edit')")
+    @Log(title = "完课模板", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        return toAjax(fsCourseFinishTempParentService.updateFsCourseFinishTempParent(fsCourseFinishTempParent));
+    }
+
+    /**
+     * 删除完课模板
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:remove')")
+    @Log(title = "完课模板", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsCourseFinishTempParentService.deleteFsCourseFinishTempParentByIds(ids));
+    }
+}

+ 1 - 1
fs-admin/src/main/java/com/fs/store/controller/FsUserController.java

@@ -159,7 +159,7 @@ public class FsUserController extends BaseController
         return getDataTable(list);
     }
 
-    @PreAuthorize("@ss.hasPermi('store:user:darkRoomList')")
+    @PreAuthorize("@ss.hasPermi('store:user:enabledUsers')")
     @PostMapping("/enabledUsers")
     @ApiOperation("批量启用会员")
     public ResponseResult<Boolean> enabledUsers(@RequestBody String[] ids) {

+ 12 - 0
fs-admin/src/main/java/com/fs/web/controller/system/SysDictTypeController.java

@@ -128,4 +128,16 @@ public class SysDictTypeController extends BaseController
         List<SysDictType> dictTypes = dictTypeService.selectDictTypeAll();
         return AjaxResult.success(dictTypes);
     }
+
+    /**
+     * 刷新字典缓存
+     */
+    @PreAuthorize("@ss.hasPermi('system:dict:remove')")
+    @Log(title = "字典类型", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/refreshCache")
+    public AjaxResult refreshCache()
+    {
+        dictTypeService.resetDictCache();
+        return AjaxResult.success();
+    }
 }

+ 4 - 2
fs-admin/src/main/resources/application.yml

@@ -5,5 +5,7 @@ server:
   port: 7011
 spring:
   profiles:
-    active: druid-fby
-    include: common,config-fby
+    active: dev
+    include: common,config-dev
+#    active: druid-fby
+#    include: common,config-fby

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

@@ -6,9 +6,9 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.core.security.LoginUser;
 import com.fs.core.security.SecurityUtils;
 import com.fs.course.domain.FsCourseFinishTemp;
-import com.fs.core.security.LoginUser;
 import com.fs.course.service.IFsCourseFinishTempService;
 import com.fs.course.vo.FsCourseFinishTempListVO;
 import org.springframework.beans.factory.annotation.Autowired;

+ 105 - 0
fs-company/src/main/java/com/fs/course/controller/FsCourseFinishTempParentController.java

@@ -0,0 +1,105 @@
+package com.fs.course.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.core.security.LoginUser;
+import com.fs.core.security.SecurityUtils;
+import com.fs.course.domain.FsCourseFinishTempParent;
+import com.fs.course.service.IFsCourseFinishTempParentService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 完课模板Controller
+ * 
+ * @author 吴树波
+ * @date 2025-05-22
+ */
+@RestController
+@RequestMapping("/course/courseFinishTempParent")
+public class FsCourseFinishTempParentController extends BaseController
+{
+    @Autowired
+    private IFsCourseFinishTempParentService fsCourseFinishTempParentService;
+
+    /**
+     * 查询完课模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        startPage();
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        fsCourseFinishTempParent.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<FsCourseFinishTempParent> list = fsCourseFinishTempParentService.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出完课模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:export')")
+    @Log(title = "完课模板", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        List<FsCourseFinishTempParent> list = fsCourseFinishTempParentService.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
+        ExcelUtil<FsCourseFinishTempParent> util = new ExcelUtil<FsCourseFinishTempParent>(FsCourseFinishTempParent.class);
+        return util.exportExcel(list, "完课模板数据");
+    }
+
+    /**
+     * 获取完课模板详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsCourseFinishTempParentService.selectFsCourseFinishTempParentById(id));
+    }
+
+    /**
+     * 新增完课模板
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:add')")
+    @Log(title = "完课模板", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsCourseFinishTempParent fsCourseFinishTempParent){
+
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        fsCourseFinishTempParent.setCompanyId(loginUser.getCompany().getCompanyId());
+        return toAjax(fsCourseFinishTempParentService.insertFsCourseFinishTempParent(fsCourseFinishTempParent));
+    }
+
+    /**
+     * 修改完课模板
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:edit')")
+    @Log(title = "完课模板", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        fsCourseFinishTempParent.setCompanyId(loginUser.getCompany().getCompanyId());
+        return toAjax(fsCourseFinishTempParentService.updateFsCourseFinishTempParent(fsCourseFinishTempParent));
+    }
+
+    /**
+     * 删除完课模板
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:remove')")
+    @Log(title = "完课模板", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsCourseFinishTempParentService.deleteFsCourseFinishTempParentByIds(ids));
+    }
+}

+ 1 - 1
fs-company/src/main/java/com/fs/users/controller/FsUserController.java

@@ -133,7 +133,7 @@ public class FsUserController extends BaseController
         return getDataTable(list);
     }
 
-    @PreAuthorize("@ss.hasPermi('users:user:darkRoomList')")
+    @PreAuthorize("@ss.hasPermi('users:user:enabledUsers')")
     @PostMapping("/enabledUsers")
     @ApiOperation("批量启用会员")
     public ResponseResult<Boolean> enabledUsers(@RequestBody String[] ids) {

+ 2 - 0
fs-company/src/main/resources/application.yml

@@ -6,3 +6,5 @@ spring:
   profiles:
     active: dev
     include: common,config-dev
+#    active: druid-fby
+#    include: common,config-fby

+ 6 - 1
fs-qw-api-msg/pom.xml

@@ -121,7 +121,12 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-websocket</artifactId>
         </dependency>
-
+        <dependency>
+            <groupId>com.fs</groupId>
+            <artifactId>fs-qw-api</artifactId>
+            <version>1.1.0</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 
     <build>

+ 27 - 3
fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java

@@ -3,6 +3,7 @@ package com.fs.app.controller;
 import com.alibaba.fastjson.JSON;
 import com.fs.app.socket.QwImSocket;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.uuid.IdUtils;
 import com.fs.fastGpt.service.AiHookService;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwUserMapper;
@@ -134,7 +135,7 @@ public class QwMsgController {
                 qwUserStatus(wxWorkMsgResp.getUuid(),0);
                 break;
             case 102000:
-                System.out.println(wxWorkMsgResp.getJson());
+
                 WxWorkMessageDTO wxWorkMessageDTO = JSON.parseObject(wxWorkMsgResp.getJson(), WxWorkMessageDTO.class);
                 if (wxWorkMessageDTO.getIs_room()!=0){
                     break;
@@ -165,14 +166,14 @@ public class QwMsgController {
                         aiHookService.qwHookNotifyAiReply(id,sender,content,wxWorkMsgResp.getUuid(),wxWorkMessageDTO.getMsgtype());
 
                         // 保存聊天消息
-                        QwMessageListVO message = aiHookService.saveQwMsg(id, sender, content, wxWorkMsgResp.getUuid(), 1, wxWorkMsgResp.getJson());
+                        QwMessageListVO message = aiHookService.saveQwMsg(id, sender, content, wxWorkMsgResp.getUuid(), 1, wxWorkMsgResp.getJson(), 1);
                         QwImSocket.broadcast(message);
                     }else {
                         System.out.println("销售发送");
                         aiHookService.qwHookNotifyAddMsg(id,receiver,content,wxWorkMsgResp.getUuid());
 
                         // 保存聊天消息
-                        QwMessageListVO message = aiHookService.saveQwMsg(id, receiver, content, wxWorkMsgResp.getUuid(), 2, wxWorkMsgResp.getJson());
+                        QwMessageListVO message = aiHookService.saveQwMsg(id, receiver, content, wxWorkMsgResp.getUuid(), 2, wxWorkMsgResp.getJson(), 1);
                         QwImSocket.broadcast(message);
                     }
 
@@ -216,12 +217,35 @@ public class QwMsgController {
                 }
                 // 图片消息
                 else if (wxWorkMessageDTO.getMsgtype() == 101){
+                    System.out.println(json);
+                    System.out.println(wxWorkMsgResp.getJson());
                     Long receiver = wxWorkMessageDTO.getReceiver();
+                    Long sender = wxWorkMessageDTO.getSender();
+
+                    long userId;
+                    int sendType;
                     if (2000000000000000L - receiver > 0){
                         System.out.println("客户发起");
+                        userId = sender;
+                        sendType= 1;
                     }else {
                         System.out.println("销售发起");
+                        userId = receiver;
+                        sendType= 2;
                     }
+
+                    String fileName = IdUtils.fastSimpleUUID() + ".jpg";
+                    WxWorkResponseDTO<String> fileUrlResp =
+                            aiHookService.getFileUrl(wxWorkMsgResp.getUuid(), wxWorkMessageDTO.getFile_id(), wxWorkMessageDTO.getAes_key(), wxWorkMessageDTO.getOpenim_cdn_authkey(), fileName, wxWorkMessageDTO.getFile_size(), serverId);
+                    if (fileUrlResp.getErrcode() != 0) {
+                        log.warn("获取图片地址失败: {}", fileUrlResp.getErrmsg());
+                        break;
+                    }
+
+                    String content = fileUrlResp.getData();
+                    // 保存聊天消息
+                    QwMessageListVO message = aiHookService.saveQwMsg(id, userId, content, wxWorkMsgResp.getUuid(), sendType, wxWorkMsgResp.getJson(), 2);
+                    QwImSocket.broadcast(message);
                 }
 
                 break;

+ 1 - 17
fs-qw-api-msg/src/main/java/com/fs/app/controller/testTask.java

@@ -1,10 +1,7 @@
-package com.fs.app.controller;//package com.fs.app.controller;
+//package com.fs.app.controller;
 //
 //import com.alibaba.fastjson.JSON;
 //import com.fs.app.service.QwDataCallbackService;
-//import com.fs.common.core.redis.RedisCache;
-//import com.fs.fastGpt.service.AiNewService;
-//import com.fs.fastGpt.service.AiService;
 //import com.fs.qw.domain.QwMessageGather;
 //import org.springframework.beans.factory.annotation.Autowired;
 //import org.springframework.scheduling.annotation.Scheduled;
@@ -17,8 +14,6 @@ package com.fs.app.controller;//package com.fs.app.controller;
 //
 //    @Autowired
 //    QwDataCallbackService qwDataCallbackService;
-//    @Autowired
-//    private AiNewService aiService;
 //
 //    @Scheduled(fixedRate = 5000) // 每10执行一次
 //    public void getMsg(){
@@ -43,22 +38,11 @@ package com.fs.app.controller;//package com.fs.app.controller;
 //            String from = qwMessageGather.getFrom();
 //            if (from.startsWith("wo")||from.startsWith("wm")){
 //                System.out.println("用户发消息");
-//                aiService.qwHookNotifyAiReply(qwMessageGather,corpId);
 //            }else {
-//                aiService.qwHookNotifyAddMsg(qwMessageGather,corpId);
 //                System.out.println("客服发送消息");
 //            }
 //            QwMessageGather.TextContent text = qwMessageGather.getText();
 //            System.out.println(text.getContent());
 //        }
 //    }
-//
-//
-//
-//    //@Scheduled(cron = "0 0/30 * * * ?")// 每10执行一次
-//    public void expire(){
-//
-//        aiService.expireAiMsg();
-//    }
-//
 //}

BIN
fs-qw-api-msg/src/main/resources/jniLibs/WeWorkFinanceSdk.dll


BIN
fs-qw-api-msg/src/main/resources/jniLibs/libcrypto-3-x64.dll


BIN
fs-qw-api-msg/src/main/resources/jniLibs/libcurl-x64.dll


+ 17 - 0
fs-qw-api/pom.xml

@@ -99,7 +99,24 @@
             <groupId>com.fs</groupId>
             <artifactId>fs-service-system</artifactId>
         </dependency>
+        <!-- 获取音频信息 -->
+        <dependency>
+            <groupId>org</groupId>
+            <artifactId>jaudiotagger</artifactId>
+            <version>2.0.3</version>
+        </dependency>
 
+        <!-- 语音识别 -->
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna</artifactId>
+            <version>5.7.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alphacephei</groupId>
+            <artifactId>vosk</artifactId>
+            <version>0.3.32</version>
+        </dependency>
 
 
     </dependencies>

+ 304 - 0
fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java

@@ -1,9 +1,11 @@
 package com.fs.app.service;
 
 import com.alibaba.fastjson.JSON;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.company.service.ICompanyConfigService;
 import com.fs.qw.domain.*;
+import com.fs.qw.mapper.QwCompanyMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.param.QwAutoRulesTagsParams;
 import com.fs.qw.service.*;
@@ -12,17 +14,33 @@ import com.fs.qwApi.Result.QwOpenidResult;
 import com.fs.qwApi.domain.QwResult;
 import com.fs.qwApi.param.QwEditUserTagParam;
 import com.fs.qwApi.param.QwOpenidByUserParams;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.tencent.wework.Finance;
+import lombok.extern.slf4j.Slf4j;
+import org.json.JSONObject;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
+import org.vosk.LibVosk;
+import org.vosk.LogLevel;
+import org.vosk.Model;
+import org.vosk.Recognizer;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
 
+import javax.crypto.Cipher;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.spec.PKCS8EncodedKeySpec;
 import java.text.SimpleDateFormat;
 import java.util.*;
 
+@Slf4j
 @Service
 public class QwDataCallbackService {
 
@@ -55,6 +73,10 @@ public class QwDataCallbackService {
 
     @Autowired
     private IQwDeptService qwDeptService;
+    @Autowired
+    private RedisCache redisCache;
+    @Autowired
+    private QwCompanyMapper qwCompanyMapper;
 
 
 
@@ -476,6 +498,288 @@ public class QwDataCallbackService {
         }
     }
 
+    /**
+     * 拉取存档内容
+     * @param
+     * @return 聊天内容列表
+     */
+    public List<String> getChatData(String corpId) {
+        QwCompany qwCompany= qwCompanyMapper.selectQwCompanyByCorpId(corpId);
+        // 初始化 SDK
+        long sdk = Finance.NewSdk();
+        long slice = Finance.NewSlice();
+        List<String> chatContents = new ArrayList<>();
+        try {
+            long seq = 0L;
+            if (redisCache.redisTemplate.hasKey("seq:"+corpId+":")) {
+                seq  = redisCache.getCacheObject("seq:"+corpId+":");
+            }
+            System.out.println("seq:"+seq);
+            // SDK 初始化参数
+            String secret = qwCompany.getMsgSecret();
+            long limit = 1000L;  // 一次最多拉取 1000 条
+            String proxy = "";
+            String passwd = "";
+            long timeout = 15L;
+            long ret = Finance.Init(sdk, corpId, secret);
+            if (ret != 0) {
+                Finance.DestroySdk(sdk);
+                System.out.println("初始化 SDK 失败,错误码1:" + ret);
+                return chatContents;
+            }
+            // 拉取会话存档数据
+            ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
+            if (ret != 0) {
+                System.out.println("拉取会话存档失败,错误码:" + ret);
+                return chatContents;
+            }
+            // 获取 JSON 内容
+            String jsonContent = Finance.GetContentFromSlice(slice);
+            if (jsonContent == null || jsonContent.isEmpty()) {
+                System.out.println("没有获取到有效的会话存档数据");
+                return chatContents;
+            }
+            // 解析 JSON 内容
+            JSONObject json = new JSONObject(jsonContent);
+            System.out.println("解析的内容"+json.toString());
+            if (json.has("chatdata")) {
+                for (Object item : json.getJSONArray("chatdata")) {
+                    JSONObject chatItem = (JSONObject) item;
+                    String encrypt_random_key = chatItem.getString("encrypt_random_key"); // 获取加密的随机密钥
+                    String encrypt_chat_msg = chatItem.getString("encrypt_chat_msg"); // 获取加密消息
+                    // 更新下一次拉取的 seq
+                    if (chatItem.has("seq")) {
+                        long nextSeq = chatItem.getLong("seq");
+                        System.out.println("将 seq 存入 Redis,key: seq:, value: " + nextSeq);
+                        redisCache.setCacheObject("seq:"+corpId+":", nextSeq); // 确保 `seq` 更新到缓存
+                    }
+                    // 使用私钥解密 encrypt_random_key 获取 encrypt_key
+                    String encrypt_key = decryptRandomKey(encrypt_random_key,qwCompany.getMsgPrivateKey());
+                    if (encrypt_key != null) {
+                        // 解密加密消息
+                        String decryptedMsg = decryptChatMessage(sdk, encrypt_key, encrypt_chat_msg);
+                        // 解析 JSON 字符串
+                        JsonObject jsonObject = JsonParser.parseString(decryptedMsg).getAsJsonObject();
+                        String plaintext = Finance.GetContentFromSlice(slice);
+                        // 获取 msgtype 的值
+                        String msgtype = jsonObject.get("msgtype").getAsString();
+                        // 处理媒体消息
+                        if (msgtype.equals("voice")) {
+                            getMedia(sdk,decryptedMsg,corpId,secret);
+                        }
+                        System.out.println("解密后的消息:" + decryptedMsg);
+                        chatContents.add(decryptedMsg);
+                    } else {
+                        System.out.println("解密随机密钥失败");
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            Finance.FreeSlice(slice);
+            Finance.DestroySdk(sdk);
+        }
+        return chatContents;
+    }
+
+    /**
+     * 解密加密的随机密钥
+     * @param encryptedRandomKey 加密的随机密钥
+     * @return 解密后的随机密钥
+     */
+    public String decryptRandomKey(String encryptedRandomKey,String msgPrivateKey) {
+        try {
+            // 加载私钥
+            PrivateKey privateKey = loadPrivateKey(msgPrivateKey);
+
+            // 使用 RSA 解密
+            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+            cipher.init(Cipher.DECRYPT_MODE, privateKey);
+
+            // 解密数据
+            byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedRandomKey));
+            return new String(decryptedBytes, StandardCharsets.UTF_8);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 获取私钥
+     * @return 私钥
+     * @throws Exception 异常
+     */
+    public PrivateKey loadPrivateKey(String privateKey) throws Exception {
+        // 解析 PEM 格式的私钥
+        String privateKeyPEM = privateKey
+                .replace("-----BEGIN PRIVATE KEY-----", "")
+                .replace("-----END PRIVATE KEY-----", "")
+                .replaceAll("\\s", "");
+        byte[] decodedKey = Base64.getDecoder().decode(privateKeyPEM);
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        return keyFactory.generatePrivate(keySpec);
+
+    }
+
+    /**
+     * 解密会话消息
+     * @param sdk SDK
+     * @param encrypt_key 解密的密钥
+     * @param encrypt_chat_msg 加密的聊天消息
+     * @return 解密后的消息内容
+     */
+    public String decryptChatMessage(long sdk, String encrypt_key, String encrypt_chat_msg) {
+        String decryptedMsg = "";
+        long msg = Finance.NewSlice();
+        try {
+            // 解密消息
+            int ret = Finance.DecryptData(sdk, encrypt_key, encrypt_chat_msg, msg);
+            if (ret == 0) {
+                decryptedMsg = Finance.GetContentFromSlice(msg); // 获取解密后的消息内容
+                decryptedMsg = new String(decryptedMsg.getBytes(), StandardCharsets.UTF_8);
+            } else {
+                System.out.println("解密失败,错误码:" + ret);
+                return null;  // 返回 null,避免上游加入错误数据
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            Finance.FreeSlice(msg);
+        }
+        return decryptedMsg;
+    }
+
+    /**
+     * 下载语音文件
+     * @param sdk SDK实例
+     * @param decryptedMsg 解密后的JSON字符串
+     * @return 转换以后的文字数据
+     */
+    public static String getMedia(long sdk, String decryptedMsg,String coreId,String secret) {
+        JsonObject jsonObject = JsonParser.parseString(decryptedMsg).getAsJsonObject();
+        if (!jsonObject.has("voice")) {
+            log.error("JSON 数据中缺少 voice 字段");
+            return null;
+        }
+        JsonObject voiceObj = jsonObject.getAsJsonObject("voice");
+        String sdkFileId = voiceObj.get("sdkfileid").getAsString();
+        long mediaData = Finance.NewMediaData();
+        byte[] amrData = downloadVoice(sdk, sdkFileId, "", mediaData,coreId,secret);
+        Finance.FreeMediaData(mediaData);
+        if (amrData == null) {
+            return null;
+        }
+        // 直接转换 AMR 数据到文本
+        try {
+            String s = convertAmrToText(amrData);
+            log.info("转换成功的文字{}",s);
+            return s;
+        } catch (IOException e) {
+            log.error("语音转换失败: {}", e.getMessage(), e);
+            return null;
+        }
+    }
 
+    /**
+     * 递归下载语音文件
+     * @param sdk SDK实例
+     * @param sdkFileId 语音文件ID
+     * @param indexbuf 获取分片数据时的索引
+     * @param mediaData 存储语音数据的结构体
+     * @return 下载的语音文件字节
+     */
+    private static byte[] downloadVoice(long sdk, String sdkFileId, String indexbuf, long mediaData, String corpId, String secret) {
+        long ret = Finance.Init(sdk, corpId, secret);
+        if (ret != 0) {
+            Finance.DestroySdk(sdk);
+            System.out.println("初始化 SDK 失败,错误码:" + ret);
+            return null;
+        }
+        ret = Finance.GetMediaData(sdk, indexbuf, sdkFileId, "", "", 15L, mediaData);
+        if (ret != 0) {
+            log.error("获取语音数据失败: ret={}, sdkFileId={}", ret, sdkFileId);
+            return null;
+        }
+        boolean isFinish = Finance.IsMediaDataFinish(mediaData) == 1;
+        String nextIndexBuf = Finance.GetOutIndexBuf(mediaData);
+        byte[] data = Finance.GetData(mediaData);
+        if (!isFinish) {
+            byte[] nextData = downloadVoice(sdk, sdkFileId, nextIndexBuf, mediaData,corpId,secret);
+            if (nextData != null) {
+                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+                try {
+                    outputStream.write(data);
+                    outputStream.write(nextData);
+                    return outputStream.toByteArray();
+                } catch (IOException e) {
+                    log.error("合并语音数据失败: {}", e.getMessage(), e);
+                    return null;
+                }
+            }
+        }
+        return data;
+    }
+
+    /**
+     * 使用 ffmpeg 将 AMR 文件转换为 WAV 格式
+     * @return WAV 格式的音频数据
+     * @throws IOException
+     */
+    private static String convertAmrToText(byte[] amrData) throws IOException {
+        // 启动 ffmpeg 进行转换
+        ProcessBuilder processBuilder = new ProcessBuilder(
+                "ffmpeg",
+                "-i", "pipe:0",  // 从标准输入读取 AMR 数据
+                "-ac", "1",
+                "-ar", "16000",
+                "-sample_fmt", "s16",
+                "-f", "wav",
+                "pipe:1"
+        );
+        processBuilder.redirectErrorStream(true);
+        Process process = processBuilder.start();
+        // 向 ffmpeg 传输 AMR 数据
+        try (OutputStream outputStream = process.getOutputStream()) {
+            outputStream.write(amrData);
+        }
+        // 读取 ffmpeg 转换后的 WAV 数据
+        ByteArrayOutputStream wavOutput = new ByteArrayOutputStream();
+        try (InputStream inputStream = process.getInputStream()) {
+            byte[] buffer = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                wavOutput.write(buffer, 0, bytesRead);
+            }
+            int exitCode = process.waitFor();
+            if (exitCode != 0) {
+                throw new IOException("ffmpeg 转换失败,退出码:" + exitCode);
+            }
+        } catch (InterruptedException e) {
+            throw new IOException("ffmpeg 转换过程中被中断", e);
+        }
+        return recognizeSpeech(wavOutput.toByteArray());
+    }
+
+    /**
+     * 使用 Vosk 识别音频
+     */
+    private static String recognizeSpeech(byte[] wavData) throws IOException {
+        LibVosk.setLogLevel(LogLevel.DEBUG);
+        try (Model model = new Model("C:/vosk-model-small-cn-0.22");
+             //try (Model model = new Model("E:/vosk-model/vosk-model-small-cn-0.22");
+             InputStream ais = new ByteArrayInputStream(wavData);
+             Recognizer recognizer = new Recognizer(model, 16000)) {
+            byte[] buffer = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = ais.read(buffer)) >= 0) {
+                recognizer.acceptWaveForm(buffer, bytesRead);
+            }
+            return recognizer.getFinalResult();
+        }
+    }
 
 }

+ 3 - 3
fs-qw-api/src/main/java/com/fs/framework/aspectj/DataScopeAspect.java → fs-qw-api/src/main/java/com/fs/core/aspectj/DataScopeAspect.java

@@ -1,4 +1,4 @@
-package com.fs.framework.aspectj;
+package com.fs.core.aspectj;
 
 import com.fs.common.annotation.DataScope;
 import com.fs.common.core.domain.BaseEntity;
@@ -7,8 +7,8 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.company.domain.CompanyRole;
 import com.fs.company.domain.CompanyUser;
-import com.fs.framework.security.LoginUser;
-import com.fs.framework.service.TokenService;
+import com.fs.core.security.LoginUser;
+import com.fs.core.service.TokenService;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.Signature;
 import org.aspectj.lang.annotation.Aspect;

+ 2 - 2
fs-qw-api/src/main/java/com/fs/framework/aspectj/DataSourceAspect.java → fs-qw-api/src/main/java/com/fs/core/aspectj/DataSourceAspect.java

@@ -1,8 +1,8 @@
-package com.fs.framework.aspectj;
+package com.fs.core.aspectj;
 
 import com.fs.common.annotation.DataSource;
 import com.fs.common.utils.StringUtils;
-import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import com.fs.core.datasource.DynamicDataSourceContextHolder;
 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.annotation.Around;
 import org.aspectj.lang.annotation.Aspect;

+ 5 - 5
fs-qw-api/src/main/java/com/fs/framework/aspectj/LogAspect.java → fs-qw-api/src/main/java/com/fs/core/aspectj/LogAspect.java

@@ -1,4 +1,4 @@
-package com.fs.framework.aspectj;
+package com.fs.core.aspectj;
 
 import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.Log;
@@ -9,10 +9,10 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.ip.IpUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.company.domain.CompanyOperLog;
-import com.fs.framework.manager.AsyncManager;
-import com.fs.framework.manager.factory.AsyncFactory;
-import com.fs.framework.security.LoginUser;
-import com.fs.framework.service.TokenService;
+import com.fs.core.manager.AsyncManager;
+import com.fs.core.manager.factory.AsyncFactory;
+import com.fs.core.security.LoginUser;
+import com.fs.core.service.TokenService;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.Signature;
 import org.aspectj.lang.annotation.AfterReturning;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/aspectj/RateLimiterAspect.java → fs-qw-api/src/main/java/com/fs/core/aspectj/RateLimiterAspect.java

@@ -1,4 +1,4 @@
-package com.fs.framework.aspectj;
+package com.fs.core.aspectj;
 
 import com.fs.common.annotation.RateLimiter;
 import com.fs.common.enums.LimitType;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/ApplicationConfig.java → fs-qw-api/src/main/java/com/fs/core/config/ApplicationConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/ArrayStringTypeHandler.java → fs-qw-api/src/main/java/com/fs/core/config/ArrayStringTypeHandler.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import org.apache.ibatis.type.BaseTypeHandler;
 import org.apache.ibatis.type.JdbcType;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/CaptchaConfig.java → fs-qw-api/src/main/java/com/fs/core/config/CaptchaConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.google.code.kaptcha.impl.DefaultKaptcha;
 import com.google.code.kaptcha.util.Config;

+ 2 - 2
fs-qw-api/src/main/java/com/fs/framework/config/DataSourceConfig.java → fs-qw-api/src/main/java/com/fs/core/config/DataSourceConfig.java

@@ -1,10 +1,10 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.alibaba.druid.pool.DruidDataSource;
 import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
 import com.alibaba.druid.util.Utils;
 import com.fs.common.enums.DataSourceType;
-import com.fs.framework.datasource.DynamicDataSource;
+import com.fs.core.datasource.DynamicDataSource;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.ConfigurationProperties;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java → fs-qw-api/src/main/java/com/fs/core/config/FastJson2JsonRedisSerializer.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.parser.ParserConfig;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/FilterConfig.java → fs-qw-api/src/main/java/com/fs/core/config/FilterConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.filter.RepeatableFilter;
 import com.fs.common.filter.XssFilter;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/KaptchaTextCreator.java → fs-qw-api/src/main/java/com/fs/core/config/KaptchaTextCreator.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.google.code.kaptcha.text.impl.DefaultTextCreator;
 

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/MyBatisConfig.java → fs-qw-api/src/main/java/com/fs/core/config/MyBatisConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
 import org.apache.ibatis.io.VFS;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/RedisConfig.java → fs-qw-api/src/main/java/com/fs/core/config/RedisConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;

+ 2 - 2
fs-qw-api/src/main/java/com/fs/framework/config/ResourcesConfig.java → fs-qw-api/src/main/java/com/fs/core/config/ResourcesConfig.java

@@ -1,8 +1,8 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.config.FSConfig;
 import com.fs.common.constant.Constants;
-import com.fs.framework.interceptor.RepeatSubmitInterceptor;
+import com.fs.core.interceptor.RepeatSubmitInterceptor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;

+ 4 - 4
fs-qw-api/src/main/java/com/fs/framework/config/SecurityConfig.java → fs-qw-api/src/main/java/com/fs/core/config/SecurityConfig.java

@@ -1,9 +1,9 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 
-import com.fs.framework.security.filter.JwtAuthenticationTokenFilter;
-import com.fs.framework.security.handle.AuthenticationEntryPointImpl;
-import com.fs.framework.security.handle.LogoutSuccessHandlerImpl;
+import com.fs.core.security.filter.JwtAuthenticationTokenFilter;
+import com.fs.core.security.handle.AuthenticationEntryPointImpl;
+import com.fs.core.security.handle.LogoutSuccessHandlerImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.http.HttpMethod;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/ServerConfig.java → fs-qw-api/src/main/java/com/fs/core/config/ServerConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.utils.ServletUtils;
 import org.springframework.stereotype.Component;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/SwaggerConfig.java → fs-qw-api/src/main/java/com/fs/core/config/SwaggerConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.config.FSConfig;
 import io.swagger.annotations.ApiOperation;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/ThreadPoolConfig.java → fs-qw-api/src/main/java/com/fs/core/config/ThreadPoolConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.utils.Threads;
 import org.apache.commons.lang3.concurrent.BasicThreadFactory;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/properties/DruidProperties.java → fs-qw-api/src/main/java/com/fs/core/config/properties/DruidProperties.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config.properties;
+package com.fs.core.config.properties;
 
 import com.alibaba.druid.pool.DruidDataSource;
 import org.springframework.beans.factory.annotation.Value;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/datasource/DynamicDataSource.java → fs-qw-api/src/main/java/com/fs/core/datasource/DynamicDataSource.java

@@ -1,4 +1,4 @@
-package com.fs.framework.datasource;
+package com.fs.core.datasource;
 
 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/datasource/DynamicDataSourceContextHolder.java → fs-qw-api/src/main/java/com/fs/core/datasource/DynamicDataSourceContextHolder.java

@@ -1,4 +1,4 @@
-package com.fs.framework.datasource;
+package com.fs.core.datasource;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/exception/GlobalExceptionHandler.java → fs-qw-api/src/main/java/com/fs/core/exception/GlobalExceptionHandler.java

@@ -1,4 +1,4 @@
-package com.fs.framework.exception;
+package com.fs.core.exception;
 
 import com.fs.common.constant.HttpStatus;
 import com.fs.common.core.domain.AjaxResult;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/interceptor/RepeatSubmitInterceptor.java → fs-qw-api/src/main/java/com/fs/core/interceptor/RepeatSubmitInterceptor.java

@@ -1,4 +1,4 @@
-package com.fs.framework.interceptor;
+package com.fs.core.interceptor;
 
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.RepeatSubmit;

+ 2 - 2
fs-qw-api/src/main/java/com/fs/framework/interceptor/impl/SameUrlDataInterceptor.java → fs-qw-api/src/main/java/com/fs/core/interceptor/impl/SameUrlDataInterceptor.java

@@ -1,4 +1,4 @@
-package com.fs.framework.interceptor.impl;
+package com.fs.core.interceptor.impl;
 
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.constant.Constants;
@@ -6,7 +6,7 @@ import com.fs.common.core.redis.RedisCache;
 import com.fs.common.filter.RepeatedlyRequestWrapper;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.http.HttpHelper;
-import com.fs.framework.interceptor.RepeatSubmitInterceptor;
+import com.fs.core.interceptor.RepeatSubmitInterceptor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/manager/AsyncManager.java → fs-qw-api/src/main/java/com/fs/core/manager/AsyncManager.java

@@ -1,4 +1,4 @@
-package com.fs.framework.manager;
+package com.fs.core.manager;
 
 import com.fs.common.utils.Threads;
 import com.fs.common.utils.spring.SpringUtils;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/manager/ShutdownManager.java → fs-qw-api/src/main/java/com/fs/core/manager/ShutdownManager.java

@@ -1,4 +1,4 @@
-package com.fs.framework.manager;
+package com.fs.core.manager;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/manager/factory/AsyncFactory.java → fs-qw-api/src/main/java/com/fs/core/manager/factory/AsyncFactory.java

@@ -1,4 +1,4 @@
-package com.fs.framework.manager.factory;
+package com.fs.core.manager.factory;
 
 import com.fs.common.constant.Constants;
 import com.fs.common.utils.LogUtils;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/security/LoginBody.java → fs-qw-api/src/main/java/com/fs/core/security/LoginBody.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security;
+package com.fs.core.security;
 
 /**
  * 用户登录对象

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/security/LoginUser.java → fs-qw-api/src/main/java/com/fs/core/security/LoginUser.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security;
+package com.fs.core.security;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fs.company.domain.Company;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/security/SecurityUtils.java → fs-qw-api/src/main/java/com/fs/core/security/SecurityUtils.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security;
+package com.fs.core.security;
 
 import com.fs.common.constant.HttpStatus;
 import com.fs.common.exception.CustomException;

+ 4 - 4
fs-qw-api/src/main/java/com/fs/framework/security/filter/JwtAuthenticationTokenFilter.java → fs-qw-api/src/main/java/com/fs/core/security/filter/JwtAuthenticationTokenFilter.java

@@ -1,10 +1,10 @@
-package com.fs.framework.security.filter;
+package com.fs.core.security.filter;
 
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.StringUtils;
-import com.fs.framework.security.LoginUser;
-import com.fs.framework.security.SecurityUtils;
-import com.fs.framework.service.TokenService;
+import com.fs.core.security.LoginUser;
+import com.fs.core.security.SecurityUtils;
+import com.fs.core.service.TokenService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.context.SecurityContextHolder;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/security/handle/AuthenticationEntryPointImpl.java → fs-qw-api/src/main/java/com/fs/core/security/handle/AuthenticationEntryPointImpl.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security.handle;
+package com.fs.core.security.handle;
 
 import com.alibaba.fastjson.JSON;
 import com.fs.common.constant.HttpStatus;

+ 5 - 5
fs-qw-api/src/main/java/com/fs/framework/security/handle/LogoutSuccessHandlerImpl.java → fs-qw-api/src/main/java/com/fs/core/security/handle/LogoutSuccessHandlerImpl.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security.handle;
+package com.fs.core.security.handle;
 
 import com.alibaba.fastjson.JSON;
 import com.fs.common.constant.Constants;
@@ -6,10 +6,10 @@ import com.fs.common.constant.HttpStatus;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
-import com.fs.framework.manager.AsyncManager;
-import com.fs.framework.manager.factory.AsyncFactory;
-import com.fs.framework.security.LoginUser;
-import com.fs.framework.service.TokenService;
+import com.fs.core.manager.AsyncManager;
+import com.fs.core.manager.factory.AsyncFactory;
+import com.fs.core.security.LoginUser;
+import com.fs.core.service.TokenService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.core.Authentication;

+ 4 - 4
fs-qw-api/src/main/java/com/fs/framework/service/CompanyLoginService.java → fs-qw-api/src/main/java/com/fs/core/service/CompanyLoginService.java

@@ -1,4 +1,4 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 import com.fs.common.constant.Constants;
 import com.fs.common.core.redis.RedisCache;
@@ -7,9 +7,9 @@ import com.fs.common.exception.user.CaptchaException;
 import com.fs.common.exception.user.CaptchaExpireException;
 import com.fs.common.exception.user.UserPasswordNotMatchException;
 import com.fs.common.utils.MessageUtils;
-import com.fs.framework.manager.AsyncManager;
-import com.fs.framework.manager.factory.AsyncFactory;
-import com.fs.framework.security.LoginUser;
+import com.fs.core.manager.AsyncManager;
+import com.fs.core.manager.factory.AsyncFactory;
+import com.fs.core.security.LoginUser;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/service/CompanyPermissionService.java → fs-qw-api/src/main/java/com/fs/core/service/CompanyPermissionService.java

@@ -1,4 +1,4 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyMenuService;

+ 2 - 2
fs-qw-api/src/main/java/com/fs/framework/service/PermissionService.java → fs-qw-api/src/main/java/com/fs/core/service/PermissionService.java

@@ -1,9 +1,9 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.CompanyRole;
-import com.fs.framework.security.LoginUser;
+import com.fs.core.security.LoginUser;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;

+ 2 - 2
fs-qw-api/src/main/java/com/fs/framework/service/TokenService.java → fs-qw-api/src/main/java/com/fs/core/service/TokenService.java

@@ -1,4 +1,4 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 import com.fs.common.constant.Constants;
 import com.fs.common.core.redis.RedisCache;
@@ -7,7 +7,7 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.ip.AddressUtils;
 import com.fs.common.utils.ip.IpUtils;
 import com.fs.common.utils.uuid.IdUtils;
-import com.fs.framework.security.LoginUser;
+import com.fs.core.security.LoginUser;
 import eu.bitwalker.useragentutils.UserAgent;
 import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.Jwts;

+ 2 - 2
fs-qw-api/src/main/java/com/fs/framework/service/UserDetailsServiceImpl.java → fs-qw-api/src/main/java/com/fs/core/service/UserDetailsServiceImpl.java

@@ -1,4 +1,4 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 
 import com.fs.common.enums.UserStatus;
@@ -8,7 +8,7 @@ import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
-import com.fs.framework.security.LoginUser;
+import com.fs.core.security.LoginUser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;

+ 128 - 0
fs-qw-api/src/main/java/com/tencent/wework/Finance.java

@@ -0,0 +1,128 @@
+package com.tencent.wework;
+
+/* sdk
+typedef struct Slice_t {
+    char* buf;
+    int len;
+} Slice_t;
+
+typedef struct MediaData {
+    char* outindexbuf;
+    int out_len;
+    char* data;    
+    int data_len;
+    int is_finish;
+} MediaData_t;
+*/
+
+public class Finance {
+    public native static long NewSdk();
+
+    /**
+     * ��ʼ������
+     * Returnֵ=0��ʾ��API���óɹ�
+     *
+     * @param [in] sdk			NewSdk���ص�sdkָ��
+     * @param [in] corpid      ������ҵ����ҵid�����磺wwd08c8exxxx5ab44d����������ҵ΢�Ź����--�ҵ���ҵ--��ҵ��Ϣ�鿴
+     * @param [in] secret		�������ݴ浵��Secret����������ҵ΢�Ź����--������--�������ݴ浵�鿴
+     * @return �����Ƿ��ʼ���ɹ�
+     * 0   - �ɹ�
+     * !=0 - ʧ��
+     */
+    public native static int Init(long sdk, String corpid, String secret);
+
+    /**
+     * ��ȡ�����¼����
+     * Returnֵ=0��ʾ��API���óɹ�
+     *
+     * @param [in]  sdk				NewSdk���ص�sdkָ��
+     * @param [in]  seq				��ָ����seq��ʼ��ȡ��Ϣ��ע����Ƿ��ص���Ϣ��seq+1��ʼ���أ�seqΪ֮ǰ�ӿڷ��ص����seqֵ���״�ʹ����ʹ��seq:0
+     * @param [in]  limit			һ����ȡ����Ϣ���������ֵ1000��������1000���᷵�ش���
+     * @param [in]  proxy			ʹ�ô����������Ҫ�����������ӡ��磺socks5://10.0.0.1:8081 ���� http://10.0.0.1:8081
+     * @param [in]  passwd			�����˺����룬��Ҫ���������˺����롣�� user_name:passwd_123
+     * @param [out] chatDatas		���ر�����ȡ��Ϣ�����ݣ�slice�ṹ��.���ݰ���errcode/errmsg���Լ�ÿ����Ϣ���ݡ�
+     * @return �����Ƿ���óɹ�
+     * 0   - �ɹ�
+     * !=0 - ʧ��
+     */
+    public native static int GetChatData(long sdk, long seq, long limit, String proxy, String passwd, long timeout, long chatData);
+
+    /**
+     * ��ȡý����Ϣ����
+     * Returnֵ=0��ʾ��API���óɹ�
+     *
+     * @param [in]  sdk				NewSdk���ص�sdkָ��
+     * @param [in]  sdkFileid		��GetChatData���ص�������Ϣ�У�ý����Ϣ������sdkfileid
+     * @param [in]  proxy			ʹ�ô����������Ҫ�����������ӡ��磺socks5://10.0.0.1:8081 ���� http://10.0.0.1:8081
+     * @param [in]  passwd			�����˺����룬��Ҫ���������˺����롣�� user_name:passwd_123
+     * @param [in]  indexbuf		ý����Ϣ��Ƭ��ȡ����Ҫ����ÿ����ȡ��������Ϣ���״β���Ҫ��д��Ĭ����ȡ512k������ÿ�ε���ֻ��Ҫ���ϴε��÷��ص�outindexbuf���뼴�ɡ�
+     * @param [out] media_data		���ر�����ȡ��ý������.MediaData�ṹ��.���ݰ���data(��������)/outindexbuf(�´�����)/is_finish(��ȡ��ɱ��)
+     * @return �����Ƿ���óɹ�
+     * 0   - �ɹ�
+     * !=0 - ʧ��
+     */
+    public native static int GetMediaData(long sdk, String indexbuf, String sdkField, String proxy, String passwd, long timeout, long mediaData);
+
+    /**
+     * @param [in]  encrypt_key, getchatdata���ص�encrypt_key
+     * @param [in]  encrypt_msg, getchatdata���ص�content
+     * @param [out] msg, ���ܵ���Ϣ����
+     * @return �����Ƿ���óɹ�
+     * 0   - �ɹ�
+     * !=0 - ʧ��
+     * @brief ��������
+     */
+    public native static int DecryptData(long sdk, String encrypt_key, String encrypt_msg, long msg);
+
+    public native static void DestroySdk(long sdk);
+
+    public native static long NewSlice();
+
+    /**
+     * @return
+     * @brief �ͷ�slice����NewSlice�ɶ�ʹ��
+     */
+    public native static void FreeSlice(long slice);
+
+    /**
+     * @return ����
+     * @brief ��ȡslice����
+     */
+    public native static String GetContentFromSlice(long slice);
+
+    /**
+     * @return ����
+     * @brief ��ȡslice���ݳ���
+     */
+    public native static int GetSliceLen(long slice);
+
+    public native static long NewMediaData();
+
+    public native static void FreeMediaData(long mediaData);
+
+    /**
+     * @return outindex
+     * @brief ��ȡmediadata outindex
+     */
+    public native static String GetOutIndexBuf(long mediaData);
+
+    /**
+     * @return data
+     * @brief ��ȡmediadata data����
+     */
+    public native static byte[] GetData(long mediaData);
+
+    public native static int GetIndexLen(long mediaData);
+
+    public native static int GetDataLen(long mediaData);
+
+    /**
+     * @return 1��ɡ�0δ���
+     * @brief �ж�mediadata�Ƿ����
+     */
+    public native static int IsMediaDataFinish(long mediaData);
+
+    static {
+        System.loadLibrary("WeWorkFinanceSdk");
+    }
+}

BIN
fs-qw-api/src/main/resources/jniLibs/WeWorkFinanceSdk.dll


BIN
fs-qw-api/src/main/resources/jniLibs/libcrypto-3-x64.dll


BIN
fs-qw-api/src/main/resources/jniLibs/libcurl-x64.dll


+ 1 - 1
fs-qw-api/src/main/resources/mybatis/mybatis-config.xml

@@ -13,7 +13,7 @@ PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 	</settings>
 
 	<typeHandlers>
-		<typeHandler handler="com.fs.framework.config.ArrayStringTypeHandler"/>
+		<typeHandler handler="com.fs.core.config.ArrayStringTypeHandler"/>
 	</typeHandlers>
 
 </configuration>

+ 1 - 0
fs-service-system/src/main/java/com/fs/course/domain/FsCourseFinishTemp.java

@@ -49,6 +49,7 @@ public class FsCourseFinishTemp extends BaseEntity
     /** 归属销售id(对多) */
     @Excel(name = "归属销售id(对多)")
     private String companyUserIds;
+    private Long parentId;
 
     /** 删除标志 */
     @Excel(name = "删除标志")

+ 48 - 0
fs-service-system/src/main/java/com/fs/course/domain/FsCourseFinishTempParent.java

@@ -0,0 +1,48 @@
+package com.fs.course.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 完课模板对象 fs_course_finish_temp_parent
+ *
+ * @author 吴树波
+ * @date 2025-05-22
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsCourseFinishTempParent extends BaseEntity{
+
+    /** id */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 名称 */
+    @Excel(name = "名称")
+    private String name;
+
+    /** 课程ID */
+    @Excel(name = "课程ID")
+    private Long courseId;
+
+    /** 公司ID */
+    @Excel(name = "公司ID")
+    private Long companyId;
+
+
+    @TableField(exist = false)
+    private Long status;
+    @TableField(exist = false)
+    private String setting;
+    @TableField(exist = false)
+    private String chatSetting;
+    @TableField(exist = false)
+    private String companyUserIds;
+    @TableField(exist = false)
+    private Integer isAllCompanyUser;
+}

+ 20 - 23
fs-service-system/src/main/java/com/fs/course/mapper/FsCourseFinishTempMapper.java

@@ -1,12 +1,13 @@
 package com.fs.course.mapper;
 
-import java.util.List;
 import com.fs.course.domain.FsCourseFinishTemp;
 import com.fs.course.vo.FsCourseFinishTempListVO;
 import com.fs.course.vo.FsCourseFinishTempVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
+import java.util.List;
+
 /**
  * 完课模板Mapper接口
  *
@@ -23,12 +24,6 @@ public interface FsCourseFinishTempMapper
      */
     public FsCourseFinishTemp selectFsCourseFinishTempById(Long id);
 
-//    @Select("select * from fs_course_finish_temp")
-//    public List<FsCourseFinishTemp> selectFsCourseFinishTempByCompanyList();
-
-    @Select("SELECT * FROM fs_course_finish_temp WHERE id = #{id}")
-    public FsCourseFinishTempVO selectFsCourseFinishTempByIdVO(Long id);
-
     /**
      * 查询完课模板列表
      *
@@ -77,6 +72,9 @@ public interface FsCourseFinishTempMapper
             "<if test = ' maps.name !=null and maps.name!=\"\" '> " +
             "and t.name like concat('%', #{maps.name}, '%') " +
             "</if>" +
+            "<if test = ' maps.parentId !=null'> " +
+            "and t.parent_id = #{maps.parentId} " +
+            "</if>" +
             "<if test = ' maps.courseId !=null  '> " +
             "and t.course_id = #{maps.courseId} " +
             "</if>" +
@@ -97,22 +95,21 @@ public interface FsCourseFinishTempMapper
             "and find_in_set(#{userId},company_user_ids) and is_del= 0 order by create_time desc  limit 1 ")
     FsCourseFinishTemp selectFsCourseFinishTempByCompanyUserId(@Param("userId") Long companyUserId, @Param("videoId")Long videoId);
 
-    @Select("SELECT *\n" +
-            "FROM fs_course_finish_temp\n" +
-            "WHERE video_id = #{videoId}\n" +
-            "  AND is_del = 0\n" +
-            "  AND (\n" +
-            "    (is_all_company_user = 1 AND company_id = #{companyId})\n" +
-            "    OR\n" +
-            "    ((is_all_company_user != 1 OR is_all_company_user IS NULL) AND FIND_IN_SET(#{userId}, company_user_ids))\n" +
-            "  )\n" +
-            "ORDER BY \n" +
-            "  CASE WHEN (is_all_company_user != 1 OR is_all_company_user IS NULL) THEN 0 ELSE 1 END,\n" +
-            "  COALESCE(update_time, create_time) DESC \n" +
-            "LIMIT 1")
+    @Select("    SELECT *\n" +
+            "    FROM fs_course_finish_temp\n" +
+            "    WHERE video_id = #{videoId}\n" +
+            "    AND is_del = 0\n" +
+            "    AND (\n" +
+            "        (is_all_company_user = 1 AND company_id = #{companyId})\n" +
+            "        OR\n" +
+            "        ((is_all_company_user != 1 OR is_all_company_user IS NULL) AND FIND_IN_SET(#{userId}, company_user_ids))\n" +
+            "    )\n" +
+            "    ORDER BY create_time DESC\n" +
+            "    LIMIT 1\n")
     FsCourseFinishTemp selectFsCourseFinishTempByCompanyId(@Param("userId") Long companyUserId,@Param("companyId") Long companyId, @Param("videoId")Long videoId);
 
-    @Select("select * from fs_course_finish_temp where type = 2 and video_id = #{videoId} " +
-            "and find_in_set(#{userId},company_user_ids) and is_del= 0 order by create_time desc  limit 1 ")
-    FsCourseFinishTemp selectFsCourseFinishTempChatSopByCompanyUserId(Long companyUserId, Long videoId);
+    @Select("SELECT * FROM fs_course_finish_temp WHERE id = #{id}")
+    public FsCourseFinishTempVO selectFsCourseFinishTempByIdVO(Long id);
+
+    void deleteByParentIds(@Param("ids") Long[] ids);
 }

+ 62 - 0
fs-service-system/src/main/java/com/fs/course/mapper/FsCourseFinishTempParentMapper.java

@@ -0,0 +1,62 @@
+package com.fs.course.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsCourseFinishTempParent;
+
+import java.util.List;
+
+/**
+ * 完课模板Mapper接口
+ * 
+ * @author 吴树波
+ * @date 2025-05-22
+ */
+public interface FsCourseFinishTempParentMapper extends BaseMapper<FsCourseFinishTempParent>{
+    /**
+     * 查询完课模板
+     * 
+     * @param id 完课模板主键
+     * @return 完课模板
+     */
+    FsCourseFinishTempParent selectFsCourseFinishTempParentById(Long id);
+
+    /**
+     * 查询完课模板列表
+     * 
+     * @param fsCourseFinishTempParent 完课模板
+     * @return 完课模板集合
+     */
+    List<FsCourseFinishTempParent> selectFsCourseFinishTempParentList(FsCourseFinishTempParent fsCourseFinishTempParent);
+
+    /**
+     * 新增完课模板
+     * 
+     * @param fsCourseFinishTempParent 完课模板
+     * @return 结果
+     */
+    int insertFsCourseFinishTempParent(FsCourseFinishTempParent fsCourseFinishTempParent);
+
+    /**
+     * 修改完课模板
+     * 
+     * @param fsCourseFinishTempParent 完课模板
+     * @return 结果
+     */
+    int updateFsCourseFinishTempParent(FsCourseFinishTempParent fsCourseFinishTempParent);
+
+    /**
+     * 删除完课模板
+     * 
+     * @param id 完课模板主键
+     * @return 结果
+     */
+    int deleteFsCourseFinishTempParentById(Long id);
+
+    /**
+     * 批量删除完课模板
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsCourseFinishTempParentByIds(Long[] ids);
+}

+ 62 - 0
fs-service-system/src/main/java/com/fs/course/service/IFsCourseFinishTempParentService.java

@@ -0,0 +1,62 @@
+package com.fs.course.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.course.domain.FsCourseFinishTempParent;
+
+import java.util.List;
+
+/**
+ * 完课模板Service接口
+ * 
+ * @author 吴树波
+ * @date 2025-05-22
+ */
+public interface IFsCourseFinishTempParentService extends IService<FsCourseFinishTempParent>{
+    /**
+     * 查询完课模板
+     * 
+     * @param id 完课模板主键
+     * @return 完课模板
+     */
+    FsCourseFinishTempParent selectFsCourseFinishTempParentById(Long id);
+
+    /**
+     * 查询完课模板列表
+     * 
+     * @param fsCourseFinishTempParent 完课模板
+     * @return 完课模板集合
+     */
+    List<FsCourseFinishTempParent> selectFsCourseFinishTempParentList(FsCourseFinishTempParent fsCourseFinishTempParent);
+
+    /**
+     * 新增完课模板
+     * 
+     * @param fsCourseFinishTempParent 完课模板
+     * @return 结果
+     */
+    int insertFsCourseFinishTempParent(FsCourseFinishTempParent fsCourseFinishTempParent);
+
+    /**
+     * 修改完课模板
+     * 
+     * @param fsCourseFinishTempParent 完课模板
+     * @return 结果
+     */
+    int updateFsCourseFinishTempParent(FsCourseFinishTempParent fsCourseFinishTempParent);
+
+    /**
+     * 批量删除完课模板
+     * 
+     * @param ids 需要删除的完课模板主键集合
+     * @return 结果
+     */
+    int deleteFsCourseFinishTempParentByIds(Long[] ids);
+
+    /**
+     * 删除完课模板信息
+     * 
+     * @param id 完课模板主键
+     * @return 结果
+     */
+    int deleteFsCourseFinishTempParentById(Long id);
+}

+ 4 - 1
fs-service-system/src/main/java/com/fs/course/service/IFsCourseFinishTempService.java

@@ -1,10 +1,11 @@
 package com.fs.course.service;
 
-import java.util.List;
 import com.fs.course.domain.FsCourseFinishTemp;
 import com.fs.course.vo.FsCourseFinishTempListVO;
 import com.fs.course.vo.FsCourseFinishTempVO;
 
+import java.util.List;
+
 /**
  * 完课模板Service接口
  *
@@ -64,6 +65,8 @@ public interface IFsCourseFinishTempService
      */
     public int deleteFsCourseFinishTempById(Long id);
 
+    void deleteByParentIds(Long[] ids);
+
     /**
      * 将所有的CompanyUserId更新为 企业微信账号
      */

+ 123 - 0
fs-service-system/src/main/java/com/fs/course/service/impl/FsCourseFinishTempParentServiceImpl.java

@@ -0,0 +1,123 @@
+package com.fs.course.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.utils.DateUtils;
+import com.fs.course.domain.FsCourseFinishTemp;
+import com.fs.course.domain.FsCourseFinishTempParent;
+import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.mapper.FsCourseFinishTempParentMapper;
+import com.fs.course.service.IFsCourseFinishTempParentService;
+import com.fs.course.service.IFsCourseFinishTempService;
+import com.fs.course.service.IFsUserCourseService;
+import com.fs.course.service.IFsUserCourseVideoService;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 完课模板Service业务层处理
+ * 
+ * @author 吴树波
+ * @date 2025-05-22
+ */
+@Service
+@AllArgsConstructor
+public class FsCourseFinishTempParentServiceImpl extends ServiceImpl<FsCourseFinishTempParentMapper, FsCourseFinishTempParent> implements IFsCourseFinishTempParentService {
+
+    private final IFsUserCourseService fsUserCourseService;
+    private final IFsUserCourseVideoService fsUserCourseVideoService;
+    private final IFsCourseFinishTempService fsCourseFinishTempService;
+
+    /**
+     * 查询完课模板
+     * 
+     * @param id 完课模板主键
+     * @return 完课模板
+     */
+    @Override
+    public FsCourseFinishTempParent selectFsCourseFinishTempParentById(Long id)
+    {
+        return baseMapper.selectFsCourseFinishTempParentById(id);
+    }
+
+    /**
+     * 查询完课模板列表
+     * 
+     * @param fsCourseFinishTempParent 完课模板
+     * @return 完课模板
+     */
+    @Override
+    public List<FsCourseFinishTempParent> selectFsCourseFinishTempParentList(FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        return baseMapper.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
+    }
+
+    /**
+     * 新增完课模板
+     * 
+     * @param fsCourseFinishTempParent 完课模板
+     * @return 结果
+     */
+    @Override
+    public int insertFsCourseFinishTempParent(FsCourseFinishTempParent fsCourseFinishTempParent){
+        Long courseId = fsCourseFinishTempParent.getCourseId();
+        FsUserCourseVideo fsUserCourseVideo = new FsUserCourseVideo();
+        fsUserCourseVideo.setCourseId(courseId);
+        List<FsUserCourseVideo> videoList = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(fsUserCourseVideo);
+        int insert = baseMapper.insert(fsCourseFinishTempParent);
+        fsCourseFinishTempParent.setCreateTime(DateUtils.getNowDate());
+        videoList.forEach(e -> {
+            FsCourseFinishTemp temp = new FsCourseFinishTemp();
+            temp.setName(e.getFileName());
+            temp.setStatus(fsCourseFinishTempParent.getStatus());
+            temp.setSetting(fsCourseFinishTempParent.getSetting());
+            temp.setChatSetting(fsCourseFinishTempParent.getChatSetting());
+            temp.setCompanyId(fsCourseFinishTempParent.getCompanyId());
+            temp.setCourseId(e.getCourseId());
+            temp.setVideoId(e.getVideoId());
+            temp.setCompanyUserIds(fsCourseFinishTempParent.getCompanyUserIds());
+            temp.setIsAllCompanyUser(fsCourseFinishTempParent.getIsAllCompanyUser());
+            temp.setParentId(fsCourseFinishTempParent.getId());
+            fsCourseFinishTempService.insertFsCourseFinishTemp(temp);
+        });
+        return insert;
+    }
+
+    /**
+     * 修改完课模板
+     * 
+     * @param fsCourseFinishTempParent 完课模板
+     * @return 结果
+     */
+    @Override
+    public int updateFsCourseFinishTempParent(FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        fsCourseFinishTempParent.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateById(fsCourseFinishTempParent);
+    }
+
+    /**
+     * 批量删除完课模板
+     * 
+     * @param ids 需要删除的完课模板主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseFinishTempParentByIds(Long[] ids){
+        int i = baseMapper.deleteFsCourseFinishTempParentByIds(ids);
+        fsCourseFinishTempService.deleteByParentIds(ids);
+        return i;
+    }
+
+    /**
+     * 删除完课模板信息
+     * 
+     * @param id 完课模板主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseFinishTempParentById(Long id){
+        return deleteFsCourseFinishTempParentByIds(new Long[]{id});
+    }
+}

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

@@ -1,34 +1,25 @@
 package com.fs.course.service.impl;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.fs.common.utils.DateUtils;
-import com.fs.common.utils.StringUtils;
-import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.course.domain.FsCourseFinishTemp;
+import com.fs.course.mapper.FsCourseFinishTempMapper;
+import com.fs.course.service.IFsCourseFinishTempService;
 import com.fs.course.vo.FsCourseFinishTempListVO;
 import com.fs.course.vo.FsCourseFinishTempVO;
 import com.fs.fastGpt.domain.FastGptChatReplaceWords;
 import com.fs.fastGpt.mapper.FastGptChatReplaceWordsMapper;
-import com.fs.fastgptApi.util.AudioUtils;
-import com.fs.fastgptApi.vo.AudioVO;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
-import com.fs.qw.vo.QwSopTempSetting;
 import com.fs.qw.vo.QwUserVO;
-import com.fs.voice.utils.StringUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import com.fs.course.mapper.FsCourseFinishTempMapper;
-import com.fs.course.domain.FsCourseFinishTemp;
-import com.fs.course.service.IFsCourseFinishTempService;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * 完课模板Service业务层处理
@@ -179,6 +170,11 @@ public class FsCourseFinishTempServiceImpl implements IFsCourseFinishTempService
         return fsCourseFinishTempMapper.deleteFsCourseFinishTempById(id);
     }
 
+    @Override
+    public void deleteByParentIds(Long[] ids) {
+        fsCourseFinishTempMapper.deleteByParentIds(ids);
+    }
+
 //    @Override
 //    public void updateFsCourseFinishTempByCompanyUserId() {
 //        List<FsCourseFinishTemp> fsCourseFinishTemps = fsCourseFinishTempMapper.selectFsCourseFinishTempByCompanyList();

+ 18 - 3
fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java

@@ -4,15 +4,18 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.PubFun;
 import com.fs.common.utils.date.DateUtil;
 import com.fs.common.utils.date.TimeTypeEnum;
 import com.fs.course.domain.FsUserCoursePeriod;
 import com.fs.course.domain.FsUserCoursePeriodDays;
+import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.mapper.FsUserCoursePeriodDaysMapper;
 import com.fs.course.mapper.FsUserCoursePeriodMapper;
 import com.fs.course.param.CompanyRedPacketParam;
 import com.fs.course.param.PeriodCountParam;
 import com.fs.course.service.IFsUserCoursePeriodDaysService;
+import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.FsPeriodCountVO;
 import com.fs.course.vo.PeriodRedPacketVO;
 import com.fs.course.vo.UpdateCourseTimeVo;
@@ -54,6 +57,7 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
     private final FsUserMapper  fsUserMapper;
 
     private final FsUserCoursePeriodDaysMapper fsUserCoursePeriodDaysMapper;
+    private final IFsUserCourseVideoService fsUserCourseVideoService;
 
     /**
      * 查询营期课程
@@ -143,15 +147,26 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
         if(dayList.size() + entity.getVideoIds().size() > days) return R.error("课程不能超过营期范围");
         if(dayList.stream().anyMatch(e -> entity.getVideoIds().contains(e.getVideoId()))) return R.error("不能添加相同章节");
         AtomicInteger i = new AtomicInteger(0);
+        FsUserCourseVideo fsUserCourseVideo = new FsUserCourseVideo();
+        fsUserCourseVideo.setCourseId(entity.getCourseId());
+        List<FsUserCourseVideo> videoList = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(fsUserCourseVideo);
+        Map<Long, FsUserCourseVideo> videoMap = PubFun.listToMapByGroupObject(videoList, FsUserCourseVideo::getVideoId);
         List<FsUserCoursePeriodDays> collect = entity.getVideoIds().stream().map(e -> {
+            FsUserCourseVideo video = videoMap.get(e);
             FsUserCoursePeriodDays day = new FsUserCoursePeriodDays();
             day.setPeriodId(entity.getPeriodId());
             day.setLesson(dayList.size() + i.getAndIncrement());
             day.setDayDate(period.getPeriodStartingTime().plusDays(day.getLesson()));
             day.setCourseId(entity.getCourseId());
-            day.setStartDateTime(LocalDateTime.of(day.getDayDate(), entity.getStartTime()));
-            day.setEndDateTime(LocalDateTime.of(day.getDayDate(), entity.getEndTime1()));
-            day.setLastJoinTime(LocalDateTime.of(day.getDayDate(), entity.getJoinTime()));
+            if(video.getViewStartTime() != null){
+                day.setStartDateTime(LocalDateTime.of(day.getDayDate(), video.getViewStartTime()));
+            }
+            if(video.getViewEndTime() != null){
+                day.setEndDateTime(LocalDateTime.of(day.getDayDate(), video.getViewEndTime()));
+            }
+            if(video.getLastJoinTime() != null){
+                day.setLastJoinTime(LocalDateTime.of(day.getDayDate(), video.getLastJoinTime()));
+            }
             day.setVideoId(e);
             day.setCreateTime(new Date());
             // 默认开启今天及以后的两天

+ 2 - 2
fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -79,8 +79,8 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     @Autowired
     private FsCourseAnswerLogsMapper fsCourseAnswerLogsMapper;
 
-    private static final String realLink = "/courseH5/pages/course/learning?course=";
-    private static final String shortLink = "/courseH5/pages/course/learning?s=";
+    private static final String realLink = "/pages/course/learning?course=";
+    private static final String shortLink = "/pages/course/learning?s=";
     private static final String userRealLink = "/pages/user/users/becomeVIP?";
 
     /**

+ 16 - 1
fs-service-system/src/main/java/com/fs/fastGpt/service/AiHookService.java

@@ -3,6 +3,7 @@ package com.fs.fastGpt.service;
 import com.fs.common.core.domain.R;
 import com.fs.qw.vo.QwMessageListVO;
 import com.fs.qwHookApi.vo.QwHookVO;
+import com.fs.wxwork.dto.WxWorkResponseDTO;
 
 public interface AiHookService {
     /** 发送ai提醒 **/
@@ -27,6 +28,20 @@ public interface AiHookService {
      * @param uuid     UUID
      * @param sendType 发送者类型 1用户 2客服
      * @param json     消息json
+     * @param msgType  消息类型 1文本 2图片
      */
-    QwMessageListVO saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json);
+    QwMessageListVO saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json, int msgType);
+
+    /**
+     * 获取文件地址
+     * @param uuid      uuid
+     * @param fileId    fileId
+     * @param aesKey    aesKey
+     * @param authKey   authKey
+     * @param fileName  fileName
+     * @param fileSize  size
+     * @param serverId  serverId
+     * @return  WxWorkResponseDTO
+     */
+    WxWorkResponseDTO<String> getFileUrl(String uuid, String fileId, String aesKey, String authKey, String fileName, Integer fileSize, Long serverId);
 }

+ 31 - 3
fs-service-system/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -1269,10 +1269,11 @@ public class AiHookServiceImpl implements AiHookService {
      * @param uuid     UUID
      * @param sendType 发送者类型 1用户 2客服
      * @param json     消息json
+     * @param msgType  消息类型 1文本 2图片
      */
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public QwMessageListVO saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json) {
+    public QwMessageListVO saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json, int msgType) {
         // 查询企微用户
         QwUser qwUser = qwUserService.selectQwUserById(qwUserId);
         if (Objects.isNull(qwUser)){
@@ -1318,7 +1319,7 @@ public class AiHookServiceImpl implements AiHookService {
         qwMsg.setSendType(sendType);
         qwMsg.setCompanyId(qwUser.getCompanyId());
         qwMsg.setCompanyUserId(qwUser.getCompanyUserId());
-        qwMsg.setMsgType(1);
+        qwMsg.setMsgType(msgType);
         qwMsg.setMsgJson(json);
         qwMsg.setStatus(0);
         qwMsg.setQwUserId(qwSession.getQwUserId());
@@ -1343,7 +1344,11 @@ public class AiHookServiceImpl implements AiHookService {
         }
 
         listVO.setCompanyId(qwUser.getCompanyId());
-        listVO.setType("text");
+        String type = "text";
+        if (msgType == 2) {
+            type = "image";
+        }
+        listVO.setType(type);
         listVO.setStatus("succeed");
         listVO.setFromUser(qwFromUser);
         listVO.setSendTime(qwMsg.getCreateTime().getTime());
@@ -1353,6 +1358,29 @@ public class AiHookServiceImpl implements AiHookService {
         return listVO;
     }
 
+    /**
+     * 获取文件地址
+     * @param fileId    fileId
+     * @param uuid      uuid
+     * @param aesKey    aesKey
+     * @param authKey   authKey
+     * @param fileName  fileName
+     * @param fileSize  size
+     * @param serverId  serverId
+     * @return  WxWorkResponseDTO
+     */
+    @Override
+    public WxWorkResponseDTO<String> getFileUrl(String uuid, String fileId, String aesKey, String authKey, String fileName, Integer fileSize, Long serverId) {
+        WxwDownloadWeChatFileDTO weChatFileDTO = new WxwDownloadWeChatFileDTO();
+        weChatFileDTO.setUuid(uuid);
+        weChatFileDTO.setUrl(fileId);
+        weChatFileDTO.setAes_key(aesKey);
+        weChatFileDTO.setAuth_key(authKey);
+        weChatFileDTO.setFile_name(fileName);
+        weChatFileDTO.setSize(fileSize);
+        return wxWorkService.downloadWeChatFile(weChatFileDTO, serverId);
+    }
+
     /**
      * 查询外部联系人
      * @param userId    用户ID

+ 2 - 1
fs-service-system/src/main/java/com/fs/qw/param/QwMsgSendParam.java

@@ -9,5 +9,6 @@ public class QwMsgSendParam implements Serializable {
     private Long sessionId;
     private String content;//内容
     private String appKey;
-
+    // 消息类型 1文本 2图片
+    private Integer msgType;
 }

+ 88 - 47
fs-service-system/src/main/java/com/fs/qw/service/impl/QwMsgServiceImpl.java

@@ -256,6 +256,10 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
             return R.error("会话ID不能为空");
         }
 
+        if (Objects.isNull(param.getMsgType())) {
+            return R.error("消息类型不能为空");
+        }
+
         // 查询会话
         QwSession qwSession = qwSessionMapper.selectQwSessionBySessionId(param.getSessionId());
         if (Objects.isNull(qwSession)) {
@@ -288,56 +292,89 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
         if (listWxWorkResponseDTO.getErrcode() != 0) {
             return R.error(listWxWorkResponseDTO.getErrmsg());
         }
-
-        // 发送消息
-        WxWorkSendTextMsgDTO textMsgDTO = new WxWorkSendTextMsgDTO();
-        textMsgDTO.setUuid(uuid);
-        textMsgDTO.setSend_userid(listWxWorkResponseDTO.getData().get(0).getUser_id());
-        textMsgDTO.setIsRoom(false);
-        textMsgDTO.setContent(param.getContent());
-        WxWorkResponseDTO<WxWorkSendTextMsgRespDTO> msgRespDTOWxWorkResponseDTO = wxWorkService.SendTextMsg(textMsgDTO, serverId);
-
-        if (msgRespDTOWxWorkResponseDTO.getErrcode() != 0) {
-            return R.error(msgRespDTOWxWorkResponseDTO.getErrmsg());
+        long sendUserId = listWxWorkResponseDTO.getData().get(0).getUser_id();
+        String msgJson;
+
+        // 发送消息  文本
+        if (param.getMsgType() == 1) {
+            WxWorkSendTextMsgDTO textMsgDTO = new WxWorkSendTextMsgDTO();
+            textMsgDTO.setUuid(uuid);
+            textMsgDTO.setSend_userid(sendUserId);
+            textMsgDTO.setIsRoom(false);
+            textMsgDTO.setContent(param.getContent());
+            WxWorkResponseDTO<WxWorkSendTextMsgRespDTO> msgRespDTOWxWorkResponseDTO = wxWorkService.SendTextMsg(textMsgDTO, serverId);
+
+            if (msgRespDTOWxWorkResponseDTO.getErrcode() != 0) {
+                return R.error(msgRespDTOWxWorkResponseDTO.getErrmsg());
+            }
+            msgJson = JSONObject.toJSONString(textMsgDTO);
         }
 
-        String msg = msgRespDTOWxWorkResponseDTO.getErrmsg();
-        if ("ok".equals(msg)) {
-            // 消息保存本地数据库
-            QwMsg qwMsg = new QwMsg();
-            qwMsg.setContent(param.getContent());
-            qwMsg.setSessionId(qwSession.getSessionId());
-            qwMsg.setSendType(2);
-            qwMsg.setCompanyId(qwUser.getCompanyId());
-            qwMsg.setCompanyUserId(qwUser.getCompanyUserId());
-            qwMsg.setMsgType(1);
-            qwMsg.setMsgJson(JSONObject.toJSONString(textMsgDTO));
-            qwMsg.setStatus(0);
-            qwMsg.setQwUserId(qwSession.getQwUserId());
-            qwMsg.setQwExtId(qwSession.getQwExtId());
-            qwMsg.setAvatar(qwExternalContact.getAvatar());
-            qwMsg.setNickName(qwExternalContact.getRemark());
-            qwMsg.setCreateTime(new Date());
-            qwMsgMapper.insertQwMsg(qwMsg);
-
-            // 组装返回消息结构
-            QwMessageListVO listVO = new QwMessageListVO();
-            QWFromUser qwFromUser = new QWFromUser();
-            qwFromUser.setId(Long.parseLong(qwMsg.getQwUserId()));
-            qwFromUser.setDisplayName(qwUser.getQwUserName());
-            qwFromUser.setAvatar("https://cos.his.cdwjyyh.com/fs/20241231/22a765a96da247d1b83ea94fef438a41.png");
-
-            listVO.setType("text");
-            listVO.setStatus("succeed");
-            listVO.setFromUser(qwFromUser);
-            listVO.setSendTime(qwMsg.getCreateTime().getTime());
-            listVO.setId(qwMsg.getMsgId().toString());
-            listVO.setContent(qwMsg.getContent());
-            listVO.setToContactId(String.valueOf(param.getSessionId()));
-            return R.ok().put("data", listVO);
+        // 图片
+        else if (param.getMsgType() == 2) {
+            WxCdnUploadImgLinkDTO linkDTO = new WxCdnUploadImgLinkDTO();
+            linkDTO.setUuid(uuid);
+            linkDTO.setUrl(param.getContent());
+            WxWorkResponseDTO<WxCdnUploadImgLinkResp> imgLinkResp = wxWorkService.cdnUploadImgLink(linkDTO, serverId);
+            if (imgLinkResp.getErrcode() != 0) {
+                return R.error(imgLinkResp.getErrmsg());
+            }
+            WxCdnUploadImgLinkResp data = imgLinkResp.getData();
+
+            // 发送图片消息
+            WxwSendCDNImgMsgDTO imgMsgDTO = new WxwSendCDNImgMsgDTO();
+            imgMsgDTO.setUuid(uuid);
+            imgMsgDTO.setSend_userid(sendUserId);
+            imgMsgDTO.setIsRoom(false);
+            imgMsgDTO.setCdnkey(data.getCdn_key());
+            imgMsgDTO.setAeskey(data.getAes_key());
+            imgMsgDTO.setMd5(data.getMd5());
+            imgMsgDTO.setFileSize(data.getSize());
+            WxWorkResponseDTO<WxwSendCDNImgMsgRespDTO> imgMsgResp = wxWorkService.SendCDNImgMsg(imgMsgDTO, serverId);
+            if (imgMsgResp.getErrcode() != 0) {
+                return R.error(imgMsgResp.getErrmsg());
+            }
+            msgJson = JSONObject.toJSONString(imgMsgDTO);
         } else {
-            return R.error(msg);
+            return R.error("暂不支持的消息类型");
+        }
+
+        // 消息保存本地数据库
+        QwMsg qwMsg = new QwMsg();
+        qwMsg.setContent(param.getContent());
+        qwMsg.setSessionId(qwSession.getSessionId());
+        qwMsg.setSendType(2);
+        qwMsg.setCompanyId(qwUser.getCompanyId());
+        qwMsg.setCompanyUserId(qwUser.getCompanyUserId());
+        qwMsg.setMsgType(param.getMsgType());
+        qwMsg.setMsgJson(msgJson);
+        qwMsg.setStatus(0);
+        qwMsg.setQwUserId(qwSession.getQwUserId());
+        qwMsg.setQwExtId(qwSession.getQwExtId());
+        qwMsg.setAvatar(qwExternalContact.getAvatar());
+        qwMsg.setNickName(qwExternalContact.getRemark());
+        qwMsg.setCreateTime(new Date());
+        qwMsgMapper.insertQwMsg(qwMsg);
+
+        // 组装返回消息结构
+        QwMessageListVO listVO = new QwMessageListVO();
+        QWFromUser qwFromUser = new QWFromUser();
+        qwFromUser.setId(Long.parseLong(qwMsg.getQwUserId()));
+        qwFromUser.setDisplayName(qwUser.getQwUserName());
+        qwFromUser.setAvatar("https://cos.his.cdwjyyh.com/fs/20241231/22a765a96da247d1b83ea94fef438a41.png");
+
+        String type = "text";
+        if (param.getMsgType() == 2) {
+            type = "image";
         }
+        listVO.setType(type);
+        listVO.setStatus("succeed");
+        listVO.setFromUser(qwFromUser);
+        listVO.setSendTime(qwMsg.getCreateTime().getTime());
+        listVO.setId(qwMsg.getMsgId().toString());
+        listVO.setContent(qwMsg.getContent());
+        listVO.setToContactId(String.valueOf(param.getSessionId()));
+        return R.ok().put("data", listVO);
     }
 
     @Override
@@ -430,7 +467,11 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
         List<QwMessageListVO> qwMessageVOS = new ArrayList<>();
         for (QwMsg record : list) {
             QwMessageListVO listVO = new QwMessageListVO();
-            listVO.setType("text");
+            String type = "text";
+            if (record.getMsgType() == 2) {
+                type = "image";
+            }
+            listVO.setType(type);
             listVO.setStatus("succeed");
             QWFromUser qwFromUser = new QWFromUser();
             //用户发送

+ 6 - 0
fs-service-system/src/main/java/com/fs/system/service/ISysDictTypeService.java

@@ -86,4 +86,10 @@ public interface ISysDictTypeService
      * @return 结果
      */
     public String checkDictTypeUnique(SysDictType dictType);
+    /**
+     * 重置字典缓存数据
+     */
+    public void resetDictCache();
+
+    public void loadingDictCache();
 }

+ 22 - 6
fs-service-system/src/main/java/com/fs/system/service/impl/SysDictTypeServiceImpl.java

@@ -1,8 +1,10 @@
 package com.fs.system.service.impl;
 
 import java.util.List;
+import java.util.Map;
 import javax.annotation.PostConstruct;
 
+import com.fs.common.utils.PubFun;
 import com.fs.system.mapper.SysDictDataMapper;
 import com.fs.system.mapper.SysDictTypeMapper;
 import com.fs.system.service.ISysDictTypeService;
@@ -36,12 +38,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
     @PostConstruct
     public void init()
     {
-        List<SysDictType> dictTypeList = dictTypeMapper.selectDictTypeAll();
-        for (SysDictType dictType : dictTypeList)
-        {
-            List<SysDictData> dictDatas = dictDataMapper.selectDictDataByType(dictType.getDictType());
-            DictUtils.setDictCache(dictType.getDictType(), dictDatas);
-        }
+        loadingDictCache();
     }
 
     /**
@@ -56,6 +53,16 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
         return dictTypeMapper.selectDictTypeList(dictType);
     }
 
+    /**
+     * 加载字典缓存数据
+     */
+    @Override
+    public void loadingDictCache(){
+        List<SysDictType> dictTypeList = dictTypeMapper.selectDictTypeAll();
+        List<SysDictData> dictDataList = dictDataMapper.selectDictDataAll();
+        Map<String, List<SysDictData>> dictDataMap = PubFun.listToMapByGroupList(dictDataList, SysDictData::getDictType);
+        dictTypeList.stream().filter(e -> dictDataMap.containsKey(e.getDictType())).forEach(e -> DictUtils.setDictCache(e.getDictType(), dictDataMap.get(e.getDictType())));
+    }
     /**
      * 根据所有字典类型
      * 
@@ -148,6 +155,15 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
         DictUtils.clearDictCache();
     }
 
+    /**
+     * 重置字典缓存数据
+     */
+    @Override
+    public void resetDictCache(){
+        clearCache();
+        loadingDictCache();
+    }
+
     /**
      * 新增保存字典类型信息
      * 

+ 5 - 0
fs-service-system/src/main/java/com/fs/wx/mp/config/WxMpConfiguration.java

@@ -2,6 +2,7 @@ package com.fs.wx.mp.config;
 
 import com.fs.wx.mp.handler.*;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.redis.JedisWxRedisOps;
 import me.chanjar.weixin.mp.api.WxMpMessageRouter;
 import me.chanjar.weixin.mp.api.WxMpService;
@@ -28,6 +29,7 @@ import static me.chanjar.weixin.mp.constant.WxMpEventConstants.POI_CHECK_NOTIFY;
  *
  * @author <a href="https://github.com/binarywang">Binary Wang</a>
  */
+@Slf4j
 @AllArgsConstructor
 @Configuration
 @EnableConfigurationProperties(WxMpProperties.class)
@@ -51,6 +53,9 @@ public class WxMpConfiguration {
         if (configs == null) {
             throw new RuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!");
         }
+        configs.forEach(e -> {
+            log.info("微信公众号APPID:{}", e.getAppId());
+        });
 
         WxMpService service = new WxMpServiceImpl();
         service.setMultiConfigStorages(configs

+ 15 - 0
fs-service-system/src/main/java/com/fs/wxwork/dto/WxCdnUploadImgLinkDTO.java

@@ -0,0 +1,15 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+@Data
+public class WxCdnUploadImgLinkDTO {
+    /**
+     * 用户UUID
+     */
+    private String uuid;
+    /**
+     * 图片地址
+     */
+    private String url;
+}

+ 30 - 0
fs-service-system/src/main/java/com/fs/wxwork/dto/WxCdnUploadImgLinkResp.java

@@ -0,0 +1,30 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+@Data
+public class WxCdnUploadImgLinkResp {
+    private String cdn_key;
+    private String fileid;
+    //加密key
+    private String aes_key;
+    //md5
+    private String md5;
+    //宽度
+    private Integer width;
+    //高度
+    private Integer height;
+    //大小
+    private Integer size;
+    //中图大小
+    private Integer mid_image_size;
+    //缩略图宽度
+    private Integer thumb_image_width;
+    //缩略图高度
+    private Integer thumb_image_height;
+    //缩略图大小
+    private Integer thumb_file_size;
+    //缩略图md5
+    private String thumb_file_md5;
+    private String retcode;
+}

+ 31 - 0
fs-service-system/src/main/java/com/fs/wxwork/dto/WxwDownloadWeChatFileDTO.java

@@ -0,0 +1,31 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+@Data
+public class WxwDownloadWeChatFileDTO {
+    /**
+     * 对应uuid
+     */
+    private String uuid;
+    /**
+     * 对应file_id
+     */
+    private String url;
+    /**
+     * 对应aes_key
+     */
+    private String aes_key;
+    /**
+     * 对应 openim_cdn_authkey
+     */
+    private String auth_key;
+    /**
+     * 文件名称
+     */
+    private String file_name;
+    /**
+     * 文件大小
+     */
+    private Integer size;
+}

+ 16 - 0
fs-service-system/src/main/java/com/fs/wxwork/service/WxWorkService.java

@@ -213,4 +213,20 @@ public interface WxWorkService {
     WxWorkResponseDTO<WxwUploadCdnLinkFileRespDTO>  uploadCdnLinkFile(WxwUploadCdnLinkFileDTO param, Long serverId);
 
     WxwSilkVoceDTO  getSilkVoice(String param, Long companyUserId);
+
+    /**
+     * 外部联系人图片视频文件下载
+     * @param param    参数
+     * @param serverId 服务器ID
+     * @return  WxWorkResponseDTO
+     */
+    WxWorkResponseDTO<String> downloadWeChatFile(WxwDownloadWeChatFileDTO param, Long serverId);
+
+    /**
+     * CDN上传网络图片
+     * @param param     参数
+     * @param serverId  服务器ID
+     * @return  WxWorkResponseDTO
+     */
+    WxWorkResponseDTO<WxCdnUploadImgLinkResp> cdnUploadImgLink(WxCdnUploadImgLinkDTO param, Long serverId);
 }

+ 25 - 0
fs-service-system/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java

@@ -278,4 +278,29 @@ public class WxWorkServiceImpl implements WxWorkService {
         String url = getUrl(serverId) + "/UserId2Vid";
         return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>>>() {});
     }
+
+    /**
+     * 外部联系人图片视频文件下载
+     * @param param    参数
+     * @param serverId 服务器ID
+     * @return  WxWorkResponseDTO
+     */
+    @Override
+    public WxWorkResponseDTO<String> downloadWeChatFile(WxwDownloadWeChatFileDTO param, Long serverId) {
+        String url = getUrl(serverId) + "/DownloadWeChatFile";
+        return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<String>>() {});
+    }
+
+    /**
+     * CDN上传网络图片
+     * @param param     参数
+     * @param serverId  服务器ID
+     * @return  WxWorkResponseDTO
+     */
+    @Override
+    public WxWorkResponseDTO<WxCdnUploadImgLinkResp> cdnUploadImgLink(WxCdnUploadImgLinkDTO param, Long serverId) {
+        String url = getUrl(serverId) + "/CdnUploadImgLink";
+        return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<WxCdnUploadImgLinkResp>>() {});
+    }
+
 }

+ 1 - 1
fs-service-system/src/main/resources/application-config-dev.yml

@@ -84,7 +84,7 @@ wx:
         msgDataFormat: JSON
 
   pay:
-    appId: wx11a2ce7c2bbc4521 #微信公众号或者小程序等的appid
+    appId: wx93ce67750e3cfba3 #微信公众号或者小程序等的appid
     mchId: 1703311381 #微信支付商户号
     mchKey: FotTIbIzn4AisMW7de712LJQIazSqqAl #微信支付商户密钥
     v3Key: y5Eo99q93qzdQRAs6E2BDKIF7f3EnS3G

+ 1 - 1
fs-service-system/src/main/resources/application-config-zkzh.yml

@@ -92,7 +92,7 @@ wx:
       port: 6379
       timeout: 2000
     configs:
-      - appId: wx961fadab9bcb792b # 第一个公众号的appid  //公众号名称:云联
+      - appId: wx93ce67750e3cfba3 # 第一个公众号的appid  //公众号名称:云联
         secret: eddde2a1d4ca0c6c443a67e542b6864c
         token: PPKOdAlCoMO # 接口配置里的Token值
         aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值

+ 17 - 14
fs-service-system/src/main/resources/mapper/course/FsCourseFinishTempMapper.xml

@@ -1,7 +1,7 @@
 <?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">
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.course.mapper.FsCourseFinishTempMapper">
 
     <resultMap type="FsCourseFinishTemp" id="FsCourseFinishTempResult">
@@ -17,15 +17,13 @@
         <result property="companyUserIds"    column="company_user_ids"    />
         <result property="updateTime"    column="update_time"    />
         <result property="isDel"    column="is_del"    />
-        <result property="chatSetting"    column="chat_setting"    />
-        <result property="isAllCompanyUser"    column="is_all_company_user"    />
     </resultMap>
 
     <sql id="selectFsCourseFinishTempVo">
-        select * from fs_course_finish_temp
+        select id, name, status, setting, company_id, create_by, create_time, course_id, video_id, company_user_ids, update_time, is_del from fs_course_finish_temp
     </sql>
 
-    <select id="selectFsCourseFinishTempList" parameterType="FsCourseFinishTemp" resultType="FsCourseFinishTemp">
+    <select id="selectFsCourseFinishTempList" parameterType="FsCourseFinishTemp" resultMap="FsCourseFinishTempResult">
         <include refid="selectFsCourseFinishTempVo"/>
         <where>
             <if test="name != null  and name != ''"> and name like concat('%', #{name}, '%')</if>
@@ -36,11 +34,10 @@
             <if test="videoId != null "> and video_id = #{videoId}</if>
             <if test="companyUserIds != null  and companyUserIds != ''"> and company_user_ids = #{companyUserIds}</if>
             <if test="isDel != null "> and is_del = #{isDel}</if>
-            <if test="isAllCompanyUser != null "> and is_all_company_user = #{isAllCompanyUser}</if>
         </where>
     </select>
 
-    <select id="selectFsCourseFinishTempById" parameterType="Long" resultType="FsCourseFinishTemp">
+    <select id="selectFsCourseFinishTempById" parameterType="Long" resultMap="FsCourseFinishTempResult">
         <include refid="selectFsCourseFinishTempVo"/>
         where id = #{id}
     </select>
@@ -59,9 +56,10 @@
             <if test="companyUserIds != null">company_user_ids,</if>
             <if test="updateTime != null">update_time,</if>
             <if test="isDel != null">is_del,</if>
-            <if test="isAllCompanyUser != null">is_all_company_user,</if>
             <if test="chatSetting != null">chat_setting,</if>
-        </trim>
+            <if test="parentId != null">parent_id,</if>
+            <if test="isAllCompanyUser != null">is_all_company_user,</if>
+         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="name != null">#{name},</if>
             <if test="status != null">#{status},</if>
@@ -74,9 +72,10 @@
             <if test="companyUserIds != null">#{companyUserIds},</if>
             <if test="updateTime != null">#{updateTime},</if>
             <if test="isDel != null">#{isDel},</if>
-            <if test="isAllCompanyUser != null">#{isAllCompanyUser},</if>
             <if test="chatSetting != null">#{chatSetting},</if>
-        </trim>
+            <if test="parentId != null">#{parentId},</if>
+            <if test="isAllCompanyUser != null">#{isAllCompanyUser},</if>
+         </trim>
     </insert>
 
     <update id="updateFsCourseFinishTemp" parameterType="FsCourseFinishTemp">
@@ -93,8 +92,6 @@
             <if test="companyUserIds != null">company_user_ids = #{companyUserIds},</if>
             <if test="updateTime != null">update_time = #{updateTime},</if>
             <if test="isDel != null">is_del = #{isDel},</if>
-            <if test="isAllCompanyUser != null">is_all_company_user = #{isAllCompanyUser},</if>
-            <if test="chatSetting != null">chat_setting = #{chatSetting},</if>
         </trim>
         where id = #{id}
     </update>
@@ -109,4 +106,10 @@
             #{id}
         </foreach>
     </delete>
+    <delete id="deleteByParentIds">
+        update fs_course_finish_temp set is_del = 1 where parent_id in
+        <foreach item="id" collection="ids" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
 </mapper>

+ 83 - 0
fs-service-system/src/main/resources/mapper/course/FsCourseFinishTempParentMapper.xml

@@ -0,0 +1,83 @@
+<?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.FsCourseFinishTempParentMapper">
+    
+    <resultMap type="FsCourseFinishTempParent" id="FsCourseFinishTempParentResult">
+        <result property="id"    column="id"    />
+        <result property="name"    column="name"    />
+        <result property="courseId"    column="course_id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="updateBy"    column="update_by"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="remark"    column="remark"    />
+    </resultMap>
+
+    <sql id="selectFsCourseFinishTempParentVo">
+        select * from fs_course_finish_temp_parent
+    </sql>
+
+    <select id="selectFsCourseFinishTempParentList" parameterType="FsCourseFinishTempParent" resultMap="FsCourseFinishTempParentResult">
+        <include refid="selectFsCourseFinishTempParentVo"/>
+        <where>  
+            <if test="name != null  and name != ''"> and name like concat('%', #{name}, '%')</if>
+            <if test="courseId != null "> and course_id = #{courseId}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+        </where>
+    </select>
+    
+    <select id="selectFsCourseFinishTempParentById" parameterType="Long" resultMap="FsCourseFinishTempParentResult">
+        <include refid="selectFsCourseFinishTempParentVo"/>
+        where id = #{id}
+    </select>
+        
+    <insert id="insertFsCourseFinishTempParent" parameterType="FsCourseFinishTempParent" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_course_finish_temp_parent
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="name != null">name,</if>
+            <if test="courseId != null">course_id,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remark != null">remark,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="name != null">#{name},</if>
+            <if test="courseId != null">#{courseId},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remark != null">#{remark},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsCourseFinishTempParent" parameterType="FsCourseFinishTempParent">
+        update fs_course_finish_temp_parent
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="name != null">name = #{name},</if>
+            <if test="courseId != null">course_id = #{courseId},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null">remark = #{remark},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsCourseFinishTempParentById" parameterType="Long">
+        delete from fs_course_finish_temp_parent where id = #{id}
+    </delete>
+
+    <delete id="deleteFsCourseFinishTempParentByIds" parameterType="String">
+        delete from fs_course_finish_temp_parent where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>