Quellcode durchsuchen

Merge remote-tracking branch 'origin/master' into ScrmStore

# Conflicts:
#	fs-admin/src/main/resources/application.yml
#	fs-service/src/main/java/com/fs/company/domain/Company.java
#	fs-service/src/main/resources/application-common.yml
yjwang vor 2 Tagen
Ursprung
Commit
eb78e3c665
61 geänderte Dateien mit 2342 neuen und 306 gelöschten Zeilen
  1. 24 2
      fs-admin/src/main/java/com/fs/crm/controller/CrmCustomerController.java
  2. 51 1
      fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderController.java
  3. 37 6
      fs-admin/src/main/java/com/fs/his/controller/FsUserController.java
  4. 3 1
      fs-admin/src/main/resources/application.yml
  5. 6 2
      fs-common/src/main/java/com/fs/common/annotation/Excel.java
  6. 14 2
      fs-common/src/main/java/com/fs/common/core/domain/entity/SysRole.java
  7. 63 0
      fs-common/src/main/java/com/fs/common/utils/ExcelUtils.java
  8. 66 45
      fs-common/src/main/java/com/fs/common/utils/poi/ExcelUtil.java
  9. 13 2
      fs-company/src/main/java/com/fs/company/controller/store/FsStoreOrderController.java
  10. 40 5
      fs-qw-task/src/main/java/com/fs/app/task/qwTask.java
  11. 10 0
      fs-qw-task/src/main/java/com/fs/app/taskService/QwExternalContactRatingMoreSevenDaysService.java
  12. 9 0
      fs-qw-task/src/main/java/com/fs/app/taskService/SopUserLogsInfoByIsDaysNotStudy.java
  13. 333 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/QwExternalContactRatingMoreSevenDaysServiceImpl.java
  14. 50 60
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  15. 262 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopUserLogsInfoByIsDaysNotStudyImpl.java
  16. 13 0
      fs-qw-task/src/main/java/com/fs/framework/config/ThreadPoolConfig.java
  17. 7 0
      fs-service/src/main/java/com/fs/company/domain/Company.java
  18. 41 0
      fs-service/src/main/java/com/fs/company/domain/CompanyMiniapp.java
  19. 62 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyMiniappMapper.java
  20. 70 0
      fs-service/src/main/java/com/fs/company/service/ICompanyMiniappService.java
  21. 141 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyMiniappServiceImpl.java
  22. 18 1
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  23. 3 0
      fs-service/src/main/java/com/fs/company/vo/CompanyVO.java
  24. 28 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  25. 1 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  26. 1 0
      fs-service/src/main/java/com/fs/his/enums/FsStoreOrderStatusEnum.java
  27. 1 1
      fs-service/src/main/java/com/fs/his/service/IFsExportTaskService.java
  28. 10 0
      fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java
  29. 6 2
      fs-service/src/main/java/com/fs/his/service/impl/FsExportTaskServiceImpl.java
  30. 127 4
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  31. 32 0
      fs-service/src/main/java/com/fs/his/vo/FsStoreOrderStatusExcelVO.java
  32. 4 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  33. 6 0
      fs-service/src/main/java/com/fs/sop/domain/SopUserLogsInfo.java
  34. 1 1
      fs-service/src/main/java/com/fs/sop/mapper/QwSopLogsMapper.java
  35. 52 0
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsInfoMapper.java
  36. 7 0
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java
  37. 1 0
      fs-service/src/main/java/com/fs/sop/params/QwRatingConfig.java
  38. 8 0
      fs-service/src/main/java/com/fs/sop/service/ISopUserLogsInfoService.java
  39. 3 0
      fs-service/src/main/java/com/fs/sop/service/ISopUserLogsService.java
  40. 318 77
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopLogsServiceImpl.java
  41. 84 13
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  42. 5 0
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java
  43. 1 1
      fs-service/src/main/java/com/fs/sop/vo/QwRatingVO.java
  44. 16 13
      fs-service/src/main/java/com/fs/system/mapper/SysRoleMapper.java
  45. 23 21
      fs-service/src/main/java/com/fs/system/service/ISysRoleService.java
  46. 37 22
      fs-service/src/main/java/com/fs/system/service/impl/SysRoleServiceImpl.java
  47. 1 1
      fs-service/src/main/resources/application-common.yml
  48. 4 0
      fs-service/src/main/resources/application-config-druid-fby.yml
  49. 1 1
      fs-service/src/main/resources/application-config-druid-hcl.yml
  50. 1 1
      fs-service/src/main/resources/application-config-druid-sxjz.yml
  51. 1 0
      fs-service/src/main/resources/application-config-druid-xzt.yml
  52. 7 1
      fs-service/src/main/resources/application-druid-sxjz.yml
  53. 3 0
      fs-service/src/main/resources/application-druid-xzt.yml
  54. 91 0
      fs-service/src/main/resources/mapper/CompanyMiniappMapper.xml
  55. 12 0
      fs-service/src/main/resources/mapper/company/CompanyMapper.xml
  56. 24 0
      fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml
  57. 12 1
      fs-service/src/main/resources/mapper/sop/SopUserLogsInfoMapper.xml
  58. 41 0
      fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml
  59. 31 18
      fs-service/src/main/resources/mapper/system/SysRoleMapper.xml
  60. 1 0
      fs-user-app/src/main/java/com/fs/app/controller/CourseController.java
  61. 4 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwLoginController.java

+ 24 - 2
fs-admin/src/main/java/com/fs/crm/controller/CrmCustomerController.java

@@ -4,6 +4,8 @@ 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.domain.entity.SysRole;
+import com.fs.common.core.domain.entity.SysUser;
 import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
@@ -19,6 +21,7 @@ import com.fs.crm.service.ICrmCustomerService;
 import com.fs.crm.vo.CrmCustomerExportVO;
 import com.fs.crm.vo.CrmCustomerListVO;
 import com.fs.framework.web.service.TokenService;
+import com.fs.system.service.ISysRoleService;
 import com.github.pagehelper.PageHelper;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -148,13 +151,14 @@ public class CrmCustomerController extends BaseController
         crmCustomer.setIsLine(0);
         List<CrmCustomerListVO> list = crmCustomerService.selectCrmCustomerListVO(crmCustomer);
         List<CrmCustomerExportVO> exportList=new ArrayList<>();
+        boolean checkPhone = isCheckPhone();
         for(CrmCustomerListVO customer:list){
             CrmCustomerExportVO vo=new CrmCustomerExportVO();
             if(customer.getSource()!=null){
                 vo.setSource(customer.getSource().toString());
             }
             BeanUtils.copyProperties(customer,vo);
-            if(customer.getMobile()!=null){
+            if(customer.getMobile()!=null && !checkPhone){
                 vo.setMobile(customer.getMobile().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
             }
             exportList.add(vo);
@@ -193,8 +197,9 @@ public class CrmCustomerController extends BaseController
         }
         List<CrmCustomerListVO> list = crmCustomerService.selectCrmCustomerListQueryParam(crmCustomer);
         if (list != null) {
+            boolean checkPhone = isCheckPhone();
             for (CrmCustomerListVO vo : list) {
-                if(vo.getMobile()!=null){
+                if(vo.getMobile()!=null && !checkPhone){
                     vo.setMobile(vo.getMobile().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
                 }
 
@@ -204,6 +209,23 @@ public class CrmCustomerController extends BaseController
         return getDataTable(list);
     }
 
+    @Autowired
+    private ISysRoleService sysRoleService;
+    private boolean isCheckPhone() {
+        SysUser user = getLoginUser().getUser();
+        boolean flag = user.isAdmin();
+        if (flag) {
+            return true;
+        }
+        List<SysRole> roles = user.getRoles();
+        if (roles != null && !roles.isEmpty()) {
+            Long[] roleIds = roles.stream().map(SysRole::getRoleId).toArray(Long[]::new);
+            return sysRoleService.getIsCheckPhone(roleIds);
+        }
+
+        return false;
+    }
+
 
     @PreAuthorize("@ss.hasPermi('crm:customer:query')")
     @GetMapping(value = "/query/{customerId}")

+ 51 - 1
fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderController.java

@@ -9,6 +9,8 @@ import java.util.stream.Collectors;
 import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.entity.SysRole;
+import com.fs.common.core.domain.entity.SysUser;
 import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.utils.ParseUtils;
 import com.fs.common.utils.SecurityUtils;
@@ -43,6 +45,7 @@ import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.*;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
+import com.fs.system.service.ISysRoleService;
 import com.github.pagehelper.PageHelper;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -213,13 +216,31 @@ public class FsStoreOrderController extends BaseController
         task.setUserId(SecurityUtils.getUserId());
         exportTaskService.insertFsExportTask(task);
         param.setTaskId(task.getTaskId());
-        exportTaskService.exportStore1Data(param);
+        boolean checkPhone = isCheckPhone();
+        exportTaskService.exportStore1Data(param,checkPhone);
 
         return new AjaxResult(200,"后台正在导出,请等待...任务ID:"+task.getTaskId(),task.getTaskId());
 
 
     }
 
+    @Autowired
+    private ISysRoleService sysRoleService;
+    private boolean isCheckPhone() {
+        SysUser user = getLoginUser().getUser();
+        boolean flag = user.isAdmin();
+        if (flag) {
+            return true;
+        }
+        List<SysRole> roles = user.getRoles();
+        if (roles != null && !roles.isEmpty()) {
+            Long[] roleIds = roles.stream().map(SysRole::getRoleId).toArray(Long[]::new);
+            return sysRoleService.getIsCheckPhone(roleIds);
+        }
+
+        return false;
+    }
+
     @GetMapping("/importTemplate")
     public AjaxResult sendExport()
     {
@@ -227,6 +248,13 @@ public class FsStoreOrderController extends BaseController
         return util.importTemplateExcel("导入运单号");
     }
 
+    @GetMapping("/importUpdateOrderTemplate")
+    public AjaxResult importUpdateOrderTemplate()
+    {
+        ExcelUtil<FsStoreOrderStatusExcelVO> util = new ExcelUtil<>(FsStoreOrderStatusExcelVO.class);
+        return util.importTemplateExcel("修改订单");
+    }
+
     @Log(title = "导入", businessType = BusinessType.IMPORT)
     @PreAuthorize("@ss.hasPermi('store:storeOrder:importExpress')")
     @PostMapping("/importExpress")
@@ -248,6 +276,17 @@ public class FsStoreOrderController extends BaseController
         return AjaxResult.success(message);
     }
 
+    @Log(title = "导入修改订单", businessType = BusinessType.IMPORT)
+    @PostMapping("/importOrderStatusData")
+    @PreAuthorize("@ss.hasPermi('his:storeOrder:editImport')")
+    public AjaxResult importOrderStatusData(MultipartFile file, boolean updateSupport) throws Exception
+    {
+        ExcelUtil<FsStoreOrderStatusExcelVO> util = new ExcelUtil<>(FsStoreOrderStatusExcelVO.class);
+        List<FsStoreOrderStatusExcelVO> list = util.importExcel(file.getInputStream());
+        String message = fsStoreOrderService.importOrderStatusData(list);
+        return AjaxResult.success(message);
+    }
+
     @GetMapping("/importExpressTemplate")
     public AjaxResult importExpressTemplate() {
         ExcelUtil<StoreOrderExpressExportDTO> util = new ExcelUtil<StoreOrderExpressExportDTO>(StoreOrderExpressExportDTO.class);
@@ -341,6 +380,17 @@ public class FsStoreOrderController extends BaseController
         return toAjax(fsStoreOrderService.updateFsStoreOrder(fsStoreOrder));
     }
 
+    /**
+     * 修改订单
+     */
+    @PreAuthorize("@ss.hasPermi('his:storeOrder:edit')")
+    @Log(title = "订单", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateStoreOrder")
+    public R updateStoreOrder(@RequestBody FsStoreOrder fsStoreOrder)
+    {
+        return fsStoreOrderService.updateStoreOrder(fsStoreOrder);
+    }
+
     @PreAuthorize("@ss.hasPermi('his:storeOrder:updateDelivery')")
     @Log(title = "修改物流", businessType = BusinessType.UPDATE)
     @PutMapping("/updateDelivery")

+ 37 - 6
fs-admin/src/main/java/com/fs/his/controller/FsUserController.java

@@ -7,6 +7,8 @@ import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
+import com.fs.common.core.domain.entity.SysRole;
+import com.fs.common.core.domain.entity.SysUser;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.ParseUtils;
 import com.fs.common.utils.SecurityUtils;
@@ -28,6 +30,7 @@ import com.fs.his.vo.UserVo;
 import com.fs.qw.dto.UserProjectDTO;
 import com.fs.store.param.h5.FsUserPageListParam;
 import com.fs.store.vo.h5.FsUserPageListVO;
+import com.fs.system.service.ISysRoleService;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import com.google.common.collect.Lists;
@@ -94,20 +97,43 @@ public class FsUserController extends BaseController
             fsUser.setPhone(encryptPhone(fsUser.getPhone()));
         }
         List<FsUserVO> list = fsUserService.selectFsUserListVO(fsUser);
+        boolean checkPhone = isCheckPhone();
         for (FsUserVO fsUserVO : list) {
             if(fsUserVO.getPhone() != null&&fsUserVO.getPhone()!=""){
-                if (fsUserVO.getPhone().length()>11){
-                    fsUserVO.setPhone(decryptPhoneMk(fsUserVO.getPhone()));
-                }else {
-                    fsUserVO.setPhone(fsUserVO.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                if (!checkPhone){
+                    if (fsUserVO.getPhone().length()>11){
+                        fsUserVO.setPhone(decryptPhoneMk(fsUserVO.getPhone()));
+                    }else {
+                        fsUserVO.setPhone(fsUserVO.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                    }
+                } else {
+                    if (fsUserVO.getPhone().length()>11) {
+                        fsUserVO.setPhone(decryptPhone(fsUserVO.getPhone()));
+                    }
                 }
-
             }
 
         }
         return getDataTable(list);
     }
 
+    @Autowired
+    private ISysRoleService sysRoleService;
+    private boolean isCheckPhone() {
+        SysUser user = getLoginUser().getUser();
+        boolean flag = user.isAdmin();
+        if (flag) {
+            return true;
+        }
+        List<SysRole> roles = user.getRoles();
+        if (roles != null && !roles.isEmpty()) {
+            Long[] roleIds = roles.stream().map(SysRole::getRoleId).toArray(Long[]::new);
+            return sysRoleService.getIsCheckPhone(roleIds);
+        }
+
+        return false;
+    }
+
     @PreAuthorize("@ss.hasPermi('his:user:list')")
     @GetMapping("/listProject")
     public TableDataInfo listProject(FsUser fsUser)
@@ -137,9 +163,14 @@ public class FsUserController extends BaseController
             return AjaxResult.error("导出数据不可超过1w条");
         }
         List<FsUserExportListVO> list = fsUserService.selectFsUserExportListVO(fsUser);
+        boolean checkPhone = isCheckPhone();
         for (FsUserExportListVO vo : list) {
-            if (vo.getMobile()!=null){
+            if (vo.getMobile()!=null && !checkPhone){
                 vo.setMobile(vo.getMobile().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+            } else {
+                if (vo.getMobile().length()>11){
+                    vo.setMobile(decryptPhone(vo.getMobile()));
+                }
             }
         }
         ExcelUtil<FsUserExportListVO> util = new ExcelUtil<FsUserExportListVO>(FsUserExportListVO.class);

+ 3 - 1
fs-admin/src/main/resources/application.yml

@@ -4,9 +4,11 @@ server:
 # Spring配置
 spring:
   profiles:
-    active: dev
+#    active: druid-myhk-test
 #    active: druid-hdt
 #    active: druid-yzt
 #    active: druid-sxjz
 #    active: druid-sft
+#    active: druid-fby
+    active: dev
 

+ 6 - 2
fs-common/src/main/java/com/fs/common/annotation/Excel.java

@@ -8,13 +8,17 @@ import java.math.BigDecimal;
 
 /**
  * 自定义导出Excel数据注解
- * 
+ *
 
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.FIELD)
 public @interface Excel
 {
+    /**
+     * 是否为必填字段
+     */
+    boolean required() default false;
     /**
      * 导出时在excel中排序
      */
@@ -162,4 +166,4 @@ public @interface Excel
             return this.value;
         }
     }
-}
+}

+ 14 - 2
fs-common/src/main/java/com/fs/common/core/domain/entity/SysRole.java

@@ -10,7 +10,7 @@ import com.fs.common.core.domain.BaseEntity;
 
 /**
  * 角色表 sys_role
- * 
+ *
 
  */
 public class SysRole extends BaseEntity
@@ -59,6 +59,9 @@ public class SysRole extends BaseEntity
     /** 部门组(数据权限) */
     private Long[] deptIds;
 
+    /** 是否可以查看手机全号 0否 1是 */
+    private Integer isCheckPhone;
+
     public SysRole()
     {
 
@@ -203,7 +206,15 @@ public class SysRole extends BaseEntity
     {
         this.deptIds = deptIds;
     }
-    
+
+    public Integer getIsCheckPhone() {
+        return isCheckPhone;
+    }
+
+    public void setIsCheckPhone(Integer isCheckPhone) {
+        this.isCheckPhone = isCheckPhone;
+    }
+
     @Override
     public String toString() {
         return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@@ -221,6 +232,7 @@ public class SysRole extends BaseEntity
             .append("updateBy", getUpdateBy())
             .append("updateTime", getUpdateTime())
             .append("remark", getRemark())
+            .append("isCheckPhone", getIsCheckPhone())
             .toString();
     }
 }

+ 63 - 0
fs-common/src/main/java/com/fs/common/utils/ExcelUtils.java

@@ -0,0 +1,63 @@
+package com.fs.common.utils;
+
+import com.fs.common.annotation.Excel;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * excel工具类
+ */
+public class ExcelUtils {
+    private static final Map<Class<?>, List<Field>> fieldCache = new ConcurrentHashMap<>();
+
+    public static void validateRequiredFields(Object obj, int rowIndex) throws Exception {
+        List<String> errorMessages = new ArrayList<>();
+        Class<?> clazz = obj.getClass();
+
+        // 从缓存中获取字段
+        List<Field> fields = getCachedFields(clazz);
+
+        for (Field field : fields) {
+            if (field.isAnnotationPresent(Excel.class)) {
+                Excel excel = field.getAnnotation(Excel.class);
+                if (excel.required()) {
+                    field.setAccessible(true);
+                    Object value = field.get(obj);
+                    if (isEmpty(value)) {
+                        errorMessages.add("第 " + rowIndex + " 行的 " + excel.name() + " 是必填项,不能为空!");
+                    }
+                }
+            }
+        }
+
+        if (!errorMessages.isEmpty()) {
+            throw new Exception(String.join("; ", errorMessages));
+        }
+    }
+
+    private static List<Field> getCachedFields(Class<?> clazz) {
+        return fieldCache.computeIfAbsent(clazz, k -> {
+            Field[] fields = k.getDeclaredFields();
+            return Arrays.asList(fields);
+        });
+    }
+
+    private static boolean isEmpty(Object value) {
+        if (value == null) {
+            return true;
+        }
+        if (value instanceof String) {
+            return ((String) value).trim().isEmpty();
+        }
+        if (value instanceof Iterable) {
+            return !((Iterable<?>) value).iterator().hasNext();
+        }
+        return false;
+    }
+
+}

+ 66 - 45
fs-common/src/main/java/com/fs/common/utils/poi/ExcelUtil.java

@@ -26,26 +26,7 @@ import org.apache.poi.hssf.usermodel.HSSFShape;
 import org.apache.poi.hssf.usermodel.HSSFSheet;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.ooxml.POIXMLDocumentPart;
-import org.apache.poi.ss.usermodel.BorderStyle;
-import org.apache.poi.ss.usermodel.Cell;
-import org.apache.poi.ss.usermodel.CellStyle;
-import org.apache.poi.ss.usermodel.CellType;
-import org.apache.poi.ss.usermodel.ClientAnchor;
-import org.apache.poi.ss.usermodel.DataValidation;
-import org.apache.poi.ss.usermodel.DataValidationConstraint;
-import org.apache.poi.ss.usermodel.DataValidationHelper;
-import org.apache.poi.ss.usermodel.DateUtil;
-import org.apache.poi.ss.usermodel.Drawing;
-import org.apache.poi.ss.usermodel.FillPatternType;
-import org.apache.poi.ss.usermodel.Font;
-import org.apache.poi.ss.usermodel.HorizontalAlignment;
-import org.apache.poi.ss.usermodel.IndexedColors;
-import org.apache.poi.ss.usermodel.PictureData;
-import org.apache.poi.ss.usermodel.Row;
-import org.apache.poi.ss.usermodel.Sheet;
-import org.apache.poi.ss.usermodel.VerticalAlignment;
-import org.apache.poi.ss.usermodel.Workbook;
-import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.ss.util.CellRangeAddressList;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.xssf.streaming.SXSSFWorkbook;
@@ -77,7 +58,7 @@ import com.fs.common.utils.reflect.ReflectUtils;
 
 /**
  * Excel相关处理
- * 
+ *
 
  */
 public class ExcelUtil<T>
@@ -164,7 +145,7 @@ public class ExcelUtil<T>
 
     /**
      * 对excel表单默认第一个索引名转换成list
-     * 
+     *
      * @param is 输入流
      * @return 转换后集合
      */
@@ -175,7 +156,7 @@ public class ExcelUtil<T>
 
     /**
      * 对excel表单指定表格索引名转换成list
-     * 
+     *
      * @param sheetName 表格索引名
      * @param is 输入流
      * @return 转换后集合
@@ -354,7 +335,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @param list 导出数据集合
      * @param sheetName 工作表的名称
      * @return 结果
@@ -367,7 +348,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @param response 返回数据
      * @param list 导出数据集合
      * @param sheetName 工作表的名称
@@ -384,7 +365,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @param sheetName 工作表的名称
      * @return 结果
      */
@@ -396,7 +377,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @param sheetName 工作表的名称
      * @return 结果
      */
@@ -410,7 +391,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @return 结果
      */
     public void exportExcel(OutputStream out)
@@ -433,7 +414,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @return 结果
      */
     public AjaxResult exportExcel()
@@ -489,7 +470,7 @@ public class ExcelUtil<T>
 
     /**
      * 填充excel数据
-     * 
+     *
      * @param index 序号
      * @param row 单元格行
      */
@@ -516,7 +497,7 @@ public class ExcelUtil<T>
 
     /**
      * 创建表格样式
-     * 
+     *
      * @param wb 工作薄对象
      * @return 样式列表
      */
@@ -591,14 +572,54 @@ public class ExcelUtil<T>
         Cell cell = row.createCell(column);
         // 写入列信息
         cell.setCellValue(attr.name());
+//        setDataValidation(attr, row, column);
+//        cell.setCellStyle(styles.get("header"));
+        // 获取默认样式
+        CellStyle cellStyle = styles.get("header");
+        // 如果是必填项,应用红色字体样式
+        if (attr.required()) {
+//            CellStyle requiredStyle = row.getSheet().getWorkbook().createCellStyle();
+//            requiredStyle.cloneStyleFrom(cellStyle); // 复制原样式
+//            Font font = row.getSheet().getWorkbook().createFont();
+//            font.setColor(IndexedColors.RED.getIndex()); // 设置字体红色
+//            requiredStyle.setFont(font);
+//            cellStyle = requiredStyle;
+
+            // 添加 Excel 注释,提示用户该字段必填
+            addCellComment(cell, row.getSheet(), "必填项");
+        }
+        // 应用单元格样式
+        cell.setCellStyle(cellStyle);
+        // 设置数据校验规则(如果有)
         setDataValidation(attr, row, column);
-        cell.setCellStyle(styles.get("header"));
         return cell;
     }
 
+    /**
+     * 给 Excel 单元格添加注释
+     */
+    private void addCellComment(Cell cell, Sheet sheet, String commentText) {
+        if (cell.getCellComment() != null) {
+            return; // 如果已存在注释,直接返回,避免重复添加
+        }
+        Drawing<?> drawing = sheet.createDrawingPatriarch();
+        CreationHelper factory = sheet.getWorkbook().getCreationHelper();
+
+        // 创建注释框
+        ClientAnchor anchor = factory.createClientAnchor();
+        // 确保 anchor 对象配置正确
+        anchor.setCol1(cell.getColumnIndex());  // 设置注释的列
+        anchor.setRow1(cell.getRowIndex());    // 设置注释的行
+        Comment comment = drawing.createCellComment(anchor);
+        comment.setString(factory.createRichTextString(commentText));
+
+        // 绑定注释到单元格
+        cell.setCellComment(comment);
+    }
+
     /**
      * 设置单元格信息
-     * 
+     *
      * @param value 单元格值
      * @param attr 注解相关
      * @param cell 单元格信息
@@ -743,7 +764,7 @@ public class ExcelUtil<T>
 
     /**
      * 设置 POI XSSFSheet 单元格提示
-     * 
+     *
      * @param sheet 表单
      * @param promptTitle 提示标题
      * @param promptContent 提示内容
@@ -766,7 +787,7 @@ public class ExcelUtil<T>
 
     /**
      * 设置某些列的值只能输入预制的数据,显示下拉框.
-     * 
+     *
      * @param sheet 要设置的sheet.
      * @param textlist 下拉框显示的内容
      * @param firstRow 开始行
@@ -800,7 +821,7 @@ public class ExcelUtil<T>
 
     /**
      * 解析导出值 0=男,1=女,2=未知
-     * 
+     *
      * @param propertyValue 参数值
      * @param converterExp 翻译注解
      * @param separator 分隔符
@@ -837,7 +858,7 @@ public class ExcelUtil<T>
 
     /**
      * 反向解析值 男=0,女=1,未知=2
-     * 
+     *
      * @param propertyValue 参数值
      * @param converterExp 翻译注解
      * @param separator 分隔符
@@ -874,7 +895,7 @@ public class ExcelUtil<T>
 
     /**
      * 解析字典值
-     * 
+     *
      * @param dictValue 字典值
      * @param dictType 字典类型
      * @param separator 分隔符
@@ -887,7 +908,7 @@ public class ExcelUtil<T>
 
     /**
      * 反向解析值字典值
-     * 
+     *
      * @param dictLabel 字典标签
      * @param dictType 字典类型
      * @param separator 分隔符
@@ -955,7 +976,7 @@ public class ExcelUtil<T>
 
     /**
      * 获取下载路径
-     * 
+     *
      * @param filename 文件名称
      */
     public String getAbsoluteFile(String filename)
@@ -971,7 +992,7 @@ public class ExcelUtil<T>
 
     /**
      * 获取bean中的属性值
-     * 
+     *
      * @param vo 实体对象
      * @param field 字段
      * @param excel 注解
@@ -1002,7 +1023,7 @@ public class ExcelUtil<T>
 
     /**
      * 以类的属性的get方法方法形式获取值
-     * 
+     *
      * @param o
      * @param name
      * @return value
@@ -1087,7 +1108,7 @@ public class ExcelUtil<T>
 
     /**
      * 创建工作表
-     * 
+     *
      * @param sheetNo sheet数量
      * @param index 序号
      */
@@ -1108,7 +1129,7 @@ public class ExcelUtil<T>
 
     /**
      * 获取单元格值
-     * 
+     *
      * @param row 获取的行
      * @param column 获取单元格列号
      * @return 单元格值
@@ -1168,7 +1189,7 @@ public class ExcelUtil<T>
 
     /**
      * 判断是否是空行
-     * 
+     *
      * @param row 判断的行
      * @return
      */

+ 13 - 2
fs-company/src/main/java/com/fs/company/controller/store/FsStoreOrderController.java

@@ -130,7 +130,7 @@ public class FsStoreOrderController extends BaseController
         task.setCompanyUserId(userId);
         exportTaskService.insertFsExportTask(task);
         param.setTaskId(task.getTaskId());
-        exportTaskService.exportStore1Data(param);
+        exportTaskService.exportStore1Data(param,false);
         return new AjaxResult(200,"后台正在导出,请等待...任务ID:"+task.getTaskId(),task.getTaskId());
 
 
@@ -172,7 +172,7 @@ public class FsStoreOrderController extends BaseController
         task.setCompanyUserId(userId);
         exportTaskService.insertFsExportTask(task);
         param.setTaskId(task.getTaskId());
-        exportTaskService.exportStore1Data(param);
+        exportTaskService.exportStore1Data(param,false);
         return new AjaxResult(200,"后台正在导出,请等待...任务ID:"+task.getTaskId(),task.getTaskId());
     }
     /**
@@ -298,6 +298,17 @@ public class FsStoreOrderController extends BaseController
         return toAjax(fsStoreOrderService.updateFsStoreOrder(fsStoreOrder));
     }
 
+    /**
+     * 修改订单
+     */
+    @PreAuthorize("@ss.hasPermi('his:storeOrder:edit')")
+    @Log(title = "订单", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateStoreOrder")
+    public R updateStoreOrder(@RequestBody FsStoreOrder fsStoreOrder)
+    {
+        return fsStoreOrderService.updateStoreOrder(fsStoreOrder);
+    }
+
     /**
      * 删除订单
      */

+ 40 - 5
fs-qw-task/src/main/java/com/fs/app/task/qwTask.java

@@ -1,9 +1,6 @@
 package com.fs.app.task;
 
-import com.fs.app.taskService.QwExternalContactRatingService;
-import com.fs.app.taskService.SopLogsChatTaskService;
-import com.fs.app.taskService.SopLogsTaskService;
-import com.fs.app.taskService.SopWxLogsService;
+import com.fs.app.taskService.*;
 import com.fs.qw.service.IQwExternalErrRetryService;
 import com.fs.qw.service.IQwGroupMsgService;
 import com.fs.qw.service.IQwWorkUserService;
@@ -75,6 +72,12 @@ public class qwTask {
     @Autowired
     private IQwSopTagService qwSopTagService;
 
+    @Autowired
+    private SopUserLogsInfoByIsDaysNotStudy logsInfoByIsDaysNotStudy;
+
+    @Autowired
+    private QwExternalContactRatingMoreSevenDaysService qwExternalContactRatingMoreSevenDaysService;
+
     /**
      * 定时任务:检查SOP规则时间
      * 执行时间:每天凌晨 1:10:00
@@ -182,7 +185,7 @@ public class qwTask {
     }
 
     /**
-     * 定时任务:获取企业微信SOP群发消息反馈结果(新版-按营期发送)
+     *
      * 执行时间:每天上午 8:00:00
      * 功能:获取通过企业微信接口发送的SOP客户群发消息的反馈结果
      */
@@ -306,6 +309,22 @@ public class qwTask {
         sopUserLogsService.repairSopUserLogsTimer();
     }
 
+
+    /**
+     * 凌晨 2点35开始,将营期小于7天中标记为 是否7天未看课的(E级) 客户的 但是看课了的恢复一下
+     */
+    @Scheduled(cron = "0 35 2 * * ?")
+    @Async
+    public void processSopUserLogsInfoByIsDaysNotStudy() {
+        long startTimeMillis = System.currentTimeMillis();
+        log.info("====== 开始选择和处理 是否7天未看课的(E级) 客户的 恢复一下 ======");
+
+        logsInfoByIsDaysNotStudy.restoreByIsDaysNotStudy();
+
+        long endTimeMillis = System.currentTimeMillis();
+        log.info("====== 用户E级恢复处理完成,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
+    }
+
     /**
      * 定时任务:客户评级处理
      * 执行时间:每天凌晨 3:45:00
@@ -327,6 +346,22 @@ public class qwTask {
         log.info("====== sop营期-用户分级处理完成,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
     }
 
+    /**
+     * 凌晨4点35开始 客户超过7天没有看课的 标记E级
+     */
+    @Scheduled(cron = "0 30 3 * * ?")
+    @Async
+    public void processQwSopExternalContactRatingMoreSevenDaysTimer() {
+        long startTimeMillis = System.currentTimeMillis();
+        log.info("====== 开始选择和处理 sop营期-用户超7天的看课情况 ======");
+
+        qwExternalContactRatingMoreSevenDaysService.ratingMoreSevenDaysUserLogs();
+
+        long endTimeMillis = System.currentTimeMillis();
+        log.info("====== sop营期-用户超7天处理完成,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
+    }
+
+
     /**
      * 更新掉所有前一天的所有待发送
      */

+ 10 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/QwExternalContactRatingMoreSevenDaysService.java

@@ -0,0 +1,10 @@
+package com.fs.app.taskService;
+
+import com.fs.common.core.domain.R;
+
+public interface QwExternalContactRatingMoreSevenDaysService {
+    /**
+     * Sop客户超7天评次
+     */
+    public R ratingMoreSevenDaysUserLogs();
+}

+ 9 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/SopUserLogsInfoByIsDaysNotStudy.java

@@ -0,0 +1,9 @@
+package com.fs.app.taskService;
+
+public interface SopUserLogsInfoByIsDaysNotStudy {
+
+    /**
+     * 将前7天营期中标记为 是否7天未看课的(E级) 客户的 恢复一下,突然有的恢复一下 (复刻版)
+     */
+    public void restoreByIsDaysNotStudy();
+}

+ 333 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/impl/QwExternalContactRatingMoreSevenDaysServiceImpl.java

@@ -0,0 +1,333 @@
+package com.fs.app.taskService.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.app.taskService.QwExternalContactRatingMoreSevenDaysService;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.sop.domain.SopUserLogs;
+import com.fs.sop.domain.SopUserLogsInfo;
+import com.fs.sop.mapper.SopUserLogsInfoMapper;
+import com.fs.sop.mapper.SopUserLogsMapper;
+import com.fs.sop.params.QwRatingConfig;
+import com.fs.sop.service.IQwSopTempDayService;
+import com.fs.sop.service.ISopUserLogsInfoService;
+import com.fs.sop.vo.QwRatingVO;
+import com.fs.system.service.ISysConfigService;
+import com.fs.voice.utils.StringUtil;
+import com.google.common.util.concurrent.AtomicDouble;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.*;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+public class QwExternalContactRatingMoreSevenDaysServiceImpl implements QwExternalContactRatingMoreSevenDaysService {
+
+
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private SopUserLogsMapper sopUserLogsMapper;
+
+    @Autowired
+    private IQwSopTempDayService qwSopTempDayService;
+
+    @Autowired
+    private SopUserLogsInfoMapper sopUserLogsInfoMapper;
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+
+    @Autowired
+    private ISopUserLogsInfoService iSopUserLogsInfoService;
+
+    @Autowired
+    private ExecutorService sopRatingExecutor;  // 自定义线程池
+
+    // 任务队列
+    private final BlockingQueue<SopUserLogs> taskQueue = new LinkedBlockingQueue<>(10000);
+
+    private volatile boolean running = true;
+    //批量更新队列
+    private final List<CompletableFuture<Void>> updateFutures = Collections.synchronizedList(new ArrayList<>());
+
+    private final Object configLock = new Object();
+
+    // 启动时初始化消费者线程
+    @PostConstruct
+    public void init() {
+
+        loadCourseConfig();
+
+        int consumerCount = Runtime.getRuntime().availableProcessors(); // 消费者线程数,默认 CPU 核心数
+        for (int i = 0; i < consumerCount; i++) {
+            sopRatingExecutor.submit(this::consumeTasks); // 提交消费者任务
+        }
+
+        log.info("初始化 {} 个消费者线程", consumerCount);
+    }
+
+    private  volatile QwRatingConfig qwRatingConfig;
+
+    private void loadCourseConfig() {
+        try {
+            String json = configService.selectConfigByKey("qwRating:config");
+            QwRatingConfig config = JSON.parseObject(json, QwRatingConfig.class);
+            if (!StringUtil.strIsNullOrEmpty(json) && config != null) {
+                qwRatingConfig = config;
+                log.info("Loaded qwRating.config successfully.");
+            } else {
+                log.error("Failed to load course.config from configService.");
+            }
+        } catch (Exception e) {
+            log.error("Exception while loading qwRating.config: {}", e.getMessage(), e);
+        }
+    }
+
+
+    @Override
+    public R ratingMoreSevenDaysUserLogs() {
+        // 分页加载并放入队列
+        int pageSize = 1000;
+        int offset = 0;
+        List<SopUserLogs> sopUserLogs;
+
+        // 获取缓存的配置
+        QwRatingConfig config;
+        synchronized(configLock) {
+            config = qwRatingConfig;
+        }
+
+        do {
+            sopUserLogs = sopUserLogsMapper.meetsTheRatingByUserInfoWithPaginationStudyDays(offset, pageSize,config.getNotStudyDays());
+            if (!sopUserLogs.isEmpty()) {
+                sopUserLogs.forEach(item -> {
+                    try {
+                        taskQueue.put(item); // 将任务放入队列
+                    } catch (InterruptedException e) {
+                        log.error("任务放入队列失败,sopId: {}", item.getSopId(), e);
+                        Thread.currentThread().interrupt();
+                    }
+                });
+                offset += pageSize;
+            }
+        } while (!sopUserLogs.isEmpty());
+
+
+        // 等待队列处理完成
+        CompletableFuture.runAsync(() -> {
+            while (!taskQueue.isEmpty()) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    log.error("等待队列处理时中断", e);
+                    Thread.currentThread().interrupt();
+                }
+            }
+        }).join(); // 等待任务完成
+
+        return R.ok();
+    }
+
+
+    private void consumeTasks() {
+
+        if (!running && taskQueue.isEmpty()) {
+            log.info("没有评级任务需要处理");
+            return; // 如果队列为空且没有正在运行的线程,则直接返回
+        }
+
+        while (running) {
+            try {
+                SopUserLogs item = taskQueue.poll(1, TimeUnit.SECONDS); // 等待 1 秒
+                if (item != null) {
+                    processSingleTask(item);
+                }
+            } catch (Exception e) {
+                log.error("消费者线程异常", e);
+            }
+        }
+    }
+
+    private void processSingleTask(SopUserLogs item) {
+
+        // 获取缓存的配置
+        QwRatingConfig config;
+        synchronized(configLock) {
+            config = qwRatingConfig;
+        }
+
+        List<SopUserLogsInfo> sopUserLogsInfosList = sopUserLogsInfoMapper
+                .selectSopUserLogsInfoListBySopId(item.getSopId(), item.getId());
+
+        if (sopUserLogsInfosList == null || sopUserLogsInfosList.isEmpty()) {
+            log.error("当前营期没有客户-sopId:{},营期id:{}", item.getSopId(), item.getId());
+            return;
+        }
+
+        List<QwExternalContact> batchQwExternalContact = sopUserLogsInfosList.stream()
+                .map(logsInfo -> processUserLog(logsInfo, config))
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (!batchQwExternalContact.isEmpty()) {
+            batchUpdateQwExternalContact(batchQwExternalContact);
+        }
+    }
+
+    private QwExternalContact processUserLog(SopUserLogsInfo logsInfo, QwRatingConfig config) {
+        try {
+            Long externalId = logsInfo.getExternalId();
+            if (externalId == null) {
+                return null;
+            }
+
+            List<QwRatingVO> ratingVOS = fsCourseWatchLogMapper
+                    .selectFsCourseWatchLogByExtIdRatingMoreStudyDays(externalId, config.getNotStudyDays());
+
+            if (ratingVOS == null || ratingVOS.isEmpty() || ratingVOS.size() < 6) {
+                log.info("没有记录或不满足条件不评级或看课记录小于6 不评级,externalId: {}", externalId);
+                return null;
+            }
+
+
+            //判断 7天的时长是否大于0
+            boolean scoreMoreStudyLevel = getScoreMoreStudyLevel(ratingVOS);
+
+            if (!scoreMoreStudyLevel) {
+                QwExternalContact externalContact = new QwExternalContact();
+                externalContact.setId(externalId);
+                externalContact.setLevel(5);
+                externalContact.setIsDaysNotStudy(1);
+                return externalContact;
+            }else {
+                QwExternalContact externalContact = new QwExternalContact();
+                externalContact.setId(externalId);
+                externalContact.setLevel(ratingVOS.get(0).getLevel());
+                externalContact.setIsDaysNotStudy(0);
+                return externalContact;
+            }
+
+
+        } catch (Exception e) {
+            log.error("计算用户积分异常,用户:{}", logsInfo, e);
+            return null;
+        }
+    }
+
+    private void batchUpdateQwExternalContact(List<QwExternalContact> notInExternalUseridList) {
+        int batchSize = 300;
+
+        for (int i = 0; i < notInExternalUseridList.size(); i += batchSize) {
+            int endIndex = Math.min(i + batchSize, notInExternalUseridList.size());
+            List<QwExternalContact> batchList = notInExternalUseridList.subList(i, endIndex);
+
+            int finalI = i;
+            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                try {
+                    qwExternalContactMapper.batchUpdateQwExternalContactByMoreStudy(batchList);
+                    iSopUserLogsInfoService.batchUpdateSopUserLogsInfoByMoreStudy(batchList);
+                    log.info("成功更新看课7天数据,起始索引: {}, 数量: {}", finalI, batchList.size());
+                } catch (Exception e) {
+                    log.error("批量更新异常,批次起始索引: {}", finalI, e);
+                }
+
+            }, sopRatingExecutor);
+
+            updateFutures.add(future);
+        }
+    }
+
+    @PreDestroy
+    public void shutdown() {
+        running = false;  // 标记消费者停止
+        log.info("正在关闭线程池...");
+
+        // **等待任务队列处理完毕**
+        while (!taskQueue.isEmpty()) {
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                log.warn("等待任务队列处理完成时被中断", e);
+            }
+        }
+
+        // **确保所有 `batchUpdateQwExternalContact` 的任务完成**
+        log.info("等待所有批量更新任务完成...");
+        CompletableFuture.allOf(updateFutures.toArray(new CompletableFuture[0])).join();
+
+        // 关闭线程池
+        sopRatingExecutor.shutdown();
+        try {
+            if (!sopRatingExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
+                List<Runnable> pendingTasks = sopRatingExecutor.shutdownNow();
+                log.warn("强制关闭线程池,未完成任务数: {}", pendingTasks.size());
+            }
+        } catch (InterruptedException e) {
+            sopRatingExecutor.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+        log.info("线程池和消费者已完全关闭");
+    }
+
+
+    /**
+     * 每6小时更新一次
+     */
+    @Scheduled(cron = "0 50 0/6 * * ?")
+    public void refreshRatingConfig() {
+
+        synchronized(configLock) {
+            try {
+                String json = configService.selectConfigByKey("qwRating:config");
+                QwRatingConfig config = JSON.parseObject(json, QwRatingConfig.class);
+                if (!StringUtil.strIsNullOrEmpty(json) && config != null) {
+                    qwRatingConfig = config;
+                    log.info("LoadedTime qwRating.config successfully.");
+                } else {
+                    log.error("Failed to load course.config from configService.");
+                }
+            } catch (Exception e) {
+                log.error("Exception while refreshing course.config: {}", e.getMessage(), e);
+            }
+        }
+
+    }
+
+
+    //查 E级
+    public boolean getScoreMoreStudyLevel(List<QwRatingVO> qwRatingVOS) {
+
+        AtomicDouble watchCount= new AtomicDouble();
+
+        qwRatingVOS.forEach(vo -> {
+            watchCount.addAndGet(vo.getWatchDuration());
+        });
+
+        // 判断总 watchDuration 是否 > 0
+        return watchCount.get() > 0;
+    }
+
+}

+ 50 - 60
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -8,7 +8,9 @@ import com.fs.common.core.domain.R;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.CompanyMiniapp;
 import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyMiniappService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.*;
@@ -150,7 +152,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private IQwGroupChatService qwGroupChatService;
     @Autowired
     private IQwGroupChatUserService qwGroupChatUserService;
-
+    @Autowired
+    private ICompanyMiniappService companyMiniappService;
     // Shutdown flags
     private volatile boolean running = true;
     @Autowired
@@ -304,44 +307,10 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         Map<String, List<SopUserLogsVo>> sopLogsGroupedById = sopUserLogsVos.stream()
                 .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));
 
+        // 查询公司关联小程序数据
+        List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
 
-        // 查询销售二级域名
-//        Set<Long> ids = sopUserLogsVos.stream().map(s -> {
-//            String[] userKey = s.getUserId().split("\\|");
-//            if (userKey.length < 3) {
-//                return null;
-//            }
-//            return Long.parseLong(userKey[1]);
-//        }).filter(Objects::nonNull).collect(Collectors.toSet());
-//
-//        List<CompanyUser> companyUserList;
-//        if (ids.isEmpty()) {
-//            companyUserList = new ArrayList<>();
-//        } else {
-//            companyUserList = companyUserService.selectCompanyUserByIds(ids);
-//        }
-//
-//        Map<String, List<SopUserLogsVo>> sopLogsGroupedById = sopUserLogsVos.stream()
-//                .peek(s -> {
-//                    String[] userKey = s.getUserId().split("\\|");
-//                    if (userKey.length < 3) {
-//                        return;
-//                    }
-//
-//                    // 销售ID
-//                    Long companyUserId = Long.parseLong(userKey[1]);
-//                    CompanyUser companyUser = companyUserList.stream().filter(cu -> Objects.equals(cu.getUserId(), companyUserId)).findFirst().orElse(null);
-//                    if (Objects.nonNull(companyUser)) {
-//                        if (!StringUtil.strIsNullOrEmpty(companyUser.getDomain())) {
-//                            s.setDomain(companyUser.getDomain().trim());
-//                        } else {
-//                            s.setDomain(config.getRealLinkDomainName().trim());
-//                        }
-//                    } else {
-//                        s.setDomain(config.getRealLinkDomainName().trim());
-//                    }
-//                })
-//                .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));
+        Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));
 
         log.info("共分组 {} 个 SOP ID 进行处理。", sopLogsGroupedById.size());
 
@@ -350,7 +319,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         for (Map.Entry<String, List<SopUserLogsVo>> entry : sopLogsGroupedById.entrySet()) {
             String sopId = entry.getKey();
             List<SopUserLogsVo> userLogsVos = entry.getValue();
-            processSopGroupAsync(sopId, userLogsVos, sopGroupLatch,currentTime, groupChatMap,config);
+            processSopGroupAsync(sopId, userLogsVos, sopGroupLatch,currentTime, groupChatMap,config,miniMap);
         }
 
         // 等待所有 SOP 分组处理完成
@@ -371,9 +340,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             backoff = @Backoff(delay = 2000)
     )
     public void processSopGroupAsync(String sopId, List<SopUserLogsVo> userLogsVos, CountDownLatch latch ,LocalDateTime currentTime,
-                                     Map<String, QwGroupChat> groupChatMap,CourseConfig config) {
+                                     Map<String, QwGroupChat> groupChatMap,CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap) {
         try {
-            processSopGroup(sopId, userLogsVos,currentTime, groupChatMap, config);
+            processSopGroup(sopId, userLogsVos,currentTime, groupChatMap, config,miniMap);
         } catch (Exception e) {
             log.error("处理 SOP ID {} 时发生异常: {}", sopId, e.getMessage(), e);
         } finally {
@@ -383,7 +352,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
     private void processSopGroup(String sopId, List<SopUserLogsVo> userLogsVos,LocalDateTime currentTime, Map<String,
-            QwGroupChat> groupChatMap,CourseConfig config) throws Exception {
+            QwGroupChat> groupChatMap,CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap) throws Exception {
         QwSopRuleTimeVO ruleTimeVO = sopMapper.selectQwSopByClickHouseId(sopId);
 
         if (ruleTimeVO == null) {
@@ -425,7 +394,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
         CountDownLatch userLogsLatch = new CountDownLatch(userLogsVos.size());
         for (SopUserLogsVo logVo : userLogsVos) {
-            processUserLogAsync(logVo, ruleTimeVO, rulesList, userLogsLatch, currentTime, groupChatMap,qwCompany.getMiniAppId(), config);
+            processUserLogAsync(logVo, ruleTimeVO, rulesList, userLogsLatch, currentTime, groupChatMap,qwCompany.getMiniAppId(), config,miniMap);
         }
 
         // 等待所有用户日志处理完成
@@ -446,9 +415,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     )
     public void processUserLogAsync(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
                                     CountDownLatch latch, LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap,
-                                    String miniAppId,CourseConfig config) {
+                                    String miniAppId,CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap) {
         try {
-            processUserLog(logVo, ruleTimeVO, tempSettings,currentTime, groupChatMap, miniAppId, config);
+            processUserLog(logVo, ruleTimeVO, tempSettings,currentTime, groupChatMap, miniAppId, config,miniMap);
         } catch (Exception e) {
             log.error("处理用户日志 {} 时发生异常: {}", logVo.getId(), e.getMessage(), e);
         } finally {
@@ -458,7 +427,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
     private void processUserLog(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
-                                LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap,String miniAppId,CourseConfig config) {
+                                LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap,String miniAppId,
+                                CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap) {
         try {
 
             LocalDate startDate = LocalDate.parse(logVo.getStartTime(), DATE_FORMATTER);
@@ -510,6 +480,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             String qwUserId = String.valueOf(qwUserByRedis.getId()).trim();
             String companyUserId = String.valueOf(qwUserByRedis.getCompanyUserId()).trim();
             String companyId = String.valueOf(qwUserByRedis.getCompanyId()).trim();
+            Integer sendMsgType = qwUserByRedis.getSendMsgType();
 
             if (StringUtil.strIsNullOrEmpty(companyUserId) || StringUtil.strIsNullOrEmpty(companyId) || "null".equals(companyUserId)) {
                 log.error("员工未绑定销售账号或公司,跳过处理:"+qwUserId);
@@ -635,7 +606,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
                         insertSopUserLogs(sopUserLogsInfos, logVo, sendTime, ruleTimeVO, content, qwUserId,
                                 companyUserId, companyId, qwUserByRedis.getWelcomeText(),qwUserByRedis.getQwUserName(),
-                                groupChatMap, miniAppId,config);
+                                groupChatMap, miniAppId,config,miniMap, sendMsgType);
 
                     }
                 } catch (Exception e) {
@@ -678,7 +649,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private void insertSopUserLogs(List<SopUserLogsInfo> sopUserLogsInfos, SopUserLogsVo logVo, Date sendTime,
                                    QwSopRuleTimeVO ruleTimeVO, QwSopTempSetting.Content content,
                                    String qwUserId,String companyUserId,String companyId,String welcomeText,String qwUserName,
-                                   Map<String, QwGroupChat> groupChatMap,String miniAppId,CourseConfig config) {
+                                   Map<String, QwGroupChat> groupChatMap,String miniAppId,CourseConfig config,
+                                   Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap, Integer sendMsgType) {
         String formattedSendTime = sendTime.toInstant()
                 .atZone(ZoneId.systemDefault())
                 .format(DATE_TIME_FORMATTER);
@@ -705,7 +677,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null, isOfficial, null);
                 handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                         type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName,
-                        null, true, miniAppId, groupChat,config);
+                        null, true, miniAppId, groupChat,config, miniMap, null, sendMsgType);
             } else {
                 if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
                     groupChat.getChatUserList().forEach(user -> {
@@ -714,7 +686,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, user.getUserId(), user.getName(), null, isOfficial, null);
                         handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                                 type, qwUserId, companyUserId, companyId, user.getId().toString(), welcomeText, qwUserName,
-                                null, false, miniAppId, groupChat,config);
+                                null, false, miniAppId, groupChat,config, miniMap, null, sendMsgType);
                     });
                 }
             }
@@ -725,9 +697,11 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     String externalId = contactId.getExternalId().toString();
                     String externalUserName = contactId.getExternalUserName();
                     Long fsUserId = contactId.getFsUserId();
+                    Integer grade = contactId.getGrade();
                     QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId, isOfficial, contactId.getExternalId());
                     handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
-                            type, qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId, false, miniAppId, null,config);
+                            type, qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId, false, miniAppId,
+                            null,config, miniMap, grade, sendMsgType);
                 } catch (Exception e) {
                     log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
                 }
@@ -831,11 +805,12 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     }
 
     private void handleLogBasedOnType(QwSopLogs sopLogs, QwSopTempSetting.Content content,
-                                      SopUserLogsVo logVo, Date sendTime, Long courseId,
-                                      Long videoId, int type, String qwUserId,
+                                      SopUserLogsVo logVo, Date sendTime, Long courseId, Long videoId, int type, String qwUserId,
                                       String companyUserId, String companyId, String externalId, String welcomeText,
                                       String qwUserName, Long fsUserId, boolean isGroupChat, String miniAppId,
-                                      QwGroupChat groupChat,CourseConfig config) {
+                                      QwGroupChat groupChat,CourseConfig config,
+                                      Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
+                                      Integer grade, Integer sendMsgType  ) {
         switch (type) {
             case 1:
                 handleNormalMessage(sopLogs, content,companyUserId);
@@ -843,7 +818,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             case 2:
                 handleCourseMessage(sopLogs, content, logVo, sendTime, courseId, videoId,
                         qwUserId, companyUserId, companyId, externalId, welcomeText,qwUserName, fsUserId,
-                        isGroupChat, miniAppId, groupChat,config);
+                        isGroupChat, miniAppId, groupChat,config,miniMap, grade, sendMsgType);
                 break;
             case 3:
                 handleOrderMessage(sopLogs, content);
@@ -873,10 +848,10 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     }
 
     private void handleCourseMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,
-                                     SopUserLogsVo logVo, Date sendTime, Long courseId,
-                                     Long videoId, String qwUserId, String companyUserId,
+                                     SopUserLogsVo logVo, Date sendTime, Long courseId, Long videoId, String qwUserId, String companyUserId,
                                      String companyId, String externalId, String welcomeText, String qwUserName,
-                                     Long fsUserId, boolean isGroupChat, String miniAppId, QwGroupChat groupChat,CourseConfig config) {
+                                     Long fsUserId, boolean isGroupChat, String miniAppId, QwGroupChat groupChat,CourseConfig config,Map<Long,
+                                     Map<Integer, List<CompanyMiniapp>>> miniMap,Integer grade, Integer sendMsgType) {
         // 深拷贝 Content 对象,避免使用 JSON
         QwSopTempSetting.Content clonedContent = deepCopyContent(content);
         if (clonedContent == null) {
@@ -968,10 +943,25 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
                             qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId());
 
-                    if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                    if (!miniMap.isEmpty() && sendMsgType==1) {
+                        Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                        if (integerListMap != null) {
+
+                            int effectiveGrade = (grade == null) ? 5 : grade;
+                            int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
+                            List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                            if (miniapps != null && !miniapps.isEmpty()) {
+                                CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                    setting.setMiniprogramAppid(companyMiniapp.getAppId());
+                                }
+                            }
+                        }
+                    }else if (!StringUtil.strIsNullOrEmpty(miniAppId)){
                         setting.setMiniprogramAppid(miniAppId);
                     }else {
-                        log.error("公司的小程序id为空:采用了前端传的固定值"+sopLogs.getSopId());
+                        log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
                     }
 
                     setting.setMiniprogramPage(sortLink.replaceAll("^[\\s\\u2005]+", ""));

+ 262 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopUserLogsInfoByIsDaysNotStudyImpl.java

@@ -0,0 +1,262 @@
+package com.fs.app.taskService.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.app.taskService.SopUserLogsInfoByIsDaysNotStudy;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.sop.domain.SopUserLogs;
+import com.fs.sop.domain.SopUserLogsInfo;
+import com.fs.sop.params.QwRatingConfig;
+import com.fs.sop.service.ISopUserLogsInfoService;
+import com.fs.sop.service.ISopUserLogsService;
+import com.fs.system.service.ISysConfigService;
+import com.fs.voice.utils.StringUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.*;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+public class SopUserLogsInfoByIsDaysNotStudyImpl implements SopUserLogsInfoByIsDaysNotStudy {
+
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+
+    @Autowired
+    private ISopUserLogsInfoService iSopUserLogsInfoService;
+
+    @Autowired
+    private ISopUserLogsService iSopUserLogsService;
+
+    @Autowired
+    private ExecutorService sopRatingExecutor;  // 自定义线程池
+
+    // 任务队列
+    private final BlockingQueue<SopUserLogs> taskQueue = new LinkedBlockingQueue<>(10000);
+
+    private volatile boolean running = true;
+    //批量更新队列
+    private final List<CompletableFuture<Void>> updateFutures = Collections.synchronizedList(new ArrayList<>());
+
+    private final Object configLock = new Object();
+
+
+    private  volatile QwRatingConfig qwRatingConfig;
+
+    // 启动时初始化消费者线程
+    @PostConstruct
+    public void init() {
+
+        loadCourseConfig();
+
+        int consumerCount = Runtime.getRuntime().availableProcessors(); // 消费者线程数,默认 CPU 核心数
+        for (int i = 0; i < consumerCount; i++) {
+            sopRatingExecutor.submit(this::consumeTasks); // 提交消费者任务
+        }
+
+    }
+
+    private void loadCourseConfig() {
+        try {
+            String json = configService.selectConfigByKey("qwRating:config");
+            QwRatingConfig config = JSON.parseObject(json, QwRatingConfig.class);
+            if (!StringUtil.strIsNullOrEmpty(json) && config != null) {
+                qwRatingConfig = config;
+                log.info("Loaded qwRating.config successfully.");
+            } else {
+                log.error("Failed to load course.config from configService.");
+            }
+        } catch (Exception e) {
+            log.error("Exception while loading qwRating.config: {}", e.getMessage(), e);
+        }
+    }
+
+
+
+    @Override
+    public void restoreByIsDaysNotStudy() {
+
+        // 分页加载并放入队列
+        int pageSize = 1000;
+        int offset = 0;
+        List<SopUserLogs> sopUserLogs;
+
+        // 获取缓存的配置
+        QwRatingConfig config;
+        synchronized(configLock) {
+            config = qwRatingConfig;
+        }
+
+        do {
+            sopUserLogs = iSopUserLogsService.meetsTherestoreByIsDaysNotStudy(offset, pageSize,config.getNotStudyDays());
+            if (!sopUserLogs.isEmpty()) {
+                sopUserLogs.forEach(item -> {
+                    try {
+                        taskQueue.put(item); // 将任务放入队列
+                    } catch (InterruptedException e) {
+                        log.error("任务放入队列失败,sopId: {}", item.getSopId(), e);
+                        Thread.currentThread().interrupt();
+                    }
+                });
+                offset += pageSize;
+            }
+        } while (!sopUserLogs.isEmpty());
+
+
+        // 等待队列处理完成
+        CompletableFuture.runAsync(() -> {
+            while (!taskQueue.isEmpty()) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    log.error("等待队列处理时中断", e);
+                    Thread.currentThread().interrupt();
+                }
+            }
+        }).join(); // 等待任务完成
+
+    }
+
+    private void consumeTasks() {
+        if (!running && taskQueue.isEmpty()) {
+            log.info("没有评级任务需要处理");
+            return; // 如果队列为空且没有正在运行的线程,则直接返回
+        }
+
+        while (running) {
+            try {
+                SopUserLogs item = taskQueue.poll(1, TimeUnit.SECONDS); // 等待 1 秒
+                if (item != null) {
+                    processRestoreByIsDaysNotStudy(item);
+                }
+            } catch (Exception e) {
+                log.error("消费者线程异常", e);
+            }
+        }
+    }
+
+    private void processRestoreByIsDaysNotStudy(SopUserLogs item) {
+
+        // 获取缓存的配置
+        QwRatingConfig config;
+        synchronized(configLock) {
+            config = qwRatingConfig;
+        }
+
+        List<SopUserLogsInfo> infos = iSopUserLogsInfoService.selectRestoreByIsDaysNotStudy(
+                item.getSopId(), item.getId());
+
+        if (infos == null || infos.isEmpty()) {
+            log.error("当前营期没有E级客户-sopId:{},营期id:{}", item.getSopId(), item.getId());
+            return;
+        }
+
+        List<QwExternalContact> contacts = infos.stream()
+                .map(info -> processUserLog(info, config))
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (!contacts.isEmpty()) {
+            batchUpdateQwExternalContact(contacts);
+        }
+    }
+
+    private void batchUpdateQwExternalContact(List<QwExternalContact> contacts) {
+        // 9. 优化分批逻辑
+        int total = contacts.size();
+        for (int i = 0; i < total; i += 300) {
+            List<QwExternalContact> batch = contacts.subList(i, Math.min(i + 300, total));
+
+            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                try {
+                    qwExternalContactMapper.batchUpdateQwExternalByIsDaysNotStudy(batch);
+                    iSopUserLogsInfoService.batchUpdateSopUserLogsInfoByIsDaysNotStudy(batch);
+                } catch (Exception e) {
+                    log.error("批量更新异常, 批次大小: {}", batch.size(), e);
+                }
+            }, sopRatingExecutor);
+
+            updateFutures.add(future);
+        }
+    }
+
+    @PreDestroy
+    public void shutdown() {
+        running = false;  // 标记消费者停止
+        log.info("正在关闭线程池...");
+
+        // **等待任务队列处理完毕**
+        while (!taskQueue.isEmpty()) {
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                log.warn("等待任务队列处理完成时被中断", e);
+            }
+        }
+
+        // **确保所有  的任务完成**
+        log.info("等待所有批量更新任务完成...");
+        CompletableFuture.allOf(updateFutures.toArray(new CompletableFuture[0])).join();
+
+        // 关闭线程池
+        sopRatingExecutor.shutdown();
+        try {
+            if (!sopRatingExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
+                List<Runnable> pendingTasks = sopRatingExecutor.shutdownNow();
+                log.warn("强制关闭线程池,未完成任务数: {}", pendingTasks.size());
+            }
+        } catch (InterruptedException e) {
+            sopRatingExecutor.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+        log.info("线程池和消费者已完全关闭");
+    }
+
+    /**
+    * 只计算时长
+    */
+    private QwExternalContact processUserLog(SopUserLogsInfo logsInfo, QwRatingConfig config) {
+        try {
+
+            Long externalId = logsInfo.getExternalId();
+            if (externalId == null) {
+                return null;
+            }
+
+            Integer sumDuration = fsCourseWatchLogMapper.selectFsCourseWatchLogByByIsDaysNotStudy(externalId, config.getNotStudyDays());
+
+            if (sumDuration!=null && sumDuration>0) {
+                QwExternalContact externalContact = new QwExternalContact();
+                externalContact.setId(externalId);
+                externalContact.setIsDaysNotStudy(0);
+                return externalContact;
+            }
+
+            return null;
+
+        } catch (Exception e) {
+            log.error("计算用户积分异常,用户:{}", logsInfo, e);
+            return null;
+        }
+    }
+
+
+}

+ 13 - 0
fs-qw-task/src/main/java/com/fs/framework/config/ThreadPoolConfig.java

@@ -4,8 +4,10 @@ import com.fs.common.utils.Threads;
 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.TaskScheduler;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
 
 import java.util.concurrent.Executor;
 import java.util.concurrent.ScheduledExecutorService;
@@ -33,6 +35,17 @@ public class ThreadPoolConfig
     // 线程池维护线程所允许的空闲时间
     private int keepAliveSeconds = 300;
 
+
+    @Bean
+    public TaskScheduler taskScheduler(){
+        ThreadPoolTaskScheduler scheduler=new ThreadPoolTaskScheduler();
+        scheduler.setPoolSize(18);
+        scheduler.setThreadNamePrefix("scheduled-task-");
+        scheduler.setAwaitTerminationSeconds(60);
+        scheduler.setWaitForTasksToCompleteOnShutdown(true);
+        return scheduler;
+    }
+
     @Bean(name = "threadPoolTaskExecutor")
     public ThreadPoolTaskExecutor threadPoolTaskExecutor()
     {

+ 7 - 0
fs-service/src/main/java/com/fs/company/domain/Company.java

@@ -112,6 +112,13 @@ public class Company extends BaseEntity
     private String courseMiniAppId;
     /** 会员是否默认黑名单,1-是;0-否(用于销售分享成为会员的操作) */
     private Integer fsUserIsDefaultBlack;
+    private Integer repeat;
+    private Integer sendIfType;
+    private Integer ifNum;
+    @TableField(exist = false)
+    private List<String> miniAppMaster;
+    @TableField(exist = false)
+    private List<String> miniAppServer;
 
     /** 后台制单是否需要付款 默认1 0-否 1-是*/
     private Integer isPay;

+ 41 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyMiniapp.java

@@ -0,0 +1,41 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 客服公司小程序对象 company_miniapp
+ *
+ * @author fs
+ * @date 2025-07-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class CompanyMiniapp extends BaseEntity{
+
+    /** id */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 客服公司ID */
+    @Excel(name = "客服公司ID")
+    private Long companyId;
+
+    /** 小程序appid */
+    @Excel(name = "小程序appid")
+    private String appId;
+
+    /** 主从 0主小程序1备用小程序 */
+    @Excel(name = "主从 0主小程序1备用小程序")
+    private Integer type;
+
+    /** 排序 */
+    @Excel(name = "排序")
+    private Integer sortNum;
+
+
+}

+ 62 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyMiniappMapper.java

@@ -0,0 +1,62 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.CompanyMiniapp;
+
+import java.util.List;
+
+/**
+ * 客服公司小程序Mapper接口
+ * 
+ * @author fs
+ * @date 2025-07-24
+ */
+public interface CompanyMiniappMapper extends BaseMapper<CompanyMiniapp>{
+    /**
+     * 查询客服公司小程序
+     * 
+     * @param id 客服公司小程序主键
+     * @return 客服公司小程序
+     */
+    CompanyMiniapp selectCompanyMiniappById(Long id);
+
+    /**
+     * 查询客服公司小程序列表
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 客服公司小程序集合
+     */
+    List<CompanyMiniapp> selectCompanyMiniappList(CompanyMiniapp companyMiniapp);
+
+    /**
+     * 新增客服公司小程序
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 结果
+     */
+    int insertCompanyMiniapp(CompanyMiniapp companyMiniapp);
+
+    /**
+     * 修改客服公司小程序
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 结果
+     */
+    int updateCompanyMiniapp(CompanyMiniapp companyMiniapp);
+
+    /**
+     * 删除客服公司小程序
+     * 
+     * @param id 客服公司小程序主键
+     * @return 结果
+     */
+    int deleteCompanyMiniappById(Long id);
+
+    /**
+     * 批量删除客服公司小程序
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteCompanyMiniappByIds(Long[] ids);
+}

+ 70 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyMiniappService.java

@@ -0,0 +1,70 @@
+package com.fs.company.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.company.domain.CompanyMiniapp;
+import com.fs.company.vo.CompanyVO;
+
+import java.util.List;
+
+/**
+ * 客服公司小程序Service接口
+ * 
+ * @author fs
+ * @date 2025-07-24
+ */
+public interface ICompanyMiniappService extends IService<CompanyMiniapp>{
+    /**
+     * 查询客服公司小程序
+     * 
+     * @param id 客服公司小程序主键
+     * @return 客服公司小程序
+     */
+    CompanyMiniapp selectCompanyMiniappById(Long id);
+
+    /**
+     * 查询客服公司小程序列表
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 客服公司小程序集合
+     */
+    List<CompanyMiniapp> selectCompanyMiniappList(CompanyMiniapp companyMiniapp);
+
+    /**
+     * 新增客服公司小程序
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 结果
+     */
+    int insertCompanyMiniapp(CompanyMiniapp companyMiniapp);
+
+    /**
+     * 修改客服公司小程序
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 结果
+     */
+    int updateCompanyMiniapp(CompanyMiniapp companyMiniapp);
+
+    /**
+     * 批量删除客服公司小程序
+     * 
+     * @param ids 需要删除的客服公司小程序主键集合
+     * @return 结果
+     */
+    int deleteCompanyMiniappByIds(Long[] ids);
+
+    /**
+     * 删除客服公司小程序信息
+     * 
+     * @param id 客服公司小程序主键
+     * @return 结果
+     */
+    int deleteCompanyMiniappById(Long id);
+
+    void insertBatch(List<String> appIds, Long companyId, Integer type);
+
+    void removeByCompanyId(Long companyId);
+
+    void setMiniAppList(List<CompanyVO> companyVOS);
+    List<CompanyMiniapp> getMiniAppListByCompanyList(List<Long> companyIds);
+}

+ 141 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyMiniappServiceImpl.java

@@ -0,0 +1,141 @@
+package com.fs.company.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.PubFun;
+import com.fs.company.domain.CompanyMiniapp;
+import com.fs.company.mapper.CompanyMiniappMapper;
+import com.fs.company.service.ICompanyMiniappService;
+import com.fs.company.vo.CompanyVO;
+import org.springframework.stereotype.Service;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+
+/**
+ * 客服公司小程序Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-07-24
+ */
+@Service
+public class CompanyMiniappServiceImpl extends ServiceImpl<CompanyMiniappMapper, CompanyMiniapp> implements ICompanyMiniappService {
+
+    public static BiFunction<Integer, List<CompanyMiniapp>, List<String>> GET_MINI_APP_STR = (type, list) -> list.stream().filter(m -> Objects.equals(m.getType(), type)).sorted(Comparator.comparing(CompanyMiniapp::getSortNum)).map(CompanyMiniapp::getAppId).collect(Collectors.toList());
+
+
+    /**
+     * 查询客服公司小程序
+     * 
+     * @param id 客服公司小程序主键
+     * @return 客服公司小程序
+     */
+    @Override
+    public CompanyMiniapp selectCompanyMiniappById(Long id)
+    {
+        return baseMapper.selectCompanyMiniappById(id);
+    }
+
+    /**
+     * 查询客服公司小程序列表
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 客服公司小程序
+     */
+    @Override
+    public List<CompanyMiniapp> selectCompanyMiniappList(CompanyMiniapp companyMiniapp)
+    {
+        return baseMapper.selectCompanyMiniappList(companyMiniapp);
+    }
+
+    /**
+     * 新增客服公司小程序
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 结果
+     */
+    @Override
+    public int insertCompanyMiniapp(CompanyMiniapp companyMiniapp)
+    {
+        companyMiniapp.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertCompanyMiniapp(companyMiniapp);
+    }
+
+    /**
+     * 修改客服公司小程序
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 结果
+     */
+    @Override
+    public int updateCompanyMiniapp(CompanyMiniapp companyMiniapp)
+    {
+        companyMiniapp.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateCompanyMiniapp(companyMiniapp);
+    }
+
+    /**
+     * 批量删除客服公司小程序
+     * 
+     * @param ids 需要删除的客服公司小程序主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCompanyMiniappByIds(Long[] ids)
+    {
+        return baseMapper.deleteCompanyMiniappByIds(ids);
+    }
+
+    /**
+     * 删除客服公司小程序信息
+     * 
+     * @param id 客服公司小程序主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCompanyMiniappById(Long id)
+    {
+        return baseMapper.deleteCompanyMiniappById(id);
+    }
+
+    @Override
+    public void insertBatch(List<String> appIds, Long companyId, Integer type) {
+        AtomicInteger i = new AtomicInteger();
+        List<CompanyMiniapp> list = appIds.stream().map(e -> {
+            CompanyMiniapp miniapp = new CompanyMiniapp();
+            miniapp.setCompanyId(companyId);
+            miniapp.setAppId(e);
+            miniapp.setType(type);
+            miniapp.setSortNum(i.getAndIncrement());
+            return miniapp;
+        }).collect(Collectors.toList());
+        super.saveBatch(list);
+    }
+
+    @Override
+    public void removeByCompanyId(Long companyId) {
+        remove(new QueryWrapper<CompanyMiniapp>().eq("company_id",companyId));
+    }
+
+    @Override
+    public void setMiniAppList(List<CompanyVO> companyVOS) {
+        List<CompanyMiniapp> miniAppList = getMiniAppListByCompanyList(PubFun.listToNewList(companyVOS, CompanyVO::getCompanyId));
+        Map<Long, List<CompanyMiniapp>> miniAppMap = PubFun.listToMapByGroupList(miniAppList, CompanyMiniapp::getCompanyId);
+        companyVOS.stream().filter(e -> miniAppMap.containsKey(e.getCompanyId())).forEach(e -> {
+            List<CompanyMiniapp> list = miniAppMap.get(e.getCompanyId());
+            e.setMiniAppMaster(GET_MINI_APP_STR.apply(0, list));
+            e.setMiniAppServer(GET_MINI_APP_STR.apply(1, list));
+        });
+    }
+
+    @Override
+    public List<CompanyMiniapp> getMiniAppListByCompanyList(List<Long> companyIds) {
+        return list(new QueryWrapper<CompanyMiniapp>().in("company_id", companyIds));
+    }
+}

+ 18 - 1
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -13,6 +13,7 @@ import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.*;
 import com.fs.company.mapper.*;
 import com.fs.company.param.CompanyParam;
+import com.fs.company.service.ICompanyMiniappService;
 import com.fs.company.service.ICompanyProfitService;
 import com.fs.company.service.ICompanyRoleService;
 import com.fs.company.vo.CompanyCrmVO;
@@ -57,6 +58,8 @@ public class CompanyServiceImpl implements ICompanyService
     @Autowired
     private CompanyMapper companyMapper;
     @Autowired
+    private ICompanyMiniappService companyMiniappService;
+    @Autowired
     private CompanyDeptMapper deptMapper;
     @Autowired
     private ICompanyRoleService roleService;
@@ -238,7 +241,7 @@ public class CompanyServiceImpl implements ICompanyService
             userPostMapper.insertCompanyUserPost(userPost);
             company.setUserId(user.getUserId());
             companyMapper.updateCompany(company);
-
+            bindMiniApp(company);
             return R.ok();
         }
         else
@@ -257,8 +260,22 @@ public class CompanyServiceImpl implements ICompanyService
     public int updateCompany(Company company)
     {
         company.setUpdateTime(DateUtils.getNowDate());
+        bindMiniApp(company);
         return companyMapper.updateCompany(company);
     }
+    // 绑定小程序
+    public void bindMiniApp(Company company){
+        companyMiniappService.removeByCompanyId(company.getCompanyId());
+        List<String> miniAppMaster = company.getMiniAppMaster();
+        if(miniAppMaster != null && !miniAppMaster.isEmpty()){
+            companyMiniappService.insertBatch(miniAppMaster, company.getCompanyId(), 0);
+        }
+        List<String> miniAppServer = company.getMiniAppServer();
+        if(miniAppServer != null && !miniAppServer.isEmpty()){
+            companyMiniappService.insertBatch(miniAppServer, company.getCompanyId(), 1);
+        }
+
+    }
 
     /**
      * 批量删除企业

+ 3 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyVO.java

@@ -7,6 +7,7 @@ import lombok.Data;
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.util.Date;
+import java.util.List;
 
 /**
  * 企业账户记录对象 company_money_logs
@@ -86,4 +87,6 @@ public class CompanyVO implements Serializable
     private String followDoctorName;
 
     private String restartTime;
+    private List<String> miniAppMaster;
+    private List<String> miniAppServer;
 }

+ 28 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -165,6 +165,34 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             "\tAND wl.create_time < CURDATE() ")
     List<QwRatingVO> selectFsCourseWatchLogByExtIdRating(@Param("externalId") Long externalId, @Param("dayNum") Integer dayNum);
 
+    @Select("SELECT\n" +
+            "\twl.duration AS watchDuration,\n" +
+            "\tcv.duration AS allDuration,\n" +
+            "\twl.finish_time,\n" +
+            "\twl.create_time," +
+            "\tec.`level` \n" +
+            "FROM\n" +
+            "\tfs_course_watch_log wl\n" +
+            "\tLEFT JOIN qw_external_contact ec ON wl.qw_external_contact_id = ec.id\n" +
+            "\tLEFT JOIN fs_user_course_video cv ON wl.video_id = cv.video_id \n" +
+            "WHERE\n" +
+            "\twl.send_type = 2 \n" +
+            "\tAND wl.qw_external_contact_id = #{externalId} \n" +
+            "\tAND wl.create_time >= DATE_SUB(CURDATE(), INTERVAL #{dayNum} DAY ) \n" +
+            "\tAND wl.create_time < CURDATE()")
+    List<QwRatingVO> selectFsCourseWatchLogByExtIdRatingMoreStudyDays(@Param("externalId") Long externalId, @Param("dayNum") Integer dayNum);
+
+    @Select("SELECT\n" +
+            "\tCOALESCE(SUM(wl.duration), 0) AS watchDuration\n" +
+            "FROM\n" +
+            "\tfs_course_watch_log wl\n" +
+            "WHERE\n" +
+            "\twl.send_type = 2 \n" +
+            "\tAND wl.qw_external_contact_id = #{externalId} \n" +
+            "\tAND wl.create_time >= DATE_SUB( CURDATE(), INTERVAL #{dayNum} DAY ) \n" +
+            "\tAND wl.create_time < CURDATE()")
+    Integer selectFsCourseWatchLogByByIsDaysNotStudy(@Param("externalId") Long externalId,@Param("dayNum") Integer dayNum);
+
     @Select("select l.*,v.title,c.course_name from fs_course_watch_log l LEFT JOIN fs_user_course_video v ON v.video_id = l.video_id LEFT JOIN fs_user_course c ON c.course_id = l.course_id WHERE l.qw_external_contact_id =#{ExtId} and l.qw_user_id=#{QwUserId} and DATE(l.create_time) =CURDATE() ORDER BY l.create_time  desc LIMIT 1  ")
     FsCourseWatchLogVO selectFsCourseWatchLogByExtIdAndQwUserId(@Param("ExtId")String ExtId,@Param("QwUserId")Long QwUserId);
 

+ 1 - 1
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java

@@ -279,7 +279,7 @@ public interface FsUserCourseMapper
     Integer selectTodayCourseWatchLogCountByUserIdAndProjectId(@Param("userId") Long userId, @Param("projectId") Long projectId);
 
     @Select("select course_id,course_name,description,img_url,second_img secondImg,views from fs_user_course where " +
-            " is_private = 0 and is_del = 0 order by sort,course_id")
+            " is_private = 0 and is_del = 0 and is_show = 1 and is_tui = 1 order by sort,course_id")
     List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoApplet();
 
     @Select("select video_id,title,course_id,video_url,SEC_TO_TIME(duration) as total_duration," +

+ 1 - 0
fs-service/src/main/java/com/fs/his/enums/FsStoreOrderStatusEnum.java

@@ -17,6 +17,7 @@ public enum FsStoreOrderStatusEnum {
     STATUS_2(2,"待发货"),
     STATUS_3(3,"待收货"),
     STATUS_4(4,"已完成"),
+    STATUS_6(6,"待推送"), //金牛代服特殊状态
 
     REFUND_STATUS_0(0,"正常"),
     REFUND_STATUS_1(1,"退款中"),

+ 1 - 1
fs-service/src/main/java/com/fs/his/service/IFsExportTaskService.java

@@ -63,7 +63,7 @@ public interface IFsExportTaskService
 
     Integer isExportType1( Long userId);
 
-    public void exportStore1Data(FsStoreOrderParam param);
+    public void exportStore1Data(FsStoreOrderParam param,boolean isAdmin);
 
     public void exportStoreData(FsStoreOrderParam fsStoreOrder);
 

+ 10 - 0
fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java

@@ -62,6 +62,14 @@ public interface IFsStoreOrderService
      */
     public int updateFsStoreOrder(FsStoreOrder fsStoreOrder);
 
+    /**
+     * 修改订单状态-收货信息
+     *
+     * @param fsStoreOrder 订单
+     * @return 结果(含具体报错信息)
+     */
+    R updateStoreOrder(FsStoreOrder fsStoreOrder);
+
     /**
      * 批量删除订单
      *
@@ -248,4 +256,6 @@ public interface IFsStoreOrderService
 
     List<FsStoreOrderListVO> selectFsStoreOrderListVOByErpAccount(FsStoreOrderParam fsStoreOrder);
     List<FsStoreOrder> selectFsStoreOrderByFsUserId(Long fsUserId);
+
+    String importOrderStatusData(List<FsStoreOrderStatusExcelVO> list);
 }

+ 6 - 2
fs-service/src/main/java/com/fs/his/service/impl/FsExportTaskServiceImpl.java

@@ -11,6 +11,7 @@ import com.fs.his.mapper.FsStorePaymentMapper;
 import com.fs.his.param.FsStoreOrderParam;
 import com.fs.his.param.FsStorePaymentParam;
 import com.fs.his.service.IFsStoreOrderService;
+import com.fs.his.utils.PhoneUtil;
 import com.fs.his.vo.FsStoreOrderExcelVO;
 import com.fs.his.vo.FsStoreOrderExportVO;
 import com.fs.his.vo.FsStorePaymentExcelVO;
@@ -117,7 +118,7 @@ public class FsExportTaskServiceImpl implements IFsExportTaskService
 
     @Async
     @Override
-    public void exportStore1Data(FsStoreOrderParam fsStoreOrder) {
+    public void exportStore1Data(FsStoreOrderParam fsStoreOrder,boolean isAdmin) {
         List<FsStoreOrderExportVO> list = fsStoreOrderMapper.selectFsStoreOrderListVOByExport(fsStoreOrder);
         //对手机号脱敏
         if (list != null) {
@@ -126,9 +127,12 @@ public class FsExportTaskServiceImpl implements IFsExportTaskService
                 if (vo.getCycle()!=null){
                     vo.setFollowCount(vo.getCycle()/ vo.getFollowFrequency());
                 }
-                if (vo.getUserPhone() != null) {
+                if (vo.getUserPhone() != null &&  !isAdmin) {
                     vo.setUserPhone(vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
                 }
+                if (vo.getUserPhone() != null && isAdmin && vo.getUserPhone().length()>11) {
+                    vo.setUserPhone(PhoneUtil.decryptPhone(vo.getUserPhone()));
+                }
                 if (vo.getUserAddress() != null) {
                     vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
                 }

+ 127 - 4
fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java

@@ -10,10 +10,7 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.CustomException;
 import com.fs.common.exception.ServiceException;
-import com.fs.common.utils.DateUtils;
-import com.fs.common.utils.ParseUtils;
-import com.fs.common.utils.ServletUtils;
-import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.*;
 import com.fs.common.utils.ip.IpUtils;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyDept;
@@ -305,6 +302,56 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
         return fsStoreOrderMapper.updateFsStoreOrder(fsStoreOrder);
     }
 
+    /**
+     * 修改订单状态-收货信息
+     *
+     * @param fsStoreOrder 订单
+     * @return 结果(含具体报错信息)
+     */
+    @Override
+    public R updateStoreOrder(FsStoreOrder fsStoreOrder) {
+        //userPhone 电话处理
+        String userPhone = fsStoreOrder.getUserPhone();
+        if (StringUtils.isNotBlank(userPhone)) {
+            if (!userPhone.contains("*")){
+                fsStoreOrder.setUserPhone(userPhone);
+            } else {
+                userPhone = null;
+                fsStoreOrder.setUserPhone(null);
+            }
+        }
+        String userAddress = fsStoreOrder.getUserAddress();
+        if (StringUtils.isNotBlank(userAddress)) {
+            if (!userAddress.contains("*")){
+                fsStoreOrder.setUserAddress(userAddress);
+            } else {
+                userAddress = null;
+                fsStoreOrder.setUserAddress(null);
+            }
+        }
+        FsStoreOrder oldOrder = fsStoreOrderMapper.selectFsStoreOrderByOrderId(fsStoreOrder.getOrderId());
+        if (oldOrder == null) return R.error("修改订单不存在");
+        if (StringUtils.isNotBlank(userAddress)){
+            if (userAddress.equals(oldOrder.getUserAddress())) {
+                userAddress = null;
+            }
+        }
+        if (StringUtils.isNotBlank(userAddress) || StringUtils.isNotBlank(userPhone)){
+            Integer status = fsStoreOrder.getStatus();
+            if (status == null){
+                status = oldOrder.getStatus();
+            }
+            if (Objects.equals(FsStoreOrderStatusEnum.STATUS_1.getValue(), status) || Objects.equals(FsStoreOrderStatusEnum.STATUS_6.getValue(), status)) {
+                fsStoreOrder.setUserAddress(StringUtils.isNotBlank(userAddress)?null:userAddress);
+                fsStoreOrder.setUserPhone(StringUtils.isNotBlank(userPhone)?null:userPhone);
+            } else {
+                return R.error("该订单状态不支持修改收货地址/电话");
+            }
+        }
+        fsStoreOrder.setUpdateTime(DateUtils.getNowDate());
+        return fsStoreOrderMapper.updateFsStoreOrder(fsStoreOrder)>0?R.ok():R.error();
+    }
+
     /**
      * 批量删除订单
      *
@@ -3422,4 +3469,80 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
         return fsStoreOrderMapper.selectFsStoreOrderByFsUserId(fsUserId);
     }
 
+    @Override
+    public String importOrderStatusData(List<FsStoreOrderStatusExcelVO> list) {
+        if (StringUtils.isNull(list) || list.isEmpty()) {
+            throw new ServiceException("导入数据不能为空!");
+        }
+        int successNum = 0;
+        int failureNum = 0;
+        StringBuilder successMsg = new StringBuilder();
+        StringBuilder failureMsg = new StringBuilder();
+        for (FsStoreOrderStatusExcelVO vo : list) {
+            try {
+                //1.必填参数
+                ExcelUtils.validateRequiredFields(vo, list.indexOf(vo) + 1); // 传入行号
+                FsStoreOrder o = fsStoreOrderMapper.selectFsStoreOrderByOrderCode(vo.getOrderCode());
+                if (o ==null){
+                    failureNum++;
+                    String msg = "<br/>" + failureNum + "、订单编号 " + vo.getOrderCode() + " 导入失败:";
+                    failureMsg.append(msg).append("订单不存在");
+                    continue;
+                }
+                FsStoreOrder param = new FsStoreOrder(); //修改订单的参数
+                param.setOrderCode(vo.getOrderCode());
+                param.setOrderId(o.getOrderId());
+                if ("6".equals(vo.getStatus())) {
+                    failureNum++;
+                    String msg = "<br/>" + failureNum + "、订单编号 " + vo.getOrderCode() + " 导入失败:";
+                    failureMsg.append(msg).append("该状态不支持修改为待推送");
+                    continue;
+                }
+
+                Integer status = o.getStatus();
+
+                if (StringUtils.isNotBlank(vo.getStatus())){
+                    param.setStatus(Integer.valueOf(vo.getStatus()));
+                    status = Integer.valueOf(vo.getStatus());
+                }
+                /**
+                 * 地址和电话仅待付款和待推送可以修改
+                 */
+                String userAddress = vo.getUserAddress();
+                String userPhone = vo.getUserPhone();
+                if (StringUtils.isNotBlank(userAddress) || StringUtils.isNotBlank(userPhone)){
+                    if (Objects.equals(FsStoreOrderStatusEnum.STATUS_1.getValue(), status) || Objects.equals(FsStoreOrderStatusEnum.STATUS_6.getValue(), status)) {
+                        param.setUserAddress(userAddress.isEmpty()?null:userAddress);
+                        param.setUserPhone(userPhone.isEmpty()?null:userPhone);
+                    } else {
+                        failureNum++;
+                        String msg = "<br/>" + failureNum + "、订单编号 " + vo.getOrderCode() + " 修改失败:";
+                        failureMsg.append(msg).append("该状态不支持修改收货人地址或电话");
+                        continue;
+                    }
+                }
+                param.setDeliveryStatus((vo.getDeliveryStatus()==null|| vo.getDeliveryStatus().isEmpty())?null:Integer.valueOf(vo.getDeliveryStatus()));
+                param.setDeliveryType(vo.getDeliveryType().isEmpty()?null:vo.getDeliveryType());
+                param.setUpdateTime(DateUtils.getNowDate());
+                fsStoreOrderMapper.updateFsStoreOrder(param);
+
+                successNum++;
+                successMsg.append("<br/>").append(successNum).append("、订单编号 ").append(vo.getOrderCode()).append(" 修改成功");
+
+            } catch (Exception e) {
+
+                failureNum++;
+                String msg = "<br/>" + failureNum + "、订单编号 " + vo.getOrderCode() + " 修改失败:";
+                failureMsg.append(msg).append(e.getMessage());
+            }
+        }
+        if (failureNum > 0) {
+            failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
+            throw new ServiceException(failureMsg.toString());
+        } else {
+            successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
+        }
+        return successMsg.toString();
+    }
+
 }

+ 32 - 0
fs-service/src/main/java/com/fs/his/vo/FsStoreOrderStatusExcelVO.java

@@ -0,0 +1,32 @@
+package com.fs.his.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+@Data
+public class FsStoreOrderStatusExcelVO {
+
+    @Excel(name = "药品订单号",required = true)
+    private String orderCode;
+
+    @Excel(name = "订单状态",dictType = "sys_order_status")
+    private String status;
+
+
+    /** 物流状态 */
+    @Excel(name = "物流状态",dictType = "sys_store_order_delivery_status")
+    private String deliveryStatus;
+
+    /** 物流跟踪状态 */
+    @Excel(name = "物流跟踪状态",dictType = "sys_delivery_type")
+    private String deliveryType;
+
+    /** shou */
+    @Excel(name = "收货人电话")
+    private String userPhone;
+    /** 详情地址 */
+    @Excel(name = "详情地址")
+    private String userAddress;
+
+
+}

+ 4 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java

@@ -78,6 +78,10 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
                                                   @Param("corpId") String corpId);
 
 
+    public int batchUpdateQwExternalContactByMoreStudy(List<QwExternalContact> qwExternalContact);
+    public int batchUpdateQwExternalByIsDaysNotStudy(List<QwExternalContact> qwExternalContact);
+
+
     @Select("SELECT * FROM qw_external_contact WHERE external_user_id = #{externalUserId}  AND corp_id=#{corpId} and user_id=#{qwUserId} limit 1")
     public QwExternalContact selectQwExternalContactByExternalUserIdAndQwUserId(@Param("externalUserId") String externalUserId,@Param("corpId") String corpId,@Param("qwUserId") String qwUserId);
 

+ 6 - 0
fs-service/src/main/java/com/fs/sop/domain/SopUserLogsInfo.java

@@ -73,6 +73,12 @@ public class SopUserLogsInfo implements Serializable {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private String inComingETime;
 
+
+    /**
+     * 评级
+     */
+    private Integer grade;
+
     /**
      * 营期时间
      */

+ 1 - 1
fs-service/src/main/java/com/fs/sop/mapper/QwSopLogsMapper.java

@@ -321,7 +321,7 @@ public interface QwSopLogsMapper extends BaseMapper<QwSopLogs> {
     @DataSource(DataSourceType.SOP)
     List<QwSopLogs> selectIpadByCorpId(@Param("corpId") String corpId, @Param("now") LocalDateTime now);
 
-    @DataSource(DataSourceType.SopREAD)
+    @DataSource(DataSourceType.SOP)
     List<QwSopLogs> selectByQwUserId(@Param("id") Long id);
 
     @Select("select * from qw_sop_logs where send_type=8 and send_status=3 and  create_time <= DATE_SUB(NOW(), INTERVAL 2 HOUR) ")

+ 52 - 0
fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsInfoMapper.java

@@ -2,6 +2,7 @@ package com.fs.sop.mapper;
 
 import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.params.BatchSopUserLogsInfoParamU;
@@ -148,11 +149,62 @@ public interface SopUserLogsInfoMapper {
     @DataSource(DataSourceType.SOP)
     List<SopUserLogsInfo> selectSopUserLogsInfoListBySopId(@Param("sopId") String sopId, @Param("userLogsId") String userLogsId);
 
+    @DataSource(DataSourceType.SOP)
+    List<SopUserLogsInfo> selectRestoreByIsDaysNotStudy(@Param("sopId") String sopId, @Param("userLogsId") String userLogsId);
+
 
 
     @DataSource(DataSourceType.SOP)
     void batchInsertSopUserLogsInfo(@Param("SopUserLogsInfo") List<SopUserLogsInfo> logsToInsert);
 
+
+    @DataSource(DataSourceType.SOP)
+    @Update("<script>" +
+            "UPDATE sop_user_logs_info " +
+            "SET is_days_not_study = CASE " +
+            "<foreach collection='contactList' item='item'> " +
+            "    WHEN external_id = #{item.id} THEN " +
+            "    CASE WHEN #{item.level} = 5 AND #{item.isDaysNotStudy}=1 THEN 1 ELSE 0 END " +
+            "</foreach> " +
+            "ELSE is_days_not_study " +
+            "END " +
+            "WHERE external_id IN " +
+            "<foreach collection='contactList' item='item' open='(' separator=',' close=')'> " +
+            "    #{item.id} " +
+            "</foreach>" +
+            "</script>")
+    void batchUpdateSopUserLogsInfoByMoreStudy(@Param("contactList") List<QwExternalContact> contactList);
+
+    @DataSource(DataSourceType.SOP)
+    @Update("<script>" +
+            "UPDATE sop_user_logs_info " +
+            "SET is_days_not_study = 0 " +
+            "WHERE external_id IN " +
+            "<foreach collection='contactList' item='item' open='(' separator=',' close=')'>" +
+            "    #{item.id} " +
+            "</foreach>" +
+            "</script>")
+    void batchUpdateSopUserLogsInfoByIsDaysNotStudy(@Param("contactList") List<QwExternalContact> contactList);
+
+    @DataSource(DataSourceType.SOP)
+    @Update("<script>" +
+            "UPDATE sop_user_logs_info " +
+            "SET grade = CASE external_id " +
+            "<foreach collection=\"contactList\" item=\"item\">" +
+            "    WHEN #{item.id} THEN #{item.level} " +
+            "</foreach>" +
+            "    ELSE grade " +
+            "END " +
+            "WHERE external_id IN " +
+            "<foreach collection='contactList' item='item' open='(' separator=',' close=')'>" +
+            "    #{item.id} " +
+            "</foreach>" +
+            "</script>")
+    void batchUpdateSopUserLogsInfoByLevel(@Param("contactList") List<QwExternalContact> contactList);
+
+
+
+
     @DataSource(DataSourceType.SOP)
     void batchUpdateSopUserLogsInfoToTime(BatchSopUserLogsInfoParamU paramU);
 

+ 7 - 0
fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java

@@ -64,6 +64,13 @@ public interface SopUserLogsMapper {
     @DataSource(DataSourceType.SOP)
     public List<SopUserLogs> meetsTheRatingByUserInfoWithPagination(@Param("offset") int offset,@Param("pageSize") int pageSize);
 
+    @DataSource(DataSourceType.SOP)
+    public List<SopUserLogs> meetsTheRatingByUserInfoWithPaginationStudyDays(@Param("offset") int offset,@Param("pageSize") int pageSize,@Param("notStudyDays") Integer notStudyDays);
+
+    @DataSource(DataSourceType.SOP)
+    public List<SopUserLogs> meetsTherestoreByIsDaysNotStudy(@Param("offset") int offset,@Param("pageSize") int pageSize,@Param("notStudyDays") Integer notStudyDays);
+
+
     @DataSource(DataSourceType.SOP)
     public List<SopUserLogs> meetsTheRatingByUserInfoBySopId(@Param("sopId") String sopId);
 

+ 1 - 0
fs-service/src/main/java/com/fs/sop/params/QwRatingConfig.java

@@ -5,6 +5,7 @@ import lombok.Data;
 @Data
 public class QwRatingConfig {
     private Integer levelDay;
+    private Integer notStudyDays;
     private Integer aLevelMin;
     private Integer aLevelMax;
     private Integer bLevelMin;

+ 8 - 0
fs-service/src/main/java/com/fs/sop/service/ISopUserLogsInfoService.java

@@ -1,6 +1,7 @@
 package com.fs.sop.service;
 
 import com.fs.common.core.domain.R;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.params.BatchSopUserLogsInfoParam;
@@ -54,6 +55,11 @@ public interface ISopUserLogsInfoService {
     List<SopUserLogsInfoVOE> selectSopUserLogsInfoListVO(SopUserLogsInfo info);
 
     void batchInsertSopUserLogsInfo(List<SopUserLogsInfo> logsToInsert);
+
+    void batchUpdateSopUserLogsInfoByMoreStudy(List<QwExternalContact> contactList);
+    void batchUpdateSopUserLogsInfoByIsDaysNotStudy(List<QwExternalContact> contactList);
+    void batchUpdateSopUserLogsInfoByLevel(List<QwExternalContact> contactList);
+
     void insertSopUserLogsInfo(SopUserLogsInfo logsToInsert);
     /**
      * 查询sopUserLogsInfo
@@ -74,5 +80,7 @@ public interface ISopUserLogsInfoService {
     public R sendUserLogsInfoMsg(SendUserLogsInfoMsgParam param);
     public R sendUserLogsInfoMsgType(SendUserLogsInfoMsgParam param);
 
+    List<SopUserLogsInfo> selectRestoreByIsDaysNotStudy(String sopId, String userLogsId);
+
     public List<ExtCourseSopWatchLogVO> getExtCourseSopWatchLog(QwExtCourseSopWatchLog qwExternalContactId);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/sop/service/ISopUserLogsService.java

@@ -61,4 +61,7 @@ public interface ISopUserLogsService {
     void updateLogDate(UpdateSopUserLogDateVo vo);
 
     void addGroupChat(AddSopUserGroupChat vo);
+
+    public List<SopUserLogs> meetsTherestoreByIsDaysNotStudy(int offset,int pageSize,Integer notStudyDays);
+
 }

+ 318 - 77
fs-service/src/main/java/com/fs/sop/service/impl/QwSopLogsServiceImpl.java

@@ -59,6 +59,7 @@ import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
 import java.util.stream.Collector;
 import java.util.stream.Collectors;
@@ -571,135 +572,375 @@ public class QwSopLogsServiceImpl extends ServiceImpl<QwSopLogsMapper, QwSopLogs
     @Override
     public void qwSopLogsResultNew() {
 
+
         logger.info("开始执行企业微信群发消息结果查询任务");
         long startTime = System.currentTimeMillis();
 
         List<QwSopLogs> qwSopLogsList = qwSopLogsMapper.selectSopLogsByCreateCorpMassSendResult();
         if (qwSopLogsList.isEmpty()) {
-            logger.info("没有需要查询结果的群发消息记录");
-            return;
+            return ;
         }
 
-
+        // 分组处理
         Map<String, List<QwSopLogs>> grouped = qwSopLogsList.stream().collect(
                 Collectors.groupingBy(log -> log.getQwUserid() + "|" + log.getCorpId() + "|" + log.getMsgId())
         );
-        for (Map.Entry<String, List<QwSopLogs>> entry : grouped.entrySet()) {
-            String key = entry.getKey();
-            List<QwSopLogs> corpLogs = entry.getValue();
 
-            String[] keys = key.split("\\|");
-            String qwUserid = keys[0];
-            String corpId = keys[1];
-            String msgID = keys[2];
+        // 线程安全的数据结构
+        List<QwSopLogs> allUpdates = Collections.synchronizedList(new ArrayList<>());
+        Queue<Map.Entry<String, List<QwSopLogs>>> taskQueue = new ConcurrentLinkedQueue<>(grouped.entrySet());
+        Queue<Map.Entry<String, List<QwSopLogs>>> apiFailedTasks = new ConcurrentLinkedQueue<>();
+        ExecutorService executor = Executors.newFixedThreadPool(10);
+
+        AtomicInteger totalGroupsProcessed = new AtomicInteger(0); // 处理的分组数
+        AtomicInteger totalRecordsUpdated = new AtomicInteger(0);  // 更新的记录数
+
+        int batchSize = 300; // 每300个组批量更新一次
+        int maxRetries = 3;   // 最大重试次数
+
+        // 处理所有任务(包括重试)
+        for (int retryCount = 0; retryCount <= maxRetries; retryCount++) {
+            int currentBatchCount = 0;
+
+            while (!taskQueue.isEmpty()) {
+                int currentBatchSize = Math.min(batchSize - currentBatchCount, taskQueue.size());
+                CountDownLatch batchLatch = new CountDownLatch(currentBatchSize);
+
+                // 处理当前批次任务
+                for (int i = 0; i < currentBatchSize; i++) {
+                    Map.Entry<String, List<QwSopLogs>> entry = taskQueue.poll();
+                    executor.submit(() -> {
+                        try {
+                            // 处理单个分组(传入apiFailedTasks)
+                            List<QwSopLogs> updates = processSingleGroup(entry, apiFailedTasks);
+                            if (updates != null) {
+                                synchronized (allUpdates) {
+                                    allUpdates.addAll(updates);
+                                }
+                                // 统计更新记录数
+                                totalRecordsUpdated.addAndGet(updates.size());
+                            }
+
+                            // 统计处理的分组数
+                            totalGroupsProcessed.incrementAndGet();
+                        } finally {
+                            batchLatch.countDown();
+                        }
+                    });
+                }
+
+                // 等待当前批次完成
+                try {
+                    batchLatch.await();
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+
+                currentBatchCount += currentBatchSize;
 
-            QwGetGroupmsgSendParam param = new QwGetGroupmsgSendParam();
-            param.setMsgid(msgID);
-            param.setUserid(qwUserid);
-            param.setLimit(1000);
+                // 每处理1000个组或任务队列空时更新数据库
+                if (currentBatchCount >= batchSize || taskQueue.isEmpty()) {
+                    synchronized (allUpdates) {
+                        if (!allUpdates.isEmpty()) {
+                            batchUpdateDatabase(new ArrayList<>(allUpdates));
+                            logger.info("每处理300个组或任务队列空时更新数据库:"+new ArrayList<>(allUpdates).size());
+                            allUpdates.clear();
+                        }
 
-            fetchAndProcessAllPages(param, corpId, corpLogs, msgID);
+                    }
+                    currentBatchCount = 0;
+                }
+            }
 
+            // 准备下一轮重试
+            if (retryCount < maxRetries) {
+                taskQueue.addAll(apiFailedTasks);
+                apiFailedTasks.clear();
+            }
         }
 
-        long endTime = System.currentTimeMillis();
-        logger.info("企业微信群发消息结果查询任务完成,处理记录总数: {},总耗时: {}ms",
-                qwSopLogsList.size(), (endTime - startTime));
+        // 最终更新剩余数据
+        synchronized (allUpdates) {
+            if (!allUpdates.isEmpty()) {
+                logger.info("最终更新剩余数据:"+new ArrayList<>(allUpdates).size());
+                batchUpdateDatabase(new ArrayList<>(allUpdates));
+            }
+        }
+
+        executor.shutdown();
+        try {
+            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
+                executor.shutdownNow();
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
 
+        long endTime = System.currentTimeMillis();
+        logger.info("成功处理分组数: " + totalGroupsProcessed.get());
+        logger.info("企业微信群发消息结果查询任务完成-更新记录总数:{},总耗时:{} " , totalRecordsUpdated.get(),(endTime - startTime));
     }
 
-    private void fetchAndProcessAllPages(QwGetGroupmsgSendParam param, String corpId, List<QwSopLogs> logs, String msgId) {
+    private List<QwSopLogs> processSingleGroup(Map.Entry<String, List<QwSopLogs>> entry, Queue<Map.Entry<String, List<QwSopLogs>>> apiFailedTasks) {
+
+        String key = entry.getKey();
+        List<QwSopLogs> corpLogs = entry.getValue();
+        String[] keys = key.split("\\|");
+        String qwUserid = keys[0];
+        String corpId = keys[1];
+        String msgID = keys[2];
+
+        QwGetGroupmsgSendParam param = new QwGetGroupmsgSendParam();
+        param.setMsgid(msgID);
+        param.setUserid(qwUserid);
+        param.setLimit(1000);
+
+        List<QwSopLogs> groupUpdates = new ArrayList<>();
         String nextCursor = null;
+        boolean apiSuccess = true;
 
         do {
             param.setCursor(nextCursor);
+            QwGroupmsgSendResult result = fetchWithRetry(param, corpId); // 重试3次
+
+            // API调用失败处理
+            if (result == null || result.getErrCode() != 0) {
+                apiSuccess = false;
+                break;
+            }
+
+            // 处理当前页结果
+            List<QwSopLogs> pageUpdates = processPageResult(result, corpLogs, corpId, msgID);
+            groupUpdates.addAll(pageUpdates);
+            nextCursor = result.getNextCursor();
+
+        } while (nextCursor != null && !nextCursor.isEmpty());
+
+        // API调用失败时返回null,将任务加入失败队列
+        if (!apiSuccess) {
+            apiFailedTasks.offer(entry);
+            return null;
+        }
+
+        return groupUpdates;
+    }
+
+    private QwGroupmsgSendResult fetchWithRetry(QwGetGroupmsgSendParam param, String corpId) {
+        int retryCount = 0;
+        while (retryCount <= 3) {
             QwGroupmsgSendResult result = qwApiService.getGroupmsgSendResult(param, corpId);
 
+            // 请求失败情况
             if (result == null) {
-                logger.error("接口调用失败: {}", param);
-                return;
+                retryCount++;
+                sleepWithJitter(2000);
+                continue;
             }
 
-            if (result.getErrCode() == 45033) {
-                try {
-                    Thread.sleep(2000 + new Random().nextInt(1000));
-                    result = qwApiService.getGroupmsgSendResult(param, corpId);
-                } catch (InterruptedException e) {
-                    Thread.currentThread().interrupt();
-                    logger.error("线程中断", e);
-                    return;
-                }
+            // 成功情况
+            if (result.getErrCode() == 0) {
+                return result;
             }
 
-            if (result.getErrCode() != 0) {
-                logger.error("查询失败: {}, errCode: {}, errMsg: {}", param, result.getErrCode(), result.getErrMsg());
-                return;
+            // 需要重试的错误码
+            if (result.getErrCode() == 45033 || result.getErrCode() == -1) {
+                retryCount++;
+                sleepWithJitter(2000 + new Random().nextInt(1000));
+                continue;
             }
 
-            processPageResult(result, logs, corpId, msgId);
-            nextCursor = result.getNextCursor();
-
-        } while (nextCursor != null && !nextCursor.isEmpty());
+            // 其他错误码不重试
+            return result;
+        }
+        return null; // 超过重试次数
     }
 
+    private void sleepWithJitter(int baseMillis) {
+        try {
+            int sleepTime = baseMillis + (new Random().nextInt(1000));
+            Thread.sleep(sleepTime);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
 
-    private void processPageResult(QwGroupmsgSendResult result, List<QwSopLogs> logs, String corpId, String msgId) {
+    private List<QwSopLogs> processPageResult(QwGroupmsgSendResult result, List<QwSopLogs> logs, String corpId, String msgId) {
         Map<String, SendItemResult> sendMap = result.getSendList().stream()
                 .collect(Collectors.toMap(
                         r -> r.getUserId() + "_" + r.getExternalUserId() + "_" + corpId + "_" + msgId,
                         Function.identity(),
-                        (a, b) -> a  // 如果重复,保留第一个
+                        (a, b) -> a
                 ));
 
         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
         String now = LocalDateTime.now().format(formatter);
-
-        // 只处理匹配得上的记录
         List<QwSopLogs> matchedLogs = new ArrayList<>();
 
         for (QwSopLogs log : logs) {
             String logKey = log.getQwUserid() + "_" + log.getExternalUserId() + "_" + log.getCorpId() + "_" + msgId;
             SendItemResult matched = sendMap.get(logKey);
-
-            if (matched != null) {
-
-                switch (matched.getStatus()) {
-                    case 0:
-                        log.setSendStatus(5L);
-                        log.setRemark("员工未在规定时间发送");
-                        break;
-                    case 1:
-                        log.setSendStatus(1L);
-                        log.setReceivingStatus(1L);
-                        break;
-                    case 2:
-                        log.setSendType(2);
-                        log.setSendStatus(3L);
-                        log.setRemark("因客户不是好友导致发送失败,补发");
-                        log.setReceivingStatus(0L);
-                        log.setSendTime(now);
-                        log.setSort(30000001);
-                    case 3:
-                        log.setSendType(2);
-                        log.setSendStatus(3L);
-                        log.setRemark("客户已经收到其他群发消息,补发");
-                        log.setReceivingStatus(0L);
-                        log.setSendTime(now);
-                        log.setSort(30000001);
-                        break;
-                    default:
-                        break;
-                }
-
-                matchedLogs.add(log);
+            if (matched == null) continue;
+
+            switch (matched.getStatus()) {
+                case 0:
+                    log.setSendStatus(5L);
+                    log.setRemark("员工未在规定时间发送");
+                    break;
+                case 1:
+                    log.setSendStatus(1L);
+                    log.setReceivingStatus(1L);
+                    break;
+                case 2:
+                    log.setSendType(2);
+                    log.setSendStatus(3L);
+                    log.setRemark("因客户不是好友导致发送失败,补发");
+                    log.setReceivingStatus(0L);
+                    log.setSendTime(now);
+                    log.setSort(30000001);
+                    break;
+                case 3:
+                    log.setSendType(2);
+                    log.setSendStatus(3L);
+                    log.setRemark("客户已经收到其他群发消息,补发");
+                    log.setReceivingStatus(0L);
+                    log.setSendTime(now);
+                    log.setSort(30000001);
+                    break;
             }
+            matchedLogs.add(log);
         }
-
-        if (!matchedLogs.isEmpty()) {
-            batchUpdateDatabase(matchedLogs);
-        }
+        return matchedLogs;
     }
 
+//    @Override
+//    public void qwSopLogsResultNew() {
+//
+//        logger.info("开始执行企业微信群发消息结果查询任务");
+//        long startTime = System.currentTimeMillis();
+//
+//        List<QwSopLogs> qwSopLogsList = qwSopLogsMapper.selectSopLogsByCreateCorpMassSendResult();
+//        if (qwSopLogsList.isEmpty()) {
+//            logger.info("没有需要查询结果的群发消息记录");
+//            return;
+//        }
+//
+//
+//        Map<String, List<QwSopLogs>> grouped = qwSopLogsList.stream().collect(
+//                Collectors.groupingBy(log -> log.getQwUserid() + "|" + log.getCorpId() + "|" + log.getMsgId())
+//        );
+//        for (Map.Entry<String, List<QwSopLogs>> entry : grouped.entrySet()) {
+//            String key = entry.getKey();
+//            List<QwSopLogs> corpLogs = entry.getValue();
+//
+//            String[] keys = key.split("\\|");
+//            String qwUserid = keys[0];
+//            String corpId = keys[1];
+//            String msgID = keys[2];
+//
+//            QwGetGroupmsgSendParam param = new QwGetGroupmsgSendParam();
+//            param.setMsgid(msgID);
+//            param.setUserid(qwUserid);
+//            param.setLimit(1000);
+//
+//            fetchAndProcessAllPages(param, corpId, corpLogs, msgID);
+//
+//        }
+//
+//        long endTime = System.currentTimeMillis();
+//        logger.info("企业微信群发消息结果查询任务完成,处理记录总数: {},总耗时: {}ms",
+//                qwSopLogsList.size(), (endTime - startTime));
+//
+//    }
+//    private void fetchAndProcessAllPages(QwGetGroupmsgSendParam param, String corpId, List<QwSopLogs> logs, String msgId) {
+//        String nextCursor = null;
+//
+//        do {
+//            param.setCursor(nextCursor);
+//            QwGroupmsgSendResult result = qwApiService.getGroupmsgSendResult(param, corpId);
+//
+//            if (result == null) {
+//                logger.error("接口调用失败: {}", param);
+//                return;
+//            }
+//
+//            if (result.getErrCode() == 45033) {
+//                try {
+//                    Thread.sleep(2000 + new Random().nextInt(1000));
+//                    result = qwApiService.getGroupmsgSendResult(param, corpId);
+//                } catch (InterruptedException e) {
+//                    Thread.currentThread().interrupt();
+//                    logger.error("线程中断", e);
+//                    return;
+//                }
+//            }
+//
+//            if (result.getErrCode() != 0) {
+//                logger.error("查询失败: {}, errCode: {}, errMsg: {}", param, result.getErrCode(), result.getErrMsg());
+//                return;
+//            }
+//
+//            processPageResult(result, logs, corpId, msgId);
+//            nextCursor = result.getNextCursor();
+//
+//        } while (nextCursor != null && !nextCursor.isEmpty());
+//    }
+//    private void processPageResult(QwGroupmsgSendResult result, List<QwSopLogs> logs, String corpId, String msgId) {
+//        Map<String, SendItemResult> sendMap = result.getSendList().stream()
+//                .collect(Collectors.toMap(
+//                        r -> r.getUserId() + "_" + r.getExternalUserId() + "_" + corpId + "_" + msgId,
+//                        Function.identity(),
+//                        (a, b) -> a  // 如果重复,保留第一个
+//                ));
+//
+//        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+//        String now = LocalDateTime.now().format(formatter);
+//
+//        // 只处理匹配得上的记录
+//        List<QwSopLogs> matchedLogs = new ArrayList<>();
+//
+//        for (QwSopLogs log : logs) {
+//            String logKey = log.getQwUserid() + "_" + log.getExternalUserId() + "_" + log.getCorpId() + "_" + msgId;
+//            SendItemResult matched = sendMap.get(logKey);
+//
+//            if (matched != null) {
+//
+//                switch (matched.getStatus()) {
+//                    case 0:
+//                        log.setSendStatus(5L);
+//                        log.setRemark("员工未在规定时间发送");
+//                        break;
+//                    case 1:
+//                        log.setSendStatus(1L);
+//                        log.setReceivingStatus(1L);
+//                        break;
+//                    case 2:
+//                        log.setSendType(2);
+//                        log.setSendStatus(3L);
+//                        log.setRemark("因客户不是好友导致发送失败,补发");
+//                        log.setReceivingStatus(0L);
+//                        log.setSendTime(now);
+//                        log.setSort(30000001);
+//                    case 3:
+//                        log.setSendType(2);
+//                        log.setSendStatus(3L);
+//                        log.setRemark("客户已经收到其他群发消息,补发");
+//                        log.setReceivingStatus(0L);
+//                        log.setSendTime(now);
+//                        log.setSort(30000001);
+//                        break;
+//                    default:
+//                        break;
+//                }
+//
+//                matchedLogs.add(log);
+//            }
+//        }
+//
+//        if (!matchedLogs.isEmpty()) {
+//            batchUpdateDatabase(matchedLogs);
+//        }
+//    }
+
 
     /**
      * 批量更新数据库

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

@@ -3,12 +3,15 @@ package com.fs.sop.service.impl;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
+import com.fs.company.domain.CompanyMiniapp;
 import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.company.service.ICompanyMiniappService;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsCourseDomainName;
 import com.fs.course.domain.FsCourseLink;
@@ -153,6 +156,9 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     @Autowired
     private QwGroupChatUserMapper qwGroupChatUserMapper;
 
+    @Autowired
+    private ICompanyMiniappService companyMiniappService;
+
     @Override
     public void save(SopUserLogsInfo sopUserLogsInfo) {
         sopUserLogsInfoMapper.insertSopUserLogsInfo(sopUserLogsInfo);
@@ -218,6 +224,21 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         sopUserLogsInfoMapper.batchInsertSopUserLogsInfo(logsToInsert);
     }
 
+    @Override
+    public void batchUpdateSopUserLogsInfoByMoreStudy(List<QwExternalContact> contactList) {
+        sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByMoreStudy(contactList);
+    }
+
+    @Override
+    public void batchUpdateSopUserLogsInfoByIsDaysNotStudy(List<QwExternalContact> contactList) {
+        sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByIsDaysNotStudy(contactList);
+    }
+
+    @Override
+    public void batchUpdateSopUserLogsInfoByLevel(List<QwExternalContact> contactList) {
+        sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByLevel(contactList);
+    }
+
     @Override
     public void insertSopUserLogsInfo(SopUserLogsInfo logsToInsert) {
         sopUserLogsInfoMapper.insertSopUserLogsInfo(logsToInsert);
@@ -698,6 +719,14 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 }).collect(Collectors.toList());
             }
         }else{
+
+
+            // 查询公司关联小程序数据
+            List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
+
+            Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));
+
+
             sopLogsList = new ArrayList<>();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             List<SopUserLogsInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectSopUserLogsInfoByIds(param.getIds());
@@ -810,12 +839,28 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
                                     qwUserId, companyUserId, companyId, item.getExternalId(), config);
 
-                            if (StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())){
-                                log.error("企业未配置小程序-"+param.getCorpId());
-                            }else {
-                                //置换各自的小程序
+                            if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                                Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                                if (integerListMap != null) {
+
+                                    int effectiveGrade = (item.getGrade() == null) ? 5 : item.getGrade();
+                                    int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
+                                    List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                                    if (miniapps != null && !miniapps.isEmpty()) {
+                                        CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                        if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                            st.setMiniprogramAppid(companyMiniapp.getAppId());
+                                        }
+                                    }
+                                }
+                            } else if (!StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
                                 st.setMiniprogramAppid(qwCompany.getMiniAppId());
+                            } else {
+                                log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
                             }
+
+
                             st.setMiniprogramPage(linkByMiniApp);
 
                             break;
@@ -874,6 +919,11 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return null;
     }
 
+    @Override
+    public List<SopUserLogsInfo> selectRestoreByIsDaysNotStudy(String sopId, String userLogsId) {
+        return sopUserLogsInfoMapper.selectRestoreByIsDaysNotStudy(sopId, userLogsId);
+    }
+
     @Override
     public List<ExtCourseSopWatchLogVO> getExtCourseSopWatchLog(QwExtCourseSopWatchLog qwExternalContactId) {
 
@@ -915,6 +965,9 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
         List<SopUserLogsInfo> sopUserLogs = sopUserLogsMapper.selectSopUserLogsByIds(param.getIds());
 
+        // 查询公司关联小程序数据
+        List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
+        Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));
 
         //排序
         int sort = 0;
@@ -955,7 +1008,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 log.error("员工信息用户不存在:" + key.getKey()+",企业id:"+key.getValue());
             }else {
 
-                List<QwSopLogs> sopLogsList = processInsertSopUserLogsInfo(logs, qwUser, param, words, config, qwCompany, finalSort, finalSendType);
+                List<QwSopLogs> sopLogsList = processInsertSopUserLogsInfo(logs, qwUser, param, words, config, qwCompany, finalSort,
+                        finalSendType,miniMap );
 
                 //批量插入 发送记录
                 if (!sopLogsList.isEmpty()) {
@@ -972,7 +1026,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
     private List<QwSopLogs> processInsertSopUserLogsInfo(List<SopUserLogsInfo> sopUserLogsInfos,QwUser qwUser,
                                                          SendUserLogsInfoMsgParam param,List<FastGptChatReplaceWords> words,
-                                                         CourseConfig config,QwCompany qwCompany,int finalSort,int finalSendType){
+                                                         CourseConfig config,QwCompany qwCompany,int finalSort,int finalSendType,
+                                                         Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap){
 
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
@@ -1026,7 +1081,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
             switch (finalSendType){
                 case 5:
-                    List<QwSopCourseFinishTempSetting.Setting> list = processSetting(item,qwUser, param, words, config, qwCompany,companyUserId,companyId,contact,dataTime, finalDomainName);
+                    List<QwSopCourseFinishTempSetting.Setting> list = processSetting(item,qwUser, param, words, config, qwCompany,companyUserId,companyId,
+                            contact,dataTime, finalDomainName,miniMap);
                     setting.setSetting(list);
                     break;
                 case 9:
@@ -1088,7 +1144,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     private List<QwSopCourseFinishTempSetting.Setting> processSetting(SopUserLogsInfo item, QwUser qwUser,
                                                                       SendUserLogsInfoMsgParam param,List<FastGptChatReplaceWords> words,
                                                                       CourseConfig config,QwCompany qwCompany,String companyUserId, String companyId,
-                                                                      QwExternalContact contact,Date dataTime,String domainName){
+                                                                      QwExternalContact contact,Date dataTime,String domainName,
+                                                                      Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap){
         List<QwSopCourseFinishTempSetting.Setting> list = JSONArray.parseArray(param.getSetting(),QwSopCourseFinishTempSetting.Setting.class);
 
         for (QwSopCourseFinishTempSetting.Setting st : list) {
@@ -1158,13 +1215,27 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 //                    }else {
 //                        st.setMiniprogramAppid(config.getMiniprogramAppid());
 //                    }
-                    if (StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())){
-                        log.error("企业未配置小程序-"+param.getCorpId());
-
-                    }else {
-                        //置换各自的小程序
+                    if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                        Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                        if (integerListMap != null) {
+
+                            int effectiveGrade = (item.getGrade() == null) ? 5 : item.getGrade();
+                            int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
+                            List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                            if (miniapps != null && !miniapps.isEmpty()) {
+                                CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                    st.setMiniprogramAppid(companyMiniapp.getAppId());
+                                }
+                            }
+                        }
+                    } else if (!StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
                         st.setMiniprogramAppid(qwCompany.getMiniAppId());
+                    } else {
+                        log.error("企业未配置小程序-" + param.getCorpId());
                     }
+
                     st.setMiniprogramPage(linkByMiniApp);
                     break;
                 default:

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

@@ -955,6 +955,11 @@ public class SopUserLogsServiceImpl  implements ISopUserLogsService {
         sopUserLogsMapper.batchInsertSopUserLogs(list);
     }
 
+    @Override
+    public List<SopUserLogs> meetsTherestoreByIsDaysNotStudy(int offset, int pageSize, Integer notStudyDays) {
+        return sopUserLogsMapper.meetsTherestoreByIsDaysNotStudy(offset,pageSize,notStudyDays);
+    }
+
     //批量更新
     private void batchUpdateQwExternalContact(List<QwExternalContact> notInExternalUseridList) {
         // 定义批量插入的大小

+ 1 - 1
fs-service/src/main/java/com/fs/sop/vo/QwRatingVO.java

@@ -16,7 +16,7 @@ public class QwRatingVO {
     */
     private Integer allDuration;
 
-
+    private Integer level;
 
     /**
      * 1升 2降 3未变动

+ 16 - 13
fs-service/src/main/java/com/fs/system/mapper/SysRoleMapper.java

@@ -2,17 +2,18 @@ package com.fs.system.mapper;
 
 import java.util.List;
 import com.fs.common.core.domain.entity.SysRole;
+import org.apache.ibatis.annotations.Param;
 
 /**
  * 角色表 数据层
- * 
+ *
 
  */
 public interface SysRoleMapper
 {
     /**
      * 根据条件分页查询角色数据
-     * 
+     *
      * @param role 角色信息
      * @return 角色数据集合信息
      */
@@ -20,7 +21,7 @@ public interface SysRoleMapper
 
     /**
      * 根据用户ID查询角色
-     * 
+     *
      * @param userId 用户ID
      * @return 角色列表
      */
@@ -28,14 +29,14 @@ public interface SysRoleMapper
 
     /**
      * 查询所有角色
-     * 
+     *
      * @return 角色列表
      */
     public List<SysRole> selectRoleAll();
 
     /**
      * 根据用户ID获取角色选择框列表
-     * 
+     *
      * @param userId 用户ID
      * @return 选中角色ID列表
      */
@@ -43,7 +44,7 @@ public interface SysRoleMapper
 
     /**
      * 通过角色ID查询角色
-     * 
+     *
      * @param roleId 角色ID
      * @return 角色对象信息
      */
@@ -51,7 +52,7 @@ public interface SysRoleMapper
 
     /**
      * 根据用户ID查询角色
-     * 
+     *
      * @param userName 用户名
      * @return 角色列表
      */
@@ -59,7 +60,7 @@ public interface SysRoleMapper
 
     /**
      * 校验角色名称是否唯一
-     * 
+     *
      * @param roleName 角色名称
      * @return 角色信息
      */
@@ -67,7 +68,7 @@ public interface SysRoleMapper
 
     /**
      * 校验角色权限是否唯一
-     * 
+     *
      * @param roleKey 角色权限
      * @return 角色信息
      */
@@ -75,7 +76,7 @@ public interface SysRoleMapper
 
     /**
      * 修改角色信息
-     * 
+     *
      * @param role 角色信息
      * @return 结果
      */
@@ -83,7 +84,7 @@ public interface SysRoleMapper
 
     /**
      * 新增角色信息
-     * 
+     *
      * @param role 角色信息
      * @return 结果
      */
@@ -91,7 +92,7 @@ public interface SysRoleMapper
 
     /**
      * 通过角色ID删除角色
-     * 
+     *
      * @param roleId 角色ID
      * @return 结果
      */
@@ -99,9 +100,11 @@ public interface SysRoleMapper
 
     /**
      * 批量删除角色信息
-     * 
+     *
      * @param roleIds 需要删除的角色ID
      * @return 结果
      */
     public int deleteRoleByIds(Long[] roleIds);
+
+    List<SysRole> selectSysRoleByIds(Long[] roleIds);
 }

+ 23 - 21
fs-service/src/main/java/com/fs/system/service/ISysRoleService.java

@@ -7,14 +7,14 @@ import com.fs.system.domain.SysUserRole;
 
 /**
  * 角色业务层
- * 
+ *
 
  */
 public interface ISysRoleService
 {
     /**
      * 根据条件分页查询角色数据
-     * 
+     *
      * @param role 角色信息
      * @return 角色数据集合信息
      */
@@ -22,7 +22,7 @@ public interface ISysRoleService
 
     /**
      * 根据用户ID查询角色列表
-     * 
+     *
      * @param userId 用户ID
      * @return 角色列表
      */
@@ -30,7 +30,7 @@ public interface ISysRoleService
 
     /**
      * 根据用户ID查询角色权限
-     * 
+     *
      * @param userId 用户ID
      * @return 权限列表
      */
@@ -38,14 +38,14 @@ public interface ISysRoleService
 
     /**
      * 查询所有角色
-     * 
+     *
      * @return 角色列表
      */
     public List<SysRole> selectRoleAll();
 
     /**
      * 根据用户ID获取角色选择框列表
-     * 
+     *
      * @param userId 用户ID
      * @return 选中角色ID列表
      */
@@ -53,7 +53,7 @@ public interface ISysRoleService
 
     /**
      * 通过角色ID查询角色
-     * 
+     *
      * @param roleId 角色ID
      * @return 角色对象信息
      */
@@ -61,7 +61,7 @@ public interface ISysRoleService
 
     /**
      * 校验角色名称是否唯一
-     * 
+     *
      * @param role 角色信息
      * @return 结果
      */
@@ -69,7 +69,7 @@ public interface ISysRoleService
 
     /**
      * 校验角色权限是否唯一
-     * 
+     *
      * @param role 角色信息
      * @return 结果
      */
@@ -77,21 +77,21 @@ public interface ISysRoleService
 
     /**
      * 校验角色是否允许操作
-     * 
+     *
      * @param role 角色信息
      */
     public void checkRoleAllowed(SysRole role);
 
     /**
      * 校验角色是否有数据权限
-     * 
+     *
      * @param roleId 角色id
      */
     public void checkRoleDataScope(Long roleId);
 
     /**
      * 通过角色ID查询角色使用数量
-     * 
+     *
      * @param roleId 角色ID
      * @return 结果
      */
@@ -99,7 +99,7 @@ public interface ISysRoleService
 
     /**
      * 新增保存角色信息
-     * 
+     *
      * @param role 角色信息
      * @return 结果
      */
@@ -107,7 +107,7 @@ public interface ISysRoleService
 
     /**
      * 修改保存角色信息
-     * 
+     *
      * @param role 角色信息
      * @return 结果
      */
@@ -115,7 +115,7 @@ public interface ISysRoleService
 
     /**
      * 修改角色状态
-     * 
+     *
      * @param role 角色信息
      * @return 结果
      */
@@ -123,7 +123,7 @@ public interface ISysRoleService
 
     /**
      * 修改数据权限信息
-     * 
+     *
      * @param role 角色信息
      * @return 结果
      */
@@ -131,7 +131,7 @@ public interface ISysRoleService
 
     /**
      * 通过角色ID删除角色
-     * 
+     *
      * @param roleId 角色ID
      * @return 结果
      */
@@ -139,7 +139,7 @@ public interface ISysRoleService
 
     /**
      * 批量删除角色信息
-     * 
+     *
      * @param roleIds 需要删除的角色ID
      * @return 结果
      */
@@ -147,7 +147,7 @@ public interface ISysRoleService
 
     /**
      * 取消授权用户角色
-     * 
+     *
      * @param userRole 用户和角色关联信息
      * @return 结果
      */
@@ -155,7 +155,7 @@ public interface ISysRoleService
 
     /**
      * 批量取消授权用户角色
-     * 
+     *
      * @param roleId 角色ID
      * @param userIds 需要取消授权的用户数据ID
      * @return 结果
@@ -164,10 +164,12 @@ public interface ISysRoleService
 
     /**
      * 批量选择授权用户角色
-     * 
+     *
      * @param roleId 角色ID
      * @param userIds 需要删除的用户数据ID
      * @return 结果
      */
     public int insertAuthUsers(Long roleId, Long[] userIds);
+
+    boolean getIsCheckPhone(Long[] roleIds);
 }

+ 37 - 22
fs-service/src/main/java/com/fs/system/service/impl/SysRoleServiceImpl.java

@@ -27,7 +27,7 @@ import com.fs.system.service.ISysRoleService;
 
 /**
  * 角色 业务层处理
- * 
+ *
 
  */
 @Service
@@ -47,7 +47,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 根据条件分页查询角色数据
-     * 
+     *
      * @param role 角色信息
      * @return 角色数据集合信息
      */
@@ -60,7 +60,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 根据用户ID查询角色
-     * 
+     *
      * @param userId 用户ID
      * @return 角色列表
      */
@@ -85,7 +85,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 根据用户ID查询权限
-     * 
+     *
      * @param userId 用户ID
      * @return 权限列表
      */
@@ -106,7 +106,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 查询所有角色
-     * 
+     *
      * @return 角色列表
      */
     @Override
@@ -117,7 +117,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 根据用户ID获取角色选择框列表
-     * 
+     *
      * @param userId 用户ID
      * @return 选中角色ID列表
      */
@@ -129,7 +129,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 通过角色ID查询角色
-     * 
+     *
      * @param roleId 角色ID
      * @return 角色对象信息
      */
@@ -141,7 +141,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 校验角色名称是否唯一
-     * 
+     *
      * @param role 角色信息
      * @return 结果
      */
@@ -159,7 +159,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 校验角色权限是否唯一
-     * 
+     *
      * @param role 角色信息
      * @return 结果
      */
@@ -177,7 +177,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 校验角色是否允许操作
-     * 
+     *
      * @param role 角色信息
      */
     @Override
@@ -191,7 +191,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 校验角色是否有数据权限
-     * 
+     *
      * @param roleId 角色id
      */
     @Override
@@ -211,7 +211,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 通过角色ID查询角色使用数量
-     * 
+     *
      * @param roleId 角色ID
      * @return 结果
      */
@@ -223,7 +223,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 新增保存角色信息
-     * 
+     *
      * @param role 角色信息
      * @return 结果
      */
@@ -238,7 +238,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 修改保存角色信息
-     * 
+     *
      * @param role 角色信息
      * @return 结果
      */
@@ -255,7 +255,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 修改角色状态
-     * 
+     *
      * @param role 角色信息
      * @return 结果
      */
@@ -267,7 +267,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 修改数据权限信息
-     * 
+     *
      * @param role 角色信息
      * @return 结果
      */
@@ -285,7 +285,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 新增角色菜单信息
-     * 
+     *
      * @param role 角色对象
      */
     public int insertRoleMenu(SysRole role)
@@ -333,7 +333,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 通过角色ID删除角色
-     * 
+     *
      * @param roleId 角色ID
      * @return 结果
      */
@@ -350,7 +350,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 批量删除角色信息
-     * 
+     *
      * @param roleIds 需要删除的角色ID
      * @return 结果
      */
@@ -376,7 +376,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 取消授权用户角色
-     * 
+     *
      * @param userRole 用户和角色关联信息
      * @return 结果
      */
@@ -388,7 +388,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 批量取消授权用户角色
-     * 
+     *
      * @param roleId 角色ID
      * @param userIds 需要取消授权的用户数据ID
      * @return 结果
@@ -401,7 +401,7 @@ public class SysRoleServiceImpl implements ISysRoleService
 
     /**
      * 批量选择授权用户角色
-     * 
+     *
      * @param roleId 角色ID
      * @param userIds 需要删除的用户数据ID
      * @return 结果
@@ -420,4 +420,19 @@ public class SysRoleServiceImpl implements ISysRoleService
         }
         return userRoleMapper.batchUserRole(list);
     }
+
+    @Override
+    public boolean getIsCheckPhone(Long[] roleIds) {
+        if (roleIds != null && roleIds.length > 0){
+            List<SysRole> roles = roleMapper.selectSysRoleByIds(roleIds);
+            if (roles != null && !roles.isEmpty()){
+                for (SysRole role : roles) {
+                    if (role.getIsCheckPhone() == 1){
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
 }

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

@@ -36,7 +36,7 @@ server:
 # 日志配置
 logging:
   level:
-    com.fs: DEBUG
+    com.fs: info
     org.springframework: warn
 
 express:

+ 4 - 0
fs-service/src/main/resources/application-config-druid-fby.yml

@@ -58,6 +58,10 @@ wx:
         secret: 8adb2a7533921449ef6e60814c2ff075
         token: PPKOdAlCoMO # 接口配置里的Token值
         aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+  # 开放平台app微信授权配置
+  open:
+    app-id: wxcb1e78baf03c0662
+    secret: d041d3e2392a68a3e86dc22d976ed4a0
 aifabu:  #爱链接
   appKey: 7b471be905ab17e00f3b858c6710dd117601d008
 watch:

+ 1 - 1
fs-service/src/main/resources/application-config-druid-hcl.yml

@@ -85,7 +85,7 @@ cloud_host:
   company_name: 恒春来
 #看课授权时显示的头像
 headerImg:
-  imgUrl: https://hcl-1b2b.obs.cn-south-1.myhuaweicloud.com/fs/20250803/1754213762409.png
+  imgUrl: http://hcl-1b2b.obs.cn-south-1.myhuaweicloud.com/fs/20250808/1754640068227.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   aiApi:

+ 1 - 1
fs-service/src/main/resources/application-config-druid-sxjz.yml

@@ -79,7 +79,7 @@ cloud_host:
 headerImg:
   imgUrl: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png
 ipad:
-  ipadUrl: http://ipad.cdwjyyh.com
+  ipadUrl: http://ipad.xintaihl.cn
   aiApi:
 wx_miniapp_temp:
   pay_order_temp_id:

+ 1 - 0
fs-service/src/main/resources/application-config-druid-xzt.yml

@@ -82,6 +82,7 @@ headerImg:
   imgUrl: https://drk-1363981074.cos.ap-chongqing.myqcloud.com/fs/logo/30d7a0d1ec31e5ac16c6e96d5ca76ad.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
+  aiApi: 1212121212
 wx_miniapp_temp:
   pay_order_temp_id:
   inquiry_temp_id:

+ 7 - 1
fs-service/src/main/resources/application-druid-sxjz.yml

@@ -146,7 +146,13 @@ rocketmq:
         group: voice-group
         access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
         secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
-
+custom:
+    token: "1o62d3YxvdHd4LEUiltnu7sK"
+    encoding-aes-key: "UJfTQ5qKTKlegjkXtp1YuzJzxeHlUKvq5GyFbERN1iU"
+    corp-id: "ww51717e2b71d5e2d3"
+    secret: "6ODAmw-8W4t6h9mdzHh2Z4Apwj8mnsyRnjEDZOHdA7k"
+    private-key-path: "privatekey.pem"
+    webhook-url: "https://your-server.com/wecom/archive"
 # token配置
 token:
     # 令牌自定义标识

+ 3 - 0
fs-service/src/main/resources/application-druid-xzt.yml

@@ -139,3 +139,6 @@ rocketmq:
         group: test-group
         access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
         secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+openIM:
+    secret: openIM123
+    userID: imAdmin

+ 91 - 0
fs-service/src/main/resources/mapper/CompanyMiniappMapper.xml

@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.CompanyMiniappMapper">
+    
+    <resultMap type="CompanyMiniapp" id="CompanyMiniappResult">
+        <result property="id"    column="id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="appId"    column="app_id"    />
+        <result property="type"    column="type"    />
+        <result property="sortNum"    column="sort_num"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="updateBy"    column="update_by"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="remark"    column="remark"    />
+    </resultMap>
+
+    <sql id="selectCompanyMiniappVo">
+        select id, company_id, app_id, type, sort_num, create_time, create_by, update_by, update_time, remark from company_miniapp
+    </sql>
+
+    <select id="selectCompanyMiniappList" parameterType="CompanyMiniapp" resultMap="CompanyMiniappResult">
+        <include refid="selectCompanyMiniappVo"/>
+        <where>  
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="appId != null  and appId != ''"> and app_id = #{appId}</if>
+            <if test="type != null "> and type = #{type}</if>
+            <if test="sortNum != null "> and sort_num = #{sortNum}</if>
+        </where>
+    </select>
+    
+    <select id="selectCompanyMiniappById" parameterType="Long" resultMap="CompanyMiniappResult">
+        <include refid="selectCompanyMiniappVo"/>
+        where id = #{id}
+    </select>
+        
+    <insert id="insertCompanyMiniapp" parameterType="CompanyMiniapp" useGeneratedKeys="true" keyProperty="id">
+        insert into company_miniapp
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="companyId != null">company_id,</if>
+            <if test="appId != null">app_id,</if>
+            <if test="type != null">type,</if>
+            <if test="sortNum != null">sort_num,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remark != null">remark,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="companyId != null">#{companyId},</if>
+            <if test="appId != null">#{appId},</if>
+            <if test="type != null">#{type},</if>
+            <if test="sortNum != null">#{sortNum},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remark != null">#{remark},</if>
+         </trim>
+    </insert>
+
+    <update id="updateCompanyMiniapp" parameterType="CompanyMiniapp">
+        update company_miniapp
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="appId != null">app_id = #{appId},</if>
+            <if test="type != null">type = #{type},</if>
+            <if test="sortNum != null">sort_num = #{sortNum},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null">remark = #{remark},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteCompanyMiniappById" parameterType="Long">
+        delete from company_miniapp where id = #{id}
+    </delete>
+
+    <delete id="deleteCompanyMiniappByIds" parameterType="String">
+        delete from company_miniapp where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 12 - 0
fs-service/src/main/resources/mapper/company/CompanyMapper.xml

@@ -35,6 +35,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="packageCateIds"    column="package_cate_ids"    />
         <result property="courseMaAppId"    column="course_ma_app_id"    />
         <result property="courseMiniAppId"    column="course_mini_app_id"    />
+        <result property="repeat"    column="repeat"    />
+        <result property="sendIfType"    column="send_if_type"    />
+        <result property="ifNum"    column="if_num"    />
     </resultMap>
 
     <sql id="selectCompanyVo">
@@ -105,6 +108,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="packageCateIds != null">package_cate_ids,</if>
             <if test="courseMaAppId != null">course_ma_app_id,</if>
             <if test="courseMiniAppId != null">course_mini_app_id,</if>
+            <if test="repeat != null">`repeat`,</if>
+            <if test="sendIfType != null">send_if_type,</if>
+            <if test="ifNum != null">if_num,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="companyName != null">#{companyName},</if>
@@ -134,6 +140,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="packageCateIds != null">#{packageCateIds},</if>
             <if test="courseMaAppId != null">#{courseMaAppId},</if>
             <if test="courseMiniAppId != null">#{courseMiniAppId},</if>
+            <if test="repeat != null">#{repeat},</if>
+            <if test="sendIfType != null">#{sendIfType},</if>
+            <if test="ifNum != null">#{ifNum},</if>
          </trim>
     </insert>
 
@@ -169,6 +178,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="courseMaAppId != null">course_ma_app_id = #{courseMaAppId},</if>
             <if test="courseMiniAppId != null">course_mini_app_id = #{courseMiniAppId},</if>
             <if test="fsUserIsDefaultBlack != null ">fs_user_is_default_black = #{fsUserIsDefaultBlack},</if>
+            <if test="repeat != null">`repeat` = #{repeat},</if>
+            <if test="sendIfType != null">send_if_type = #{sendIfType},</if>
+            <if test="ifNum != null">if_num = #{ifNum},</if>
         </trim>
         where company_id = #{companyId}
     </update>

+ 24 - 0
fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml

@@ -201,6 +201,30 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
     </update>
 
+    <update id="batchUpdateQwExternalContactByMoreStudy" parameterType="map">
+        UPDATE qw_external_contact
+        SET
+        level = CASE id
+        <foreach collection="list" item="item">
+            WHEN #{item.id} THEN #{item.level}
+        </foreach>
+        ELSE level
+        END
+        WHERE id IN
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            #{item.id}
+        </foreach>
+    </update>
+
+    <update id="batchUpdateQwExternalByIsDaysNotStudy" parameterType="map">
+        UPDATE qw_external_contact
+        SET level =  NULL
+        WHERE id IN
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            #{item.id}
+        </foreach>
+    </update>
+
 
     <insert id="insertQwExternalContact" parameterType="QwExternalContact" useGeneratedKeys="true" keyProperty="id" >
         insert into qw_external_contact

+ 12 - 1
fs-service/src/main/resources/mapper/sop/SopUserLogsInfoMapper.xml

@@ -20,6 +20,7 @@
         <result property="updateTime" column="update_time" jdbcType="VARCHAR" />
         <result property="tagIds" column="tag_ids" jdbcType="VARCHAR" />
         <result property="isDaysNotStudy" column="is_days_not_study"/>
+        <result property="grade" column="grade"/>
     </resultMap>
 
     <sql id="selectSopUserLogsInfoVo">
@@ -185,7 +186,7 @@
 
     <!-- 根据ID查询单条记录 -->
     <select id="selectById" parameterType="String" resultMap="SopUserLogsInfoResult">
-        SELECT id, sop_id, user_logs_id, external_contact_id,qw_user_id,corp_id,external_id, fs_user_id, external_user_name,create_time,crt_Time,update_time
+        SELECT id, sop_id, user_logs_id, external_contact_id,qw_user_id,corp_id,external_id, fs_user_id, external_user_name,create_time,crt_Time,update_time,grade
         FROM sop_user_logs_info
         WHERE id = #{id}
     </select>
@@ -235,6 +236,16 @@
         from sop_user_logs_info where sop_id = #{sopId} and user_logs_id=#{userLogsId}
     </select>
 
+    <select id="selectRestoreByIsDaysNotStudy" parameterType="String" resultMap="SopUserLogsInfoResult">
+        select
+            id,external_id
+        from sop_user_logs_info
+        where sop_id = #{sopId}
+          and user_logs_id=#{userLogsId}
+          and is_days_not_study=1
+    </select>
+
+
     <!-- 查询所有记录 -->
     <select id="selectAll" resultMap="SopUserLogsInfoResult">
         SELECT id, sop_id, user_logs_id, external_contact_id,qw_user_id,corp_id,external_id,

+ 41 - 0
fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml

@@ -253,6 +253,47 @@
             LIMIT #{offset}, #{pageSize}
     </select>
 
+
+    <select id="meetsTheRatingByUserInfoWithPaginationStudyDays" resultType="Integer"  resultMap="SopUserLogsResult">
+        SELECT
+            ul.id,
+            ul.sop_id,
+            ul.sop_temp_id,
+            ul.qw_user_id,
+            ul.corp_id,
+            ul.start_time,
+            ul.`status`,
+            ul.user_id,
+            DATEDIFF( CURRENT_DATE, ul.start_time ) AS count_days
+        FROM
+            sop_user_logs ul  LEFT JOIN qw_sop qs on ul.sop_id=qs.id
+        WHERE
+            ul.`status` = '1'
+          and qs.type=2
+          and qs.send_type=2
+          and qs.`status` in (2,3)
+          AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) >= #{notStudyDays}
+        ORDER BY id ASC
+            LIMIT #{offset}, #{pageSize}
+    </select>
+
+    <select id="meetsTherestoreByIsDaysNotStudy" resultType="Integer"  resultMap="SopUserLogsResult">
+        SELECT
+            ul.id,
+            ul.sop_id
+        FROM
+            sop_user_logs ul  LEFT JOIN qw_sop qs on ul.sop_id=qs.id
+        WHERE
+            ul.`status` = '1'
+          and qs.type=2
+          and qs.send_type=2
+          and qs.`status` in (2,3)
+          AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) &lt; #{notStudyDays}
+        ORDER BY id ASC
+            LIMIT #{offset}, #{pageSize}
+    </select>
+
+
     <select id="meetsTheRatingByUserInfoBySopId" resultType="String"  resultMap="SopUserLogsResult">
         SELECT
             ul.id,

+ 31 - 18
fs-service/src/main/resources/mapper/system/SysRoleMapper.xml

@@ -19,17 +19,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 		<result property="updateBy"           column="update_by"             />
 		<result property="updateTime"         column="update_time"           />
 		<result property="remark"             column="remark"                />
+		<result property="isCheckPhone"       column="is_check_phone"                />
 	</resultMap>
-	
+
 	<sql id="selectRoleVo">
 	    select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly,
-            r.status, r.del_flag, r.create_time, r.remark 
+            r.status, r.del_flag, r.create_time, r.remark,r.is_check_phone
         from sys_role r
 	        left join sys_user_role ur on ur.role_id = r.role_id
 	        left join sys_user u on u.user_id = ur.user_id
 	        left join sys_dept d on u.dept_id = d.dept_id
     </sql>
-    
+
     <select id="selectRoleList" parameterType="SysRole" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
 		where r.del_flag = '0'
@@ -51,20 +52,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 		<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
 			and date_format(r.create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
 		</if>
+		<if test="params.isCheckPhone != null"><!-- 结束时间检索 -->
+			and r.is_check_phone = #{params.isCheckPhone}
+		</if>
 		<!-- 数据范围过滤 -->
 		${params.dataScope}
 		order by r.role_sort
 	</select>
-    
+
 	<select id="selectRolePermissionByUserId" parameterType="Long" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
 		WHERE r.del_flag = '0' and ur.user_id = #{userId}
 	</select>
-	
+
 	<select id="selectRoleAll" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
 	</select>
-	
+
 	<select id="selectRoleListByUserId" parameterType="Long" resultType="Integer">
 		select r.role_id
         from sys_role r
@@ -72,28 +76,34 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 	        left join sys_user u on u.user_id = ur.user_id
 	    where u.user_id = #{userId}
 	</select>
-	
+
 	<select id="selectRoleById" parameterType="Long" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
 		where r.role_id = #{roleId}
 	</select>
-	
+
 	<select id="selectRolesByUserName" parameterType="String" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
 		WHERE r.del_flag = '0' and u.user_name = #{userName}
 	</select>
-	
+
 	<select id="checkRoleNameUnique" parameterType="String" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
 		 where r.role_name=#{roleName} limit 1
 	</select>
-	
+
 	<select id="checkRoleKeyUnique" parameterType="String" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
 		 where r.role_key=#{roleKey} limit 1
 	</select>
-	
- 	<insert id="insertRole" parameterType="SysRole" useGeneratedKeys="true" keyProperty="roleId">
+	<select id="selectSysRoleByIds" resultType="com.fs.common.core.domain.entity.SysRole">
+		select role_id,is_check_phone from sys_role where role_id in
+		<foreach collection="array" item="roleId" open="(" separator="," close=")">
+			#{roleId}
+		</foreach>
+	</select>
+
+	<insert id="insertRole" parameterType="SysRole" useGeneratedKeys="true" keyProperty="roleId">
  		insert into sys_role(
  			<if test="roleId != null and roleId != 0">role_id,</if>
  			<if test="roleName != null and roleName != ''">role_name,</if>
@@ -105,6 +115,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  			<if test="status != null and status != ''">status,</if>
  			<if test="remark != null and remark != ''">remark,</if>
  			<if test="createBy != null and createBy != ''">create_by,</if>
+ 			<if test="isCheckPhone != null">is_check_phone,</if>
  			create_time
  		)values(
  			<if test="roleId != null and roleId != 0">#{roleId},</if>
@@ -117,10 +128,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  			<if test="status != null and status != ''">#{status},</if>
  			<if test="remark != null and remark != ''">#{remark},</if>
  			<if test="createBy != null and createBy != ''">#{createBy},</if>
+ 			<if test="isCheckPhone != null">#{isCheckPhone},</if>
  			sysdate()
  		)
 	</insert>
-	
+
 	<update id="updateRole" parameterType="SysRole">
  		update sys_role
  		<set>
@@ -133,20 +145,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  			<if test="status != null and status != ''">status = #{status},</if>
  			<if test="remark != null">remark = #{remark},</if>
  			<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+ 			<if test="isCheckPhone != null">is_check_phone = #{isCheckPhone},</if>
  			update_time = sysdate()
  		</set>
  		where role_id = #{roleId}
 	</update>
-	
+
 	<delete id="deleteRoleById" parameterType="Long">
 		update sys_role set del_flag = '2' where role_id = #{roleId}
  	</delete>
- 	
+
  	<delete id="deleteRoleByIds" parameterType="Long">
  	    update sys_role set del_flag = '2' where role_id in
  		<foreach collection="array" item="roleId" open="(" separator="," close=")">
  			#{roleId}
-        </foreach> 
+        </foreach>
  	</delete>
- 	
-</mapper> 
+
+</mapper>

+ 1 - 0
fs-user-app/src/main/java/com/fs/app/controller/CourseController.java

@@ -58,6 +58,7 @@ public class CourseController extends  AppBaseController{
             return R.error("操作异常");
         }
     }
+
     @Cacheable(value = "getProductCateByPid", key = "#pid")
     @ApiOperation("获取子分类")
     @GetMapping("/getProductCateByPid")

+ 4 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwLoginController.java

@@ -100,8 +100,12 @@ public class CourseQwLoginController extends AppBaseController {
             final WxMaService wxService = wxServiceSupplier.get();
             WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(param.getCode());
             this.logger.info("获取{} Session:{}", logName, session);
+            if (session.getUnionid()==null){
+                return R.error("未绑定开放平台");
+            }
 
             FsUser user = userService.selectFsUserByUnionid(session.getUnionid());
+
             boolean isNewUser = false;
 
             // 用户存在时的更新逻辑