zyp 2 недель назад
Родитель
Сommit
ad8a8fb3fc

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

@@ -2286,11 +2286,11 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 } else {
                     log.info("完课消息-客户信息有误,不生成完课消息: {}", finishLog.getQwExternalContactId());
                 }
-                try {
-                    fsUserCompanyBindService.finish(externalContact.getFsUserId(), externalContact.getQwUserId(), externalContact.getCompanyUserId(), finishLog);
-                }catch (Exception e){
-                    log.error("更新重粉看课状态失败",e);
-                }
+//                try {
+//                    fsUserCompanyBindService.finish(externalContact.getFsUserId(), externalContact.getQwUserId(), externalContact.getCompanyUserId(), finishLog);
+//                }catch (Exception e){
+//                    log.error("更新重粉看课状态失败",e);
+//                }
             } catch (Exception e) {
                 log.error("处理完课记录失败: {}", finishLog.getLogId(), e);
             }

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

@@ -0,0 +1,121 @@
+package com.fs.course.config;
+
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.core.Cursor;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.ScanOptions;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+@Component
+public class RedisKeyScanner {
+
+    private final StringRedisTemplate redisTemplate;
+
+    public RedisKeyScanner(StringRedisTemplate redisTemplate) {
+        this.redisTemplate = redisTemplate;
+    }
+
+    public Set<String> scan(String pattern) {
+        Set<String> keys = new HashSet<>();
+
+        // 这里指定 match + count
+        ScanOptions options = ScanOptions.scanOptions()
+                .match(pattern)
+                .count(1000)
+                .build();
+
+        // 使用 RedisConnection 提供的 scan
+        redisTemplate.execute((RedisConnection connection) -> {
+            try (Cursor<byte[]> cursor = connection.scan(options)) {
+                RedisSerializer<String> keySerializer = redisTemplate.getStringSerializer();
+                cursor.forEachRemaining(item -> keys.add(keySerializer.deserialize(item)));
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            return null;
+        });
+
+        return keys;
+    }
+
+    public Set<String> scanKeys(String pattern) {
+        Set<String> keys = new HashSet<>();
+        ScanOptions options = ScanOptions.scanOptions().match(pattern).count(1000).build();
+
+        redisTemplate.execute((RedisConnection connection) -> {
+            try (Cursor<byte[]> cursor = connection.scan(options)) {
+                while (cursor.hasNext()) {
+                    byte[] rawKey = cursor.next();
+                    String key = (String) redisTemplate.getKeySerializer().deserialize(rawKey);
+                    keys.add(key);
+                }
+            } catch (IOException e) {
+                throw new RuntimeException("Error during scan", e);
+            }
+            return null;
+        });
+
+        return keys;
+    }
+
+    /**
+     * 使用 SCAN 替代 KEYS,避免阻塞 Redis
+     *
+     * @param pattern key 匹配模式,例如 "h5user:watch:duration:*"
+     * @return 匹配到的 key 集合
+     */
+    public Collection<String> scanPatternKeys(final String pattern) {
+        Set<String> keys = new HashSet<>();
+
+        // 每次扫描的数量,可根据实际情况调整(越大速度越快,但对 CPU 影响越大)
+        ScanOptions options = ScanOptions.scanOptions()
+                .match(pattern)
+                .count(1000)
+                .build();
+
+        // 使用 redisTemplate 的 execute 保证连接获取 & 释放正确
+        redisTemplate.execute((RedisConnection connection) -> {
+            try (Cursor<byte[]> cursor = connection.scan(options)) {
+                RedisSerializer<String> keySerializer = redisTemplate.getStringSerializer();
+                while (cursor.hasNext()) {
+                    keys.add(keySerializer.deserialize(cursor.next()));
+                }
+            } catch (IOException e) {
+                throw new RuntimeException("Error during redis SCAN", e);
+            }
+            return null;
+        });
+
+        return keys;
+    }
+
+    public Set<String> scanMatchKey(String pattern) {
+        return (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
+            Set<String> keys = new HashSet<>();
+            // 使用 try-with-resources 确保 Cursor 无论是否异常都会被关闭
+            try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions()
+                    .match(pattern)
+                    .count(1000) // 每次扫描的批量大小,可根据实际情况调整
+                    .build())) {
+
+                while (cursor.hasNext()) {
+                    keys.add(new String(cursor.next(), StandardCharsets.UTF_8)); // 显式指定字符集,避免依赖默认值
+                }
+            } catch (IOException e) {
+                // 在实际项目中,应该使用更合适的日志记录和异常处理方式
+                // 例如抛出自定义异常或记录错误日志
+                throw new RuntimeException("Error during SCAN operation", e);
+            }
+            return keys;
+        });
+    }
+
+}

+ 61 - 0
fs-service/src/main/java/com/fs/course/dto/RedisKeyInfo.java

@@ -0,0 +1,61 @@
+package com.fs.course.dto;
+
+import com.fs.course.enums.RedisKeyType;
+import lombok.Data;
+
+@Data
+public class RedisKeyInfo {
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 视频ID
+     */
+    private Long videoId;
+
+    /**
+     * 企业用户ID
+     */
+    private Long companyUserId;
+
+    /**
+     * Redis key类型
+     */
+    private RedisKeyType keyType;
+
+    /**
+     * 原始key
+     */
+    private String originalKey;
+
+    /**
+     * 验证信息是否完整
+     */
+    public boolean isValid() {
+        return userId != null && videoId != null && companyUserId != null;
+    }
+
+    /**
+     * 构建时长key
+     */
+    public String buildDurationKey() {
+        return String.format("h5wxuser:watch:duration:%d:%d:%d",
+                userId, videoId, companyUserId);
+    }
+
+    /**
+     * 构建心跳key
+     */
+    public String buildHeartbeatKey() {
+        return String.format("h5wxuser:watch:heartbeat:%d:%d:%d",
+                userId, videoId, companyUserId);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("userId=%d, videoId=%d, companyUserId=%d",
+                userId, videoId, companyUserId);
+    }
+}

+ 26 - 0
fs-service/src/main/java/com/fs/course/enums/RedisKeyType.java

@@ -0,0 +1,26 @@
+package com.fs.course.enums;
+
+/**
+ * Redis key类型枚举
+ */
+
+public enum RedisKeyType {
+    DURATION("duration", "看课时长"),
+    HEARTBEAT("heartbeat", "心跳记录");
+
+    private final String code;
+    private final String desc;
+
+    RedisKeyType(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+}

+ 2 - 1
fs-service/src/main/resources/application-config-fzbt.yml

@@ -74,12 +74,13 @@ cloud_host:
   company_name: 福州白兔
   projectCode: FZBT
   spaceName: myhk-2114522511
+  volcengineUrl: https://myhkvolcengine.ylrztop.com
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://fs-1346741853.cos.ap-chengdu.myqcloud.com/fs/20250323/6189704f2e134b84ad9c9e7c9999f103.jpg
 ipad:
   ipadUrl: http://qwipad.fjbaitu.com
-  aiApi: http://152.136.202.157:3000/api
+  aiApi: http://49.232.181.28:3000/api
   voiceApi:
   commonApi:
 wx_miniapp_temp: