Просмотр исходного кода

Merge remote-tracking branch 'origin/master'

lk 1 неделя назад
Родитель
Сommit
c2fb567bea
19 измененных файлов с 546 добавлено и 206 удалено
  1. 97 1
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  2. 1 0
      fs-admin/src/main/java/com/fs/his/controller/FsUserController.java
  3. 4 34
      fs-common/src/main/java/com/fs/common/core/redis/service/StockDeductService.java
  4. 3 3
      fs-ipad-task/src/main/resources/logback.xml
  5. 3 2
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  6. 50 8
      fs-qw-task/src/main/java/com/fs/app/task/CourseWatchLogScheduler.java
  7. 5 4
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  8. 0 1
      fs-service/src/main/java/com/fs/course/config/RedisKeyScanner.java
  9. 323 113
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  10. 4 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  11. 3 2
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderListVO.java
  12. 32 28
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductPurchaseLimitScrmServiceImpl.java
  13. 10 4
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  14. 2 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderVO.java
  15. 1 1
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  16. 3 1
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  17. 2 2
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
  18. 2 2
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductPurchaseLimitScrmMapper.xml
  19. 1 0
      fs-user-app/src/main/java/com/fs/app/controller/DoctorArticleController.java

+ 97 - 1
fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java

@@ -148,6 +148,7 @@ public class FsIntegralOrderController extends BaseController
     @PreAuthorize("@ss.hasPermi('his:integralOrder:export')")
     @Log(title = "积分商品订单", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
+
     public AjaxResult export(FsIntegralOrderParam fsIntegralOrder) {
         List<FsIntegralOrderListVO> fsIntegralOrderListVOS = new ArrayList<>();
         if (CloudHostUtils.hasCloudHostName("金牛明医")){
@@ -162,6 +163,101 @@ public class FsIntegralOrderController extends BaseController
         }
         SysRole sysRole = isCheckPermission();
         // 处理商品名称:解析item_json并设置goodsName
+        for (FsIntegralOrderListVO vo : fsIntegralOrderListVOS) {
+            if (StringUtils.isNotEmpty(vo.getItemJson())) {
+                try {
+                    // 尝试解析JSON格式的商品信息
+                    if (vo.getItemJson().startsWith("[")) {
+                        // 数组格式,遍历所有商品,用换行符分隔
+                        com.alibaba.fastjson.JSONArray jsonArray = com.alibaba.fastjson.JSONArray.parseArray(vo.getItemJson());
+                        if (jsonArray != null && !jsonArray.isEmpty()) {
+                            StringBuilder goodsNameBuilder = new StringBuilder();
+                            StringBuilder barCodeBuilder = new StringBuilder();
+
+                            for (int i = 0; i < jsonArray.size(); i++) {
+                                com.alibaba.fastjson.JSONObject goods = jsonArray.getJSONObject(i);
+
+                                // 处理商品名称和数量
+                                if (goods != null && goods.getString("goodsName") != null) {
+                                    String name = goods.getString("goodsName");
+                                    String num = goods.getString("num");
+                                    if (StringUtils.isEmpty(num)) {
+                                        num = "1"; // 默认数量为1
+                                    }
+                                    if (goodsNameBuilder.length() > 0) {
+                                        goodsNameBuilder.append("\r\n");
+                                    }
+                                    goodsNameBuilder.append(name).append(",数量:").append(num).append("个");
+                                }
+
+                                // 处理商品编码
+                                if (goods != null && goods.getString("barCode") != null) {
+                                    String barCode = goods.getString("barCode");
+                                    if (barCodeBuilder.length() > 0) {
+                                        barCodeBuilder.append("\r\n");
+                                    }
+                                    barCodeBuilder.append(barCode);
+                                }
+                            }
+
+                            // 设置商品名称
+                            if (goodsNameBuilder.length() > 0) {
+                                vo.setGoodsName(goodsNameBuilder.toString());
+                            }
+
+                            // 设置商品编码
+                            if (barCodeBuilder.length() > 0) {
+                                vo.setBarCode(barCodeBuilder.toString());
+                            }
+                        }
+                    } else if (vo.getItemJson().startsWith("{")) {
+                        // 对象格式
+                        com.alibaba.fastjson.JSONObject goods = com.alibaba.fastjson.JSONObject.parseObject(vo.getItemJson());
+
+                        // 处理商品名称和数量
+                        if (goods != null && goods.getString("goodsName") != null) {
+                            String name = goods.getString("goodsName");
+                            String num = goods.getString("num");
+                            if (StringUtils.isEmpty(num)) {
+                                num = "1"; // 默认数量为1
+                            }
+                            vo.setGoodsName(name + ",数量:" + num + "个");
+                        }
+
+                        // 处理商品编码
+                        if (goods != null && goods.getString("barCode") != null) {
+                            vo.setBarCode(goods.getString("barCode"));
+                        }
+                    }
+                } catch (Exception e) {
+                    // 解析失败时保持goodsName为空,避免导出出错
+                    log.warn("解析商品信息失败,订单编号:{}, 商品信息:{}", vo.getOrderCode(), vo.getItemJson());
+                }
+            }
+            if (!(sysRole.getIsCheckPhone()==1)){
+                // 加密手机号
+                vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+            }
+
+        }
+        ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
+        return util.exportExcel(new ArrayList<>(fsIntegralOrderListVOS), "积分商品订单数据");
+    }
+
+    /*public AjaxResult export(FsIntegralOrderParam fsIntegralOrder) {
+        List<FsIntegralOrderListVO> fsIntegralOrderListVOS = new ArrayList<>();
+        if (CloudHostUtils.hasCloudHostName("金牛明医")){
+            *//*目前只有金牛有状态为6的查询,其他项目避免使用6状态码*//*
+            if (fsIntegralOrder.getStatus() != null && fsIntegralOrder.getStatus().equals("6")) {
+                fsIntegralOrder.setStatus("1");
+                fsIntegralOrder.setIsPush(0);
+            }
+            fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+        } else {
+            fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
+        }
+        SysRole sysRole = isCheckPermission();
+        // 处理商品名称:解析item_json并设置goodsName
         for (FsIntegralOrderListVO vo : fsIntegralOrderListVOS) {
             if (StringUtils.isNotEmpty(vo.getItemJson())) {
                 try {
@@ -201,7 +297,7 @@ public class FsIntegralOrderController extends BaseController
         }
         ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
         return util.exportExcel(new ArrayList<>(fsIntegralOrderListVOS), "积分商品订单数据");
-    }
+    }*/
     /**
      * 发货
      */

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

@@ -342,6 +342,7 @@ public class FsUserController extends BaseController
     public R listBySearch(FsUser user)
     {
         if (Objects.nonNull(user) && StringUtils.isNotBlank(user.getPhone())){
+            user.setPhoneUnencrypted(user.getPhone());
             user.setPhone(PhoneUtil.encryptPhone(user.getPhone()));
         }
         List<FsUser> list = fsUserService.selectFsUserList(user);

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

@@ -133,31 +133,6 @@ public class StockDeductService {
             // 1. 参数校验(Java 8 Optional 空值处理)
             Integer num = Optional.ofNullable(deductNum).orElse(1);
             String stockKey = RedisConstant.STOCK_KEY_PREFIX + liveId + ":" + productId;
-            String lockKey = RedisConstant.LOCK_KEY_PREFIX + liveId + ":" + productId;
-
-            // 3. 尝试获取分布式锁(非阻塞重试,Java 8 Stream API 实现重试)
-// 3. 尝试获取分布式锁(优化:加入随机延迟,避免惊群效应)
-            boolean isLockAcquired = IntStream.range(0, RedisConstant.LOCK_MAX_RETRY).anyMatch(retryCount -> {
-                Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, userId, RedisConstant.LOCK_EXPIRE_SECONDS, TimeUnit.SECONDS);
-                if (Boolean.TRUE.equals(result)) {
-                    return true;
-                }
-                try {
-                    // 随机延迟:50ms~150ms,避免所有请求同时重试
-                    long randomDelay = RedisConstant.LOCK_RETRY_INTERVAL + new Random().nextInt(100);
-                    TimeUnit.MILLISECONDS.sleep(randomDelay);
-                } catch (InterruptedException e) {
-                    Thread.currentThread().interrupt();
-                    return false;
-                }
-                return false;
-            });
-            // 4. 未获取到锁,直接返回失败
-            if (!isLockAcquired) {
-                System.err.println("商品" + productId + "获取锁失败,高并发限流中");
-                return false;
-            }
-
             try {
                 // 5. 执行库存扣减Lua脚本(原子操作,防超卖)
                 // 新增日志:打印当前库存值和扣减数量
@@ -165,11 +140,7 @@ public class StockDeductService {
                 log.info("拿到锁成功 → 库存Key:{},当前库存值:{},扣减数量:{}", stockKey, currentStockStr, num);
 
                 // 执行库存扣减Lua脚本
-                Long remainingStock = redisTemplate.execute(
-                        STOCK_DEDUCT_SCRIPT,
-                        Collections.singletonList(stockKey),
-                        1
-                );
+                Long remainingStock = redisTemplate.execute(STOCK_DEDUCT_SCRIPT, Collections.singletonList(stockKey), 1);
 
                 // 新增日志:打印Lua返回结果
                 log.info("Lua脚本返回值:{}", remainingStock);
@@ -199,10 +170,9 @@ public class StockDeductService {
                     log.info("商品{}扣减失败:{}", productId, errorMsg);
                     return false;
                 }
-            } finally {
-                // 7. 释放分布式锁(Lua脚本保证原子性,仅释放自己持有的锁)
-                redisTemplate.execute(LOCK_RELEASE_SCRIPT, Collections.singletonList(lockKey), userId);
-                log.info("商品{}锁释放成功,持有者:{}", productId, userId);
+            }catch (Exception e) {
+                log.error("支付失败获取失败", e);
+                return false;
             }
         });
     }

+ 3 - 3
fs-ipad-task/src/main/resources/logback.xml

@@ -21,7 +21,7 @@
             <!-- 日志文件名格式 -->
 			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
 			<!-- 日志最大的历史 60天 -->
-			<maxHistory>60</maxHistory>
+			<maxHistory>6</maxHistory>
 		</rollingPolicy>
 		<encoder>
 			<pattern>${log.pattern}</pattern>
@@ -43,7 +43,7 @@
             <!-- 日志文件名格式 -->
             <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
 			<!-- 日志最大的历史 60天 -->
-			<maxHistory>60</maxHistory>
+			<maxHistory>6</maxHistory>
         </rollingPolicy>
         <encoder>
             <pattern>${log.pattern}</pattern>
@@ -65,7 +65,7 @@
             <!-- 按天回滚 daily -->
             <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
             <!-- 日志最大的历史 60天 -->
-            <maxHistory>60</maxHistory>
+            <maxHistory>6</maxHistory>
         </rollingPolicy>
         <encoder>
             <pattern>${log.pattern}</pattern>

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

@@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.uuid.IdUtils;
 import com.fs.fastGpt.domain.FastGptRole;
 import com.fs.fastGpt.service.AiHookService;
@@ -322,9 +323,9 @@ public class QwMsgController {
                         ste.setUuid(wxWorkMsgResp.getUuid());
                         WxWorkResponseDTO<WxwSpeechToTextEntityRespDTO> dto = wxWorkService.SpeechToTextEntity(ste, serverId);
                         System.out.println(dto);
-                        if(dto.getErrcode() != 0){
+                        if(dto.getErrcode() != 0 || Objects.isNull(dto.getData()) || StringUtils.isBlank(dto.getData().getText())){
                             try {
-                                TimeUnit.SECONDS.sleep(1); // 阻塞1
+                                TimeUnit.SECONDS.sleep(2); // 阻塞2
                             } catch (InterruptedException e) {
                                 Thread.currentThread().interrupt(); // 处理中断异常
                                 log.info("id:{}, 第一次语音转换失败", id);

+ 50 - 8
fs-qw-task/src/main/java/com/fs/app/task/CourseWatchLogScheduler.java

@@ -9,10 +9,12 @@ import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.system.service.ISysConfigService;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
+import java.util.Calendar;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 @Component
@@ -55,29 +57,69 @@ public class CourseWatchLogScheduler {
 
 
 
-    /**
-     * 检查看课状态
-     */
+//    /**
+//     * 检查看课状态
+//     */
+//    @Scheduled(fixedRate = 60000) // 每分钟执行一次
+//    public void checkWatchStatus() {
+//        // 尝试设置标志为 true,表示任务开始执行
+//        if (!isRunning1.compareAndSet(false, true)) {
+//            log.warn("检查看课中任务执行 - 上一个任务尚未完成,跳过此次执行");
+//            return;
+//        }
+//        try {
+//            log.info("检查看课中任务执行>>>>>>>>>>>>");
+//            courseWatchLogService.scheduleBatchUpdateToDatabase();
+////            courseWatchLogService.scheduleBatchUpdateToDatabaseIsOpen();
+//            courseWatchLogService.checkWatchStatus();
+//            log.info("检查看课中任务执行完成>>>>>>>>>>>>");
+//        }catch (Exception e) {
+//            log.error("检查看课中任务执行完成 - 定时任务执行失败", e);
+//        } finally {
+//            // 重置标志为 false,表示任务已完成
+//            isRunning1.set(false);
+//        }
+//
+//    }
+
+
     @Scheduled(fixedRate = 60000) // 每分钟执行一次
     public void checkWatchStatus() {
         // 尝试设置标志为 true,表示任务开始执行
         if (!isRunning1.compareAndSet(false, true)) {
-            log.warn("检查看课中任务执行 - 上一个任务尚未完成,跳过此次执行");
+            log.info("检查看课中任务执行 - 上一个任务尚未完成,跳过此次执行");
             return;
         }
+
         try {
             log.info("检查看课中任务执行>>>>>>>>>>>>");
             courseWatchLogService.scheduleBatchUpdateToDatabase();
-            courseWatchLogService.scheduleBatchUpdateToDatabaseIsOpen();
             courseWatchLogService.checkWatchStatus();
             log.info("检查看课中任务执行完成>>>>>>>>>>>>");
-        }catch (Exception e) {
-            log.error("检查看课中任务执行完成 - 定时任务执行失败", e);
+
+            // 检查当前时间是否为整五分钟(0, 5, 10, 15, ... 55分钟)
+            Calendar calendar = Calendar.getInstance();
+            int minute = calendar.get(Calendar.MINUTE);
+
+            // 只有当分钟数是5的倍数时才执行创建完课消息
+            if (minute % 5 == 0) {
+                try {
+                    long startTime = System.currentTimeMillis();
+                    log.info("创建完课消息 - 定时任务开始"+System.currentTimeMillis());
+                    sopLogsTaskService.createCourseFinishMsg();
+                    long endTime = System.currentTimeMillis();
+                    long duration = endTime - startTime;
+                    log.info("创建完课消息 - 定时任务成功完成"+duration);
+                } catch (Exception e) {
+                    log.error("创建完课消息 - 定时任务执行失败", ExceptionUtils.getStackTrace(e));
+                }
+            }
+        } catch (Exception e) {
+            log.error("检查看课中任务执行完成 - 定时任务执行失败", ExceptionUtils.getStackTrace(e));
         } finally {
             // 重置标志为 false,表示任务已完成
             isRunning1.set(false);
         }
-
     }
 
 

+ 5 - 4
fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java

@@ -14,10 +14,7 @@ import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.CustomException;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.exception.file.OssException;
-import com.fs.common.utils.DateUtils;
-import com.fs.common.utils.PatternUtils;
-import com.fs.common.utils.SecurityUtils;
-import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.*;
 import com.fs.company.domain.*;
 import com.fs.company.mapper.*;
 import com.fs.company.param.CompanyUserAreaParam;
@@ -1040,6 +1037,10 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     public R getBindInfo(Long companyUserId) {
         //链接
         String url = bindBaseUrl + companyUserId;
+        if (CloudHostUtils.hasCloudHostName("木易华康")) {
+            url = "https://h5api.muyikp.com/bindcompanyuser?companyUserId=" + companyUserId;
+        }
+
         //查询码是否存在
         CompanyUser companyUser = selectCompanyUserById(companyUserId);
         if (companyUser != null) {

+ 0 - 1
fs-service/src/main/java/com/fs/course/config/RedisKeyScanner.java

@@ -105,7 +105,6 @@ public class RedisKeyScanner {
                     .match(pattern)
                     .count(1000) // 每次扫描的批量大小,可根据实际情况调整
                     .build())) {
-
                 while (cursor.hasNext()) {
                     keys.add(new String(cursor.next(), StandardCharsets.UTF_8)); // 显式指定字符集,避免依赖默认值
                 }

+ 323 - 113
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -20,6 +20,7 @@ import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyMapper;
 import com.fs.course.config.CourseConfig;
+import com.fs.course.config.RedisKeyScanner;
 import com.fs.course.domain.*;
 import com.fs.course.mapper.*;
 import com.fs.course.param.*;
@@ -72,6 +73,9 @@ import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -808,143 +812,349 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     private ISysConfigService configService;
 
+    @Autowired
+    private RedisKeyScanner redisKeyScanner;
+
     @Override
     public void scheduleBatchUpdateToDatabase() {
-        log.info("开始更新看课时长,检查完课>>>>>>");
-        //读取所有的key
-        Collection<String> keys = redisCache.keys("h5user:watch:duration:*");
-        //读取看课配置
-        String json = configService.selectConfigByKey("course.config");
-        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        try {
+            log.info("开始更新看课时长,检查完课>>>>>>");
 
-        List<FsCourseWatchLog> logs = new ArrayList<>();
-        List<FsCourseWatchLog> finishedLogs = new ArrayList<>();
-        for (String key : keys) {
-            //取key中数据
-            Long qwUserId = null;
-            Long videoId = null;
-            Long externalId = null;
-            try {
-                String[] parts = key.split(":");
-                qwUserId = Long.parseLong(parts[3]);
-                externalId = Long.parseLong(parts[4]);
-                videoId = Long.parseLong(parts[5]);
-            } catch (Exception e) {
-                log.error("key中id为null:{}", key);
-                continue;
-            }
-            String durationStr = redisCache.getCacheObject(key);
-            if (com.fs.common.utils.StringUtils.isEmpty(durationStr)) {
-                redisCache.deleteObject(key);
-                log.error("key中数据为null:{}", key);
-                continue;  // 如果 Redis 中没有记录,跳过
+            // 读取所有的key
+            Set<String> keys = redisKeyScanner.scanMatchKey("h5user:watch:duration:*");
+            log.info("共扫描到 {} 个待处理键", keys.size());
+
+            // 如果keys为空,直接返回
+            if (CollectionUtils.isEmpty(keys)) {
+                return;
             }
-            Long duration = Long.valueOf(durationStr);
 
-            FsCourseWatchLog watchLog = new FsCourseWatchLog();
-            watchLog.setVideoId(videoId);
-            watchLog.setQwUserId(qwUserId);
-            watchLog.setQwExternalContactId(externalId);
-            watchLog.setDuration(duration);
+            // 读取看课配置
+            String json = configService.selectConfigByKey("course.config");
+            CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
 
-            //取对应视频的时长
-            Long videoDuration = 0L;
-            try {
-                videoDuration = getVideoDuration(videoId);
-            } catch (Exception e) {
-                log.error("视频时长识别错误:{}", key);
-                continue;
-            }
+            // 创建线程安全的集合
+            List<FsCourseWatchLog> logs = Collections.synchronizedList(new ArrayList<>());
 
-            if (videoDuration != null && videoDuration != 0) {
-                boolean complete = false;
-                // 判断百分比
-                if (config.getCompletionMode() == 1 && config.getAnswerRate() != null) {
-                    long percentage = (duration * 100 / videoDuration);
-                    complete = percentage >= config.getAnswerRate();
-                }
-                // 判断分钟数
-                if (config.getCompletionMode() == 2 && config.getMinutesNum() != null) {
-                    int i = config.getMinutesNum() * 60;
-                    complete = videoDuration > i;
-                }
-                //判断是否完课
-                if (complete) {
-                    watchLog.setLogType(2); // 设置状态为“已完成”
-                    watchLog.setFinishTime(new Date());
-                    String heartbeatKey = "h5user:watch:heartbeat:" + qwUserId + ":" + externalId + ":" + videoId;
-                    // 完课删除心跳记录
-                    redisCache.deleteObject(heartbeatKey);
-                    // 完课删除看课时长记录
-                    redisCache.deleteObject(key);
+            // 创建线程池(根据服务器配置调整线程数)
+            int threadCount = Math.min(Runtime.getRuntime().availableProcessors() * 2, 8);
+            ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
 
-                    finishedLogs.add(watchLog);
-                }
+            // 将keys分成多个批次,每个线程处理一批
+            List<List<String>> keyBatches = partitionKeys(new ArrayList<>(keys), threadCount * 10);
+
+            log.info("开始多线程处理,共 {} 个批次", keyBatches.size());
+
+            int type = 1; //检查完课任务
+
+            // 创建所有任务
+            List<CompletableFuture<Void>> futures = new ArrayList<>();
+            for (List<String> batchKeys : keyBatches) {
+                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                    processKeyBatch(batchKeys, config, logs,type);
+                }, executorService);
+                futures.add(future);
             }
-            //集合中增加
-            logs.add(watchLog);
-        }
 
-        batchUpdateFsCourseWatchLog(logs, 100);
+            // 等待所有任务完成
+            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+
+            // 关闭线程池
+            executorService.shutdown();
+
+            // 批量更新到数据库
+            batchUpdateFsCourseWatchLog(logs, 100);
 
-        // 完课打标签
-        if (CollectionUtils.isNotEmpty(finishedLogs)) {
-            fsTagUpdateService.onCourseWatchFinishedBatch(finishedLogs);
+        } catch (Exception e) {
+            log.error("定时任务执行失败 scheduleBatchUpdateToDatabase", e);
         }
+//        log.info("开始更新看课时长,检查完课>>>>>>");
+//        //读取所有的key
+//        Collection<String> keys = redisCache.keys("h5user:watch:duration:*");
+//        //读取看课配置
+//        String json = configService.selectConfigByKey("course.config");
+//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+//
+//        List<FsCourseWatchLog> logs = new ArrayList<>();
+//        List<FsCourseWatchLog> finishedLogs = new ArrayList<>();
+//        for (String key : keys) {
+//            //取key中数据
+//            Long qwUserId = null;
+//            Long videoId = null;
+//            Long externalId = null;
+//            try {
+//                String[] parts = key.split(":");
+//                qwUserId = Long.parseLong(parts[3]);
+//                externalId = Long.parseLong(parts[4]);
+//                videoId = Long.parseLong(parts[5]);
+//            } catch (Exception e) {
+//                log.error("key中id为null:{}", key);
+//                continue;
+//            }
+//            String durationStr = redisCache.getCacheObject(key);
+//            if (com.fs.common.utils.StringUtils.isEmpty(durationStr)) {
+//                redisCache.deleteObject(key);
+//                log.error("key中数据为null:{}", key);
+//                continue;  // 如果 Redis 中没有记录,跳过
+//            }
+//            Long duration = Long.valueOf(durationStr);
+//
+//            FsCourseWatchLog watchLog = new FsCourseWatchLog();
+//            watchLog.setVideoId(videoId);
+//            watchLog.setQwUserId(qwUserId);
+//            watchLog.setQwExternalContactId(externalId);
+//            watchLog.setDuration(duration);
+//
+//            //取对应视频的时长
+//            Long videoDuration = 0L;
+//            try {
+//                videoDuration = getVideoDuration(videoId);
+//            } catch (Exception e) {
+//                log.error("视频时长识别错误:{}", key);
+//                continue;
+//            }
+//
+//            if (videoDuration != null && videoDuration != 0) {
+//                boolean complete = false;
+//                // 判断百分比
+//                if (config.getCompletionMode() == 1 && config.getAnswerRate() != null) {
+//                    long percentage = (duration * 100 / videoDuration);
+//                    complete = percentage >= config.getAnswerRate();
+//                }
+//                // 判断分钟数
+//                if (config.getCompletionMode() == 2 && config.getMinutesNum() != null) {
+//                    int i = config.getMinutesNum() * 60;
+//                    complete = videoDuration > i;
+//                }
+//                //判断是否完课
+//                if (complete) {
+//                    watchLog.setLogType(2); // 设置状态为“已完成”
+//                    watchLog.setFinishTime(new Date());
+//                    String heartbeatKey = "h5user:watch:heartbeat:" + qwUserId + ":" + externalId + ":" + videoId;
+//                    // 完课删除心跳记录
+//                    redisCache.deleteObject(heartbeatKey);
+//                    // 完课删除看课时长记录
+//                    redisCache.deleteObject(key);
+//
+//                    finishedLogs.add(watchLog);
+//                }
+//            }
+//            //集合中增加
+//            logs.add(watchLog);
+//        }
+//
+//        batchUpdateFsCourseWatchLog(logs, 100);
+//
+//        // 完课打标签
+//        if (CollectionUtils.isNotEmpty(finishedLogs)) {
+//            fsTagUpdateService.onCourseWatchFinishedBatch(finishedLogs);
+//        }
     }
 
     @Override
     public void checkWatchStatus() {
-        log.info("开始更新看课中断记录>>>>>");
-        // 从 Redis 中获取所有正在看课的用户记录
-        Collection<String> keys = redisCache.keys("h5user:watch:heartbeat:*");
-        LocalDateTime now = LocalDateTime.now();
-        List<FsCourseWatchLog> logs = new ArrayList<>();
+        try {
+            log.info("开始更新看课中断记录>>>>>");
 
-        List<FsCourseWatchLog> watchingLogs = new ArrayList<>();
-        for (String key : keys) {
-            FsCourseWatchLog watchLog = new FsCourseWatchLog();
-            //取key中数据
-            Long qwUserId = null;
-            Long videoId = null;
-            Long externalId = null;
-            try {
-                String[] parts = key.split(":");
-                qwUserId = Long.parseLong(parts[3]);
-                externalId = Long.parseLong(parts[4]);
-                videoId = Long.parseLong(parts[5]);
-            } catch (Exception e) {
-                log.error("key中id为null:{}", key);
-                continue;
+            // 读取所有的key
+            Set<String> keys = redisKeyScanner.scanMatchKey("h5user:watch:heartbeat:*");
+            log.info("共扫描到 {} 个待处理键", keys.size());
+
+            // 如果keys为空,直接返回
+            if (CollectionUtils.isEmpty(keys)) {
+                return;
             }
-            // 获取最后心跳时间
-            String lastHeartbeatStr = redisCache.getCacheObject(key);
-            if (com.fs.common.utils.StringUtils.isEmpty(lastHeartbeatStr)) {
-                redisCache.deleteObject(key);
-                continue; // 如果 Redis 中没有记录,跳过
+
+            // 读取看课配置
+            String json = configService.selectConfigByKey("course.config");
+            CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+
+            // 创建线程安全的集合
+            List<FsCourseWatchLog> logs = Collections.synchronizedList(new ArrayList<>());
+
+            // 创建线程池(根据服务器配置调整线程数)
+            int threadCount = Math.min(Runtime.getRuntime().availableProcessors() * 2, 8);
+            ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
+
+            // 将keys分成多个批次,每个线程处理一批
+            List<List<String>> keyBatches = partitionKeys(new ArrayList<>(keys), threadCount * 10);
+
+            log.info("开始多线程处理,共 {} 个批次", keyBatches.size());
+
+            int type = 2; //检查看课中断
+
+            // 创建所有任务
+            List<CompletableFuture<Void>> futures = new ArrayList<>();
+            for (List<String> batchKeys : keyBatches) {
+                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                    processKeyBatch(batchKeys, config, logs, type);
+                }, executorService);
+                futures.add(future);
             }
-            LocalDateTime lastHeartbeatTime = LocalDateTime.parse(lastHeartbeatStr);
-            Duration duration = Duration.between(lastHeartbeatTime, now);
 
-            watchLog.setVideoId(videoId);
-            watchLog.setQwUserId(qwUserId);
-            watchLog.setQwExternalContactId(externalId);
-            // 如果超过一分钟没有心跳,标记为“观看中断”
-            if (duration.getSeconds() >= 60) {
-                watchLog.setLogType(4);
-                // 从 Redis 中删除该记录
-                redisCache.deleteObject(key);
-            } else {
-                watchLog.setLogType(1);
-                watchingLogs.add(watchLog);
+            // 等待所有任务完成
+            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+
+            // 关闭线程池
+            executorService.shutdown();
+
+            // 批量更新到数据库
+            batchUpdateFsCourseWatchLog(logs, 100);
+
+        } catch (Exception e) {
+            log.error("定时任务执行失败checkWatchStatus", e);
+        }
+//        log.info("开始更新看课中断记录>>>>>");
+//        // 从 Redis 中获取所有正在看课的用户记录
+//        Collection<String> keys = redisCache.keys("h5user:watch:heartbeat:*");
+//        LocalDateTime now = LocalDateTime.now();
+//        List<FsCourseWatchLog> logs = new ArrayList<>();
+//
+//        List<FsCourseWatchLog> watchingLogs = new ArrayList<>();
+//        for (String key : keys) {
+//            FsCourseWatchLog watchLog = new FsCourseWatchLog();
+//            //取key中数据
+//            Long qwUserId = null;
+//            Long videoId = null;
+//            Long externalId = null;
+//            try {
+//                String[] parts = key.split(":");
+//                qwUserId = Long.parseLong(parts[3]);
+//                externalId = Long.parseLong(parts[4]);
+//                videoId = Long.parseLong(parts[5]);
+//            } catch (Exception e) {
+//                log.error("key中id为null:{}", key);
+//                continue;
+//            }
+//            // 获取最后心跳时间
+//            String lastHeartbeatStr = redisCache.getCacheObject(key);
+//            if (com.fs.common.utils.StringUtils.isEmpty(lastHeartbeatStr)) {
+//                redisCache.deleteObject(key);
+//                continue; // 如果 Redis 中没有记录,跳过
+//            }
+//            LocalDateTime lastHeartbeatTime = LocalDateTime.parse(lastHeartbeatStr);
+//            Duration duration = Duration.between(lastHeartbeatTime, now);
+//
+//            watchLog.setVideoId(videoId);
+//            watchLog.setQwUserId(qwUserId);
+//            watchLog.setQwExternalContactId(externalId);
+//            // 如果超过一分钟没有心跳,标记为“观看中断”
+//            if (duration.getSeconds() >= 60) {
+//                watchLog.setLogType(4);
+//                // 从 Redis 中删除该记录
+//                redisCache.deleteObject(key);
+//            } else {
+//                watchLog.setLogType(1);
+//                watchingLogs.add(watchLog);
+//            }
+//            logs.add(watchLog);
+//        }
+//        batchUpdateFsCourseWatchLog(logs, 100);
+//
+//        if (CollectionUtils.isNotEmpty(watchingLogs)) {
+//            fsTagUpdateService.onCourseWatchingBatch(watchingLogs);
+//        }
+    }
+
+    /**
+     * 处理一个key批次
+     */
+    private void processKeyBatch(List<String> batchKeys, CourseConfig config, List<FsCourseWatchLog> logs,int type) {
+        LocalDateTime now = LocalDateTime.now();
+        for (String key : batchKeys) {
+            try {
+                // 取key中数据
+                Long qwUserId = null;
+                Long videoId = null;
+                Long externalId = null;
+                try {
+                    String[] parts = key.split(":");
+                    qwUserId = Long.parseLong(parts[3]);
+                    externalId = Long.parseLong(parts[4]);
+                    videoId = Long.parseLong(parts[5]);
+                } catch (Exception e) {
+                    log.error("key中id为null:{}", key);
+                    continue;
+                }
+                FsCourseWatchLog watchLog = new FsCourseWatchLog();
+                //检查完课
+                if (type==1){
+                    String durationStr = redisCache.getCacheObject(key);
+                    if (com.fs.common.utils.StringUtils.isEmpty(durationStr)) {
+                        redisCache.deleteObject(key);
+                        log.error("key中数据为null:{}", key);
+                        continue;  // 如果 Redis 中没有记录,跳过
+                    }
+                    Long duration = Long.valueOf(durationStr);
+
+                    watchLog.setDuration(duration);
+
+                    // 取对应视频的时长
+                    Long videoDuration;
+                    try {
+                        videoDuration = getVideoDuration(videoId);
+                    } catch (Exception e) {
+                        log.error("视频时长识别错误:{}", key);
+                        continue;
+                    }
+
+
+                    if (videoDuration != null && videoDuration != 0) {
+                        // 判断是否完课
+                        long percentage = (duration * 100 / videoDuration);
+                        if (percentage >= config.getAnswerRate()) {
+                            watchLog.setLogType(2); // 设置状态为"已完成"
+                            watchLog.setFinishTime(new Date());
+                            String heartbeatKey = "h5user:watch:heartbeat:" + qwUserId + ":" + externalId + ":" + videoId;
+                            // 完课删除心跳记录
+                            redisCache.deleteObject(heartbeatKey);
+                            // 完课删除看课时长记录
+                            redisCache.deleteObject(key);
+                        }
+                    }
+                }else{
+                    //检查看课中断
+                    // 获取最后心跳时间
+                    String lastHeartbeatStr = redisCache.getCacheObject(key);
+                    if (com.fs.common.utils.StringUtils.isEmpty(lastHeartbeatStr)) {
+                        redisCache.deleteObject(key);
+                        continue; // 如果 Redis 中没有记录,跳过
+                    }
+                    LocalDateTime lastHeartbeatTime = LocalDateTime.parse(lastHeartbeatStr);
+                    Duration duration = Duration.between(lastHeartbeatTime, now);
+                    // 如果超过一分钟没有心跳,标记为“观看中断”
+                    if (duration.getSeconds() >= 60) {
+                        watchLog.setLogType(4);
+                        // 从 Redis 中删除该记录
+                        redisCache.deleteObject(key);
+                    } else {
+                        watchLog.setLogType(1);
+                    }
+                }
+                watchLog.setVideoId(videoId);
+                watchLog.setQwUserId(qwUserId);
+                watchLog.setQwExternalContactId(externalId);
+
+
+                // 线程安全地添加到集合
+                logs.add(watchLog);
+
+            } catch (Exception e) {
+                log.error("处理key {} 时发生异常: {}", key, e.getMessage());
             }
-            logs.add(watchLog);
         }
-        batchUpdateFsCourseWatchLog(logs, 100);
+    }
 
-        if (CollectionUtils.isNotEmpty(watchingLogs)) {
-            fsTagUpdateService.onCourseWatchingBatch(watchingLogs);
+    /**
+     * 将keys分成多个批次
+     */
+    private List<List<String>> partitionKeys(List<String> keys, int batchSize) {
+        List<List<String>> batches = new ArrayList<>();
+        for (int i = 0; i < keys.size(); i += batchSize) {
+            int end = Math.min(i + batchSize, keys.size());
+            batches.add(keys.subList(i, end));
         }
+        return batches;
     }
 
     @Override

+ 4 - 0
fs-service/src/main/java/com/fs/his/domain/FsUser.java

@@ -43,6 +43,10 @@ public class FsUser extends BaseEntity
     /** 手机号码 */
     @Excel(name = "手机号码")
     private String phone;
+
+    /** 不加密手机号 */
+    private String phoneUnencrypted;
+
     private BigDecimal nowMoney;
     private BigDecimal brokeragePrice;
 

+ 3 - 2
fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderListVO.java

@@ -34,9 +34,10 @@ public class FsIntegralOrderListVO {
     private String userAddress;
 
     /** 商品信息 */
-    @Excel(name = "商品信息")
+    //@Excel(name = "商品信息")
     private String itemJson;
-    @Excel(name = "商品名称")
+    //@Excel(name = "商品名称")
+    @Excel(name = "商品信息")//卓美 导出时候把商品名称 数据拼接在一起
     private String goodsName;
 
     /** 商品条码 */

+ 32 - 28
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductPurchaseLimitScrmServiceImpl.java

@@ -8,6 +8,7 @@ import java.util.concurrent.TimeUnit;
 
 import com.fs.common.constant.LiveKeysConstant;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.core.redis.RedisCacheT;
 import com.fs.common.utils.DateUtils;
 import com.fs.hisStore.domain.FsStoreProductAttrScrm;
 import com.fs.hisStore.domain.FsStoreProductAttrValueScrm;
@@ -28,14 +29,15 @@ import org.springframework.stereotype.Service;
  * @date 2024-01-01
  */
 @Service
-public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProductPurchaseLimitScrmService
-{
+public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProductPurchaseLimitScrmService {
     @Autowired
     private FsStoreProductPurchaseLimitScrmMapper fsStoreProductPurchaseLimitMapper;
 
     @Autowired
     private RedisCache redisCache;
     @Autowired
+    private RedisCacheT<FsStoreProductPurchaseLimitScrm> redisCacheT;
+    @Autowired
     private FsStoreProductScrmMapper fsStoreProductScrmMapper;
     @Autowired
     private FsStoreProductAttrScrmMapper fsStoreProductAttrScrmMapper;
@@ -50,8 +52,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * @return 商品限购
      */
     @Override
-    public FsStoreProductPurchaseLimitScrm selectFsStoreProductPurchaseLimitById(Long id)
-    {
+    public FsStoreProductPurchaseLimitScrm selectFsStoreProductPurchaseLimitById(Long id) {
         return fsStoreProductPurchaseLimitMapper.selectFsStoreProductPurchaseLimitById(id);
     }
 
@@ -62,8 +63,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * @return 商品限购
      */
     @Override
-    public List<FsStoreProductPurchaseLimitScrm> selectFsStoreProductPurchaseLimitList(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit)
-    {
+    public List<FsStoreProductPurchaseLimitScrm> selectFsStoreProductPurchaseLimitList(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit) {
         return fsStoreProductPurchaseLimitMapper.selectFsStoreProductPurchaseLimitList(fsStoreProductPurchaseLimit);
     }
 
@@ -71,13 +71,19 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * 根据商品ID和用户ID查询限购记录
      *
      * @param productId 商品ID
-     * @param userId 用户ID
+     * @param userId    用户ID
      * @return 商品限购
      */
     @Override
-    public FsStoreProductPurchaseLimitScrm selectByProductIdAndUserId(Long productId, Long userId)
-    {
-        return fsStoreProductPurchaseLimitMapper.selectByProductIdAndUserId(productId, userId);
+    public FsStoreProductPurchaseLimitScrm selectByProductIdAndUserId(Long productId, Long userId) {
+        String key = "live:order:limit:" + productId + ":" + userId;
+        FsStoreProductPurchaseLimitScrm scrm = redisCacheT.getCacheObject(key);
+        if(scrm != null){
+            return scrm;
+        }
+        scrm = fsStoreProductPurchaseLimitMapper.selectByProductIdAndUserId(productId, userId);
+        redisCacheT.setCacheObject(key, scrm);
+        return scrm;
     }
 
     /**
@@ -87,8 +93,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * @return 结果
      */
     @Override
-    public int insertFsStoreProductPurchaseLimit(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit)
-    {
+    public int insertFsStoreProductPurchaseLimit(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit) {
         fsStoreProductPurchaseLimit.setCreateTime(DateUtils.getNowDate());
         return fsStoreProductPurchaseLimitMapper.insertFsStoreProductPurchaseLimit(fsStoreProductPurchaseLimit);
     }
@@ -100,8 +105,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * @return 结果
      */
     @Override
-    public int updateFsStoreProductPurchaseLimit(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit)
-    {
+    public int updateFsStoreProductPurchaseLimit(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit) {
         fsStoreProductPurchaseLimit.setUpdateTime(DateUtils.getNowDate());
         return fsStoreProductPurchaseLimitMapper.updateFsStoreProductPurchaseLimit(fsStoreProductPurchaseLimit);
     }
@@ -113,8 +117,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * @return 结果
      */
     @Override
-    public int deleteFsStoreProductPurchaseLimitByIds(Long[] ids)
-    {
+    public int deleteFsStoreProductPurchaseLimitByIds(Long[] ids) {
         return fsStoreProductPurchaseLimitMapper.deleteFsStoreProductPurchaseLimitByIds(ids);
     }
 
@@ -125,8 +128,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * @return 结果
      */
     @Override
-    public int deleteFsStoreProductPurchaseLimitById(Long id)
-    {
+    public int deleteFsStoreProductPurchaseLimitById(Long id) {
         return fsStoreProductPurchaseLimitMapper.deleteFsStoreProductPurchaseLimitById(id);
     }
 
@@ -134,13 +136,12 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * 增加用户限购数量
      *
      * @param productId 商品ID
-     * @param userId 用户ID
-     * @param num 增加的数量
+     * @param userId    用户ID
+     * @param num       增加的数量
      * @return 结果
      */
     @Override
-    public int increasePurchaseLimit(Long productId, Long userId, Integer num)
-    {
+    public int increasePurchaseLimit(Long productId, Long userId, Integer num) {
         String cacheKey = String.format(LiveKeysConstant.PRODUCT_DETAIL_CACHE, productId);
         Map<String, Object> cachedData = redisCache.getCacheObject(cacheKey);
 
@@ -154,7 +155,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
         } else {
             // 缓存中没有,从数据库查询
             product = fsStoreProductScrmMapper.selectFsStoreProductById(productId);
-            if(product==null){
+            if (product == null) {
                 return -1;
             }
             productAttr = fsStoreProductAttrScrmMapper.selectFsStoreProductAttrByProductId(productId);
@@ -167,18 +168,22 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
             cacheData.put("productValues", productValues);
             redisCache.setCacheObject(cacheKey, cacheData, LiveKeysConstant.PRODUCT_DETAIL_CACHE_EXPIRE, TimeUnit.SECONDS);
         }
+
         if (product != null && product.getPurchaseLimit() != null && product.getPurchaseLimit() > 0) {
             FsStoreProductPurchaseLimitScrm limit = selectByProductIdAndUserId(productId, userId);
+            String key = "live:order:limit:" + productId + ":" + userId;
             if (limit == null) {
                 // 创建新记录
                 limit = new FsStoreProductPurchaseLimitScrm();
                 limit.setProductId(productId);
                 limit.setUserId(userId);
                 limit.setNum(num);
+                redisCacheT.setCacheObject(key, limit);
                 return insertFsStoreProductPurchaseLimit(limit);
             } else {
                 // 更新现有记录
                 limit.setNum(limit.getNum() + num);
+                redisCacheT.setCacheObject(key, limit);
                 return updateFsStoreProductPurchaseLimit(limit);
             }
         }
@@ -189,13 +194,12 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * 减少用户限购数量
      *
      * @param productId 商品ID
-     * @param userId 用户ID
-     * @param num 减少的数量
+     * @param userId    用户ID
+     * @param num       减少的数量
      * @return 结果
      */
     @Override
-    public int decreasePurchaseLimit(Long productId, Long userId, Integer num)
-    {
+    public int decreasePurchaseLimit(Long productId, Long userId, Integer num) {
         String cacheKey = String.format(LiveKeysConstant.PRODUCT_DETAIL_CACHE, productId);
         Map<String, Object> cachedData = redisCache.getCacheObject(cacheKey);
 
@@ -209,7 +213,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
         } else {
             // 缓存中没有,从数据库查询
             product = fsStoreProductScrmMapper.selectFsStoreProductById(productId);
-            if(product==null){
+            if (product == null) {
                 return -1;
             }
             productAttr = fsStoreProductAttrScrmMapper.selectFsStoreProductAttrByProductId(productId);

+ 10 - 4
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java

@@ -147,7 +147,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     @Autowired
     private RedisCache redisCache;
     @Autowired
-    private RedisCacheT<Integer> redisCacheT;
+    private RedisCacheT<FsStoreProductScrm> redisCacheT;
 
     /**
      * 清除商品详情缓存
@@ -180,9 +180,15 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
      * @return 商品
      */
     @Override
-    public FsStoreProductScrm selectFsStoreProductById(Long productId)
-    {
-        return fsStoreProductMapper.selectFsStoreProductById(productId);
+    public FsStoreProductScrm selectFsStoreProductById(Long productId){
+        String key = "fs:product:id:" + productId;
+        FsStoreProductScrm scrm = redisCacheT.getCacheObject(key);
+        if(scrm != null){
+            return scrm;
+        }
+        scrm = fsStoreProductMapper.selectFsStoreProductById(productId);
+        redisCacheT.setCacheObject(key, scrm);
+        return scrm;
     }
 
     /**

+ 2 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderVO.java

@@ -277,4 +277,6 @@ public class FsStoreOrderVO implements Serializable
     // 是否审核,1-是,0-否
     private Boolean isAudit;
 
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date auditTime;
 }

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

@@ -147,7 +147,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and l.update_time &gt;= #{maps.upSTime}
             </if>
             <if test='maps.upETime != null '>
-                and l.update_time &lt;= #{maps.upETime}
+                and l.update_time &lt; date_add(#{maps.upETime}, interval 1 day)
             </if>
             <if test="maps.sopIds != null and maps.sopIds.size() > 0">
                 and l.sop_id in

+ 3 - 1
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -62,7 +62,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userId != null">and user_id = #{userId}</if>
             <if test="nickName != null  and nickName != ''"> and nick_name like concat( #{nickName}, '%')</if>
             <if test="avatar != null  and avatar != ''"> and avatar = #{avatar}</if>
-            <if test="phone != null  and phone != ''"> and phone = #{phone}</if>
+            <if test="phone != null  and phone != '' and phoneUnencrypted != null and phoneUnencrypted != ''">
+                and (phone = #{phone} or phone = #{phoneUnencrypted})
+            </if>
             <if test="integral != null "> and integral = #{integral}</if>
             <if test="signNum != null "> and sign_num = #{signNum}</if>
             <if test="status != null "> and status = #{status}</if>

+ 2 - 2
fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml

@@ -1721,7 +1721,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectFsStoreOrderListVO" resultType="com.fs.hisStore.vo.FsStoreOrderVO">
         select DISTINCT o.id,o.order_code,o.extend_order_id,o.pay_order_id,o.bank_order_id,o.user_id,o.real_name,o.user_phone,o.user_address,o.cart_id,o.freight_price,o.total_num,o.total_price,o.total_postage,o.pay_price,o.pay_postage,o.pay_delivery,o.pay_money,o.deduction_price,o.coupon_id,o.coupon_price,o.paid,o.pay_time,o.pay_type,o.create_time,o.update_time,o.status,o.refund_status,o.refund_reason_wap_img,o.refund_reason_wap_explain,o.refund_reason_time,o.refund_reason_wap,o.refund_reason,o.refund_price,o.delivery_sn,o.delivery_name,o.delivery_type,o.delivery_id,o.gain_integral,o.use_integral,o.pay_integral,o.back_integral,o.mark,o.is_del,o.remark,o.verify_code,o.store_id,o.shipping_type,o.is_channel,o.is_remind,o.is_sys_del,o.is_prescribe,o.prescribe_id,o.company_id,o.company_user_id,o.is_package,o.package_json,o.order_type,o.package_id,o.finish_time,o.delivery_status,o.delivery_pay_status,o.delivery_time,o.delivery_pay_time,o.delivery_pay_money,o.tui_money,o.tui_money_status,o.delivery_import_time,o.tui_user_id,o.tui_user_money_status,o.order_create_type,o.store_house_code,o.dept_id,o.is_edit_money,o.customer_id,o.is_pay_remain,o.delivery_send_time,o.certificates,o.upload_time,o.item_json,o.schedule_id,o.delivery_pay_type,o.order_visit,o.service_fee,o.cycle,o.prescribe_price,o.follow_doctor_id,o.follow_time,o.user_coupon_id,o.order_medium,o.erp_phone
         ,u.phone,u.register_code,u.register_date,u.source, c.company_name ,cu.nick_name as company_user_nick_name ,cu.phonenumber as company_usere_phonenumber
-        , csc.name miniProgramName,fsp.cost, fspc.cate_name,spavs.bar_code, sp_latest.bank_transaction_id as bankTransactionId, o.is_audit
+        , csc.name miniProgramName,fsp.cost, fspc.cate_name,spavs.bar_code, sp_latest.bank_transaction_id as bankTransactionId, o.is_audit, o.audit_time
         from fs_store_order_scrm o
         left join fs_user u on o.user_id=u.user_id
         left join company c on c.company_id=o.company_id
@@ -2272,7 +2272,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <!-- 批量更新订单审核状态 -->
     <update id="batchUpdateAuditStatus">
         UPDATE fs_store_order_scrm
-        SET is_audit = #{isAudit}
+        SET is_audit = #{isAudit},audit_time = now()
         WHERE id IN
         <foreach collection="orderIds" item="orderId" open="(" separator="," close=")">
             #{orderId}

+ 2 - 2
fs-service/src/main/resources/mapper/hisStore/FsStoreProductPurchaseLimitScrmMapper.xml

@@ -31,8 +31,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where id = #{id}
     </select>
 
-    <select id="selectByProductIdAndUserId" resultMap="FsStoreProductPurchaseLimitResult">
-        <include refid="selectFsStoreProductPurchaseLimitVo"/>
+    <select id="selectByProductIdAndUserId" resultType="FsStoreProductPurchaseLimitScrm">
+        select * from fs_store_product_purchase_limit_scrm
         where product_id = #{productId} and user_id = #{userId}
     </select>
 

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

@@ -43,6 +43,7 @@ public class DoctorArticleController extends  AppBaseController {
     public R getArticleCateList(){
         FsDoctorArticleCate map=new FsDoctorArticleCate();
         map.setIsDel(0);
+        map.setStatus(1);
         List<FsDoctorArticleCate> list=articleCateService.selectFsDoctorArticleCateList(map);
         return R.ok().put("data",list);
     }