Ver Fonte

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

dongdong.xiang há 1 semana atrás
pai
commit
fa6df0b70e
100 ficheiros alterados com 2448 adições e 362 exclusões
  1. 1 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyUserController.java
  2. 97 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseFinishTempParentController.java
  3. 2 4
      fs-admin/src/main/java/com/fs/course/controller/FsCourseQuestionBankController.java
  4. 93 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java
  5. 9 2
      fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java
  6. 1 1
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseTrainingCampController.java
  7. 1 2
      fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java
  8. 1 1
      fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java
  9. 2 2
      fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java
  10. 86 0
      fs-admin/src/main/java/com/fs/stats/SalesWatchStatisController.java
  11. 42 2
      fs-admin/src/main/java/com/fs/store/controller/FsStoreOrderController.java
  12. 1 1
      fs-admin/src/main/java/com/fs/store/controller/FsUserController.java
  13. 44 2
      fs-admin/src/main/java/com/fs/task/StoreTask.java
  14. 12 0
      fs-admin/src/main/java/com/fs/web/controller/system/SysDictTypeController.java
  15. 2 0
      fs-admin/src/main/resources/application.yml
  16. 93 0
      fs-admin/src/main/resources/logback.xml
  17. 34 26
      fs-admin/src/test/java/com/fs/task/StoreTaskTest.java
  18. 0 1
      fs-common/pom.xml
  19. 106 106
      fs-common/src/main/java/com/fs/common/config/FSSysConfig.java
  20. 2 2
      fs-common/src/main/java/com/fs/common/enums/BizResponseEnum.java
  21. 0 16
      fs-company-app/src/main/java/com/fs/app/controller/FsUserController.java
  22. 17 5
      fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  23. 14 2
      fs-company-app/src/main/java/com/fs/app/controller/QwWorkTaskController.java
  24. 4 2
      fs-company-app/src/main/resources/application.yml
  25. 93 0
      fs-company-app/src/main/resources/logback.xml
  26. 6 6
      fs-company/src/main/java/com/fs/company/controller/CompanyRoleController.java
  27. 104 0
      fs-company/src/main/java/com/fs/company/controller/CompanyWxDialogController.java
  28. 120 0
      fs-company/src/main/java/com/fs/company/controller/CompanyWxUserGroupController.java
  29. 1 1
      fs-company/src/main/java/com/fs/course/controller/FsCourseFinishTempController.java
  30. 105 0
      fs-company/src/main/java/com/fs/course/controller/FsCourseFinishTempParentController.java
  31. 1 1
      fs-company/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java
  32. 115 0
      fs-company/src/main/java/com/fs/crm/controller/CrmCustomerAssistController.java
  33. 36 0
      fs-company/src/main/java/com/fs/crm/controller/CrmCustomerController.java
  34. 103 0
      fs-company/src/main/java/com/fs/qw/QwMsgController.java
  35. 1 0
      fs-company/src/main/java/com/fs/qw/QwQwWorkTaskController.java
  36. 2 2
      fs-company/src/main/java/com/fs/qw/QwSopTempController.java
  37. 6 5
      fs-company/src/main/java/com/fs/qw/QwUserController.java
  38. 34 6
      fs-company/src/main/java/com/fs/qw/qw/QwWorkTaskController.java
  39. 0 18
      fs-company/src/main/java/com/fs/qw/vo/QwContactListVO.java
  40. 0 34
      fs-company/src/main/java/com/fs/qw/vo/QwMessageListVO.java
  41. 1 6
      fs-company/src/main/java/com/fs/users/controller/FsUserController.java
  42. 2 0
      fs-company/src/main/resources/application.yml
  43. 93 0
      fs-company/src/main/resources/logback.xml
  44. 10 2
      fs-qw-api-msg/pom.xml
  45. 133 9
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  46. 1 17
      fs-qw-api-msg/src/main/java/com/fs/app/controller/testTask.java
  47. 101 0
      fs-qw-api-msg/src/main/java/com/fs/app/socket/QwImSocket.java
  48. 22 0
      fs-qw-api-msg/src/main/java/com/fs/app/socket/configurator/QwImConfigurator.java
  49. 160 0
      fs-qw-api-msg/src/main/java/com/fs/app/util/AudioUtils.java
  50. 3 3
      fs-qw-api-msg/src/main/java/com/fs/core/aspectj/DataScopeAspect.java
  51. 2 2
      fs-qw-api-msg/src/main/java/com/fs/core/aspectj/DataSourceAspect.java
  52. 5 5
      fs-qw-api-msg/src/main/java/com/fs/core/aspectj/LogAspect.java
  53. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/aspectj/RateLimiterAspect.java
  54. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/ApplicationConfig.java
  55. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/ArrayStringTypeHandler.java
  56. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/CaptchaConfig.java
  57. 2 2
      fs-qw-api-msg/src/main/java/com/fs/core/config/DataSourceConfig.java
  58. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/FastJson2JsonRedisSerializer.java
  59. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/FilterConfig.java
  60. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/KaptchaTextCreator.java
  61. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/MyBatisConfig.java
  62. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/RedisConfig.java
  63. 2 2
      fs-qw-api-msg/src/main/java/com/fs/core/config/ResourcesConfig.java
  64. 6 5
      fs-qw-api-msg/src/main/java/com/fs/core/config/SecurityConfig.java
  65. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/ServerConfig.java
  66. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/SwaggerConfig.java
  67. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/ThreadPoolConfig.java
  68. 17 0
      fs-qw-api-msg/src/main/java/com/fs/core/config/WebSocketConfig.java
  69. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/properties/DruidProperties.java
  70. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/datasource/DynamicDataSource.java
  71. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/datasource/DynamicDataSourceContextHolder.java
  72. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/exception/GlobalExceptionHandler.java
  73. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/interceptor/RepeatSubmitInterceptor.java
  74. 2 2
      fs-qw-api-msg/src/main/java/com/fs/core/interceptor/impl/SameUrlDataInterceptor.java
  75. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/manager/AsyncManager.java
  76. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/manager/ShutdownManager.java
  77. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/manager/factory/AsyncFactory.java
  78. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/security/LoginBody.java
  79. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/security/LoginUser.java
  80. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/security/SecurityUtils.java
  81. 4 4
      fs-qw-api-msg/src/main/java/com/fs/core/security/filter/JwtAuthenticationTokenFilter.java
  82. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/security/handle/AuthenticationEntryPointImpl.java
  83. 5 5
      fs-qw-api-msg/src/main/java/com/fs/core/security/handle/LogoutSuccessHandlerImpl.java
  84. 4 4
      fs-qw-api-msg/src/main/java/com/fs/core/service/CompanyLoginService.java
  85. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/service/CompanyPermissionService.java
  86. 2 2
      fs-qw-api-msg/src/main/java/com/fs/core/service/PermissionService.java
  87. 2 2
      fs-qw-api-msg/src/main/java/com/fs/core/service/TokenService.java
  88. 2 2
      fs-qw-api-msg/src/main/java/com/fs/core/service/UserDetailsServiceImpl.java
  89. BIN
      fs-qw-api-msg/src/main/resources/jniLibs/WeWorkFinanceSdk.dll
  90. BIN
      fs-qw-api-msg/src/main/resources/jniLibs/libcrypto-3-x64.dll
  91. BIN
      fs-qw-api-msg/src/main/resources/jniLibs/libcurl-x64.dll
  92. 1 1
      fs-qw-api-msg/src/main/resources/mybatis/mybatis-config.xml
  93. 17 0
      fs-qw-api/pom.xml
  94. 304 0
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  95. 3 3
      fs-qw-api/src/main/java/com/fs/core/aspectj/DataScopeAspect.java
  96. 2 2
      fs-qw-api/src/main/java/com/fs/core/aspectj/DataSourceAspect.java
  97. 5 5
      fs-qw-api/src/main/java/com/fs/core/aspectj/LogAspect.java
  98. 117 0
      fs-qw-api/src/main/java/com/fs/core/aspectj/RateLimiterAspect.java
  99. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/ApplicationConfig.java
  100. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/ArrayStringTypeHandler.java

+ 1 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyUserController.java

@@ -160,6 +160,7 @@ public class CompanyUserController extends BaseController
      * 更换会员归属销售
      * @return
      */
+    @PreAuthorize("@ss.hasPermi('company:companyUser:change')")
     @Log(title = "更换会员归属", businessType = BusinessType.OTHER)
     @PostMapping("/changeCompanyUser")
     public AjaxResult changeCompanyUser(@RequestBody List<Long> userIds, @RequestParam Long companyUserId, @RequestParam Long companyId)

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

+ 2 - 4
fs-admin/src/main/java/com/fs/course/controller/FsCourseQuestionBankController.java

@@ -3,7 +3,6 @@ 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.ServletUtils;
@@ -13,7 +12,6 @@ import com.fs.core.web.service.TokenService;
 import com.fs.course.domain.FsCourseQuestionBank;
 import com.fs.course.dto.FsCourseQuestionBankImportDTO;
 import com.fs.course.service.IFsCourseQuestionBankService;
-
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -57,8 +55,8 @@ public class FsCourseQuestionBankController extends BaseController
     @GetMapping("/export")
     public AjaxResult export(FsCourseQuestionBank fsCourseQuestionBank)
     {
-        List<FsCourseQuestionBank> list = fsCourseQuestionBankService.selectFsCourseQuestionBankList(fsCourseQuestionBank);
-        ExcelUtil<FsCourseQuestionBank> util = new ExcelUtil<FsCourseQuestionBank>(FsCourseQuestionBank.class);
+        List<FsCourseQuestionBankImportDTO> list = fsCourseQuestionBankService.exportData(fsCourseQuestionBank);
+        ExcelUtil<FsCourseQuestionBankImportDTO> util = new ExcelUtil<>(FsCourseQuestionBankImportDTO.class);
         return util.exportExcel(list, "题库数据");
     }
 

+ 93 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java

@@ -42,6 +42,18 @@ public class FsUserCourseController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 查询公域课程列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourse:publicList')")
+    @GetMapping("/publicList")
+    public TableDataInfo publicList(FsUserCourse fsUserCourse)
+    {
+        startPage();
+        List<FsUserCourseListPVO> list = fsUserCourseService.selectFsUserCourseListPVO(fsUserCourse);
+        return getDataTable(list);
+    }
+
     /**
      * 导出课程列表
      */
@@ -55,6 +67,19 @@ public class FsUserCourseController extends BaseController
         return util.exportExcel(list, "课程数据");
     }
 
+    /**
+     * 导出课程列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourse:publicExport')")
+    @Log(title = "课程", businessType = BusinessType.EXPORT)
+    @GetMapping("/publicExport")
+    public AjaxResult publicExport(FsUserCourse fsUserCourse)
+    {
+        List<FsUserCourse> list = fsUserCourseService.selectFsUserCourseList(fsUserCourse);
+        ExcelUtil<FsUserCourse> util = new ExcelUtil<FsUserCourse>(FsUserCourse.class);
+        return util.exportExcel(list, "课程数据");
+    }
+
     /**
      * 获取课程详细信息
      */
@@ -65,6 +90,16 @@ public class FsUserCourseController extends BaseController
         return AjaxResult.success(fsUserCourseService.selectFsUserCourseByCourseId(courseId));
     }
 
+    /**
+     * 获取公域课程详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourse:publicQuery')")
+    @GetMapping(value = "/public/{courseId}")
+    public AjaxResult publicGetInfo(@PathVariable("courseId") Long courseId)
+    {
+        return AjaxResult.success(fsUserCourseService.selectFsUserCourseByCourseId(courseId));
+    }
+
     /**
      * 新增课程
      */
@@ -76,6 +111,17 @@ public class FsUserCourseController extends BaseController
         return toAjax(fsUserCourseService.insertFsUserCourse(fsUserCourse));
     }
 
+    /**
+     * 新增公域课程
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourse:publicAdd')")
+    @Log(title = "课程", businessType = BusinessType.INSERT)
+    @PostMapping("/public")
+    public AjaxResult publicAdd(@RequestBody FsUserCourse fsUserCourse)
+    {
+        return toAjax(fsUserCourseService.insertFsUserCourse(fsUserCourse));
+    }
+
     /**
      * 修改课程
      */
@@ -87,6 +133,17 @@ public class FsUserCourseController extends BaseController
         return toAjax(fsUserCourseService.updateFsUserCourse(fsUserCourse));
     }
 
+    /**
+     * 修改公域课程
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourse:publicEdit')")
+    @Log(title = "课程", businessType = BusinessType.UPDATE)
+    @PutMapping("/public")
+    public AjaxResult publicEdit(@RequestBody FsUserCourse fsUserCourse)
+    {
+        return toAjax(fsUserCourseService.updateFsUserCourse(fsUserCourse));
+    }
+
     /**
      * 删除课程
      */
@@ -98,6 +155,17 @@ public class FsUserCourseController extends BaseController
         return toAjax(fsUserCourseService.deleteFsUserCourseByCourseIds(courseIds));
     }
 
+    /**
+     * 删除公域课程
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourse:publicRemove')")
+    @Log(title = "课程", businessType = BusinessType.DELETE)
+    @DeleteMapping("/public/{courseIds}")
+    public AjaxResult publicRemove(@PathVariable Long[] courseIds)
+    {
+        return toAjax(fsUserCourseService.deleteFsUserCourseByCourseIds(courseIds));
+    }
+
 
     @GetMapping("/getAllList")
     public R getAllList()
@@ -106,6 +174,7 @@ public class FsUserCourseController extends BaseController
         return R.ok().put("data", list);
     }
 
+    @PreAuthorize("@ss.hasPermi('course:userCourse:updateIsShow')")
     @Log(title = "课程上架", businessType = BusinessType.UPDATE)
     @PostMapping("/updateIsShow")
     public AjaxResult updateIsShow(@RequestBody FsUserCourse fsUserCourse)
@@ -113,6 +182,14 @@ public class FsUserCourseController extends BaseController
        return toAjax(fsUserCourseService.updateFsUserCourse(fsUserCourse));
     }
 
+    @PreAuthorize("@ss.hasPermi('course:userCourse:publicUpdateIsShow')")
+    @Log(title = "课程上架", businessType = BusinessType.UPDATE)
+    @PostMapping("/publicUpdateIsShow")
+    public AjaxResult publicUpdateIsShow(@RequestBody FsUserCourse fsUserCourse)
+    {
+        return toAjax(fsUserCourseService.updateFsUserCourse(fsUserCourse));
+    }
+
     @PreAuthorize("@ss.hasPermi('course:userCourse:putOn')")
     @Log(title = "课程批量上架", businessType = BusinessType.UPDATE)
     @PostMapping("/putOn/{courseIds}")
@@ -121,6 +198,14 @@ public class FsUserCourseController extends BaseController
         return toAjax(fsUserCourseService.updateFsUserCourseIsShow(courseIds,1));
     }
 
+    @PreAuthorize("@ss.hasPermi('course:userCourse:publicPutOn')")
+    @Log(title = "课程批量上架", businessType = BusinessType.UPDATE)
+    @PostMapping("/publicPutOn/{courseIds}")
+    public AjaxResult publicPutOn(@PathVariable Long[] courseIds)
+    {
+        return toAjax(fsUserCourseService.updateFsUserCourseIsShow(courseIds,1));
+    }
+
     @PreAuthorize("@ss.hasPermi('course:userCourse:putOn')")
     @Log(title = "课程批量下架", businessType = BusinessType.UPDATE)
     @PostMapping("/pullOff/{courseIds}")
@@ -128,4 +213,12 @@ public class FsUserCourseController extends BaseController
     {
         return toAjax(fsUserCourseService.updateFsUserCourseIsShow(courseIds,0));
     }
+
+    @PreAuthorize("@ss.hasPermi('course:userCourse:publicPutOff')")
+    @Log(title = "课程批量下架", businessType = BusinessType.UPDATE)
+    @PostMapping("/publicPullOff/{courseIds}")
+    public AjaxResult publicPullOff(@PathVariable Long[] courseIds)
+    {
+        return toAjax(fsUserCourseService.updateFsUserCourseIsShow(courseIds,0));
+    }
 }

+ 9 - 2
fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java

@@ -61,6 +61,7 @@ public class FsUserCoursePeriodController extends BaseController {
         return getDataTable(list);
     }
 
+    @PreAuthorize("@ss.hasPermi('course:period:list')")
     @PostMapping("/page")
     @ApiOperation("自定义查询主列表分页")
     public R pageList(@RequestBody FsUserCoursePeriod fsUserCoursePeriod)
@@ -94,8 +95,7 @@ public class FsUserCoursePeriodController extends BaseController {
      */
     @PreAuthorize("@ss.hasPermi('course:period:query')")
     @GetMapping(value = "/{periodId}")
-    public AjaxResult getInfo(@PathVariable("periodId") Long periodId)
-    {
+    public AjaxResult getInfo(@PathVariable("periodId") Long periodId) {
         return AjaxResult.success(fsUserCoursePeriodService.selectFsUserCoursePeriodById(periodId));
     }
 
@@ -139,10 +139,13 @@ public class FsUserCoursePeriodController extends BaseController {
         return getDataTable(list);
     }
 
+    @PreAuthorize("@ss.hasPermi('course:period:addCourse')")
     @PostMapping("/addCourse")
     public R addCourse(@RequestBody FsUserCoursePeriodDays entity){
         return fsUserCoursePeriodDaysService.addCourse(entity);
     }
+
+    @PreAuthorize("@ss.hasPermi('course:period:updateCourseTime')")
     @PostMapping("/updateCourseTime")
     public R updateCourseTime(@RequestBody UpdateCourseTimeVo vo){
         return fsUserCoursePeriodDaysService.updateCourseTime(vo);
@@ -170,6 +173,7 @@ public class FsUserCoursePeriodController extends BaseController {
         return R.ok().put("data", periodRedPacketList);
     }
 
+    @PreAuthorize("@ss.hasPermi('course:period:setCourseRedPacket')")
     @ApiOperation("按课程批量保存设置红包金额")
     @PostMapping("/batchRedPacket")
     public R batchRedPacketMoney(@RequestBody List<FsUserCourseVideoRedPackage> videoRedPackageList) {
@@ -182,6 +186,7 @@ public class FsUserCoursePeriodController extends BaseController {
         return R.ok();
     }
 
+    @PreAuthorize("@ss.hasPermi('course:period:setRedPacket')")
     @ApiOperation("按营期批量保存设置红包金额")
     @PostMapping("/batchRedPacket/byPeriod")
     public R batchRedPacketByPeriod(@RequestBody List<FsBatchPeriodRedPackageParam> periodRedPackageList) {
@@ -220,12 +225,14 @@ public class FsUserCoursePeriodController extends BaseController {
         return R.ok().put("data", new PageInfo<>(periodList));
     }
 
+    @PreAuthorize("@ss.hasPermi('course:period:courseMove')")
     @ApiOperation("营期课程-上移/下移")
     @PutMapping("/courseMove")
     public R periodCourseMove(Long id, Long targetId) {
         return fsUserCoursePeriodDaysService.periodCourseMove(id, targetId);
     }
 
+    @PreAuthorize("@ss.hasPermi('course:period:close')")
     @ApiOperation("结束营期")
     @PostMapping("/closePeriod")
     public R closePeriod(Long id) {

+ 1 - 1
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseTrainingCampController.java

@@ -99,7 +99,7 @@ public class FsUserCourseTrainingCampController {
      * @param id    参数
      * @return  AjaxResult
      */
-    @PreAuthorize("@ss.hasPermi('course:trainingCamp:add')")
+    @PreAuthorize("@ss.hasPermi('course:trainingCamp:copy')")
     @Log(title = "训练营", businessType = BusinessType.INSERT)
     @PostMapping("/copy/{id}")
     public AjaxResult copy(@PathVariable Long id) {

+ 1 - 2
fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java

@@ -7,7 +7,6 @@ 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.DateUtils;
 import com.fs.course.domain.FsVideoResource;
 import com.fs.course.service.IFsVideoResourceService;
 import com.fs.course.vo.FsVideoResourceVO;
@@ -103,7 +102,7 @@ public class FsVideoResourceController extends BaseController {
         return AjaxResult.success();
     }
 
-    @PreAuthorize("@ss.hasPermi('course:videoResource:add')")
+    @PreAuthorize("@ss.hasPermi('course:videoResource:batchAdd')")
     @Log(title = "视频素材库", businessType = BusinessType.INSERT)
     @PostMapping("/batchAddVideoResource")
     public AjaxResult batchAddVideoResource(@RequestBody List<FsVideoResource> list) {

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

@@ -70,7 +70,7 @@ public class QwFsCourseWatchLogController extends BaseController
         return getDataTable(list);
     }
 
-    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:statisticsList')")
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:qw:statisticsList')")
     @GetMapping("/statisticsList")
     public TableDataInfo statisticsList(FsCourseWatchLogStatisticsListParam param)
     {

+ 2 - 2
fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java

@@ -128,8 +128,8 @@ public class QwSopTempController extends BaseController
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         qwSopTemp.setCreateBy(loginUser.getUser().getUserId().toString());
         int i = qwSopTempService.addNew(qwSopTemp);
-        if(qwSopTemp.getSendType() == 5){
-            qwSopTempService.createSopTempRules(qwSopTemp);
+        if(qwSopTemp.getSendType() == 11){
+            new Thread(() -> qwSopTempService.createSopTempRules(qwSopTemp)).start();
         }
         return toAjax(i);
     }

+ 86 - 0
fs-admin/src/main/java/com/fs/stats/SalesWatchStatisController.java

@@ -0,0 +1,86 @@
+package com.fs.stats;
+
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.company.service.ICompanyService;
+import com.fs.company.vo.CompanyDataVO;
+import com.fs.sop.service.IQwSopService;
+import com.fs.sop.vo.QwSopTask;
+import com.fs.statis.domain.FsStatisPeriodWatch;
+import com.fs.statis.domain.FsStatisSalerWatch;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+import com.fs.statis.service.FsStatisEveryDayWatchService;
+import com.fs.statis.service.FsStatisPeriodWatchService;
+import com.fs.statis.service.FsStatisSalerWatchService;
+import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 看课统计接口
+ */
+@RestController
+@RequestMapping("/stats")
+@AllArgsConstructor
+public class SalesWatchStatisController {
+
+    @Autowired
+    private FsStatisSalerWatchService fsStatisSalerWatchService;
+
+    @Autowired
+    private FsStatisPeriodWatchService fsStatisPeriodWatchService;
+
+    @Autowired
+    private FsStatisEveryDayWatchService fsStatisEveryDayWatchService;
+
+    @Autowired
+    private IQwSopService qwSopService;
+
+    /**
+     * 销售完播统计查询
+     * @param param param
+     * @return R
+     */
+    @PostMapping("/seller/pageList")
+    public R sellerQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        List<FsStatisSalerWatch> list = fsStatisSalerWatchService.queryList(param);
+        return R.ok().put("data",list);
+    }
+
+    /**
+     * 训练营完播统计查询
+     * @param param param
+     * @return R
+     */
+    @PostMapping("/period/pageList")
+    public R periodQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        List<FsStatisPeriodWatch> list = fsStatisPeriodWatchService.queryList(param);
+        return R.ok().put("data", list);
+    }
+
+    /**
+     * 每日完播统计查询
+     * @param param param
+     * @return R
+     */
+    @PostMapping("/everyDay/pageList")
+    public R everyDayQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        List<FsStatisSalerWatch> list = fsStatisEveryDayWatchService.queryList(param);
+        return R.ok().put("data", list);
+    }
+
+    /**
+     * 获取SOP任务数据
+     * @return
+     */
+    @GetMapping("/sopTaskData")
+    public R getSOPTaskData(){
+        List<QwSopTask> qwSopTaskList = qwSopService.getQwSopTaskList();
+
+        return R.ok().put("data",qwSopTaskList);
+    }
+
+}

+ 42 - 2
fs-admin/src/main/java/com/fs/store/controller/FsStoreOrderController.java

@@ -24,6 +24,8 @@ import com.fs.erp.domain.ErpOrderQuery;
 import com.fs.erp.dto.ErpOrderQueryRequert;
 import com.fs.erp.dto.ErpOrderQueryResponse;
 import com.fs.erp.service.IErpOrderService;
+import com.fs.his.config.FsErpConfig;
+import com.fs.his.utils.ConfigUtil;
 import com.fs.store.domain.*;
 import com.fs.store.dto.ExpressInfoDTO;
 import com.fs.store.dto.FsStoreCartDTO;
@@ -34,6 +36,7 @@ import com.fs.store.param.*;
 import com.fs.store.service.*;
 import com.fs.store.vo.*;
 import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
@@ -72,13 +75,48 @@ public class FsStoreOrderController extends BaseController {
     @Autowired
     private IFsStoreOrderStatusService orderStatusService;
 
-    @Autowired
-    IErpOrderService erpOrderService;
     @Autowired
     IFsStorePaymentService paymentService;
 
     @Autowired
     private ICompanyMoneyLogsService moneyLogsService;
+    @Autowired
+    @Qualifier("erpOrderServiceImpl")
+    private IErpOrderService gyOrderService;
+
+    @Autowired
+    @Qualifier("wdtErpOrderServiceImpl")
+    private IErpOrderService wdtOrderService;
+
+    @Autowired
+    @Qualifier("k9OrderServiceImpl")
+    private IErpOrderService k9OrderService;
+    @Autowired
+    private ConfigUtil configUtil;
+
+    private IErpOrderService getErpService(){
+        //判断是否开启erp
+        IErpOrderService erpOrderService = null;
+        FsErpConfig erpConfig = configUtil.getErpConfig();
+        Integer erpOpen = erpConfig.getErpOpen();
+        if (erpOpen != null && erpOpen == 1) {
+            //判断erp类型
+            Integer erpType = erpConfig.getErpType();
+            if (erpType != null) {
+                if (erpType == 1) {
+                    //管易
+                    erpOrderService = gyOrderService;
+                } else if (erpType == 2) {
+                    //旺店通
+                    erpOrderService = wdtOrderService;
+                } else if (erpType == 3) {
+                    //旺店通
+                    erpOrderService = k9OrderService;
+                }
+            }
+        }
+        return erpOrderService;
+    }
 
     /**
      * 查询订单列表
@@ -391,6 +429,7 @@ public class FsStoreOrderController extends BaseController {
     @PreAuthorize("@ss.hasPermi('store:storeOrder:updateErpOrder')")
     @PostMapping("/updateErpOrder")
     public R updateErpOrder(@Validated @RequestBody FsStoreOrderExpressEditParam param) {
+        IErpOrderService erpOrderService = getErpService();
         FsStoreOrder order = fsStoreOrderService.selectFsStoreOrderById(param.getOrderId());
         ErpOrderQueryRequert request = new ErpOrderQueryRequert();
         request.setCode(order.getExtendOrderId());
@@ -482,6 +521,7 @@ public class FsStoreOrderController extends BaseController {
     @PreAuthorize("@ss.hasPermi('store:storeOrder:getEroOrder')")
     @GetMapping("/getEroOrder")
     public R getEroOrder(@RequestParam("extendOrderId") String extendOrderId) {
+        IErpOrderService erpOrderService = getErpService();
         ErpOrderQueryRequert request = new ErpOrderQueryRequert();
         request.setCode(extendOrderId);
         ErpOrderQueryResponse response = erpOrderService.getOrder(request);

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

+ 44 - 2
fs-admin/src/main/java/com/fs/task/StoreTask.java

@@ -15,6 +15,8 @@ import com.fs.erp.dto.ErpOrderQueryRequert;
 import com.fs.erp.dto.ErpOrderQueryResponse;
 import com.fs.erp.service.IErpGoodsService;
 import com.fs.erp.service.IErpOrderService;
+import com.fs.his.config.FsErpConfig;
+import com.fs.his.utils.ConfigUtil;
 import com.fs.pay.pay.domain.OrderResult;
 import com.fs.pay.pay.dto.OrderQueryDTO;
 import com.fs.pay.pay.service.PayService;
@@ -32,6 +34,7 @@ import com.fs.store.service.*;
 import com.fs.system.service.ISysConfigService;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Component;
 
@@ -64,8 +67,6 @@ public class StoreTask
     @Autowired
     private IFsUserService userService;
 
-    @Autowired
-    private IErpOrderService erpOrderService;
 
     @Autowired
     private PayService ybPayService;
@@ -105,6 +106,21 @@ public class StoreTask
     @Autowired
     private IFsStoreAfterSalesService fsStoreAfterSalesService;
 
+    @Autowired
+    @Qualifier("erpOrderServiceImpl")
+    private IErpOrderService gyOrderService;
+
+    @Autowired
+    @Qualifier("wdtErpOrderServiceImpl")
+    private IErpOrderService wdtOrderService;
+
+    @Autowired
+    @Qualifier("k9OrderServiceImpl")
+    private IErpOrderService k9OrderService;
+
+    @Autowired
+    private ConfigUtil configUtil;
+
     public void PushErp() throws ParseException {
         List<Long> ids = fsStoreOrderMapper.selectFsStoreOrderNoCreateOms();
         for (Long id : ids) {
@@ -118,6 +134,7 @@ public class StoreTask
     //每5分钟执行一次
     public void deliveryOp()
     {
+        IErpOrderService erpOrderService = getErpOrderService();
         Set<String> orders=redisTemplate.keys(DELIVERY+":*");
         for(String extndOrderId:orders){
             String orderCode=(String)redisTemplate.opsForValue().get(extndOrderId);
@@ -230,6 +247,7 @@ public class StoreTask
     }
 
     public void returnDeliveryId(){
+        IErpOrderService erpOrderService = getErpOrderService();
         List<String> list = fsStoreOrderMapper.selectErpCode();
         for (String s : list) {
             ErpOrderQueryRequert request = new ErpOrderQueryRequert();
@@ -373,6 +391,30 @@ public class StoreTask
 
     }
 
+    private IErpOrderService getErpOrderService(){
+        //判断是否开启erp
+        IErpOrderService erpOrderService = null;
+        FsErpConfig erpConfig = configUtil.getErpConfig();
+        Integer erpOpen = erpConfig.getErpOpen();
+        if (erpOpen != null && erpOpen == 1) {
+            //判断erp类型
+            Integer erpType = erpConfig.getErpType();
+            if (erpType != null) {
+                if (erpType == 1) {
+                    //管易
+                    erpOrderService = gyOrderService;
+                } else if (erpType == 2) {
+                    //旺店通
+                    erpOrderService = wdtOrderService;
+                } else if (erpType == 3) {
+                    //旺店通
+                    erpOrderService = k9OrderService;
+                }
+            }
+        }
+        return erpOrderService;
+    }
+
 
 
 

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

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

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

+ 93 - 0
fs-admin/src/main/resources/logback.xml

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 日志存放路径 -->
+	<property name="log.path" value="/home/fs-admin/logs" />
+    <!-- 日志输出格式 -->
+	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+
+	<!-- 控制台输出 -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- 系统日志输出 -->
+	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 30 -->
+			<maxHistory>30</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+	</appender>
+
+	<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 30 -->
+			<maxHistory>30</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+			<!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+			<!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+	<!-- 用户访问日志输出  -->
+    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/sys-user.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 按天回滚 daily -->
+            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 30 -->
+            <maxHistory>30</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+
+	<!-- 系统模块日志级别控制  -->
+	<logger name="com.fs" level="info" />
+	<!-- Spring日志级别控制  -->
+	<logger name="org.springframework" level="warn" />
+
+	<root level="info">
+		<appender-ref ref="console" />
+	</root>
+
+	<!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+    </root>
+
+	<!--系统用户操作日志-->
+    <logger name="sys-user" level="info">
+        <appender-ref ref="sys-user"/>
+    </logger>
+</configuration>

+ 34 - 26
fs-admin/src/test/java/com/fs/task/StoreTaskTest.java

@@ -1,26 +1,34 @@
-//package com.fs.task;
-//
-//
-//import com.fs.FSAdminApplication;
-//import com.fs.course.service.IFsCourseWatchLogService;
-//import org.junit.Test;
-//import org.junit.runner.RunWith;
-//import org.springframework.beans.factory.annotation.Autowired;
-//import org.springframework.boot.test.context.SpringBootTest;
-//
-//@RunWith(value = org.springframework.test.context.junit4.SpringRunner.class)
-//@SpringBootTest(classes = FSAdminApplication.class)
-//public class StoreTaskTest {
-//    @Autowired
-//    private IFsCourseWatchLogService fsCourseWatchLogService;
-//    @Autowired
-//    private FsCourseTask fsCourseTask;
-//    @Test
-//    public void addQwWatchLog() {
-//        fsCourseWatchLogService.addCourseWatchLogDayNew();
-//    }
-//    @Test
-//    public void test() throws Exception {
-//        fsCourseTask.hyWorkTask();
-//    }
-//}
+package com.fs.task;
+
+
+import com.fs.FSAdminApplication;
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.statis.service.FsStatisSalerWatchService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@RunWith(value = org.springframework.test.context.junit4.SpringRunner.class)
+@SpringBootTest(classes = FSAdminApplication.class)
+public class StoreTaskTest {
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+    @Autowired
+    private FsCourseTask fsCourseTask;
+    @Autowired
+    private FsStatisSalerWatchService fsStatisSalerWatchService;
+    @Test
+    public void addQwWatchLog() {
+        fsCourseWatchLogService.addCourseWatchLogDayNew();
+    }
+    @Test
+    public void test() throws Exception {
+        fsCourseTask.hyWorkTask();
+    }
+
+    @Test
+    public void testWriteData(){
+        fsStatisSalerWatchService.writeData();
+    }
+}

+ 0 - 1
fs-common/pom.xml

@@ -51,7 +51,6 @@
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
-            <version>2.10.5.1</version>
         </dependency>
 
         <!-- 阿里JSON解析器 -->

+ 106 - 106
fs-common/src/main/java/com/fs/common/config/FSSysConfig.java

@@ -28,12 +28,12 @@ public class FSSysConfig
     String manuId;
     String callbackUrl;
     //erp接口
-    Integer erpOpen;//是否开启ERP
-    String erpAppKey;
-    String erpSessionKey;
-    String erpSecret;
-    String erpUrl;
-    String erpShopCode;//店铺CODE
+//    Integer erpOpen;//是否开启ERP
+//    String erpAppKey;
+//    String erpSessionKey;
+//    String erpSecret;
+//    String erpUrl;
+//    String erpShopCode;//店铺CODE
     //支付接口
     Integer payOpen;//是否开启
     String payPartnerId;
@@ -42,12 +42,12 @@ public class FSSysConfig
     String payNotifyUrl;
     String refundNotifyUrl;
     //金博erp
-    private String kingbosan;//账套名称
-    private String kingbosSecret;//金博密钥
-    private String kingbosUrl;//金博地址
-    private String corgid;//机构编码
-    private String cwarehouseCode;
-    private String cwarehouseName;
+//    private String kingbosan;//账套名称
+//    private String kingbosSecret;//金博密钥
+//    private String kingbosUrl;//金博地址
+//    private String corgid;//机构编码
+//    private String cwarehouseCode;
+//    private String cwarehouseName;
 
     public String getKdnId() {
         return kdnId;
@@ -145,53 +145,53 @@ public class FSSysConfig
         this.callbackUrl = callbackUrl;
     }
 
-    public Integer getErpOpen() {
-        return erpOpen;
-    }
-
-    public void setErpOpen(Integer erpOpen) {
-        this.erpOpen = erpOpen;
-    }
-
-    public String getErpAppKey() {
-        return erpAppKey;
-    }
-
-    public void setErpAppKey(String erpAppKey) {
-        this.erpAppKey = erpAppKey;
-    }
-
-    public String getErpSessionKey() {
-        return erpSessionKey;
-    }
-
-    public void setErpSessionKey(String erpSessionKey) {
-        this.erpSessionKey = erpSessionKey;
-    }
-
-    public String getErpSecret() {
-        return erpSecret;
-    }
-
-    public void setErpSecret(String erpSecret) {
-        this.erpSecret = erpSecret;
-    }
-
-    public String getErpUrl() {
-        return erpUrl;
-    }
-
-    public void setErpUrl(String erpUrl) {
-        this.erpUrl = erpUrl;
-    }
-
-    public String getErpShopCode() {
-        return erpShopCode;
-    }
-
-    public void setErpShopCode(String erpShopCode) {
-        this.erpShopCode = erpShopCode;
-    }
+//    public Integer getErpOpen() {
+//        return erpOpen;
+//    }
+//
+//    public void setErpOpen(Integer erpOpen) {
+//        this.erpOpen = erpOpen;
+//    }
+//
+//    public String getErpAppKey() {
+//        return erpAppKey;
+//    }
+//
+//    public void setErpAppKey(String erpAppKey) {
+//        this.erpAppKey = erpAppKey;
+//    }
+//
+//    public String getErpSessionKey() {
+//        return erpSessionKey;
+//    }
+//
+//    public void setErpSessionKey(String erpSessionKey) {
+//        this.erpSessionKey = erpSessionKey;
+//    }
+//
+//    public String getErpSecret() {
+//        return erpSecret;
+//    }
+//
+//    public void setErpSecret(String erpSecret) {
+//        this.erpSecret = erpSecret;
+//    }
+//
+//    public String getErpUrl() {
+//        return erpUrl;
+//    }
+//
+//    public void setErpUrl(String erpUrl) {
+//        this.erpUrl = erpUrl;
+//    }
+//
+//    public String getErpShopCode() {
+//        return erpShopCode;
+//    }
+//
+//    public void setErpShopCode(String erpShopCode) {
+//        this.erpShopCode = erpShopCode;
+//    }
 
     public Integer getPayOpen() {
         return payOpen;
@@ -241,51 +241,51 @@ public class FSSysConfig
         this.refundNotifyUrl = refundNotifyUrl;
     }
 
-    public String getKingbosan() {
-        return kingbosan;
-    }
-
-    public void setKingbosan(String kingbosan) {
-        this.kingbosan = kingbosan;
-    }
-
-    public String getCorgid() {
-        return corgid;
-    }
-
-    public void setCorgid(String corgid) {
-        this.corgid = corgid;
-    }
-
-    public String getKingbosUrl() {
-        return kingbosUrl;
-    }
-
-    public void setKingbosUrl(String kingbosUrl) {
-        this.kingbosUrl = kingbosUrl;
-    }
-
-    public String getKingbosSecret() {
-        return kingbosSecret;
-    }
-
-    public void setKingbosSecret(String kingbosSecret) {
-        this.kingbosSecret = kingbosSecret;
-    }
-
-    public String getCwarehouseCode() {
-        return cwarehouseCode;
-    }
-
-    public void setCwarehouseCode(String cwarehouseCode) {
-        this.cwarehouseCode = cwarehouseCode;
-    }
-
-    public String getCwarehouseName() {
-        return cwarehouseName;
-    }
-
-    public void setCwarehouseName(String cwarehouseName) {
-        this.cwarehouseName = cwarehouseName;
-    }
+//    public String getKingbosan() {
+//        return kingbosan;
+//    }
+//
+//    public void setKingbosan(String kingbosan) {
+//        this.kingbosan = kingbosan;
+//    }
+//
+//    public String getCorgid() {
+//        return corgid;
+//    }
+//
+//    public void setCorgid(String corgid) {
+//        this.corgid = corgid;
+//    }
+//
+//    public String getKingbosUrl() {
+//        return kingbosUrl;
+//    }
+//
+//    public void setKingbosUrl(String kingbosUrl) {
+//        this.kingbosUrl = kingbosUrl;
+//    }
+//
+//    public String getKingbosSecret() {
+//        return kingbosSecret;
+//    }
+//
+//    public void setKingbosSecret(String kingbosSecret) {
+//        this.kingbosSecret = kingbosSecret;
+//    }
+//
+//    public String getCwarehouseCode() {
+//        return cwarehouseCode;
+//    }
+//
+//    public void setCwarehouseCode(String cwarehouseCode) {
+//        this.cwarehouseCode = cwarehouseCode;
+//    }
+//
+//    public String getCwarehouseName() {
+//        return cwarehouseName;
+//    }
+//
+//    public void setCwarehouseName(String cwarehouseName) {
+//        this.cwarehouseName = cwarehouseName;
+//    }
 }

+ 2 - 2
fs-common/src/main/java/com/fs/common/enums/BizResponseEnum.java

@@ -7,8 +7,8 @@ public enum BizResponseEnum {
     SUCCESS(200, "操作成功"),
     FAIL(500, "操作失败"),
     PARAM_ERROR(400, "参数错误"),
-    DATA_NOT_EXIST(1002, "数据不存在");
-
+    DATA_NOT_EXIST(1002, "数据不存在"),
+    WAIT_APPROVAL(505, "等待审核");
     private final Integer code;
     private final String msg;
 

+ 0 - 16
fs-company-app/src/main/java/com/fs/app/controller/FsUserController.java

@@ -348,20 +348,4 @@ public class FsUserController extends AppBaseController {
         userCourseCountService.insertFsUserCourseCountTask();
     }
 
-//    public static void main(String[] args) throws IOException, WriterException {
-//
-//    }
-
-    @GetMapping("/qrcode")
-    public byte[] test() throws IOException, WriterException{
-        QRCodeWriter qrCodeWriter = new QRCodeWriter();
-        BitMatrix bitMatrix = qrCodeWriter.encode("chenys_only", BarcodeFormat.QR_CODE, 200, 200);
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        MatrixToImageWriter.writeToStream(bitMatrix, "PNG", outputStream);
-        System.out.println(Arrays.toString(outputStream.toByteArray()));
-        return outputStream.toByteArray();
-    }
-
-
-
 }

+ 17 - 5
fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -5,11 +5,13 @@ import com.fs.app.config.ImageStorageConfig;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
 import com.fs.common.utils.StringUtils;
+import com.fs.course.domain.FsUserCoursePeriod;
 import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.param.newfs.FsCourseSortLinkParam;
 import com.fs.course.param.newfs.FsUserCourseListParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
 import com.fs.course.service.IFsCourseLinkService;
+import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.FsUserCourseParticipationRecordVO;
@@ -25,8 +27,6 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
-
-import java.io.File;
 import java.io.InputStream;
 import java.util.HashMap;
 import java.util.List;
@@ -51,6 +51,9 @@ public class FsUserCourseVideoController extends AppBaseController {
     @Autowired
     private ImageStorageConfig imageConfig;
 
+    @Autowired
+    private IFsUserCoursePeriodService fsUserCoursePeriodService;
+
     @Login
     @GetMapping("/pageList")
     @ApiOperation("课程分页列表")
@@ -141,10 +144,19 @@ public class FsUserCourseVideoController extends AppBaseController {
             log.info("获取的logo图片路径,fileUrl:{}", path);
             InputStream inputStream = fsUserCourseService.handleImage("", path);
 
-            if (StringUtils.isEmpty(param.getImgUrl())) {
-                return R.error(400, "课程封面不能为空!");
+            // 获取营期的课程风格url
+            String imgUrl;
+            FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(param.getPeriodId());
+            if (fsUserCoursePeriod != null) {
+                imgUrl = fsUserCoursePeriod.getCourseStyle();
+            } else {
+                imgUrl = param.getImgUrl();
             }
-            String base64Image = fsUserCourseService.createCourseImageQR(realLink, param.getImgUrl(), inputStream, "png", param.getTitle(), param.getDuration());
+            if(StringUtils.isEmpty(imgUrl)){
+                return R.error(400, "营期风格图片或课程封面不能为空!");
+            }
+
+            String base64Image = fsUserCourseService.createCourseImageQR(realLink, imgUrl, inputStream, "png", param.getTitle(), param.getDuration());
             // 返回Base64编码的图片字符串
             Map<String, Object> map = new HashMap<>();
             map.put("url", base64Image);

+ 14 - 2
fs-company-app/src/main/java/com/fs/app/controller/QwWorkTaskController.java

@@ -36,7 +36,7 @@ public class QwWorkTaskController extends AppBaseController {
     @Login
     @ApiOperation("企微任务看板列表")
     @GetMapping("/list")
-    public R list (@Valid QwWorkTaskQueryParam param) {
+    public R list(@Valid QwWorkTaskQueryParam param) {
         log.debug("企微任务看板列表:{}", JSON.toJSONString(param));
 
         Map<String, Object> params = new HashMap<>();
@@ -67,11 +67,23 @@ public class QwWorkTaskController extends AppBaseController {
     @ApiOperation("催课看板会员列表")
     @GetMapping("/getUserList")
     public R getUserList(@RequestParam(required = false, defaultValue = "1") Integer pageNum,
-                      @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+                         @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
         Long userId = Long.parseLong(getUserId());
         PageHelper.startPage(pageNum, pageSize);
         List<UserVOs> list = qwUserService.getUserList(userId);
         return R.ok().put("data", new PageInfo<>(list));
     }
 
+    @Login
+    @ApiOperation("催课看板企微会员列表")
+    @GetMapping("/getQwUserList")
+    public R getQwUserList(@RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                           @RequestParam(required = false, defaultValue = "10") Integer pageSize,
+                           @RequestParam(required = false, defaultValue = "10") String qwUserId) {
+        Long userId = Long.parseLong(getUserId());
+        PageHelper.startPage(pageNum, pageSize);
+        List<UserVOs> list = qwUserService.getQwUserList(userId, qwUserId);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
 }

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

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

+ 93 - 0
fs-company-app/src/main/resources/logback.xml

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 日志存放路径 -->
+	<property name="log.path" value="/home/fs-company-app/logs" />
+    <!-- 日志输出格式 -->
+	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+
+	<!-- 控制台输出 -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- 系统日志输出 -->
+	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 30 -->
+			<maxHistory>30</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+	</appender>
+
+	<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 30 -->
+			<maxHistory>30</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+			<!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+			<!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+	<!-- 用户访问日志输出  -->
+    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/sys-user.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 按天回滚 daily -->
+            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 30 -->
+            <maxHistory>30</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+
+	<!-- 系统模块日志级别控制  -->
+	<logger name="com.fs" level="info" />
+	<!-- Spring日志级别控制  -->
+	<logger name="org.springframework" level="warn" />
+
+	<root level="info">
+		<appender-ref ref="console" />
+	</root>
+
+	<!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+    </root>
+
+	<!--系统用户操作日志-->
+    <logger name="sys-user" level="info">
+        <appender-ref ref="sys-user"/>
+    </logger>
+</configuration>

+ 6 - 6
fs-company/src/main/java/com/fs/company/controller/CompanyRoleController.java

@@ -33,8 +33,8 @@ import com.fs.common.utils.poi.ExcelUtil;
 
 /**
  * 角色信息
- * 
- 
+ *
+
  */
 @RestController
 @RequestMapping("/company/role")
@@ -45,10 +45,10 @@ public class CompanyRoleController extends BaseController
 
     @Autowired
     private TokenService tokenService;
-    
+
     @Autowired
     private CompanyPermissionService permissionService;
-    
+
     @Autowired
     private ICompanyUserService userService;
 
@@ -80,7 +80,7 @@ public class CompanyRoleController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('company:role:query')")
     @GetMapping(value = "/{roleId}")
-    public AjaxResult getInfo(@PathVariable Long roleId)
+    public AjaxResult getInfo(@PathVariable("roleId") Long roleId)
     {
         return AjaxResult.success(roleService.selectCompanyRoleById(roleId));
     }
@@ -128,7 +128,7 @@ public class CompanyRoleController extends BaseController
             return AjaxResult.error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在");
         }
         role.setUpdateBy(SecurityUtils.getUsername());
-        
+
         if (roleService.updateRole(role) > 0)
         {
             // 更新缓存用户权限

+ 104 - 0
fs-company/src/main/java/com/fs/company/controller/CompanyWxDialogController.java

@@ -0,0 +1,104 @@
+package com.fs.company.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.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.CompanyWxDialog;
+import com.fs.company.service.ICompanyWxDialogService;
+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 fs
+ * @date 2024-12-06
+ */
+@RestController
+@RequestMapping("/company/wxDialog")
+public class CompanyWxDialogController extends BaseController
+{
+    @Autowired
+    private ICompanyWxDialogService companyWxDialogService;
+
+    /**
+     * 查询添加微信话术列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:wxDialog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyWxDialog companyWxDialog)
+    {
+        startPage();
+        List<CompanyWxDialog> list = companyWxDialogService.selectCompanyWxDialogList(companyWxDialog);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('company:wxDialog:list')")
+    @GetMapping("/listAll")
+    public R listAll(CompanyWxDialog companyWxDialog){
+        return R.ok().put("data", companyWxDialogService.selectCompanyWxDialogList(companyWxDialog));
+    }
+
+    /**
+     * 导出添加微信话术列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:wxDialog:export')")
+    @Log(title = "添加微信话术", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyWxDialog companyWxDialog)
+    {
+        List<CompanyWxDialog> list = companyWxDialogService.selectCompanyWxDialogList(companyWxDialog);
+        ExcelUtil<CompanyWxDialog> util = new ExcelUtil<CompanyWxDialog>(CompanyWxDialog.class);
+        return util.exportExcel(list, "wxDialog");
+    }
+
+    /**
+     * 获取添加微信话术详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:wxDialog:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(companyWxDialogService.selectCompanyWxDialogById(id));
+    }
+
+    /**
+     * 新增添加微信话术
+     */
+    @PreAuthorize("@ss.hasPermi('company:wxDialog:add')")
+    @Log(title = "添加微信话术", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyWxDialog companyWxDialog)
+    {
+        return toAjax(companyWxDialogService.insertCompanyWxDialog(companyWxDialog));
+    }
+
+    /**
+     * 修改添加微信话术
+     */
+    @PreAuthorize("@ss.hasPermi('company:wxDialog:edit')")
+    @Log(title = "添加微信话术", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyWxDialog companyWxDialog)
+    {
+        return toAjax(companyWxDialogService.updateCompanyWxDialog(companyWxDialog));
+    }
+
+    /**
+     * 删除添加微信话术
+     */
+    @PreAuthorize("@ss.hasPermi('company:wxDialog:remove')")
+    @Log(title = "添加微信话术", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(companyWxDialogService.deleteCompanyWxDialogByIds(ids));
+    }
+}

+ 120 - 0
fs-company/src/main/java/com/fs/company/controller/CompanyWxUserGroupController.java

@@ -0,0 +1,120 @@
+package com.fs.company.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.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.core.security.LoginUser;
+import com.fs.core.web.service.TokenService;
+import com.fs.wxUser.domain.CompanyWxUserGroup;
+import com.fs.wxUser.service.ICompanyWxUserGroupService;
+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 fs
+ * @date 2024-10-25
+ */
+@RestController
+@RequestMapping("/company/wxUserGroup")
+public class CompanyWxUserGroupController extends BaseController
+{
+    @Autowired
+    private ICompanyWxUserGroupService companyWxUserGroupService;
+    @Autowired
+    private TokenService tokenService;
+    /**
+     * 查询个微 分组列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:wxUserGroup:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyWxUserGroup companyWxUserGroup)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        companyWxUserGroup.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<CompanyWxUserGroup> list = companyWxUserGroupService.selectCompanyWxUserGroupList(companyWxUserGroup);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出个微 分组列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:wxUserGroup:export')")
+    @Log(title = "个微 分组", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyWxUserGroup companyWxUserGroup)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        companyWxUserGroup.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<CompanyWxUserGroup> list = companyWxUserGroupService.selectCompanyWxUserGroupList(companyWxUserGroup);
+        ExcelUtil<CompanyWxUserGroup> util = new ExcelUtil<CompanyWxUserGroup>(CompanyWxUserGroup.class);
+        return util.exportExcel(list, "个微 分组数据");
+    }
+
+    /**
+     * 获取个微 分组详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:wxUserGroup:query')")
+    @GetMapping(value = "/{groupId}")
+    public AjaxResult getInfo(@PathVariable("groupId") Long groupId)
+    {
+        return AjaxResult.success(companyWxUserGroupService.selectCompanyWxUserGroupByGroupId(groupId));
+    }
+
+    /**
+     * 新增个微 分组
+     */
+    @PreAuthorize("@ss.hasPermi('company:wxUserGroup:add')")
+    @Log(title = "个微 分组", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyWxUserGroup companyWxUserGroup)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        companyWxUserGroup.setCompanyId(loginUser.getCompany().getCompanyId());
+        return toAjax(companyWxUserGroupService.insertCompanyWxUserGroup(companyWxUserGroup));
+    }
+
+    /**
+     * 修改个微 分组
+     */
+    @PreAuthorize("@ss.hasPermi('company:wxUserGroup:edit')")
+    @Log(title = "个微 分组", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyWxUserGroup companyWxUserGroup)
+    {
+        return toAjax(companyWxUserGroupService.updateCompanyWxUserGroup(companyWxUserGroup));
+    }
+
+    /**
+     * 删除个微 分组
+     */
+    @PreAuthorize("@ss.hasPermi('company:wxUserGroup:remove')")
+    @Log(title = "个微 分组", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{groupIds}")
+    public AjaxResult remove(@PathVariable Long[] groupIds)
+    {
+        return toAjax(companyWxUserGroupService.deleteCompanyWxUserGroupByGroupIds(groupIds));
+    }
+
+
+    //查询个微分组-添加sop任务-个微-选择的分组(不要权限版)
+    @GetMapping("/sopList")
+    public TableDataInfo sopList(CompanyWxUserGroup companyWxUserGroup)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        companyWxUserGroup.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<CompanyWxUserGroup> list = companyWxUserGroupService.selectCompanyWxUserGroupList(companyWxUserGroup);
+        return getDataTable(list);
+    }
+
+}

+ 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/course/controller/qw/QwFsCourseWatchLogController.java

@@ -71,7 +71,7 @@ public class QwFsCourseWatchLogController extends BaseController
         return getDataTable(list);
     }
 
-    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:statisticsList')")
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:qw:statisticsList')")
     @GetMapping("/statisticsList")
     public TableDataInfo statisticsList(FsCourseWatchLogStatisticsListParam param)
     {

+ 115 - 0
fs-company/src/main/java/com/fs/crm/controller/CrmCustomerAssistController.java

@@ -0,0 +1,115 @@
+package com.fs.crm.controller;
+
+import java.util.List;
+
+import com.fs.crm.param.CrmCustomerAssistDeLParam;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.crm.domain.CrmCustomerAssist;
+import com.fs.crm.service.ICrmCustomerAssistService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 客户员工协作Controller
+ *
+ * @author fs
+ * @date 2025-05-27
+ */
+@RestController
+@RequestMapping("/crm/assist")
+public class CrmCustomerAssistController extends BaseController
+{
+    @Autowired
+    private ICrmCustomerAssistService crmCustomerAssistService;
+
+    /**
+     * 查询客户员工协作列表
+     */
+    @PreAuthorize("@ss.hasPermi('crm:assist:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CrmCustomerAssist crmCustomerAssist)
+    {
+        startPage();
+        List<CrmCustomerAssist> list = crmCustomerAssistService.selectCrmCustomerAssistList(crmCustomerAssist);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出客户员工协作列表
+     */
+    @PreAuthorize("@ss.hasPermi('crm:assist:export')")
+    @Log(title = "客户员工协作", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CrmCustomerAssist crmCustomerAssist)
+    {
+        List<CrmCustomerAssist> list = crmCustomerAssistService.selectCrmCustomerAssistList(crmCustomerAssist);
+        ExcelUtil<CrmCustomerAssist> util = new ExcelUtil<CrmCustomerAssist>(CrmCustomerAssist.class);
+        return util.exportExcel(list, "assist");
+    }
+
+    /**
+     * 获取客户员工协作详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('crm:assist:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(crmCustomerAssistService.selectCrmCustomerAssistById(id));
+    }
+
+    /**
+     * 新增客户员工协作
+     */
+    @PreAuthorize("@ss.hasPermi('crm:assist:add')")
+    @Log(title = "客户员工协作", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CrmCustomerAssist crmCustomerAssist)
+    {
+        return toAjax(crmCustomerAssistService.insertCrmCustomerAssist(crmCustomerAssist));
+    }
+
+    /**
+     * 修改客户员工协作
+     */
+    @PreAuthorize("@ss.hasPermi('crm:assist:edit')")
+    @Log(title = "客户员工协作", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CrmCustomerAssist crmCustomerAssist)
+    {
+        return toAjax(crmCustomerAssistService.updateCrmCustomerAssist(crmCustomerAssist));
+    }
+
+    /**
+     * 删除客户员工协作
+     */
+    @Log(title = "客户员工协作", businessType = BusinessType.DELETE)
+    @PostMapping("/remove")
+    public AjaxResult removeByCustomer(@RequestBody CrmCustomerAssistDeLParam param)
+    {
+        return toAjax(crmCustomerAssistService.removeByCustomer(param));
+    }
+
+    /**
+     * 删除客户员工协作
+     */
+    @PreAuthorize("@ss.hasPermi('crm:assist:remove')")
+    @Log(title = "客户员工协作", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(crmCustomerAssistService.deleteCrmCustomerAssistByIds(ids));
+    }
+}

+ 36 - 0
fs-company/src/main/java/com/fs/crm/controller/CrmCustomerController.java

@@ -119,6 +119,29 @@ public class CrmCustomerController extends BaseController
 
     }
 
+    @ApiOperation("获取我的协作客户列表")
+    @PreAuthorize("@ss.hasPermi('crm:customer:assistList')")
+    @GetMapping("/getMyAssistList")
+    public TableDataInfo getMyAssistList(CrmMyCustomerListQueryParam param){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        param.setCompanyUserId(loginUser.getUser().getUserId());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
+            param.setCustomerCreateTime(param.getCreateTimeRange().split("--"));
+        }
+        List<CrmMyCustomerListQueryVO> list = crmCustomerService.selectCrmMyAssistListQuery(param);
+        if (list != null) {
+            for (CrmMyCustomerListQueryVO vo : list) {
+                if(vo.getMobile()!=null){
+                    vo.setMobile(vo.getMobile().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                }
+            }
+        }
+        return getDataTable(list);
+
+    }
+
     @ApiOperation("获取客户列表")
     @GetMapping("/getCustomerList")
     @PreAuthorize("@ss.hasPermi('crm:customer:list')")
@@ -212,6 +235,19 @@ public class CrmCustomerController extends BaseController
         return crmCustomerService.assignToUser(loginUser.getUsername(),loginUser.getUser().getUserId(),param);
     }
 
+    //添加协作人
+    @PreAuthorize("@ss.hasPermi('crm:customer:assistToUser')")
+    @PostMapping("/assistToUser")
+    public R assistToUser(@RequestBody CrmCustomeAssignParam param)
+    {
+        if(param.getCustomerIds().size()>1000){
+            return R.error("分配数据超出范围,最大1000条");
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        return crmCustomerService.assistToUser(loginUser.getUsername(),loginUser.getUser().getUserId(),param);
+    }
+
 
     @PreAuthorize("@ss.hasPermi('crm:customer:add')")
     @Log(title = "创建客户", businessType = BusinessType.INSERT)

+ 103 - 0
fs-company/src/main/java/com/fs/qw/QwMsgController.java

@@ -6,18 +6,32 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.exception.CustomException;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.core.security.LoginUser;
 import com.fs.core.web.service.TokenService;
+import com.fs.course.param.FsCourseListBySidebarParam;
+import com.fs.course.service.IFsUserCourseService;
+import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.vo.FsCourseListBySidebarVO;
+import com.fs.course.vo.FsCourseVideoListBySidebarVO;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwExternalContactInfo;
 import com.fs.qw.domain.QwMsg;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.param.QwMsgSendParam;
 import com.fs.qw.param.QwSessionParam;
+import com.fs.qw.service.IQwExternalContactInfoService;
+import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.IQwMsgService;
+import com.fs.qw.service.IQwUserService;
 import com.fs.qw.vo.QwContactListVO;
 import com.fs.qw.vo.QwMessageListVO;
+import com.fs.statis.dto.WatchCourseStatisticsDTO;
+import com.fs.statis.param.WatchCourseStatisticsParam;
+import com.fs.statis.service.IStatisticsService;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
@@ -44,6 +58,18 @@ public class QwMsgController extends BaseController
     private IQwMsgService qwMsgService;
     @Autowired
     private TokenService tokenService;
+    @Autowired
+    private IQwExternalContactInfoService qwExternalContactInfoService;
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+    @Autowired
+    private IQwUserService qwUserService;
+    @Autowired
+    private IStatisticsService statisticsService;
+    @Autowired
+    private IFsUserCourseService fsUserCourseService;
+    @Autowired
+    private IFsUserCourseVideoService fsUserCourseVideoService;
 
     /**
      * 查询企微聊天记录列表
@@ -160,4 +186,81 @@ public class QwMsgController extends BaseController
         QwContactListVO data = qwMsgService.selectQwSessionBycId(param.getConversationId(),param.getUserId());
         return R.ok().put("data",data);
     }
+
+    @GetMapping("/getQwExternalContactDetails")
+    public R getQwExternalContactDetails(@RequestParam(value = "qwExternalContactId") Long qwExternalContactId){
+        return R.ok().put("data", qwExternalContactService.getQwExternalContactDetailsById(qwExternalContactId));
+    }
+
+    @GetMapping("/getQwUserInfo")
+    @ApiOperation("获取企微用户信息")
+    public R getQwUserInfo(@RequestParam(value = "qwExternalContactId") Long qwExternalContactId){
+        if(qwExternalContactId == null) {
+            throw new CustomException("企微外部联系人id不能为空!");
+        }
+        QwExternalContactInfo contactInfo = qwExternalContactInfoService.selectQwExternalContactInfoByExternalContactId(qwExternalContactId);
+        if (contactInfo==null){
+
+            contactInfo = new QwExternalContactInfo();
+            contactInfo.setExternalContactId(qwExternalContactId);
+            qwExternalContactInfoService.insertQwExternalContactInfo(contactInfo);
+
+        }
+
+        return R.ok().put("moreInfo",contactInfo);
+    }
+
+    @PostMapping("/updateQwUserInfo")
+    @ApiOperation("更新企微用户信息")
+    public R updateQwUserInfo(@RequestBody QwExternalContactInfo qwExternalContactInfo){
+        if(qwExternalContactInfo.getExternalContactId() == null) {
+            throw new CustomException("企微外部联系人id不能为空!");
+        }
+        qwExternalContactInfoService.updateQwExternalContactInfoByExternalContactId(qwExternalContactInfo);
+        return R.ok();
+    }
+
+    @PostMapping("/course/watch")
+    @ApiOperation("查询看课记录")
+    public R queryCourseWatchStatistics(@RequestBody WatchCourseStatisticsParam param) {
+        if(param.getQwExternalContactId() == null) {
+            throw new CustomException("外部联系人id为空!");
+        }
+
+        WatchCourseStatisticsDTO watchCourseStatisticsDTO = statisticsService.queryWatchCourse(param);
+
+        return R.ok().put("data",watchCourseStatisticsDTO);
+    }
+
+    @PostMapping("/getFsCourseListBySidebar")
+    @ApiOperation("获取视频课程下拉列表 侧边栏")
+    public R getFsCourseListBySidebar(@RequestBody FsCourseListBySidebarParam param) {
+
+        QwExternalContact externalContact = qwExternalContactService.getById(param.getExtId());
+        QwUser qwUser = qwUserService.selectQwUserById(externalContact.getQwUserId());
+
+        if (qwUser == null || qwUser.getCompanyId() == null) {
+            return R.error("员工未绑定 销售公司 或 未获取到员工信息,请重试!");
+        }
+        param.setCompanyId(qwUser.getCompanyId());
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsCourseListBySidebarVO> fsCourseListBySidebar = fsUserCourseService.getFsCourseListBySidebar(param);
+        PageInfo<FsCourseListBySidebarVO> result = new PageInfo<>(fsCourseListBySidebar);
+        return R.ok().put("data", result);
+    }
+
+    @PostMapping("/getFsCourseVideoListBySidebar")
+    @ApiOperation("获取视频课程的课节下拉列表 侧边栏")
+    public R getFsCourseVideoListBySidebar(@RequestBody FsCourseListBySidebarParam param) {
+
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsCourseVideoListBySidebarVO> videoListBySidebar = fsUserCourseVideoService.getFsCourseVideoListBySidebar(param);
+        PageInfo<FsCourseVideoListBySidebarVO> result = new PageInfo<>(videoListBySidebar);
+        return R.ok().put("data", result);
+    }
 }

+ 1 - 0
fs-company/src/main/java/com/fs/qw/QwQwWorkTaskController.java

@@ -17,6 +17,7 @@ import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.param.QwWorkTaskListParam;
 import com.fs.qw.service.IHyWorkTaskService;
 import com.fs.qw.service.IQwWorkTaskService;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import com.github.pagehelper.PageInfo;
 import org.springframework.beans.factory.annotation.Autowired;

+ 2 - 2
fs-company/src/main/java/com/fs/qw/QwSopTempController.java

@@ -136,8 +136,8 @@ public class QwSopTempController extends BaseController
         qwSopTemp.setCompanyId(loginUser.getCompany().getCompanyId());
         qwSopTemp.setCreateBy(loginUser.getUser().getUserId().toString());
         int i = qwSopTempService.addNew(qwSopTemp);
-        if(qwSopTemp.getSendType() == 5){
-            qwSopTempService.createSopTempRules(qwSopTemp);
+        if(qwSopTemp.getSendType() == 11){
+            new Thread(() -> qwSopTempService.createSopTempRules(qwSopTemp)).start();
         }
         return toAjax(i);
     }

+ 6 - 5
fs-company/src/main/java/com/fs/qw/QwUserController.java

@@ -140,7 +140,7 @@ public class QwUserController extends BaseController
         return qwUserService.loginQwCode(loginParam);
     }
 
-    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+    @PreAuthorize("@ss.hasPermi('qw:user:loginQwIpad')")
     @PostMapping("/loginQwIpad")
     public R loginQwIpad(@RequestBody QwLoginHookParam loginParam){
         return qwUserService.loginQwIpad(loginParam);
@@ -151,28 +151,27 @@ public class QwUserController extends BaseController
      * @param loginParam
      * @return
      */
-    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+    @PreAuthorize("@ss.hasPermi('qw:user:allocateRemoteHost')")
     @PostMapping("/allocateRemoteHost")
     @RepeatSubmit
     public R allocateRemoteHost(@RequestBody QwLoginHookParam loginParam){
         return qwUserService.allocateRemoteHost(loginParam);
     }
 
-    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+    @PreAuthorize("@ss.hasPermi('qw:user:getQwIpad')")
     @PostMapping("/getQwIpad")
     @RepeatSubmit
     public R getQwIpad(@RequestBody QwLoginHookParam loginParam){
         return qwUserService.getQwIpad(loginParam);
     }
 
-    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+    @PreAuthorize("@ss.hasPermi('qw:user:delQwIpad')")
     @PostMapping("/delQwIpad")
     @RepeatSubmit
     public R delQwIpad(@RequestBody QwLoginHookParam loginParam){
         return qwUserService.delQwIpad(loginParam);
     }
 
-    @PreAuthorize("@ss.hasPermi('qw:user:login')")
     @PostMapping("/qrCodeStatus")
     public R qrCodeStatus(@RequestBody QwLoginHookParam loginParam){
         return qwUserService.qrCodeStatus(loginParam);
@@ -183,11 +182,13 @@ public class QwUserController extends BaseController
         return qwUserService.qrCodeVerify(loginParam);
     }
 
+    @PreAuthorize("@ss.hasPermi('qw:user:outLoginQwIpad')")
     @PostMapping("/outLoginQwIpad")
     public R outLoginQwIpad(@RequestBody QwLoginHookParam loginParam){
         return qwUserService.outLoginQwIpad(loginParam);
     }
 
+    @PreAuthorize("@ss.hasPermi('qw:user:twoCode')")
     @PostMapping("/twoCode")
     public R twoCode(@RequestBody QwLoginHookParam loginParam){
         return qwUserService.getTwoCode(loginParam);

+ 34 - 6
fs-company/src/main/java/com/fs/qw/qw/QwWorkTaskController.java

@@ -14,6 +14,7 @@ import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.param.QwWorkTaskListParam;
 import com.fs.qw.service.IQwWorkTaskService;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -41,7 +42,7 @@ public class QwWorkTaskController extends BaseController
     /**
      * 查询企微任务看板列表
      */
-    @PreAuthorize("@ss.hasPermi('qw:QwWorkTask:list')")
+    @PreAuthorize("@ss.hasPermi('qw:qw:QwWorkTask:list')")
     @GetMapping("/list")
     public TableDataInfo list(QwWorkTaskListParam qwWorkTask)
     {
@@ -58,10 +59,37 @@ public class QwWorkTaskController extends BaseController
         return getDataTable(list);
     }
 
+    @GetMapping("/glList")
+    public TableDataInfo glList(QwWorkTaskListParam qwWorkTask)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwWorkTask.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<QwWorkTaskListVO> list = qwWorkTaskService.selectQwWorkTaskListVO(qwWorkTask);
+        for (QwWorkTaskListVO qwWorkTaskListVO : list) {
+            qwWorkTaskListVO.setLogs(fsCourseWatchLogMapper.selectFsCourseWatchLog7DayByExtId(qwWorkTaskListVO.getExtId()));
+        }
+        return getDataTable(list);
+    }
+
+    @GetMapping("/allList")
+    public TableDataInfo allList(QwWorkTaskListParam qwWorkTask)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwWorkTask.setCompanyId(loginUser.getCompany().getCompanyId());
+        if (qwWorkTask.getSTime()==null||qwWorkTask.getETime()==null){
+            return new TableDataInfo();
+        }
+        List<QwWorkTaskAllListVO> list = qwWorkTaskService.selectQwWorkTaskAllListVO(qwWorkTask);
+
+        return getDataTable(list);
+    }
+
     /**
      * 导出企微任务看板列表
      */
-    @PreAuthorize("@ss.hasPermi('qw:QwWorkTask:export')")
+    @PreAuthorize("@ss.hasPermi('qw:qw:QwWorkTask:export')")
     @Log(title = "企微任务看板", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     public AjaxResult export(QwWorkTask qwWorkTask)
@@ -74,7 +102,7 @@ public class QwWorkTaskController extends BaseController
     /**
      * 获取企微任务看板详细信息
      */
-    @PreAuthorize("@ss.hasPermi('qw:QwWorkTask:query')")
+    @PreAuthorize("@ss.hasPermi('qw:qw:QwWorkTask:query')")
     @GetMapping(value = "/{id}")
     public AjaxResult getInfo(@PathVariable("id") Long id)
     {
@@ -84,7 +112,7 @@ public class QwWorkTaskController extends BaseController
     /**
      * 新增企微任务看板
      */
-    @PreAuthorize("@ss.hasPermi('qw:QwWorkTask:add')")
+    @PreAuthorize("@ss.hasPermi('qw:qw:QwWorkTask:add')")
     @Log(title = "企微任务看板", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody QwWorkTask qwWorkTask)
@@ -95,7 +123,7 @@ public class QwWorkTaskController extends BaseController
     /**
      * 修改企微任务看板
      */
-    @PreAuthorize("@ss.hasPermi('qw:QwWorkTask:edit')")
+    @PreAuthorize("@ss.hasPermi('qw:qw:QwWorkTask:edit')")
     @Log(title = "企微任务看板处理", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody QwWorkTask qwWorkTask)
@@ -111,7 +139,7 @@ public class QwWorkTaskController extends BaseController
     /**
      * 删除企微任务看板
      */
-    @PreAuthorize("@ss.hasPermi('qw:QwWorkTask:remove')")
+    @PreAuthorize("@ss.hasPermi('qw:qw:QwWorkTask:remove')")
     @Log(title = "企微任务看板", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{ids}")
     public AjaxResult remove(@PathVariable Long[] ids)

+ 0 - 18
fs-company/src/main/java/com/fs/qw/vo/QwContactListVO.java

@@ -1,18 +0,0 @@
-package com.fs.qw.vo;
-
-import lombok.Data;
-
-@Data
-public class QwContactListVO {
-    private Long id;
-    private String conversationId;
-    private String displayName;
-    private String avatar;
-    private String index;
-    private Boolean isGroup;
-    private Integer unread;
-    private Long roomId;
-    private Long lastSendTime;
-    private String lastContent;
-
-}

+ 0 - 34
fs-company/src/main/java/com/fs/qw/vo/QwMessageListVO.java

@@ -1,34 +0,0 @@
-package com.fs.qw.vo;
-
-import com.fs.qw.domain.QWFromUser;
-import com.fs.qw.domain.QwMsg;
-import lombok.Data;
-
-@Data
-public class QwMessageListVO {
-    private String id;
-    private String status;
-    private String type;
-    private Long sendTime;
-    private String content;
-    private String url;
-    private Long fileSize;
-    private String fileName;
-    private Integer fileShowType;
-    private String imageUrl;
-    private Integer duration; //时长
-    private String toContactId;
-    private QWFromUser fromUser;
-
-    //获取fromUser
-    public  QWFromUser getQwFromUser(Long senderId,QwMsg qwMsg){
-        QWFromUser fromUser=new QWFromUser();
-        fromUser.setId(senderId);
-        if(qwMsg!=null){
-            fromUser.setAvatar(qwMsg.getAvatar());
-            fromUser.setDisplayName(qwMsg.getAvatar());
-        }
-        return fromUser;
-    }
-
-}

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

@@ -133,16 +133,11 @@ 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) {
         Boolean r = fsUserService.disabledUser(ids, true);
         return ResponseResult.ok(r);
     }
-
-    @GetMapping("/getUserInfoBySessionId")
-    public R getUserInfoBySessionId(@RequestParam Long sessionId) {
-        return R.ok().put("data", fsUserService.getUserInfoBySessionId(sessionId));
-    }
 }

+ 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

+ 93 - 0
fs-company/src/main/resources/logback.xml

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 日志存放路径 -->
+	<property name="log.path" value="/home/fs-company/logs" />
+    <!-- 日志输出格式 -->
+	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+
+	<!-- 控制台输出 -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- 系统日志输出 -->
+	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 30 -->
+			<maxHistory>30</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+	</appender>
+
+	<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 30 -->
+			<maxHistory>30</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+			<!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+			<!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+	<!-- 用户访问日志输出  -->
+    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/sys-user.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 按天回滚 daily -->
+            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 30 -->
+            <maxHistory>30</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+
+	<!-- 系统模块日志级别控制  -->
+	<logger name="com.fs" level="info" />
+	<!-- Spring日志级别控制  -->
+	<logger name="org.springframework" level="warn" />
+
+	<root level="info">
+		<appender-ref ref="console" />
+	</root>
+
+	<!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+    </root>
+
+	<!--系统用户操作日志-->
+    <logger name="sys-user" level="info">
+        <appender-ref ref="sys-user"/>
+    </logger>
+</configuration>

+ 10 - 2
fs-qw-api-msg/pom.xml

@@ -117,8 +117,16 @@
             <artifactId>vosk</artifactId>
             <version>0.3.32</version>
         </dependency>
-
-
+        <dependency>
+            <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>

+ 133 - 9
fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java

@@ -1,11 +1,16 @@
 package com.fs.app.controller;
 
 import com.alibaba.fastjson.JSON;
+import com.fs.app.socket.QwImSocket;
+import com.fs.app.util.AudioUtils;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.StringUtils;
+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;
 import com.fs.qw.service.IQwUserVoiceLogService;
+import com.fs.qw.vo.QwMessageListVO;
 import com.fs.wxwork.dto.*;
 import com.fs.wxwork.service.WxWorkService;
 import io.swagger.annotations.Api;
@@ -140,14 +145,27 @@ public class QwMsgController {
                 if (wxWorkMessageDTO.getReferid()!=0){
                     break;
                 }
+
+                Long receiver = wxWorkMessageDTO.getReceiver();
+                Long sender = wxWorkMessageDTO.getSender();
+
+                // 1客户 2销售
+                int sendType = 2;
+
+                // 消息发送者用户ID
+                Long userId = receiver;
+                if (2000000000000000L - receiver > 0){
+                    sendType = 1;
+                    userId = sender;
+                }
+
                 if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16){
 
                     String content = wxWorkMessageDTO.getContent();
                     System.out.println("接收人:"+wxWorkMessageDTO.getReceiver());
                     System.out.println("发送人:"+wxWorkMessageDTO.getSender());
                     System.out.println("内容:"+content);
-                    Long receiver = wxWorkMessageDTO.getReceiver();
-                    Long sender = wxWorkMessageDTO.getSender();
+
                     if(wxWorkMessageDTO.getMsgtype()==16){
                         WxwSpeechToTextEntityDTO ste = new WxwSpeechToTextEntityDTO();
                         ste.setMsgid(wxWorkMessageDTO.getMsg_id());
@@ -161,24 +179,25 @@ public class QwMsgController {
                     if (2000000000000000L-receiver>0){
                         System.out.println("客户发送");
                         aiHookService.qwHookNotifyAiReply(id,sender,content,wxWorkMsgResp.getUuid(),wxWorkMessageDTO.getMsgtype());
-
-                        // 保存聊天消息
-                        aiHookService.saveQwMsg(id, sender, content, wxWorkMsgResp.getUuid(), 1);
                     }else {
                         System.out.println("销售发送");
                         aiHookService.qwHookNotifyAddMsg(id,receiver,content,wxWorkMsgResp.getUuid());
-
-                        // 保存聊天消息
-                        aiHookService.saveQwMsg(id, receiver, content, wxWorkMsgResp.getUuid(), 2);
                     }
 
+                    // 处理文本消息
+                    if (wxWorkMessageDTO.getMsgtype() == 2 || wxWorkMessageDTO.getMsgtype() == 0) {
+                        processTextMessage(id, userId, content, wxWorkMsgResp, sendType);
+                    }
+                    // 语音消息
+                    if (wxWorkMessageDTO.getMsgtype() == 16) {
+                        processVoiceMessage(serverId, content, wxWorkMessageDTO, wxWorkMsgResp, id, userId, sendType);
+                    }
                 }
                 //语音通话
                 if (wxWorkMessageDTO.getMsgtype()==40){
                     if (wxWorkMessageDTO.getRecordtype()==null){
                         break;
                     }
-                    Long receiver = wxWorkMessageDTO.getReceiver();
                     Long extId=null;
                     Integer totalSeconds=0;
                     if (2000000000000000L-receiver>0){
@@ -210,6 +229,14 @@ public class QwMsgController {
 
                     qwUserVoiceLogService.addQuUserVoiceByIpadCallback(id,extId,recordType,totalSeconds,wxWorkMsgResp.getUuid());
                 }
+                // 图片消息
+                else if (wxWorkMessageDTO.getMsgtype() == 101){
+                    processImageMessage(serverId, wxWorkMessageDTO, wxWorkMsgResp, id, userId, sendType);
+                }
+                // gif 表情消息
+                else if (wxWorkMessageDTO.getMsgtype() == 104){
+                    processEmotionDynamicMessage(wxWorkMessageDTO, wxWorkMsgResp, id, userId, sendType);
+                }
 
                 break;
 
@@ -221,6 +248,103 @@ public class QwMsgController {
         return map;
     }
 
+    /**
+     * 处理文本消息
+     * @param id                企微用户ID
+     * @param userId            消息发送者ID
+     * @param content           消息内容
+     * @param wxWorkMsgResp     回调信息对象
+     * @param sendType          发送者类型 1客户 2销售
+     */
+    private void processTextMessage(Long id, Long userId, String content, WxWorkMsgResp wxWorkMsgResp, Integer sendType) {
+        // 保存聊天消息
+        QwMessageListVO message = aiHookService.saveQwMsg(id, userId, content, wxWorkMsgResp.getUuid(), sendType, wxWorkMsgResp.getJson(), 1);
+        QwImSocket.broadcast(message);
+    }
+
+    /**
+     * 处理语音消息
+     * @param serverId          服务器ID
+     * @param wxWorkMessageDTO  消息DTO
+     * @param content           翻译后的内容
+     * @param wxWorkMsgResp     回调信息对象
+     * @param id                企微用户ID
+     * @param userId            消息发送者ID
+     * @param sendType          发送者类型 1客户 2销售
+     */
+    private void processVoiceMessage(Long serverId, String content, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, Long id, Long userId, Integer sendType) {
+        String voiceFileName = IdUtils.fastSimpleUUID() + ".silk";
+        WxWorkResponseDTO<String> fileUrlResp =
+                aiHookService.getFileUrl(wxWorkMsgResp.getUuid(), wxWorkMessageDTO.getVoice_id(), wxWorkMessageDTO.getAes_key(), 5, voiceFileName, wxWorkMessageDTO.getVoice_size(), serverId);
+        if (fileUrlResp.getErrcode() != 0) {
+            log.warn("获取语音地址失败: {}", fileUrlResp.getErrmsg());
+            return;
+        }
+
+        // silk转map3
+        String url = AudioUtils.convertSilk2Mp3(fileUrlResp.getData());
+        if (StringUtils.isBlank(url)) {
+            log.warn("转换silk语音格式失败");
+            return;
+        }
+
+        // 转换内容为空时再尝试一次
+        if (StringUtils.isBlank(content)) {
+            WxwSpeechToTextEntityDTO ste = new WxwSpeechToTextEntityDTO();
+            ste.setMsgid(wxWorkMessageDTO.getMsg_id());
+            ste.setUuid(wxWorkMsgResp.getUuid());
+            WxWorkResponseDTO<WxwSpeechToTextEntityRespDTO> dto = wxWorkService.SpeechToTextEntity(ste, serverId);
+            content = dto.getData().getText();
+        }
+
+        JSONObject json = new JSONObject();
+        json.put("url", url);
+        json.put("content", content);
+
+        // 保存聊天消息
+        QwMessageListVO message = aiHookService.saveQwMsg(id, userId, json.toString(), wxWorkMsgResp.getUuid(), sendType, wxWorkMsgResp.getJson(), 4);
+        QwImSocket.broadcast(message);
+    }
+
+    /**
+     * 处理图片消息
+     * @param serverId          服务器ID
+     * @param wxWorkMessageDTO  消息DTO
+     * @param wxWorkMsgResp     回调信息对象
+     * @param id                企微用户ID
+     * @param userId            消息发送者ID
+     * @param sendType          发送者类型 1客户 2销售
+     */
+    private void processImageMessage(Long serverId, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, Long id, Long userId, Integer sendType) {
+        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());
+            return;
+        }
+
+        String content = fileUrlResp.getData();
+        // 保存聊天消息
+        QwMessageListVO message = aiHookService.saveQwMsg(id, userId, content, wxWorkMsgResp.getUuid(), sendType, wxWorkMsgResp.getJson(), 2);
+        QwImSocket.broadcast(message);
+    }
+
+    /**
+     * 处理动态表情消息
+     * @param wxWorkMessageDTO  消息DTO
+     * @param wxWorkMsgResp     回调信息对象
+     * @param id                企微用户ID
+     * @param userId            消息发送者ID
+     * @param sendType          发送者类型 1客户 2销售
+     */
+    private void processEmotionDynamicMessage(WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, Long id, Long userId, int sendType) {
+        String content = wxWorkMessageDTO.getUrl();
+        // 保存聊天消息
+        QwMessageListVO message = aiHookService.saveQwMsg(id, userId, content, wxWorkMsgResp.getUuid(), sendType, wxWorkMsgResp.getJson(), 3);
+        QwImSocket.broadcast(message);
+    }
+
     void qwUserStatus(String uid,Integer status){
         Long id = redisCache.getCacheObject("qrCode:uuid:"+uid);
         QwUser qwUser = new QwUser();

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

+ 101 - 0
fs-qw-api-msg/src/main/java/com/fs/app/socket/QwImSocket.java

@@ -0,0 +1,101 @@
+package com.fs.app.socket;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.app.socket.configurator.QwImConfigurator;
+import com.fs.qw.vo.QwMessageListVO;
+import org.springframework.stereotype.Component;
+
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+@ServerEndpoint(value = "/qwImSocket/{companyId}", configurator = QwImConfigurator.class)
+@Component
+public class QwImSocket {
+
+    private static final ConcurrentHashMap<Long, CopyOnWriteArraySet<Session>> companySessions = new ConcurrentHashMap<>();
+
+    /**
+     * 连接建立成功调用的方法
+     * @param session   连接会话
+     * @param companyId 公司ID
+     */
+    @OnOpen
+    public void onOpen(Session session, @PathParam("companyId") Long companyId) {
+        // 将当前会话加入到会话池中
+        companySessions.computeIfAbsent(companyId, k -> new CopyOnWriteArraySet<>()).add(session);
+    }
+
+    /**
+     * 连接关闭调用的方法
+     * @param session   连接会话
+     * @param companyId 公司ID
+     */
+    @OnClose
+    public void onClose(Session session, @PathParam("companyId") Long companyId) {
+        // 从会话池中移除当前会话
+        CopyOnWriteArraySet<Session> sessions = companySessions.get(companyId);
+        if (sessions != null) {
+            sessions.remove(session);
+            // 如果直播间没人了,可以移除该直播间
+            if (sessions.isEmpty()) {
+                companySessions.remove(companyId);
+            }
+        }
+    }
+
+    /**
+     * 发生错误时调用的方法
+     * @param session   连接会话
+     * @param companyId 公司ID
+     * @param error     错误对象
+     */
+    @OnError
+    public void onError(Session session, @PathParam("companyId") Long companyId, Throwable error) {
+        System.err.println("发生错误!会话ID: " + session.getId());
+        CopyOnWriteArraySet<Session> sessions = companySessions.get(companyId);
+        if (sessions != null) {
+            sessions.remove(session);
+            // 如果直播间没人了,可以移除该直播间
+            if (sessions.isEmpty()) {
+                companySessions.remove(companyId);
+            }
+        }
+    }
+
+    /**
+     * 群发消息
+     * @param message   要发送的消息
+     */
+    public static void broadcast(QwMessageListVO message) {
+        if (Objects.isNull(message)) {
+            return;
+        }
+
+        String msg = JSON.toJSONString(message);
+        CopyOnWriteArraySet<Session> sessions = companySessions.get(message.getCompanyId());
+        if (sessions != null) {
+            for (Session session : sessions) {
+                if (session.isOpen()) {
+                    try {
+                        session.getBasicRemote().sendText(msg);
+                    } catch (IOException e) {
+                        System.err.println("发送消息给会话[" + session.getId() + "]失败: " + e.getMessage());
+                        // 移除无效会话
+                        sessions.remove(session);
+                    }
+                } else {
+                    sessions.remove(session); // 移除已关闭的会话
+                }
+            }
+        }
+    }
+
+}

+ 22 - 0
fs-qw-api-msg/src/main/java/com/fs/app/socket/configurator/QwImConfigurator.java

@@ -0,0 +1,22 @@
+package com.fs.app.socket.configurator;
+
+import com.fs.app.exception.FSException;
+
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class QwImConfigurator extends ServerEndpointConfig.Configurator {
+
+    @Override
+    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
+        Map<String, List<String>> parameterMap = request.getParameterMap();
+        List<String> token = parameterMap.get("token");
+        if (Objects.isNull(token)) {
+            throw new FSException("Unauthorized access to WebSocket endpoint.");
+        }
+    }
+}

+ 160 - 0
fs-qw-api-msg/src/main/java/com/fs/app/util/AudioUtils.java

@@ -0,0 +1,160 @@
+package com.fs.app.util;
+
+import com.fs.common.utils.uuid.IdUtils;
+import com.fs.system.oss.CloudStorageService;
+import com.fs.system.oss.OSSFactory;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.springframework.http.HttpStatus;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class AudioUtils {
+
+    /**
+     * silk转换为mp3
+     * @param silkUrl silk语音链接地址
+     * @return  mp3链接地址
+     */
+    public static String convertSilk2Mp3(String silkUrl) {
+        String uniqueId = IdUtils.fastSimpleUUID();
+        Path uploadDirPath = Paths.get(System.getProperty("java.io.tmpdir"), "/");
+        Path downloadedSilkFilePath = uploadDirPath.resolve(uniqueId + ".silk");
+        Path pcmFilePath = uploadDirPath.resolve(uniqueId + ".pcm");
+        Path mp3FilePath = uploadDirPath.resolve(uniqueId + ".mp3");
+
+        try {
+            // 1. 从网络下载 SILK 文件
+            downloadFile(silkUrl, downloadedSilkFilePath);
+            log.info("SILK file downloaded to: {}", downloadedSilkFilePath);
+
+            // 2. 使用 silk-v3-decoder 解码 SILK 到 PCM
+            List<String> silkDecodeCommand = new ArrayList<>();
+            silkDecodeCommand.add("silk_v3_decoder");
+            silkDecodeCommand.add(downloadedSilkFilePath.toString());
+            silkDecodeCommand.add(pcmFilePath.toString());
+
+            ProcessBuilder silkDecoderPb = new ProcessBuilder(silkDecodeCommand);
+            silkDecoderPb.redirectErrorStream(true); // 将错误流合并到标准输出
+            Process silkDecoderProcess = silkDecoderPb.start();
+
+            String silkDecoderOutput = readInputStreamToString(silkDecoderProcess.getInputStream());
+
+            boolean silkDecoderExited = silkDecoderProcess.waitFor(60, TimeUnit.SECONDS);
+            if (!silkDecoderExited || silkDecoderProcess.exitValue() != 0) {
+                log.error("silk conversion failed or timed out. error: {}", silkDecoderOutput);
+                return null;
+            }
+            log.info("SILK decoder to PCM successfully.");
+
+            // 3. 使用 FFmpeg 将 PCM 转码为 MP3
+            Process ffmpegProcess = getFfmpegProcess(pcmFilePath, mp3FilePath);
+            String ffmpegOutput = readInputStreamToString(ffmpegProcess.getInputStream());
+
+            boolean ffmpegExited = ffmpegProcess.waitFor(120, TimeUnit.SECONDS);
+            if (!ffmpegExited || ffmpegProcess.exitValue() != 0) {
+                log.error("ffmpeg conversion failed or timed out. error: {}", ffmpegOutput);
+                return null;
+            }
+            log.info("ffmpeg conversion to MP3 successfully.");
+
+            // 4. 上传oss
+            String fileName = mp3FilePath.getFileName().toString();
+            String suffix = fileName.substring(fileName.lastIndexOf("."));
+            CloudStorageService storage = OSSFactory.build();
+            return storage.uploadSuffix(Files.newInputStream(mp3FilePath), suffix);
+
+        } catch (IOException | InterruptedException | NullPointerException e) {
+            log.error("Conversion error: {}", e.getMessage());
+            return null;
+        } finally {
+            // 清理临时文件 (重要!)
+            try {
+                if (Files.exists(downloadedSilkFilePath)) Files.delete(downloadedSilkFilePath);
+                if (Files.exists(pcmFilePath)) Files.delete(pcmFilePath);
+                if (Files.exists(mp3FilePath)) Files.delete(mp3FilePath);
+            } catch (IOException e) {
+                log.error("Error cleaning up temporary files:: {}", e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * 执行ffmpeg
+     * @param pcmFilePath   pcm文件
+     * @param mp3FilePath   mp3地址
+     * @return  process
+     * @throws IOException exception
+     */
+    private static Process getFfmpegProcess(Path pcmFilePath, Path mp3FilePath) throws IOException {
+        List<String> ffmpegCommand = new ArrayList<>();
+        ffmpegCommand.add("ffmpeg");
+        ffmpegCommand.add("-y");
+        ffmpegCommand.add("-f");
+        ffmpegCommand.add("s16le");
+        ffmpegCommand.add("-ar");
+        ffmpegCommand.add("24000"); // 注意:这里假设是 24kHz,如果你的 SILK 文件是其他采样率,请调整
+        ffmpegCommand.add("-ac");
+        ffmpegCommand.add("1");
+        ffmpegCommand.add("-i");
+        ffmpegCommand.add(pcmFilePath.toString());
+        ffmpegCommand.add(mp3FilePath.toString());
+
+        ProcessBuilder ffmpegPb = new ProcessBuilder(ffmpegCommand);
+        ffmpegPb.redirectErrorStream(true);
+        return ffmpegPb.start();
+    }
+
+    /**
+     * 处理文件流
+     * @param is 输入流
+     * @return  输出
+     * @throws IOException exception
+     */
+    private static String readInputStreamToString(InputStream is) throws IOException {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int len;
+        while ((len = is.read(buffer)) != -1) {
+            bos.write(buffer, 0, len);
+        }
+        return bos.toString("UTF-8"); // 使用 UTF-8 编码
+    }
+
+    /**
+     * 下载网络文件
+     * @param fileUrl       网络文件
+     * @param destination   临时文件
+     * @throws IOException  exception
+     */
+    private static void downloadFile(String fileUrl, Path destination) throws IOException {
+        try (CloseableHttpClient httpClient = HttpClients.createDefault();
+             CloseableHttpResponse response = httpClient.execute(new HttpGet(fileUrl));
+             InputStream inputStream = response.getEntity().getContent();
+             FileOutputStream outputStream = new FileOutputStream(destination.toFile())) {
+
+            if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) {
+                throw new IOException("Failed to download file from " + fileUrl + ", HTTP Status: " + response.getStatusLine().getStatusCode());
+            }
+
+            byte[] buffer = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                outputStream.write(buffer, 0, bytesRead);
+            }
+        }
+    }
+}

+ 3 - 3
fs-qw-api-msg/src/main/java/com/fs/framework/aspectj/DataScopeAspect.java → fs-qw-api-msg/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-msg/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-msg/src/main/java/com/fs/framework/aspectj/LogAspect.java → fs-qw-api-msg/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-msg/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-msg/src/main/java/com/fs/framework/config/ApplicationConfig.java → fs-qw-api-msg/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-msg/src/main/java/com/fs/framework/config/ArrayStringTypeHandler.java → fs-qw-api-msg/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-msg/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-msg/src/main/java/com/fs/framework/config/DataSourceConfig.java → fs-qw-api-msg/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-msg/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-msg/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-msg/src/main/java/com/fs/framework/config/KaptchaTextCreator.java → fs-qw-api-msg/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-msg/src/main/java/com/fs/framework/config/MyBatisConfig.java → fs-qw-api-msg/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-msg/src/main/java/com/fs/framework/config/RedisConfig.java → fs-qw-api-msg/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-msg/src/main/java/com/fs/framework/config/ResourcesConfig.java → fs-qw-api-msg/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;

+ 6 - 5
fs-qw-api-msg/src/main/java/com/fs/framework/config/SecurityConfig.java → fs-qw-api-msg/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;
@@ -106,7 +106,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                         "/**/*.html",
                         "/**/*.css",
                         "/**/*.js",
-                        "/profile/**"
+                        "/profile/**",
+                        "/qwImSocket/**"
                 ).permitAll()
 
                 .antMatchers("/**").anonymous()

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/ServerConfig.java → fs-qw-api-msg/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-msg/src/main/java/com/fs/framework/config/SwaggerConfig.java → fs-qw-api-msg/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-msg/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;

+ 17 - 0
fs-qw-api-msg/src/main/java/com/fs/core/config/WebSocketConfig.java

@@ -0,0 +1,17 @@
+package com.fs.core.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+@Configuration
+public class WebSocketConfig {
+    /**
+     * ServerEndpointExporter 作用
+     * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
+     */
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+}

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/properties/DruidProperties.java → fs-qw-api-msg/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-msg/src/main/java/com/fs/framework/datasource/DynamicDataSource.java → fs-qw-api-msg/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-msg/src/main/java/com/fs/framework/datasource/DynamicDataSourceContextHolder.java → fs-qw-api-msg/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-msg/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-msg/src/main/java/com/fs/framework/interceptor/RepeatSubmitInterceptor.java → fs-qw-api-msg/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-msg/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-msg/src/main/java/com/fs/framework/manager/AsyncManager.java → fs-qw-api-msg/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-msg/src/main/java/com/fs/framework/manager/ShutdownManager.java → fs-qw-api-msg/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-msg/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-msg/src/main/java/com/fs/framework/security/LoginBody.java → fs-qw-api-msg/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-msg/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-msg/src/main/java/com/fs/framework/security/SecurityUtils.java → fs-qw-api-msg/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-msg/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-msg/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-msg/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-msg/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-msg/src/main/java/com/fs/framework/service/CompanyPermissionService.java → fs-qw-api-msg/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-msg/src/main/java/com/fs/framework/service/PermissionService.java → fs-qw-api-msg/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-msg/src/main/java/com/fs/framework/service/TokenService.java → fs-qw-api-msg/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-msg/src/main/java/com/fs/framework/service/UserDetailsServiceImpl.java → fs-qw-api-msg/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;

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


+ 1 - 1
fs-qw-api-msg/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>

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

+ 117 - 0
fs-qw-api/src/main/java/com/fs/core/aspectj/RateLimiterAspect.java

@@ -0,0 +1,117 @@
+package com.fs.core.aspectj;
+
+import com.fs.common.annotation.RateLimiter;
+import com.fs.common.enums.LimitType;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ip.IpUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.RedisScript;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 限流处理
+ *
+
+ */
+@Aspect
+@Component
+public class RateLimiterAspect
+{
+    private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
+
+    private RedisTemplate<Object, Object> redisTemplate;
+
+    private RedisScript<Long> limitScript;
+
+    @Autowired
+    public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate)
+    {
+        this.redisTemplate = redisTemplate;
+    }
+
+    @Autowired
+    public void setLimitScript(RedisScript<Long> limitScript)
+    {
+        this.limitScript = limitScript;
+    }
+
+    // 配置织入点
+    @Pointcut("@annotation(com.fs.common.annotation.RateLimiter)")
+    public void rateLimiterPointCut()
+    {
+    }
+
+    @Before("rateLimiterPointCut()")
+    public void doBefore(JoinPoint point) throws Throwable
+    {
+        RateLimiter rateLimiter = getAnnotationRateLimiter(point);
+        String key = rateLimiter.key();
+        int time = rateLimiter.time();
+        int count = rateLimiter.count();
+
+        String combineKey = getCombineKey(rateLimiter, point);
+        List<Object> keys = Collections.singletonList(combineKey);
+        try
+        {
+            Long number = redisTemplate.execute(limitScript, keys, count, time);
+            if (StringUtils.isNull(number) || number.intValue() > count)
+            {
+                throw new ServiceException("访问过于频繁,请稍后再试");
+            }
+            log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key);
+        }
+        catch (ServiceException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("服务器限流异常,请稍后再试");
+        }
+    }
+
+    /**
+     * 是否存在注解,如果存在就获取
+     */
+    private RateLimiter getAnnotationRateLimiter(JoinPoint joinPoint)
+    {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+
+        if (method != null)
+        {
+            return method.getAnnotation(RateLimiter.class);
+        }
+        return null;
+    }
+
+    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point)
+    {
+        StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
+        if (rateLimiter.limitType() == LimitType.IP)
+        {
+            stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest()));
+        }
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        Method method = signature.getMethod();
+        Class<?> targetClass = method.getDeclaringClass();
+        stringBuffer.append("-").append(targetClass.getName()).append("- ").append(method.getName());
+        return stringBuffer.toString();
+    }
+}

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

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff