|
@@ -11,11 +11,11 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
import com.fs.app.domain.FsAppRole;
|
|
import com.fs.app.domain.FsAppRole;
|
|
|
import com.fs.app.mapper.FsAppRoleMapper;
|
|
import com.fs.app.mapper.FsAppRoleMapper;
|
|
|
import com.fs.common.exception.CustomException;
|
|
import com.fs.common.exception.CustomException;
|
|
|
-import com.fs.company.domain.CompanyMiniapp;
|
|
|
|
|
import com.fs.company.service.ICompanyMiniappService;
|
|
import com.fs.company.service.ICompanyMiniappService;
|
|
|
import com.fs.company.service.ICompanySmsService;
|
|
import com.fs.company.service.ICompanySmsService;
|
|
|
import com.fs.core.config.WxMaConfiguration;
|
|
import com.fs.core.config.WxMaConfiguration;
|
|
|
import com.fs.core.utils.OrderCodeUtils;
|
|
import com.fs.core.utils.OrderCodeUtils;
|
|
|
|
|
+import com.fs.course.config.CourseConfig;
|
|
|
import com.fs.course.domain.*;
|
|
import com.fs.course.domain.*;
|
|
|
import com.fs.course.mapper.*;
|
|
import com.fs.course.mapper.*;
|
|
|
import com.fs.course.service.IFsCoursePlaySourceConfigService;
|
|
import com.fs.course.service.IFsCoursePlaySourceConfigService;
|
|
@@ -24,6 +24,13 @@ import com.fs.fastGpt.domain.FastgptChatVoiceHomo;
|
|
|
import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
|
|
import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
|
|
|
import com.fs.fastgptApi.util.AudioUtils;
|
|
import com.fs.fastgptApi.util.AudioUtils;
|
|
|
import com.fs.fastgptApi.vo.AudioVO;
|
|
import com.fs.fastgptApi.vo.AudioVO;
|
|
|
|
|
+import com.fs.feishu.domain.FeishuAccount;
|
|
|
|
|
+import com.fs.feishu.domain.FeishuLinkError;
|
|
|
|
|
+import com.fs.feishu.mapper.FeishuAccountMapper;
|
|
|
|
|
+import com.fs.feishu.mapper.FeishuLinkErrorMapper;
|
|
|
|
|
+import com.fs.feishu.service.FeishuClientPool;
|
|
|
|
|
+import com.fs.feishu.service.FeishuDocApiService;
|
|
|
|
|
+import com.fs.feishu.util.FeishuErrorCode;
|
|
|
import com.fs.hisStore.param.FsStoreOrderExpressEditParam;
|
|
import com.fs.hisStore.param.FsStoreOrderExpressEditParam;
|
|
|
import com.fs.hisStore.service.IFsStoreOrderScrmService;
|
|
import com.fs.hisStore.service.IFsStoreOrderScrmService;
|
|
|
import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
|
|
import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
|
|
@@ -114,6 +121,7 @@ import com.fs.wx.service.ShippingService;
|
|
|
import com.fs.xiaoshouyi.dto.TokenResponse;
|
|
import com.fs.xiaoshouyi.dto.TokenResponse;
|
|
|
import com.fs.xiaoshouyi.service.XiaoShouYiOAuthService;
|
|
import com.fs.xiaoshouyi.service.XiaoShouYiOAuthService;
|
|
|
import com.google.gson.Gson;
|
|
import com.google.gson.Gson;
|
|
|
|
|
+import com.lark.oapi.Client;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import me.chanjar.weixin.common.error.WxErrorException;
|
|
import me.chanjar.weixin.common.error.WxErrorException;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
@@ -387,6 +395,10 @@ public class Task {
|
|
|
|
|
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private IFsCourseReissueConfigService fsCourseReissueConfigService;
|
|
private IFsCourseReissueConfigService fsCourseReissueConfigService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FsUserCourseMapper fsUserCourseMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FsUserCourseVideoMapper fsUserCourseVideoMapper;
|
|
|
|
|
|
|
|
public void syncExpressToWx() {
|
|
public void syncExpressToWx() {
|
|
|
List<FsWxExpressTask> fsWxExpressTasks = fsWxExpressTaskMapper.selectPendingData();
|
|
List<FsWxExpressTask> fsWxExpressTasks = fsWxExpressTaskMapper.selectPendingData();
|
|
@@ -3184,4 +3196,209 @@ public class Task {
|
|
|
return link.getRealLink();
|
|
return link.getRealLink();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private static final String FEISHU_COMPANY_USER_KEY = "feishu:account_company_user:";
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FeishuAccountMapper feishuAccountMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FeishuDocApiService docApiService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FeishuClientPool clientPool;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FeishuLinkErrorMapper feishuLinkErrorMapper;
|
|
|
|
|
+ public void generateFeiShuRedisCache() {
|
|
|
|
|
+
|
|
|
|
|
+ //课程id数据
|
|
|
|
|
+ List<Long> courseIds = fsUserCourseMapper.selectPrivateFsUserCourseIds();
|
|
|
|
|
+ if (CollectionUtils.isEmpty(courseIds)) {
|
|
|
|
|
+ logger.error("未查询到私教课程");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //小节数据
|
|
|
|
|
+ List<FsUserCourseVideo> fsUserCourseVideos = fsUserCourseVideoMapper.selectVideoByCourseIds(courseIds);
|
|
|
|
|
+ if (CollectionUtils.isEmpty(fsUserCourseVideos)) {
|
|
|
|
|
+ logger.error("未查询到小节数据");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //营期中企业外部联系人
|
|
|
|
|
+ List<Long> externalIds = sopUserLogsInfoMapper.selectExternalIds();
|
|
|
|
|
+ if (CollectionUtils.isEmpty(externalIds)) {
|
|
|
|
|
+ logger.error("未查询到营期中企业外部联系人");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //外部联系人数据
|
|
|
|
|
+ List<QwExternalContact> contacts = qwExternalContactMapper.selectQwExternalContactByIds(externalIds);
|
|
|
|
|
+ if (CollectionUtils.isEmpty(contacts)) {
|
|
|
|
|
+ logger.error("未查询到外部联系人数据");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ CourseConfig config = getCourseConfig();
|
|
|
|
|
+ if (!Boolean.TRUE.equals(config.getEnableFeishuNewLink())) {
|
|
|
|
|
+ logger.error("未开启飞书看课");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (QwExternalContact contact : contacts) {
|
|
|
|
|
+ for (FsUserCourseVideo video : fsUserCourseVideos) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ String key = "fei_shu_doc_id:" + contact.getId() + "_" + video.getVideoId();
|
|
|
|
|
+ String feiShuDocId = redisCache.getCacheObject(key);
|
|
|
|
|
+ if (StringUtils.isNotBlank(feiShuDocId)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ feiShuDocId = getFeiShuDocId(video, contact.getCompanyId(), contact.getCompanyUserId());
|
|
|
|
|
+ redisCache.setCacheObject("fei_shu_doc_id:" + contact.getId() + "_" + video.getVideoId(), feiShuDocId);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("生成飞书文档失败, 课程id: {}, 外部联系人id: {}, 错误信息: {}", video.getCourseId(), contact.getExternalUserId(), e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public List<FeishuAccount> getFeiShuAccountCompanyUser(Long companyId, Long companyUserId) {
|
|
|
|
|
+ String cacheKey = FEISHU_COMPANY_USER_KEY + companyId + ":" + companyUserId;
|
|
|
|
|
+ String cached = redisCache.getCacheObject(cacheKey);
|
|
|
|
|
+ if (StringUtils.isNotBlank(cached)) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return JSONUtil.parseArray(cached).toList(FeishuAccount.class);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("缓存数据解析失败,key: {}, 将清除缓存", cacheKey, e);
|
|
|
|
|
+ redisCache.deleteObject(cacheKey);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ FeishuAccount account = new FeishuAccount();
|
|
|
|
|
+ account.setCompanyId(companyId);
|
|
|
|
|
+ account.setCompanyUserId(companyUserId);
|
|
|
|
|
+ account.setStatus(1);
|
|
|
|
|
+ List<FeishuAccount> feiShuAccounts = feishuAccountMapper.selectFeishuAccountList(account);
|
|
|
|
|
+ if (feiShuAccounts.isEmpty()) {
|
|
|
|
|
+ redisCache.setCacheObject(cacheKey, "[]", 1, TimeUnit.MINUTES);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ redisCache.setCacheObject(cacheKey, JSONUtil.toJsonStr(feiShuAccounts), 1, TimeUnit.DAYS);
|
|
|
|
|
+ }
|
|
|
|
|
+ return feiShuAccounts;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void disablePersonalAccount(FeishuAccount account, String errorMsg) {
|
|
|
|
|
+ feishuAccountMapper.updateStatusAndErrorMsg(account.getId(), 0, errorMsg);
|
|
|
|
|
+ clearCompanyUserCache(account.getCompanyId(), account.getCompanyUserId());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void clearCompanyUserCache(Long companyId, Long companyUserId) {
|
|
|
|
|
+ if (companyId != null && companyUserId != null) {
|
|
|
|
|
+ String cacheKey = FEISHU_COMPANY_USER_KEY + companyId + ":" + companyUserId;
|
|
|
|
|
+ redisCache.deleteObject(cacheKey);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private CourseConfig getCourseConfig() {
|
|
|
|
|
+ String json = sysConfigService.selectConfigByKey("course.config");
|
|
|
|
|
+ if (StringUtils.isBlank(json)) {
|
|
|
|
|
+ throw new CustomException("飞书看课配置为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ return JSONUtil.toBean(json, CourseConfig.class);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Client buildClient(FeishuAccount account) {
|
|
|
|
|
+ return Client.newBuilder(account.getAppId(), account.getAppSecret())
|
|
|
|
|
+ .logReqAtDebug(true)
|
|
|
|
|
+ .build();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public String getFeiShuDocId(FsUserCourseVideo video, Long companyId,
|
|
|
|
|
+ Long companyUserId) {
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 尝试销售个人账号创建文档
|
|
|
|
|
+ List<FeishuAccount> personalAccounts = getFeiShuAccountCompanyUser(companyId, companyUserId);
|
|
|
|
|
+ if (!personalAccounts.isEmpty()) {
|
|
|
|
|
+ for (FeishuAccount account : personalAccounts) {
|
|
|
|
|
+ if (account.getStatus() != 1) continue;
|
|
|
|
|
+ Client client = buildClient(account);
|
|
|
|
|
+ try {
|
|
|
|
|
+ String docId = docApiService.createDocument(client, video.getTitle() + "-注册");
|
|
|
|
|
+ redisCache.setCacheObject("fei_shu_client:" + docId, client);
|
|
|
|
|
+ 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)) {
|
|
|
|
|
+ disablePersonalAccount(account, e.getMessage());
|
|
|
|
|
+ log.warn("个人账号[{}]被禁用,尝试下一个", account.getAppId());
|
|
|
|
|
+ } else if (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code)) {
|
|
|
|
|
+ log.warn("个人账号[{}]可重试错误,切换下一个", account.getAppId());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 业务错误或其他,记录失败并返回
|
|
|
|
|
+ recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ log.warn("所有个人账号失败,降级到公共账号池");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 公共账号池
|
|
|
|
|
+ int retries = clientPool.getAvailableCount();
|
|
|
|
|
+ for (int i = 0; i < retries; i++) {
|
|
|
|
|
+ Client client = clientPool.getClient();
|
|
|
|
|
+ try {
|
|
|
|
|
+ String docId = docApiService.createDocument(client, video.getTitle() + "-注册");
|
|
|
|
|
+ redisCache.setCacheObject("fei_shu_client:" + docId, client);
|
|
|
|
|
+ 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)) {
|
|
|
|
|
+ clientPool.disableClient(client, e.getMessage());
|
|
|
|
|
+ log.warn("公共账号被禁用,尝试下一个");
|
|
|
|
|
+ } else if (FeishuErrorCode.isRateLimit(code) || FeishuErrorCode.isInternalRetryable(code)) {
|
|
|
|
|
+ log.warn("公共账号可重试错误,切换下一个");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ recordLinkError(video.getVideoId(), companyId, video.getCourseId(), companyUserId, "定时生成的缓存docId", e.getMessage(), 1);
|
|
|
|
|
+ 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);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void recordLinkError(Long videoId, Long companyId, Long courseId,
|
|
|
|
|
+ Long companyUserId, String link, String errorInfo, Integer type) {
|
|
|
|
|
+ FeishuLinkError error = new FeishuLinkError();
|
|
|
|
|
+ error.setVideoId(videoId);
|
|
|
|
|
+ error.setCompanyId(companyId);
|
|
|
|
|
+ error.setCourseId(courseId);
|
|
|
|
|
+ error.setCompanyUserId(companyUserId);
|
|
|
|
|
+ error.setLink(link);
|
|
|
|
|
+ error.setErrorInfo(errorInfo);
|
|
|
|
|
+ error.setCreateTime(DateUtils.getNowDate());
|
|
|
|
|
+ error.setType(type);
|
|
|
|
|
+// FsCourseLink fsCourseLink = courseLinkMapper.selectFsCourseLinkByLink(link);
|
|
|
|
|
+//// if (fsCourseLink != null) {
|
|
|
|
|
+//// error.setQwUserId(fsCourseLink.getQwUserId());
|
|
|
|
|
+//// error.setQwExternalId(fsCourseLink.getQwExternalId());
|
|
|
|
|
+//// error.setCorpId(fsCourseLink.getCorpId());
|
|
|
|
|
+//// }
|
|
|
|
|
+ feishuLinkErrorMapper.insertFeishuLinkError(error);
|
|
|
|
|
+ log.info("飞书链接创建失败已记录,videoId={}, link={}, error={}", videoId, link, errorInfo);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
}
|
|
}
|