From 4aeafd0dbd4d90b2800322568991551689f377a7 Mon Sep 17 00:00:00 2001 From: YXY <932687738@qq.com> Date: Thu, 31 Aug 2023 21:46:45 +0800 Subject: [PATCH] =?UTF-8?q?=E7=95=85=E6=8D=B7=E9=80=9A=E6=8A=A5=E8=A1=A8?= =?UTF-8?q?=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ruoyi/common/enums/AppType.java | 1 + .../common/enums/TableRelationTypeEnum.java | 1 + .../quartz/domain/CJTRequestReportBody.java | 32 ++ .../ruoyi/quartz/domain/CJTResponseBody.java | 2 + .../quartz/domain/CJTResponseReportBody.java | 23 ++ .../domain/CJTResponseReportBodyDetail.java | 16 + .../CJTResponseReportBodyDetailRow.java | 20 + .../ruoyi/quartz/task/CJT/SyncReportJob.java | 372 ++++++++++++++++++ 8 files changed, 467 insertions(+) create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTRequestReportBody.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseReportBody.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseReportBodyDetail.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseReportBodyDetailRow.java create mode 100644 ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/CJT/SyncReportJob.java diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/AppType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/AppType.java index 6c79e1f..0b77ab6 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/enums/AppType.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/AppType.java @@ -20,6 +20,7 @@ public enum AppType { SYNC_CJT_SALE_DELIVERY_DATA_TO_MULTI_TABLE("SYNC_CJT_SALE_DELIVERY_DATA_TO_MULTI_TABLE", "同步CJT销货单数据到多维表格"), SYNC_CJT_SALE_ORDER_DATA_TO_MULTI_TABLE("SYNC_CJT_SALE_ORDER_DATA_TO_MULTI_TABLE", "同步CJT销售订单数据到多维表格"), SYNC_CJT_SALE_DISPATCH_DATA_TO_MULTI_TABLE("SYNC_CJT_SALE_DISPATCH_DATA_TO_MULTI_TABLE", "同步CJT销售出库单数据到多维表格"), + SYNC_CJT_REPORT_TO_MULTI_TABLE("SYNC_CJT_REPORT_TO_MULTI_TABLE", "同步CJT报表数据到多维表格"), SYNC_ODOO_DATA_TO_MULTI_TABLE("SYNC_ODOO_DATA_TO_MULTI_TABLE", "同步odoo数据到多维表格"); private final String code; diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/TableRelationTypeEnum.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/TableRelationTypeEnum.java index db507e5..2d3a0ea 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/enums/TableRelationTypeEnum.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/TableRelationTypeEnum.java @@ -23,6 +23,7 @@ public enum TableRelationTypeEnum { SYNC_CJT_SALE_DELIVERY("SYNC_CJT_SALE_DELIVERY", "同步畅捷通销货单"), SYNC_CJT_SALE_ORDER("SYNC_CJT_SALE_ORDER", "同步畅捷通销售订单"), SYNC_CJT_SALE_DISPATCH("SYNC_CJT_SALE_DISPATCH", "同步畅捷通销售出库单"), + SYNC_CJT_CURRENT_STOCK_REPORT_TO_MULTI_TABLE("ST_CurrentStockRpt", "同步畅捷通报表"), SYNC_ODOO_ACCOUNT_MOVE_LINE("SYNC_ODOO_ACCOUNT_MOVE_LINE", "每天晚上7点后更新"), SYNC_ODOO_PRODUCT_PRODUCT_ONE("SYNC_ODOO_PRODUCT_PRODUCT_ONE", "仓库id=1"), SYNC_ODOO_PRODUCT_PRODUCT_SIX("SYNC_ODOO_PRODUCT_PRODUCT_SIX", "仓库id=6"), diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTRequestReportBody.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTRequestReportBody.java new file mode 100644 index 0000000..5548004 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTRequestReportBody.java @@ -0,0 +1,32 @@ +package com.ruoyi.quartz.domain; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * https://open.chanjet.com/docs/file/apiFile/tcloud/t+bb/t+tybb + * @author yuxiangyong + * @create 2023-07-17 20:31 + */ +@Data +@NoArgsConstructor +public class CJTRequestReportBody { + private Integer PageIndex; + private Integer PageSize; + private String ReportName; + private String TaskSessionID; + private String queryFields; + + public CJTRequestReportBody(Integer pageIndex, Integer pageSize, String reportName,String queryFields) { + PageIndex = pageIndex; + PageSize = pageSize; + ReportName = reportName; + this.queryFields = queryFields; + } + + public void addPage(){ + this.PageIndex += 1; + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseBody.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseBody.java index b287b45..f49b533 100644 --- a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseBody.java +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseBody.java @@ -3,6 +3,8 @@ package com.ruoyi.quartz.domain; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + /** * @author yuxiangyong * @create 2023-07-17 20:31 diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseReportBody.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseReportBody.java new file mode 100644 index 0000000..a47b4ce --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseReportBody.java @@ -0,0 +1,23 @@ +package com.ruoyi.quartz.domain; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author yuxiangyong + * @create 2023-07-17 20:31 + */ +@Data +@NoArgsConstructor +public class CJTResponseReportBody { + private String ReportName; + private CJTResponseReportBodyDetail DataSource; + private Integer Pages; + private Integer PageIndex; + private Integer PageSize; + private Integer TotalRecords; + private String TaskSessionID; + private String ErrorMessage; + private String ErrorCode; + private Integer Status; +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseReportBodyDetail.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseReportBodyDetail.java new file mode 100644 index 0000000..304f424 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseReportBodyDetail.java @@ -0,0 +1,16 @@ +package com.ruoyi.quartz.domain; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author yuxiangyong + * @create 2023-07-17 20:31 + */ +@Data +@NoArgsConstructor +public class CJTResponseReportBodyDetail { + private List Rows; +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseReportBodyDetailRow.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseReportBodyDetailRow.java new file mode 100644 index 0000000..f60a737 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/CJTResponseReportBodyDetailRow.java @@ -0,0 +1,20 @@ +package com.ruoyi.quartz.domain; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author yuxiangyong + * @create 2023-07-17 20:31 + */ +@Data +@NoArgsConstructor +public class CJTResponseReportBodyDetailRow { + private String Warehouse; + private String IDWarehouse; + private String InventoryCode; + private String Inventory; + private String IDInventory; + private String BaseQuantity; + private String canuseBaseQuantity; +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/CJT/SyncReportJob.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/CJT/SyncReportJob.java new file mode 100644 index 0000000..8e0dafd --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/CJT/SyncReportJob.java @@ -0,0 +1,372 @@ +package com.ruoyi.quartz.task.CJT; + +import com.alibaba.fastjson.JSONObject; +import com.lark.oapi.service.bitable.v1.model.CreateAppTableRecordRespBody; +import com.ruoyi.common.constant.RedisConstants; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.enums.*; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.flyingbook.LarkHelper.LarkRobotHelper; +import com.ruoyi.flyingbook.LarkHelper.LarkTableHelper; +import com.ruoyi.flyingbook.domain.ErpLarkRelation; +import com.ruoyi.flyingbook.domain.LarkCompanyRelation; +import com.ruoyi.flyingbook.domain.LarkTableRelation; +import com.ruoyi.flyingbook.domain.lark.LarkTableRequest; +import com.ruoyi.flyingbook.mapper.ErpLarkRelationMapper; +import com.ruoyi.flyingbook.mapper.LarkCompanyRelationMapper; +import com.ruoyi.flyingbook.mapper.LarkTableRelationMapper; +import com.ruoyi.quartz.domain.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static com.ruoyi.quartz.helper.OkHttpHelper.post; + +/** + * 同步畅捷通账套信息 + * + * @author yuxiangyong + * @create 2023-07-17 20:29 + */ +@Slf4j +public abstract class SyncReportJob { + + @Autowired + private RedisCache redisCache; + @Autowired + protected ErpLarkRelationMapper erpLarkRelationMapper; + @Autowired + private LarkCompanyRelationMapper larkCompanyRelationMapper; + @Autowired + private LarkTableRelationMapper larkTableRelationMapper; + @Autowired + private LarkTableHelper larkTableHelper; + @Autowired + private LarkRobotHelper larkRobotHelper; + @Autowired + private CJTCreateLarkTableJob cjtCreateLarkTableJob; + @Value("${lark.robot.group}") + private String ROBOT_GROUP; + @Value("${sync.flag}") + private Boolean syncFlag; + /** + * 分页条数 + */ + protected static final Integer PAGE_SIZE = 200; + + /** + * 畅捷通域名 + */ + protected static final String REQUEST_ROOT_PATH = "https://openapi.chanjet.com"; + /** + * 重置畅捷通ticket的接口 + */ + private static final String REQUEST_RESET_TICKET_PATH = REQUEST_ROOT_PATH + "/auth/appTicket/resend"; + /** + * 生成调用畅捷通接口的token信息 + */ + private static final String REQUEST_GENERATE_TOKEN_PATH = REQUEST_ROOT_PATH + "/v1/common/auth/selfBuiltApp/generateToken"; + + + private CJTJobContext initContext(String cjt) { + CJTJobContext context = new CJTJobContext(); + context.setCjt(cjt); + return context; + } + + + public void executeSync(String cjt) { + if (Boolean.FALSE.equals(syncFlag)) { + return; + } + CJTJobContext context = initContext(cjt); + try { + log.info("===================== {} strat ======================", this.getClassName()); + //初始化飞书信息及相关配置 + initLarkInfo(context); + //重置ticket + resetTicket(context); + //执行分页同步 + sync(context); + } catch (Exception e) { + log.error("{} 执行失败", getClassName(), e); + larkRobotHelper.sendMessageByBot(ROBOT_GROUP, buildRobotErrorCountMessage(e)); + } finally { + log.info("===================== {} end ======================", this.getClassName()); + } + } + + protected CjtAccountEnum cjtAccount(String cjt) { + return CjtAccountEnum.valueOf(cjt); + } + + private String buildRobotErrorCountMessage(Exception e) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("同步报表任务", getClassName()); + String errorMessage = e.getMessage(); + if (org.apache.commons.lang3.StringUtils.isNotBlank(errorMessage)) { + errorMessage = errorMessage.replaceAll("\\\\", ""); + } + jsonObject.put("异常信息", errorMessage); + return jsonObject.toJSONString(); + } + +/**----------------------------可继承修改-----------------------------------*/ + /** + * 获取同步多维表格类型配置 + */ + protected AppType syncLarkAppType() { + return AppType.SYNC_CJT_REPORT_TO_MULTI_TABLE; + } + + ; + + protected TableRelationTypeEnum syncLarkType() { + return TableRelationTypeEnum.SYNC_CJT_CURRENT_STOCK_REPORT_TO_MULTI_TABLE; + } + + ; + + /** + * 获取请求url的地址 + */ + protected String getRequestUrl() { + return REQUEST_ROOT_PATH + "/tplus/api/v2/reportQuery/GetReportData"; + } + + /** + * 获取请求url的地址 + */ + protected String getReportName() { + return "ST_CurrentStockRpt"; + } + + /** + * 获取待查询的字段 + */ + protected String getQueryFields(){ + return "Warehouse,InventoryCode,Inventory,BaseQuantity,canuseBaseQuantity"; + }; + + + /** + * 获取查询条件 + */ + protected abstract String getQueryKey(); + + /** + * 获取当前执行类名 + */ + protected String getClassName() { + return this.getClass().getSimpleName(); + } + + ; + + /** + * 同步数据 + * + * @param context + */ + protected void sync(CJTJobContext context) { + String errorMessage = null; + + JSONObject cjtRequestReport = new JSONObject(); + CJTRequestReportBody cjtRequestBody = new CJTRequestReportBody(0, PAGE_SIZE, getReportName(),getQueryFields()); + cjtRequestReport.put("request", cjtRequestBody); + CJTRequest cjtRequest = buildCJTRequest(context, cjtRequestReport); + + LarkCompanyRelation companyRelation = context.getCompanyRelation(); + LarkTableRelation tableRelation = context.getTableRelation(); + LarkTableRequest addRecordRequest = new LarkTableRequest(companyRelation.getAppId(), companyRelation.getSecret(), tableRelation.getToAppToken(), tableRelation.getToTableId()); + List rows = new ArrayList<>(); + do { + try { + cjtRequest.setOpenToken(generateToken(context)); + //请求接口并序列化数据 + CJTResponseReportBodyDetail bodyDetail = request(cjtRequest); + //实际返回数据 + rows = bodyDetail.getRows(); + if (!CollectionUtils.isEmpty(rows)) { + //批量同步飞书 + List errorCodeList = syncLarkBatch(rows, addRecordRequest, context.getCjt()); + if (!CollectionUtils.isEmpty(errorCodeList)) { + String errorKey = String.join(",", errorCodeList); + throw new RuntimeException(String.format("存在同步失败的记录 %s", errorKey)); + } + } + } catch (Exception e) { + log.error("{} exception", this.getClassName(), e); + errorMessage = buildErrorBody(cjtRequest, e.getMessage()); + } finally { + cjtRequestBody.addPage(); + } + } while (!CollectionUtils.isEmpty(rows)); + if (!StringUtils.isEmpty(errorMessage)) { + throw new RuntimeException(errorMessage); + } + } + + + private Object changeValueType(String value, CJTSyncTypeRelation cjtSyncTypeRelation) { + TableFieldTypeEnum type = cjtSyncTypeRelation.getType(); + if (org.apache.commons.lang3.StringUtils.isBlank(value) || type == null) { + return value; + } + + switch (type) { + case NUMBER: + return Double.valueOf(value); + case DATE: + LocalDateTime localDateTime = DateUtils.str2ldt(value, cjtSyncTypeRelation.getPattern()); + return DateUtils.ldt2Long(localDateTime); + default: + return value; + } + } + + protected Map buildLarkBody(CJTResponseReportBodyDetailRow rowDetail){ + Map body = new HashMap<>(); + body.put("仓库",rowDetail.getWarehouse()); + body.put("存货编码",rowDetail.getInventoryCode()); + body.put("现存量",rowDetail.getBaseQuantity()); + body.put("可用量",rowDetail.getCanuseBaseQuantity()); + return body; + } + + /** + * 执行字段映射 + */ + protected List syncLarkBatch(List rows, LarkTableRequest addRecordRequest, String cjt) { + //错误唯一键 + List errorKey = new ArrayList<>(); + for (CJTResponseReportBodyDetailRow row : rows) { + try { + Map body = buildLarkBody(row); + addRecordRequest.setBody(body); + //在飞书创建一行,并根据创建返回的行id在本地保留一条映射纪律 + larkTableHelper.addTableRecord(addRecordRequest); + } catch (Exception e) { + log.error("{} addOrUpdate exception", this.getClassName(), e); + } + } + return errorKey; + } + + + private String buildCacheUniqueKey(String key, String appKey) { + return String.format("%s:%s", key, appKey); + } + + /** + * 获取ticket,失效前重置 + */ + private void resetTicket(CJTJobContext context) { + String ticketCacheKey = buildCacheUniqueKey(RedisConstants.CJT_TICKET_CACHE_KEY, context.getAppKey()); + Long expireTime = redisCache.getExpireTime(ticketCacheKey); + //重置ticket存在响应时间,提前十秒重置 + if (expireTime == null || expireTime < 10) { + log.info("重置ticket"); + CJTRequest cjtRequest = new CJTRequest(REQUEST_RESET_TICKET_PATH + , context.getAppKey() + , context.getAppSecret()); + post(cjtRequest); + } + try { + Thread.sleep(1000); + } catch (Exception e) { + e.printStackTrace(); + } + String ticket = (String) redisCache.getCacheObject(ticketCacheKey); + context.setTicket(ticket); + } + + /** + * 生成调用接口的token信息 + */ + private String generateToken(CJTJobContext context) { + String tokenCacheKey = buildCacheUniqueKey(RedisConstants.CJT_TOKEN_CACHE_KEY, context.getAppKey()); + String openToken = (String) redisCache.getCacheObject(tokenCacheKey); + if (StringUtils.isEmpty(openToken)) { + CJTRequest cjtRequest = new CJTRequest(REQUEST_GENERATE_TOKEN_PATH + , context.getAppKey() + , context.getAppSecret() + , context.getTicket() + , context.getCertificate()); + cjtRequest.buildGenerateBody(); + JSONObject body = JSONObject.parseObject(post(cjtRequest)); + JSONObject value = body.getJSONObject("value"); + String token = value.getString("accessToken"); + context.setOpenToken(token); + redisCache.setCacheObject(tokenCacheKey, token, 5, TimeUnit.MINUTES); + return token; + } else { + context.setOpenToken(openToken); + return openToken; + } + } + + /** + * 生成调用接口的token信息 + */ + private void initLarkInfo(CJTJobContext context) { + CjtAccountEnum cjtAccountEnum = cjtAccount(context.getCjt()); + LarkCompanyRelation companyRelationQuery = new LarkCompanyRelation(); + companyRelationQuery.setAppType(syncLarkAppType().getCode()); + companyRelationQuery.setFlag(FlagStatus.OK.getCode()); + companyRelationQuery.setRemark(cjtAccountEnum.getAppKey()); + List larkCompanyRelations = larkCompanyRelationMapper.selectLarkCompanyRelationList(companyRelationQuery); + LarkCompanyRelation companyRelation = larkCompanyRelations.get(0); + context.setCompanyRelation(companyRelation); + LarkTableRelation tableRelationQuery = new LarkTableRelation(); + tableRelationQuery.setLarkCompanyRelationId(companyRelation.getId()); + tableRelationQuery.setFlag(FlagStatus.OK.getCode()); + tableRelationQuery.setRelationType(syncLarkType().getCode()); + List larkTableRelations = larkTableRelationMapper.selectLarkTableRelationList(tableRelationQuery); + LarkTableRelation tableRelation = larkTableRelations.get(0); + context.setTableRelation(tableRelation); + context.setAppKey(cjtAccountEnum.getAppKey()); + context.setAppSecret(cjtAccountEnum.getAppSecret()); + context.setCertificate(cjtAccountEnum.getCertificate()); + } + + /** + * 构建畅捷通请求对象 + */ + protected CJTRequest buildCJTRequest(CJTJobContext context, JSONObject cjtRequestReport) { + CJTRequest req = new CJTRequest(getRequestUrl(), context.getAppKey(), context.getAppSecret(), null); + req.setJsonBody(cjtRequestReport); + return req; + } + + + /** + * 请求接口并序列化返回对象 + */ + protected CJTResponseReportBodyDetail request(CJTRequest req) { + String post = post(req); + CJTResponseReportBody responseBody = JSONObject.parseObject(post, CJTResponseReportBody.class); + if (!"0".equals(responseBody.getStatus())) { + log.error("{} exception", this.getClassName(), responseBody.getErrorMessage()); + throw new RuntimeException(buildErrorBody(req, responseBody.getErrorMessage())); + } + return responseBody.getDataSource(); + } + + protected String buildErrorBody(CJTRequest req, String errorMessage) { + JSONObject errorInfo = new JSONObject(); + errorInfo.put("url", req.getUrl()); + errorInfo.put("body", JSONObject.toJSONString(req.getBody())); + errorInfo.put("errorMessage", errorMessage); + return errorInfo.toJSONString(); + } +}