wangxy 1 неделя назад
Родитель
Сommit
02b60e2cb7

+ 1 - 1
fs-admin/src/main/java/com/fs/task/FeiShuFileCleanTask.java

@@ -10,7 +10,7 @@ import org.springframework.stereotype.Component;
  * 飞书云文档清理定时任务
  * 每天凌晨2点删除今天0点之前创建的飞书云文档
  */
-@Component
+@Component("feiShuFileCleanTask")
 @Slf4j
 public class FeiShuFileCleanTask {
 

+ 26 - 0
fs-service/src/main/java/com/fs/feishu/domain/FsAuthLinkToken.java

@@ -0,0 +1,26 @@
+package com.fs.feishu.domain;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 飞书授权链接短token映射
+ * 用于替代明文JSON参数,避免微信管控
+ */
+@Data
+public class FsAuthLinkToken implements Serializable {
+
+    /** 短token(主键) */
+    private String token;
+
+    /** 授权参数JSON */
+    private String paramsJson;
+
+    /** 创建时间 */
+    private Date createTime;
+
+    /** 过期时间 */
+    private Date expireTime;
+}

+ 18 - 0
fs-service/src/main/java/com/fs/feishu/mapper/FsAuthLinkTokenMapper.java

@@ -0,0 +1,18 @@
+package com.fs.feishu.mapper;
+
+import com.fs.feishu.domain.FsAuthLinkToken;
+
+/**
+ * 飞书授权链接token Mapper
+ */
+public interface FsAuthLinkTokenMapper {
+
+    /** 新增token记录 */
+    int insertFsAuthLinkToken(FsAuthLinkToken fsAuthLinkToken);
+
+    /** 根据token查询 */
+    FsAuthLinkToken selectFsAuthLinkTokenByToken(String token);
+
+    /** 根据token删除 */
+    int deleteFsAuthLinkTokenByToken(String token);
+}

+ 18 - 14
fs-service/src/main/java/com/fs/feishu/service/FeiShuService.java

@@ -39,6 +39,8 @@ public class FeiShuService {
     private ISysConfigService configService;
     @Autowired
     private FsUserCourseVideoMapper videoMapper;
+    @Autowired
+    private IFsAuthLinkTokenService authLinkTokenService;
 
     private Client client;
 
@@ -205,12 +207,13 @@ public class FeiShuService {
     }
 
     /**
-     * 拼接授权url(课程参数JSON方式,适配飞书iframe,双重URL编码)
+     * 拼接授权url(生成短token,参数存Redis+数据库,适配飞书iframe,双重URL编码)
      */
     private String buildAuthLink(Long companyId, Long companyUserId, Long courseId, Long videoId, Long periodId, Long projectId,Long id) {
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
 
+        // 授权参数:生成短token,参数存Redis+数据库
         Map<String, Object> authParam = new LinkedHashMap<>();
         authParam.put("periodId", periodId);
         authParam.put("companyUserId", companyUserId);
@@ -219,12 +222,12 @@ public class FeiShuService {
         authParam.put("companyId", companyId);
         authParam.put("courseId", courseId);
         authParam.put("id", id);
-        String authJson = JSONUtil.toJsonStr(authParam);
+        String token = authLinkTokenService.saveAuthParams(authParam);
 
         try {
-            String encodedJson = URLEncoder.encode(authJson, "UTF-8");
-            String baseUrl =config.getFeishuNewLinkDomainName();
-            String fullUrl = baseUrl + "/feishuCourse/pages_course/wxauth?course=" + encodedJson;
+            String baseUrl = config.getFeishuNewLinkDomainName();
+            String AUTH_PATH ="/pages/wxauth";
+            String fullUrl = baseUrl + AUTH_PATH + "?t=" + token;
             return URLEncoder.encode(fullUrl, "UTF-8");
         } catch (UnsupportedEncodingException e) {
             throw new CustomException("授权URL拼接失败", e);
@@ -388,19 +391,21 @@ public class FeiShuService {
     }
 
     /**
-     * 删除天之前创建的飞书云文档
+     * 删除天之前创建的飞书云文档
      * 1. 查询云文档列表
      * 2. 遍历比较创建时间(飞书返回秒级时间戳)
-     * 3. 创建时间在今天 00:00:00 之前的执行删除
+     * 3. 创建时间在两天 之前的执行删除
      */
     public void deleteFilesBeforeToday() throws Exception {
         // 今天 00:00:00 的时间戳(秒)
-        Calendar today = Calendar.getInstance();
-        today.set(Calendar.HOUR_OF_DAY, 0);
-        today.set(Calendar.MINUTE, 0);
-        today.set(Calendar.SECOND, 0);
-        today.set(Calendar.MILLISECOND, 0);
-        long todayStartSec = today.getTimeInMillis() / 1000;
+        Calendar twoDaysAgo  = Calendar.getInstance();
+        twoDaysAgo .set(Calendar.HOUR_OF_DAY, 0);
+        twoDaysAgo .set(Calendar.MINUTE, 0);
+        twoDaysAgo .set(Calendar.SECOND, 0);
+        twoDaysAgo .set(Calendar.MILLISECOND, 0);
+        // 减去两天
+        twoDaysAgo.add(Calendar.DAY_OF_MONTH, -2);
+        long todayStartSec = twoDaysAgo .getTimeInMillis() / 1000;
 
         List<FeishuFileDTO> files = listFiles(200);
         int total = files.size();
@@ -427,5 +432,4 @@ public class FeiShuService {
         }
         log.info("飞书云文档清理完成 | 列表总数={} | 删除成功={}", total, deleted);
     }
-
 }

+ 27 - 0
fs-service/src/main/java/com/fs/feishu/service/IFsAuthLinkTokenService.java

@@ -0,0 +1,27 @@
+package com.fs.feishu.service;
+
+import java.util.Map;
+
+/**
+ * 飞书授权链接token Service
+ * Redis + 数据库双写,查询优先 Redis,miss 回源数据库并回填
+ */
+public interface IFsAuthLinkTokenService {
+
+    /**
+     * 生成短token并保存授权参数(Redis + 数据库双写)
+     *
+     * @param params 授权参数
+     * @return 短token
+     */
+    String saveAuthParams(Map<String, Object> params);
+
+    /**
+     * 根据token查询授权参数
+     * 优先查 Redis,miss 则查数据库并回填 Redis
+     *
+     * @param token 短token
+     * @return 授权参数,token无效或过期返回 null
+     */
+    Map<String, Object> getAuthParamsByToken(String token);
+}

+ 102 - 0
fs-service/src/main/java/com/fs/feishu/service/impl/FsAuthLinkTokenServiceImpl.java

@@ -0,0 +1,102 @@
+package com.fs.feishu.service.impl;
+
+import cn.hutool.json.JSONUtil;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.feishu.domain.FsAuthLinkToken;
+import com.fs.feishu.mapper.FsAuthLinkTokenMapper;
+import com.fs.feishu.service.IFsAuthLinkTokenService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.security.SecureRandom;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 飞书授权链接token Service实现
+ * Redis主存(1天过期)+ 数据库持久化双写
+ */
+@Slf4j
+@Service
+public class FsAuthLinkTokenServiceImpl implements IFsAuthLinkTokenService {
+
+    private static final String REDIS_KEY_PREFIX = "feishu:auth:token:";
+    private static final int TOKEN_LENGTH = 8;
+    private static final int EXPIRE_HOURS = 24;
+    private static final String TOKEN_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
+
+    @Autowired
+    private FsAuthLinkTokenMapper fsAuthLinkTokenMapper;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Override
+    public String saveAuthParams(Map<String, Object> params) {
+        String token = generateToken();
+        String paramsJson = JSONUtil.toJsonStr(params);
+
+        // Redis 主存(1天过期)
+        String redisKey = REDIS_KEY_PREFIX + token;
+        redisCache.setCacheObject(redisKey, paramsJson, EXPIRE_HOURS, TimeUnit.HOURS);
+
+        // 数据库持久化
+        Date now = new Date();
+        FsAuthLinkToken record = new FsAuthLinkToken();
+        record.setToken(token);
+        record.setParamsJson(paramsJson);
+        record.setCreateTime(now);
+        record.setExpireTime(new Date(now.getTime() + (long) EXPIRE_HOURS * 3600 * 1000));
+        try {
+            fsAuthLinkTokenMapper.insertFsAuthLinkToken(record);
+        } catch (Exception e) {
+            log.error("授权token入库失败,token={},仅Redis可用", token, e);
+        }
+
+        return token;
+    }
+
+    @Override
+    public Map<String, Object> getAuthParamsByToken(String token) {
+        if (token == null || token.isEmpty()) {
+            return null;
+        }
+
+        // 优先查 Redis
+        String redisKey = REDIS_KEY_PREFIX + token;
+        String paramsJson = redisCache.getCacheObject(redisKey);
+        if (paramsJson == null) {
+            // Redis miss,回源数据库
+            FsAuthLinkToken record = fsAuthLinkTokenMapper.selectFsAuthLinkTokenByToken(token);
+            if (record == null) {
+                return null;
+            }
+            // 校验数据库过期时间
+            if (record.getExpireTime() != null && record.getExpireTime().before(new Date())) {
+                return null;
+            }
+            paramsJson = record.getParamsJson();
+            // 回填 Redis(剩余有效时长,秒为单位)
+            long remainMs = record.getExpireTime() != null
+                    ? record.getExpireTime().getTime() - System.currentTimeMillis() : 0;
+            int remainSeconds = (int) (remainMs / 1000);
+            if (remainSeconds > 0) {
+                redisCache.setCacheObject(redisKey, paramsJson, remainSeconds, TimeUnit.SECONDS);
+            }
+        }
+
+        return JSONUtil.toBean(paramsJson, Map.class);
+    }
+
+    /** 生成8位随机token(小写字母+数字) */
+    private String generateToken() {
+        SecureRandom random = new SecureRandom();
+        StringBuilder sb = new StringBuilder(TOKEN_LENGTH);
+        for (int i = 0; i < TOKEN_LENGTH; i++) {
+            sb.append(TOKEN_CHARS.charAt(random.nextInt(TOKEN_CHARS.length())));
+        }
+        return sb.toString();
+    }
+}

+ 35 - 0
fs-service/src/main/resources/mapper/feishu/FsAuthLinkTokenMapper.xml

@@ -0,0 +1,35 @@
+<?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.feishu.mapper.FsAuthLinkTokenMapper">
+
+    <resultMap id="FsAuthLinkTokenResult" type="com.fs.feishu.domain.FsAuthLinkToken">
+        <result property="token"       column="token"/>
+        <result property="paramsJson"  column="params_json"/>
+        <result property="createTime"  column="create_time"/>
+        <result property="expireTime"  column="expire_time"/>
+    </resultMap>
+
+    <sql id="selectFsAuthLinkTokenVo">
+        select token, params_json, create_time, expire_time
+        from fs_auth_link_token
+    </sql>
+
+    <insert id="insertFsAuthLinkToken" parameterType="com.fs.feishu.domain.FsAuthLinkToken">
+        insert into fs_auth_link_token
+        (token, params_json, create_time, expire_time)
+        values
+        (#{token}, #{paramsJson}, #{createTime}, #{expireTime})
+    </insert>
+
+    <select id="selectFsAuthLinkTokenByToken" parameterType="String" resultMap="FsAuthLinkTokenResult">
+        <include refid="selectFsAuthLinkTokenVo"/>
+        where token = #{token}
+    </select>
+
+    <delete id="deleteFsAuthLinkTokenByToken" parameterType="String">
+        delete from fs_auth_link_token where token = #{token}
+    </delete>
+
+</mapper>

+ 13 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/FeiShuCourseController.java

@@ -10,6 +10,7 @@ import com.fs.common.utils.StringUtils;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.FsUserCourseVideoH5VO;
+import com.fs.feishu.service.IFsAuthLinkTokenService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -27,6 +28,8 @@ public class FeiShuCourseController {
     private IFsUserCourseService courseService;
     @Autowired
     private IFsUserCourseVideoService courseVideoService;
+    @Autowired
+    private IFsAuthLinkTokenService authLinkTokenService;
 
     @ApiOperation("飞书获取唯一标识")
     @GetMapping("/getUniqueIdentifier")
@@ -87,6 +90,16 @@ public class FeiShuCourseController {
         courseVideoService.getFeiShuInternetTraffic(identifier, companyUserId, videoId, periodId, param.getUuId(), param.getBufferRate());
     }
 
+    @ApiOperation("根据短token查询授权参数")
+    @GetMapping("/getAuthParams")
+    public R getAuthParams(@RequestParam String token) {
+        Map<String, Object> params = authLinkTokenService.getAuthParamsByToken(token);
+        if (params == null || params.isEmpty()) {
+            return R.error("授权链接无效或已过期");
+        }
+        return R.ok().put("data", params);
+    }
+
     /**
      * 安全获取课程信息
      */