瀏覽代碼

飞书看课-优化飞书记录错误日志

cgp 4 天之前
父節點
當前提交
e8c4394fbe

+ 15 - 28
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -3424,25 +3424,17 @@ public class Task {
                     redisCache.setCacheObject("fei_shu_client:" + docId, account);
                     return docId;
 
-                } catch (CustomException e) {
-                    Integer code = e.getCode();
-                    if (code == null) {
-                        recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
-                        return null;
-                    }
-                    if (FeishuErrorCode.isAccountDisable(code)) {
+                } catch (Exception e) {
+                    Integer code = FeishuErrorCode.parseCodeFromException(e);
+                    if (code != null && FeishuErrorCode.isAccountDisable(code)) {
                         disablePersonalAccount(account, e.getMessage());
                         log.warn("个人账号[{}]被禁用,尝试下一个", account.getAppId());
-                    } else if (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code)) {
+                    } else if (code != null && (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code))) {
                         log.warn("个人账号[{}]可重试错误,切换下一个", account.getAppId());
                     } else {
-                        // 业务错误或其他,记录失败并返回
-                        recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
+                        recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1, code);
                         return null;
                     }
-                } catch (Exception e) {
-                    recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
-                    return null;
                 }
             }
             log.warn("所有个人账号失败,降级到公共账号池");
@@ -3459,32 +3451,26 @@ public class Task {
                 redisCache.setCacheObject("fei_shu_client:" + docId, account);
                 return docId;
 
-            } catch (CustomException e) {
-                Integer code = e.getCode();
-                if (code == null) {
-                    recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
-                    return null;
-                }
-                if (FeishuErrorCode.isAccountDisable(code)) {
+            } catch (Exception e) {
+                Integer code = FeishuErrorCode.parseCodeFromException(e);
+                if (code != null && FeishuErrorCode.isAccountDisable(code)) {
                     clientPool.disableAccount(account, e.getMessage());
                     log.warn("公共账号被禁用,尝试下一个");
-                } else if (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code)) {
+                } else if (code != null && (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code))) {
                     log.warn("公共账号可重试错误,切换下一个");
                 } else {
-                    recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
+                    recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1, code);
                     return null;
                 }
-            } catch (Exception e) {
-                recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
-                return null;
             }
         }
-        recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", "所有公共账号不可用", 1);
+        recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", "所有公共账号不可用", 1, null);
         return null;
     }
 
     private void recordLinkError(Long videoId, Long companyId, Long courseId,
-                                 Long companyUserId, String link, String errorInfo, Integer type) {
+                                 Long companyUserId, String link, String errorInfo,
+                                 Integer type, Integer errorCode) {
         FeishuLinkError error = new FeishuLinkError();
         error.setVideoId(videoId);
         error.setCompanyId(companyId);
@@ -3494,6 +3480,7 @@ public class Task {
         error.setErrorInfo(errorInfo);
         error.setCreateTime(DateUtils.getNowDate());
         error.setType(type);
+        error.setErrorCode(errorCode);
 //        FsCourseLink fsCourseLink = courseLinkMapper.selectFsCourseLinkByLink(link);
 ////        if (fsCourseLink != null) {
 ////            error.setQwUserId(fsCourseLink.getQwUserId());
@@ -3501,7 +3488,7 @@ public class Task {
 ////            error.setCorpId(fsCourseLink.getCorpId());
 ////        }
         feishuLinkErrorMapper.insertFeishuLinkError(error);
-        log.info("飞书链接创建失败已记录,videoId={}, link={}, error={}", videoId, link, errorInfo);
+        log.info("飞书链接创建失败已记录,videoId={}, link={}, code={}, error={}", videoId, link, errorCode, errorInfo);
     }
 
     public void checkQwCompanyAppId(){

+ 14 - 36
fs-ipad-task/src/main/java/com/fs/app/service/FeishuAsyncService.java

@@ -43,35 +43,24 @@ public class FeishuAsyncService {
             String amountKey = FEISHU_CLIENT+docId;
             account.setDocumentIdFillFlag(Boolean.TRUE);
             redisCache.setCacheObject(amountKey, account,3, TimeUnit.DAYS);
-        } catch (CustomException e) {
-            // 调用原Service的方法处理异常
-            Integer code = e.getCode();
-            if (code == null) {
-                feiShuService.recordLinkError(videoId, companyId, courseId,
-                        companyUserId, shortLink,
-                        e.getMessage(), 1);
-                return;
-            }
-            if (FeishuErrorCode.isAccountDisable(code)) {
+        } catch (Exception e) {
+            Integer code = FeishuErrorCode.parseCodeFromException(e);
+            if (code != null && FeishuErrorCode.isAccountDisable(code)) {
                 feiShuService.disablePersonalAccount(account, e.getMessage());
                 log.warn("个人账号[{}]被禁用-", account.getAppId());
                 feiShuService.recordLinkError(videoId, companyId, courseId,
                         companyUserId, shortLink,
-                        "账号被禁用:" + e.getMessage(), 2);
-            } else if (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code)) {
+                        "账号被禁用:" + e.getMessage(), 2, code);
+            } else if (code != null && (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code))) {
                 log.warn("个人账号[{}]可重试错误-", account.getAppId());
                 feiShuService.recordLinkError(videoId, companyId, courseId,
                         companyUserId, shortLink,
-                        e.getMessage(), 1);
+                        e.getMessage(), 1, code);
             } else {
                 feiShuService.recordLinkError(videoId, companyId, courseId,
                         companyUserId, shortLink,
-                        e.getMessage(), 1);
+                        e.getMessage(), 1, code);
             }
-        } catch (Exception e) {
-            feiShuService.recordLinkError(videoId, companyId, courseId,
-                    companyUserId, shortLink,
-                    e.getMessage(), 1);
         }
     }
 
@@ -87,37 +76,26 @@ public class FeishuAsyncService {
             String amountKey = FEISHU_CLIENT+docId;
             account.setCourseFillFlag(Boolean.TRUE);
             redisCache.setCacheObject(amountKey, account,3, TimeUnit.DAYS);
-        } catch (CustomException e) {
-            // 调用原Service的方法处理异常
-            Integer code = e.getCode();
+        } catch (Exception e) {
+            Integer code = FeishuErrorCode.parseCodeFromException(e);
             log.error("异步填充课程失败,docId={}, videoId={}, appId={}, code={}, msg={}",
                     docId, videoId, account.getAppId(), code, e.getMessage(), e);
-            if (code == null) {
-                feiShuService.recordLinkError(videoId, companyId, courseId,
-                        companyUserId, shortLink,
-                        e.getMessage(), 2);
-                return;
-            }
-            if (FeishuErrorCode.isAccountDisable(code)) {
+            if (code != null && FeishuErrorCode.isAccountDisable(code)) {
                 feiShuService.disablePersonalAccount(account, e.getMessage());
                 log.warn("个人账号[{}]被禁用", account.getAppId());
                 feiShuService.recordLinkError(videoId, companyId, courseId,
                         companyUserId, shortLink,
-                        "账号被禁用:" + e.getMessage(), 2);
-            } else if (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code)) {
+                        "账号被禁用:" + e.getMessage(), 2, code);
+            } else if (code != null && (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code))) {
                 log.warn("个人账号[{}]可重试错误", account.getAppId());
                 feiShuService.recordLinkError(videoId, companyId, courseId,
                         companyUserId, shortLink,
-                        e.getMessage(), 2);
+                        e.getMessage(), 2, code);
             } else {
                 feiShuService.recordLinkError(videoId, companyId, courseId,
                         companyUserId, shortLink,
-                        e.getMessage(), 2);
+                        e.getMessage(), 2, code);
             }
-        } catch (Exception e) {
-            feiShuService.recordLinkError(videoId, companyId, courseId,
-                    companyUserId, shortLink,
-                    e.getMessage(), 2);
         }
     }
 }

+ 6 - 0
fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java

@@ -352,6 +352,12 @@ public class IpadSendServer {
             log.info("ID:{}-链接消息已标记失败,跳过发送,原因:{}", vo.getId(), content.getSendRemarks());
             return;
         }
+        if (StringUtils.isEmpty(content.getLinkUrl())) {
+            log.info("ID:{}-链接消息已标记失败,跳过发送,原因:{}", vo.getId(), "链接获取失败");
+            content.setSendStatus(2);
+            content.setSendRemarks("发送失败:链接获取失败");
+            return;
+        }
         // 文本
         LinkVo linkVo = LinkVo.builder()
                 .title(content.getLinkTitle())

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

@@ -1885,25 +1885,17 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     redisCache.setCacheObject("fei_shu_client:" + docId, account,2,TimeUnit.DAYS);
                     return docId;
 
-                } catch (CustomException e) {
-                    Integer code = e.getCode();
-                    if (code == null) {
-                        recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
-                        return null;
-                    }
-                    if (FeishuErrorCode.isAccountDisable(code)) {
+                } catch (Exception e) {
+                    Integer code = FeishuErrorCode.parseCodeFromException(e);
+                    if (code != null && FeishuErrorCode.isAccountDisable(code)) {
                         disablePersonalAccount(account, e.getMessage());
                         log.warn("个人账号[{}]被禁用,尝试下一个", account.getAppId());
-                    } else if (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code)) {
+                    } else if (code != null && (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code))) {
                         log.warn("个人账号[{}]可重试错误,切换下一个", account.getAppId());
                     } else {
-                        // 业务错误或其他,记录失败并返回
-                        recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
+                        recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1, code);
                         return null;
                     }
-                } catch (Exception e) {
-                    recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
-                    return null;
                 }
             }
             log.warn("所有个人账号失败,降级到公共账号池");
@@ -1920,32 +1912,26 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 redisCache.setCacheObject("fei_shu_client:" + docId, account);
                 return docId;
 
-            } catch (CustomException e) {
-                Integer code = e.getCode();
-                if (code == null) {
-                    recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
-                    return null;
-                }
-                if (FeishuErrorCode.isAccountDisable(code)) {
+            } catch (Exception e) {
+                Integer code = FeishuErrorCode.parseCodeFromException(e);
+                if (code != null && FeishuErrorCode.isAccountDisable(code)) {
                     clientPool.disableAccount(account, e.getMessage());
                     log.warn("公共账号被禁用,尝试下一个");
-                } else if (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code)) {
+                } else if (code != null && (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code))) {
                     log.warn("公共账号可重试错误,切换下一个");
                 } else {
-                    recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
+                    recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1, code);
                     return null;
                 }
-            } catch (Exception e) {
-                recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
-                return null;
             }
         }
-        recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", "所有公共账号不可用", 1);
+        recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", "所有公共账号不可用", 1, null);
         return null;
     }
 
     private void recordLinkError(Long videoId, Long companyId, Long courseId,
-                                 Long companyUserId, String link, String errorInfo, Integer type) {
+                                 Long companyUserId, String link, String errorInfo,
+                                 Integer type, Integer errorCode) {
         FeishuLinkError error = new FeishuLinkError();
         error.setVideoId(videoId);
         error.setCompanyId(companyId);
@@ -1955,8 +1941,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         error.setErrorInfo(errorInfo);
         error.setCreateTime(DateUtils.getNowDate());
         error.setType(type);
+        error.setErrorCode(errorCode);
         feishuLinkErrorMapper.insertFeishuLinkError(error);
-        log.info("飞书链接创建失败已记录,videoId={}, link={}, error={}", videoId, link, errorInfo);
+        log.info("飞书链接创建失败已记录,videoId={}, link={}, code={}, error={}", videoId, link, errorCode, errorInfo);
     }
 
     private Long getCourseProjectType(Long courseId){

+ 1 - 0
fs-service/src/main/java/com/fs/feishu/domain/FeishuLinkError.java

@@ -21,6 +21,7 @@ public class FeishuLinkError {
     private Long qwUserId;       // qw_user主键id
     private Long qwExternalId;      // 企微外部联系人
     private String errorInfo;       // 飞书错误信息
+    private Integer errorCode;      // 飞书错误码
 
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date createTime;        // 生成时间

+ 26 - 60
fs-service/src/main/java/com/fs/feishu/service/FeiShuService.java

@@ -159,27 +159,18 @@ public class FeiShuService {
                     }
                     String result = "https://www.feishu.cn/docx/" + docId;
                     return result;
-                } catch (CustomException e) {
-                    Integer code = e.getCode();
-                    if (code == null) {
-                        recordLinkError(param.getVideoId(), param.getCompanyId(), param.getCourseId(),
-                                param.getCompanyUserId(), param.getLink(), e.getMessage(), 2);
-                        return null;
-                    }
-                    if (FeishuErrorCode.isAccountDisable(code)) {
+                } catch (Exception e) {
+                    Integer code = FeishuErrorCode.parseCodeFromException(e);
+                    if (code != null && FeishuErrorCode.isAccountDisable(code)) {
                         disablePersonalAccount(account, e.getMessage());
                         log.warn("个人账号[{}]被禁用,尝试下一个", account.getAppId());
-                    } else if (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code)) {
+                    } else if (code != null && (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code))) {
                         log.warn("个人账号[{}]可重试错误,切换下一个", account.getAppId());
                     } else {
                         recordLinkError(param.getVideoId(), param.getCompanyId(), param.getCourseId(),
-                                param.getCompanyUserId(), param.getLink(), e.getMessage(), 2);
+                                param.getCompanyUserId(), param.getLink(), e.getMessage(), 2, code);
                         return null;
                     }
-                } catch (Exception e) {
-                    recordLinkError(param.getVideoId(), param.getCompanyId(), param.getCourseId(),
-                            param.getCompanyUserId(), param.getLink(), e.getMessage(), 2);
-                    return null;
                 }
             }
             log.warn("所有个人账号失败,降级到公共账号池");
@@ -207,31 +198,22 @@ public class FeiShuService {
                 }
                 String result = "https://www.feishu.cn/docx/" + docId;
                 return result;
-            } catch (CustomException e) {
-                Integer code = e.getCode();
-                if (code == null) {
-                    recordLinkError(param.getVideoId(), param.getCompanyId(), param.getCourseId(),
-                            param.getCompanyUserId(), param.getLink(), e.getMessage(), 2);
-                    return null;
-                }
-                if (FeishuErrorCode.isAccountDisable(code)) {
+            } catch (Exception e) {
+                Integer code = FeishuErrorCode.parseCodeFromException(e);
+                if (code != null && FeishuErrorCode.isAccountDisable(code)) {
                     clientPool.disableAccount(account, e.getMessage());
                     log.warn("公共账号被禁用,尝试下一个");
-                } else if (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code)) {
+                } else if (code != null && (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code))) {
                     log.warn("公共账号可重试错误,切换下一个");
                 } else {
                     recordLinkError(param.getVideoId(), param.getCompanyId(), param.getCourseId(),
-                            param.getCompanyUserId(), param.getLink(), e.getMessage(), 2);
+                            param.getCompanyUserId(), param.getLink(), e.getMessage(), 2, code);
                     return null;
                 }
-            } catch (Exception e) {
-                recordLinkError(param.getVideoId(), param.getCompanyId(), param.getCourseId(),
-                        param.getCompanyUserId(), param.getLink(), e.getMessage(), 2);
-                return null;
             }
         }
         recordLinkError(param.getVideoId(), param.getCompanyId(), param.getCourseId(),
-                param.getCompanyUserId(), param.getLink(), "所有公共账号不可用", 2);
+                param.getCompanyUserId(), param.getLink(), "所有公共账号不可用", 2, null);
         return null;
     }
 
@@ -274,27 +256,18 @@ public class FeiShuService {
                     String result = "https://www.feishu.cn/docx/" + docId;
                     redisCache.setCacheObject(cacheKey, result, 72, TimeUnit.HOURS);
                     return result;
-                } catch (CustomException e) {
-                    Integer code = e.getCode();
-                    if (code == null) {
-                        recordLinkError(param.getVideoId(), param.getCompanyId(), param.getCourseId(),
-                                param.getCompanyUserId(), param.getLink(), e.getMessage(), 2);
-                        throw new CustomException(e.getMessage());
-                    }
-                    if (FeishuErrorCode.isAccountDisable(code)) {
+                } catch (Exception e) {
+                    Integer code = FeishuErrorCode.parseCodeFromException(e);
+                    if (code != null && FeishuErrorCode.isAccountDisable(code)) {
                         disablePersonalAccount(account, e.getMessage());
                         log.warn("个人账号[{}]被禁用,尝试下一个", account.getAppId());
-                    } else if (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code)) {
+                    } else if (code != null && (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code))) {
                         log.warn("个人账号[{}]可重试错误,切换下一个", account.getAppId());
                     } else {
                         recordLinkError(param.getVideoId(), param.getCompanyId(), param.getCourseId(),
-                                param.getCompanyUserId(), param.getLink(), e.getMessage(), 2);
+                                param.getCompanyUserId(), param.getLink(), e.getMessage(), 2, code);
                         throw new CustomException(e.getMessage());
                     }
-                } catch (Exception e) {
-                    recordLinkError(param.getVideoId(), param.getCompanyId(), param.getCourseId(),
-                            param.getCompanyUserId(), param.getLink(), e.getMessage(), 2);
-                    throw new CustomException(e.getMessage());
                 }
             }
             log.warn("所有个人账号失败,降级到公共账号池");
@@ -315,31 +288,22 @@ public class FeiShuService {
                 String result = "https://www.feishu.cn/docx/" + docId;
                 redisCache.setCacheObject(cacheKey, result, 72, TimeUnit.HOURS);
                 return result;
-            } catch (CustomException e) {
-                Integer code = e.getCode();
-                if (code == null) {
-                    recordLinkError(param.getVideoId(), param.getCompanyId(), param.getCourseId(),
-                            param.getCompanyUserId(), param.getLink(), e.getMessage(), 2);
-                    throw new CustomException(e.getMessage());
-                }
-                if (FeishuErrorCode.isAccountDisable(code)) {
+            } catch (Exception e) {
+                Integer code = FeishuErrorCode.parseCodeFromException(e);
+                if (code != null && FeishuErrorCode.isAccountDisable(code)) {
                     clientPool.disableAccount(account, e.getMessage());
                     log.warn("公共账号被禁用,尝试下一个");
-                } else if (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code)) {
+                } else if (code != null && (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code))) {
                     log.warn("公共账号可重试错误,切换下一个");
                 } else {
                     recordLinkError(param.getVideoId(), param.getCompanyId(), param.getCourseId(),
-                            param.getCompanyUserId(), param.getLink(), e.getMessage(), 2);
+                            param.getCompanyUserId(), param.getLink(), e.getMessage(), 2, code);
                     throw new CustomException(e.getMessage());
                 }
-            } catch (Exception e) {
-                recordLinkError(param.getVideoId(), param.getCompanyId(), param.getCourseId(),
-                        param.getCompanyUserId(), param.getLink(), e.getMessage(), 2);
-                throw new CustomException(e.getMessage());
             }
         }
         recordLinkError(param.getVideoId(), param.getCompanyId(), param.getCourseId(),
-                param.getCompanyUserId(), param.getLink(), "所有公共账号不可用", 2);
+                param.getCompanyUserId(), param.getLink(), "所有公共账号不可用", 2, null);
         log.error("所有飞书账号创建文档失败");
         throw new CustomException("课程不存在或已过期,请联系管理员");
     }
@@ -372,7 +336,8 @@ public class FeiShuService {
     //记录错误
     @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void recordLinkError(Long videoId, Long companyId, Long courseId,
-                                 Long companyUserId, String link, String errorInfo, Integer type) {
+                                 Long companyUserId, String link, String errorInfo,
+                                 Integer type, Integer errorCode) {
         FeishuLinkError error = new FeishuLinkError();
         error.setVideoId(videoId);
         error.setCompanyId(companyId);
@@ -382,6 +347,7 @@ public class FeiShuService {
         error.setErrorInfo(errorInfo);
         error.setCreateTime(DateUtils.getNowDate());
         error.setType(type);
+        error.setErrorCode(errorCode);
         FsCourseLink fsCourseLink = courseLinkMapper.selectFsCourseLinkByLink(link);
         if (fsCourseLink != null) {
             error.setQwUserId(fsCourseLink.getQwUserId());
@@ -389,7 +355,7 @@ public class FeiShuService {
             error.setCorpId(fsCourseLink.getCorpId());
         }
         feishuLinkErrorMapper.insertFeishuLinkError(error);
-        log.info("飞书链接创建失败已记录,videoId={}, link={}, error={}", videoId, link, errorInfo);
+        log.info("飞书链接创建失败已记录,videoId={}, link={}, code={}, error={}", videoId, link, errorCode, errorInfo);
     }
 
     private CourseConfig getCourseConfig() {

+ 38 - 0
fs-service/src/main/java/com/fs/feishu/util/FeishuErrorCode.java

@@ -1,7 +1,11 @@
 package com.fs.feishu.util;
 
+import com.fs.common.exception.CustomException;
 import lombok.Getter;
 
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 /**
  * 飞书错误码枚举,按处理策略分类
  */
@@ -104,4 +108,38 @@ public enum FeishuErrorCode {
         BUSINESS_ERROR,    // 业务错误,不重试
         UNKNOWN            // 未知
     }
+
+    // ==================== 错误码解析工具 ====================
+
+    /** 匹配异常消息中的 "code:数字" 模式 */
+    private static final Pattern CODE_PATTERN = Pattern.compile("code:(\\d+)");
+
+    /**
+     * 从异常消息中提取飞书错误码,未匹配到则返回 null
+     */
+    public static Integer parseCodeFromMessage(String message) {
+        if (message == null) {
+            return null;
+        }
+        Matcher m = CODE_PATTERN.matcher(message);
+        if (m.find()) {
+            try {
+                return Integer.parseInt(m.group(1));
+            } catch (NumberFormatException ignored) {
+                return null;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 从异常中提取飞书错误码:
+     * CustomException 直接取 getCode(),其他异常从 message 中正则提取。
+     */
+    public static Integer parseCodeFromException(Exception e) {
+        if (e instanceof CustomException) {
+            return ((CustomException) e).getCode();
+        }
+        return parseCodeFromMessage(e.getMessage());
+    }
 }

+ 8 - 1
fs-service/src/main/resources/mapper/his/FeishuLinkErrorMapper.xml

@@ -15,12 +15,13 @@
         <result property="qwUserId" column="qw_user_id"/>
         <result property="qwExternalId" column="qw_external_id"/>
         <result property="errorInfo" column="error_info"/>
+        <result property="errorCode" column="error_code"/>
         <result property="createTime" column="create_time"/>
     </resultMap>
 
     <sql id="Base_Column_List">
         SELECT id, type, corp_id, company_id, company_user_id, video_id, course_id,
-        link, qw_user_id, qw_external_id, error_info, create_time FROM feishu_link_error
+        link, qw_user_id, qw_external_id, error_info, error_code, create_time FROM feishu_link_error
     </sql>
 
     <!-- 通用查询条件 -->
@@ -56,6 +57,9 @@
             <if test="errorInfo != null and errorInfo != ''">
                 AND error_info like concat('%', #{errorInfo}, '%')
             </if>
+            <if test="errorCode != null">
+                AND error_code = #{errorCode}
+            </if>
             <!-- 如果有时间范围查询,可以额外增加参数,此处省略 -->
         </where>
     </sql>
@@ -87,6 +91,7 @@
             <if test="qwUserId != null">qw_user_id,</if>
             <if test="qwExternalId != null">qw_external_id,</if>
             <if test="errorInfo != null and errorInfo != ''">error_info,</if>
+            <if test="errorCode != null">error_code,</if>
             <if test="createTime != null">create_time,</if>
         </trim>
         <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
@@ -100,6 +105,7 @@
             <if test="qwUserId != null">#{qwUserId},</if>
             <if test="qwExternalId != null">#{qwExternalId},</if>
             <if test="errorInfo != null and errorInfo != ''">#{errorInfo},</if>
+            <if test="errorCode != null">#{errorCode},</if>
             <if test="createTime != null">#{createTime},</if>
         </trim>
     </insert>
@@ -118,6 +124,7 @@
             <if test="qwUserId != null">qw_user_id = #{qwUserId},</if>
             <if test="qwExternalId != null">qw_external_id = #{qwExternalId},</if>
             <if test="errorInfo != null and errorInfo != ''">error_info = #{errorInfo},</if>
+            <if test="errorCode != null">error_code = #{errorCode},</if>
             <if test="createTime != null">create_time = #{createTime},</if>
         </set>
         WHERE id = #{id}