feat(zanhuo): 添加用友云套件接口相关功能- 新增 EventType 枚举类,定义了各种事件类型

- 添加 YsConstant 类,包含用友云套件的常量配置
- 新增多个 Yonsuite 相关的 DTO 类,用于处理销售订单、支付计划等数据
- 实现了获取 Access Token 和调用业务接口的工具类
- 在 TocSalesSettlement 类中集成了 CallYsBusinessApi 服务
This commit is contained in:
liuy 2025-09-03 10:07:38 +08:00
parent 2cc010b3d9
commit d127e534f8
23 changed files with 1602 additions and 0 deletions

View File

@ -0,0 +1,245 @@
package com.hzya.frame.plugin.zanhuo.constant;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.constant
* @ProjectkangarooDataCenterV3
* @nameEventType
* @Date2025/9/2 17:37
* @FilenameEventType
*/
public enum EventType {
/**
* 检查回调地址有效性
*/
CHECK_URL,
/**
* 员工增加
*/
STAFF_ADD,
/**
* 员工更改
*/
STAFF_UPDATE,
/**
* 员工启用
*/
STAFF_ENABLE,
/**
* 员工停用
*/
STAFF_DISABLE,
/**
* 员工删除
*/
STAFF_DELETE,
/**
* 部门创建
*/
DEPT_ADD,
/**
* 部门修改
*/
DEPT_UPDATE,
/**
* 部门启用
*/
DEPT_ENABLE,
/**
* 部门停用
*/
DEPT_DISABLE,
/**
* 部门删除
*/
DEPT_DELETE,
/**
* 用户增加
*/
USER_ADD,
/**
* 用户删除
*/
USER_DELETE,
/**
* 用户启用
*/
USER_ENABLE,
/**
* 用户停用
*/
USER_DISABLE,
/**
* 用户注销事件
*/
DELETE_DIWORKSESSION,
/**
* 元数据修改
*/
META_ALTER,
/**
* UNKNOWN
**/
UNKNOWN,
/**
* 任职增加
*/
JOBINFO_ADD,
/**
* 任职更新
*/
JOBINFO_UPDATE,
/**
* 物料档案新增
*/
YXYBASEDOC_PC_PRODUCT_INSERT,
/**
* 物料档案修改
*/
YXYBASEDOC_PC_PRODUCT_UPDATE,
/**
* 物料档案删除
*/
YXYBASEDOC_PC_PRODUCT_DELETE,
/**
* 物料分类新增
*/
YXYBASEDOC_PC_MANAGEMENTCLASS_INSERT,
/**
* 物料分类修改
*/
YXYBASEDOC_PC_MANAGEMENTCLASS_UPDATE,
/**
* 物料分类删除
*/
YXYBASEDOC_PC_MANAGEMENTCLASS_DELETE,
/**
* 计量单位新增
*/
YXYBASEDOC_PC_UNIT_INSERT,
/**
* 计量单位修改
*/
YXYBASEDOC_PC_UNIT_UPDATE,
/**
* 计量单位删除
*/
YXYBASEDOC_PC_UNIT_DELETE,
/**
* SKU属性新增
*/
YXYBASEDOC_PC_PRODUCTSKUPRO_INSERT,
/**
* SKU属性修改
*/
YXYBASEDOC_PC_PRODUCTSKUPRO_UPDATE,
/**
* SKU属性删除
*/
YXYBASEDOC_PC_PRODUCTSKUPRO_DELETE,
/**
* 物料属性新增
*/
YXYBASEDOC_PC_PRODUCTPRO_INSERT,
/**
* 物料属性修改
*/
YXYBASEDOC_PC_PRODUCTPRO_UPDATE,
/**
* 物料属性删除
*/
YXYBASEDOC_PC_PRODUCTPRO_DELETE,
/**
* 仓库新增
*/
YXYBASEDOC_AA_WAREHOUSE_INSERT,
/**
* 仓库修改
*/
YXYBASEDOC_AA_WAREHOUSE_UPDATE,
/**
* 仓库删除
*/
YXYBASEDOC_AA_WAREHOUSE_DELETE,
/**
* 客户档案新增
*/
YXYBASEDOC_AA_MERCHANT_INSERT,
/**
* 客户档案修改
*/
YXYBASEDOC_AA_MERCHANT_UPDATE,
/**
* 客户档案删除
*/
YXYBASEDOC_AA_MERCHANT_DELETE,
/**
* 物料规格新增
*/
YXYBASEDOC_PC_SPEPRO_INSERT,
/**
* 物料规格修改
*/
YXYBASEDOC_PC_SPEPRO_UPDATE,
/**
* 物料规格删除
*/
YXYBASEDOC_PC_SPEPRO_DELETE,
/**
* 物料新增-old
*/
YXYBASEDOC_ADD_NOTIFY,
/**
* 物料修改-old
*/
YXYBASEDOC_UPDATE_NOTIFY,
/**
* 物料删除-old
*/
YXYBASEDOC_DELETE_NOTIFY;
}

View File

@ -0,0 +1,44 @@
package com.hzya.frame.plugin.zanhuo.constant;
/**
* YS常量
*
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.constant
* @ProjectkangarooDataCenterV3
* @nameYsConstant
* @Date2025/9/2 17:15
* @FilenameYsConstant
*/
public class YsConstant {
/**
* 赞货租户id
*/
public static String tenantId = "ipi140f4";
/**
* 赞货中台appKey
*/
public static String appKey = "c9d8c213f65e488facb03170abb7f9be";
/**
* 赞货应用AppSecret
*/
public static String appSecret = "db86b3a6f4248ecde1f5b3e8ad521b0a4e3c075e";
/**
* 获取租户所在数据中心域名对应的地址
*/
public static String openApiUrl = "https://apigateway.yonyoucloud.com";
/**
* 租户所在数据中心的核心网关域名调用业务接口时需要拼接核心网关域名和接口相对路径
*/
public static String gatewayUrl = "https://c4.yonyoucloud.com/iuap-api-gateway";
/**
* 租户所在数据中心的auth域名调用获取token接口时需要拼接auth域名和获取token接口相对路径
*/
public static String tokenUrl = "https://c4.yonyoucloud.com/iuap-api-auth";
}

View File

@ -11,6 +11,7 @@ import com.hzya.frame.plugin.zanhuo.dto.jackyun.datadto.salessettlement.TradeSet
import com.hzya.frame.plugin.zanhuo.jackyun.JackyunApi;
import com.hzya.frame.plugin.zanhuo.util.SplitListByCountUtil;
import com.hzya.frame.plugin.zanhuo.util.jackyun.JackYunApiUtil;
import com.hzya.frame.plugin.zanhuo.yonsuite.util.CallYsBusinessApi;
import com.hzya.frame.web.entity.JsonResultEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -46,6 +47,9 @@ public class TocSalesSettlement extends PluginBaseEntity {
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private CallYsBusinessApi callYsBusinessApi;
@Override
public void initialize() {
logger.info(getPluginLabel() + "执行初始化方法initialize()");

View File

@ -0,0 +1,17 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.dto.salesorder;
import lombok.Data;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.dto.salesorder
* @ProjectkangarooDataCenterV3
* @nameYsOrderDefineCharacter
* @Date2025/9/2 15:54
* @FilenameYsOrderDefineCharacter
*/
@Data
public class YsOrderDefineCharacter {
private String YWY;
private String id;
}

View File

@ -0,0 +1,127 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.dto.salesorder;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.dto.salesorder
* @ProjectkangarooDataCenterV3
* @nameYsOrderDetails
* @Date2025/9/2 15:55
* @FilenameYsOrderDetails
*/
@Data
public class YsOrderDetails {
private String id;
private String stockId;
@JsonProperty("orderDetailPrices!discountRate")
private String orderDetailPrices_discountRate;
private String costCenter_code;
private String providerProfitCenter_code;
private String consumerProfitCenter_code;
private String taxIssuedDiscountline;
private String isExpiryDateManage;
private String bizId;
@JsonProperty("orderDetailPrices!particularlyMoneyDomesticTaxfree")
private String orderDetailPrices_particularlyMoneyDomesticTaxfree;
private String isSkuReserve;
@JsonProperty("orderDetailPrices!natSum")
private String orderDetailPrices_natSum;
@JsonProperty("orderDetailPrices!natMoney")
private String orderDetailPrices_natMoney;
private String productId;
@JsonProperty("orderDetailPrices!orderDetailId")
private String orderDetailPrices_orderDetailId;
private String pointsMoney;
private String promotionMoney;
@JsonProperty("orderDetailPrices!saleCost_domestic_taxfree")
private String orderDetailPrices_saleCost_domestic_taxfree;
private String productId_priceListFlag;
private String masterUnitId;
private String cusDiscountMoney;
private String invExchRate;
private String cusDiscountRate;
private String unitExchangeTypePrice;
@JsonProperty("orderDetailPrices!saleCost_orig_taxfree")
private String orderDetailPrices_saleCost_orig_taxfree;
@JsonProperty("orderDetailPrices!oriTax")
private String orderDetailPrices_oriTax;
private String particularlyMoney;
private String iProductAuxUnitId;
@JsonProperty("orderDetailPrices!particularlyMoneyOrigTaxfree")
private String orderDetailPrices_particularlyMoneyOrigTaxfree;
@JsonProperty("orderDetailPrices!natUnitPrice")
private String orderDetailPrices_natUnitPrice;
@JsonProperty("orderDetailPrices!saleCost_domestic")
private String orderDetailPrices_saleCost_domestic;
private String invPriceExchRate;
private String isBatchManage;
@JsonProperty("orderDetailPrices!isDeleted")
private String orderDetailPrices_isDeleted;
private String oriSum;
private String noTaxSalePrice;
private String salePrice;
@JsonProperty("orderDetailPrices!oriMoney")
private String orderDetailPrices_oriMoney;
private String lowestPrice;
private String priceQty;
private String stockOrgId;
private String prodPrice;
private String iProductUnitId;
@JsonProperty("orderDetailPrices!natTaxUnitPrice")
private String orderDetailPrices_natTaxUnitPrice;
private String priceMark;
private String orderProductType;
private String subQty;
private String consignTime;
private String skuId;
private String ordRealMoney;
@JsonProperty("orderDetailPrices!salePrice_domestic")
private String orderDetailPrices_salePrice_domestic;
private String marketPricie;
private String taxId;
private String qty;
private String realProductCode;
private String settlementOrgId;
private String oriTaxUnitPrice;
@JsonProperty("orderDetailPrices!lineDiscountMoney")
private String orderDetailPrices_lineDiscountMoney;
@JsonProperty("orderDetailPrices!natTax")
private String orderDetailPrices_natTax;
private String isreserve;
private String prodCost;
private String productId_pbatchName;
private String specDescription;
@JsonProperty("orderDetailPrices!salePrice_domestic_taxfree")
private String orderDetailPrices_salePrice_domestic_taxfree;
private String unitExchangeType;
private String noTaxSaleCost;
private String saleCost;
@JsonProperty("orderDetailPrices!oriUnitPrice")
private String orderDetailPrices_oriUnitPrice;
private String salesOrgId;
private String orderRebateMoney;
private String cashRebateMoney;
private String _status;
private String orderProductApportionName;
private String memo;
private String projectId_code;
private String isAdvRecInv;
@JsonProperty("orderDetailPrices!prepayInvRvnRecogBkgMeth")
private String orderDetailPrices_prepayInvRvnRecogBkgMeth;
// private String orderDetailDefineCharacter;
// private String voucher.order.OrderDetail;
// private String orderDetailCharacteristics;
// private String voucher.order.OrderDetail;
@JsonProperty("orderDetailPrices!checkByRevenueManagement")
private String orderDetailPrices_checkByRevenueManagement;
@JsonProperty("orderDetailPrices!serviceStartDate")
private String orderDetailPrices_serviceStartDate;
@JsonProperty("orderDetailPrices!serviceEndDate")
private String orderDetailPrices_serviceEndDate;
@JsonProperty(" orderDetailPrices!calBase")
private String orderDetailPrices_calBase;
private String batchId;
private String batchNo;
}

View File

@ -0,0 +1,30 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.dto.salesorder;
import lombok.Data;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.dto.salesorder
* @ProjectkangarooDataCenterV3
* @nameYsPaymentSchedules
* @Date2025/9/2 16:37
* @FilenameYsPaymentSchedules
*/
@Data
public class YsPaymentSchedules {
private String startDateTime;
private String fixAccoutDateTime;
private String expiringDateTime;
private String number;
private String name;
private String payRatio;
private String startDate;
private String startName;
private String accountType;
private String accountUnit;
private String accountDay;
private String currencyPk;
private String rate;
private String natCurrencyPk;
private String amount;
}

View File

@ -0,0 +1,118 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.dto.salesorder;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.dto.salesorder
* @ProjectkangarooDataCenterV3
* @nameYsSaleOrder
* @Date2025/9/2 15:27
* @FilenameYsSaleOrder
*/
@Data
public class YsSaleOrder {
private String resubmitCheckKey;
private String salesOrgId;
private String transactionTypeId;
private String vouchdate;
private String code;
private String agentId;
private String corpContact;
private String saleDepartmentId;
private String retailInvestors;
private String retailAgentName;
private String receiveContacter;
private String receiveContacterPhone;
private String settlementOrgId;
private String hopeReceiveDate;
private String sendDate;
private String source;
private String orderDate;
private String bizId;
private String agentRelationId;
private String invoiceMoney;
private String version;
private String isWfControlled;
@JsonProperty("orderPrices!currency")
private String orderPrices_currency;
@JsonProperty("orderPrices!signFileId")
private String orderPrices_signFileId;
@JsonProperty("orderPrices!exchRate")
private String orderPrices_exchRate;
@JsonProperty("orderPrices!exchangeRateType")
private String orderPrices_exchangeRateType;
@JsonProperty("orderPrices!natCurrency")
private String orderPrices_natCurrency;
@JsonProperty("orderPrices!taxInclusive;")
private String orderPrices_taxInclusive;
private String creditBalance;
private String agentTaxItem;
private String lockIn;
private String bmake_voucher_delivery;
private String bizFlow;
private String bizFlow_version;
private String isFlowCoreBill;
private String rebateValidFlag;
private String id;
private String receiveId;
private String receiveZipCode;
private String receiveTelePhone;
private String receiver;
private String receiveMobile;
private String receivecode;
private String receiveAddress;
private String orderPayType;
private String settlement;
private String shippingChoiceId;
private String invoiceAgentId;
private String tradeRouteID;
private String modifyInvoiceType;
private String bdInvoiceTypeId;
private String invoiceUpcType;
private String invoiceTitle;
private String invoiceTitleType;
private String bankName;
private String receievInvoiceMobile;
private String receievInvoiceEmail;
private String taxNum;
private String subBankName;
private String bankAccount;
private String invoiceTelephone;
private String invoiceAddress;
private String promotionMoney;
private String rebateMoney;
private String rebateCashMoney;
private String pointsMoney;
private String reight;
private String payMoney;
private String orderPayMoney;
private String realMoney;
private String orderRealMoney;
@JsonProperty("orderPrices!totalMoneyOrigTaxfree")
private String orderPrices_totalMoneyOrigTaxfree;
@JsonProperty("orderPrices!orderPayMoneyOrigTaxfree")
private String orderPrices_orderPayMoneyOrigTaxfree;
@JsonProperty("orderPrices!orderPayMoneyDomestic")
private String orderPrices_orderPayMoneyDomestic;
@JsonProperty("orderPrices!orderPayMoneyDomesticTaxfree")
private String orderPrices_orderPayMoneyDomesticTaxfree;
@JsonProperty("orderPrices!orderId")
private String orderPrices_orderId;
@JsonProperty("orderPrices!wholeDiscountRate")
private String orderPrices_wholeDiscountRate;
@JsonProperty("orderPrices!discountAfterMoney")
private String orderPrices_discountAfterMoney;
@JsonProperty("orderPrices!discountMoney")
private String orderPrices_discountMoney;
private YsOrderDefineCharacter orderDefineCharacter;
// private String voucher.order.Order;
private YsOrderDetails orderDetails;
private YsPaymentSchedules paymentSchedules;
private String _status;
private String memo;
private String opposeMemo;
private YsSignSubjects signSubjects;
}

View File

@ -0,0 +1,30 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.dto.salesorder;
import lombok.Data;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.dto.salesorder
* @ProjectkangarooDataCenterV3
* @nameYsSignSubjects
* @Date2025/9/2 16:43
* @FilenameYsSignSubjects
*/
@Data
public class YsSignSubjects {
private String id;
private String mainid;
private String signOrder;
private String signSubjectType;
private String signOrgId;
private String signAgentId;
private String address;
private String postalCode;
private String telephone;
private String facsimile;
private String bankOfDeposit;
private String bankAccount;
private String memo;
private String isOriginator;
private String isDefault;
}

View File

@ -0,0 +1,40 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.dto.token;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.dto.token
* @ProjectkangarooDataCenterV3
* @nameAccessTokenResponse
* @Date2025/9/2 17:26
* @FilenameAccessTokenResponse
*/
public class AccessTokenResponse {
/**
* 获取的访问令牌 access_token
*/
@JsonProperty(value = "access_token")
private String accessToken;
/**
* 访问令牌的过期时间单位秒
*/
private long expire;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public long getExpire() {
return expire;
}
public void setExpire(long expire) {
this.expire = expire;
}
}

View File

@ -0,0 +1,32 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.dto.token;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.dto.token
* @ProjectkangarooDataCenterV3
* @nameDcUrlResult
* @Date2025/9/2 17:27
* @FilenameDcUrlResult
*/
public class DcUrlResult {
private String gatewayUrl;
private String tokenUrl;
public String getGatewayUrl() {
return gatewayUrl;
}
public void setGatewayUrl(String gatewayUrl) {
this.gatewayUrl = gatewayUrl;
}
public String getTokenUrl() {
return tokenUrl;
}
public void setTokenUrl(String tokenUrl) {
this.tokenUrl = tokenUrl;
}
}

View File

@ -0,0 +1,123 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.dto.token;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.hzya.frame.plugin.zanhuo.constant.EventType;
import java.io.Serializable;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.dto.token
* @ProjectkangarooDataCenterV3
* @nameEventContent
* @Date2025/9/2 17:27
* @FilenameEventContent
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class EventContent implements Serializable {
/**
* 事件类型
**/
private EventType type;
/**
* 事件唯一的业务 uuid
**/
private String eventId;
/**
* 事件创建的 unix 时间戳
**/
private long timestamp;
/**
* 事件涉及的租户 id
**/
private String tenantId;
/**
* 变动的 staff id
**/
private String[] staffId;
/**
* 变动的 dept id
**/
private String[] deptId;
/**
* 变动的 user id
*/
private String[] userId;
/**
* 附加消息内容一般是json字符串下发相关额外事件参数
*/
private String content;
public EventType getType() {
return type;
}
public void setType(EventType type) {
this.type = type;
}
public String getEventId() {
return eventId;
}
public void setEventId(String eventId) {
this.eventId = eventId;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
public String[] getStaffId() {
return staffId;
}
public void setStaffId(String[] staffId) {
this.staffId = staffId;
}
public String[] getDeptId() {
return deptId;
}
public void setDeptId(String[] deptId) {
this.deptId = deptId;
}
public String[] getUserId() {
return userId;
}
public void setUserId(String[] userId) {
this.userId = userId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}

View File

@ -0,0 +1,51 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.dto.token;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.dto.token
* @ProjectkangarooDataCenterV3
* @nameGenericResponse
* @Date2025/9/2 17:28
* @FilenameGenericResponse
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class GenericResponse<T> {
public static final String SUCCESS_CODE = "00000";
private String code;
private String message;
private T data;
public boolean isSuccess() {
return SUCCESS_CODE.equals(code);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}

View File

@ -0,0 +1,35 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.dto.token;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.dto.token
* @ProjectkangarooDataCenterV3
* @nameRefreshTokenResponse
* @Date2025/9/2 17:36
* @FilenameRefreshTokenResponse
*/
public class RefreshTokenResponse {
@JsonProperty("user_access_token")
private String userAccessToken;
private long expired;
public String getUserAccessToken() {
return userAccessToken;
}
public void setUserAccessToken(String userAccessToken) {
this.userAccessToken = userAccessToken;
}
public long getExpired() {
return expired;
}
public void setExpired(long expired) {
this.expired = expired;
}
}

View File

@ -0,0 +1,47 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.dto.token;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.dto.token
* @ProjectkangarooDataCenterV3
* @nameSuite
* @Date2025/9/2 17:30
* @FilenameSuite
*/
public class Suite {
/**
* 套件 key
*/
private String suiteKey;
private String suiteSecret;
/**
* 推送的套件票据
*/
private String suiteTicket;
public String getSuiteKey() {
return suiteKey;
}
public void setSuiteKey(String suiteKey) {
this.suiteKey = suiteKey;
}
public String getSuiteTicket() {
return suiteTicket;
}
public void setSuiteTicket(String suiteTicket) {
this.suiteTicket = suiteTicket;
}
public String getSuiteSecret() {
return suiteSecret;
}
public void setSuiteSecret(String suiteSecret) {
this.suiteSecret = suiteSecret;
}
}

View File

@ -0,0 +1,74 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.dto.token;
import java.time.Instant;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.dto.token
* @ProjectkangarooDataCenterV3
* @nameSuiteAuth
* @Date2025/9/2 17:32
* @FilenameSuiteAuth
*/
/**
* 记录套件租户的授权信息
*/
public class SuiteAuth {
/**
* 授权的套件
*/
private String suiteKey;
/**
* 授权的租户 id
*/
private String tenantId;
/**
* 该租户对应的访问令牌
*/
private String accessToken;
/**
* 访问令牌过期时间点
*/
private Instant expireAt;
public void updateAccessToken(AccessTokenResponse response) {
this.accessToken = response.getAccessToken();
this.expireAt = Instant.now().plusSeconds(response.getExpire());
}
public String getSuiteKey() {
return suiteKey;
}
public void setSuiteKey(String suiteKey) {
this.suiteKey = suiteKey;
}
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public Instant getExpireAt() {
return expireAt;
}
public void setExpireAt(Instant expireAt) {
this.expireAt = expireAt;
}
}

View File

@ -0,0 +1,27 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.dto.token;
import lombok.Data;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.dto.token
* @ProjectkangarooDataCenterV3
* @nameTokenUrlDto
* @Date2025/9/3 10:00
* @FilenameTokenUrlDto
*/
@Data
public class TokenUrlDto {
private String accessToken;
private String gatewayUrl;
private String tokenUrl;
public TokenUrlDto() {
}
public TokenUrlDto(String accessToken, String gatewayUrl, String tokenUrl) {
this.accessToken = accessToken;
this.gatewayUrl = gatewayUrl;
this.tokenUrl = tokenUrl;
}
}

View File

@ -0,0 +1,74 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.dto.token;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.dto.token
* @ProjectkangarooDataCenterV3
* @nameUserTokenResponse
* @Date2025/9/2 17:32
* @FilenameUserTokenResponse
*/
public class UserTokenResponse {
@JsonProperty("user_access_token")
private String userAccessToken;
@JsonProperty("token_type")
private String tokenType;
@JsonProperty("refresh_token")
private String refreshToken;
@JsonProperty("expires_in")
private long expiresIn;
@JsonProperty("id_token")
private String idToken;
public String getUserAccessToken() {
return userAccessToken;
}
public UserTokenResponse setUserAccessToken(String userAccessToken) {
this.userAccessToken = userAccessToken;
return this;
}
public String getTokenType() {
return tokenType;
}
public UserTokenResponse setTokenType(String tokenType) {
this.tokenType = tokenType;
return this;
}
public String getRefreshToken() {
return refreshToken;
}
public UserTokenResponse setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
return this;
}
public long getExpiresIn() {
return expiresIn;
}
public UserTokenResponse setExpiresIn(long expiresIn) {
this.expiresIn = expiresIn;
return this;
}
public String getIdToken() {
return idToken;
}
public UserTokenResponse setIdToken(String idToken) {
this.idToken = idToken;
return this;
}
}

View File

@ -0,0 +1,62 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.util;
import cn.hutool.core.lang.Assert;
import com.hzya.frame.plugin.zanhuo.yonsuite.dto.token.TokenUrlDto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* 调用YS业务接口
*
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.util
* @ProjectkangarooDataCenterV3
* @nameCallYsBusinessApi
* @Date2025/9/3 09:25
* @FilenameCallYsBusinessApi
*/
@Component
public class CallYsBusinessApi {
Logger logger = LoggerFactory.getLogger(CallYsBusinessApi.class);
@Autowired
private YonSuiteTokenManager tokenManager;
/**
* 调用YS业务接口 (POST JSON)
*
* @param requestUrl 接口的URL (不包含查询参数)
* @param businessJsonData 业务数据的JSON字符串
* @return 接口返回的原始字符串
*/
public String callYsBusinessApi(String requestUrl, String businessJsonData) {
String result = null;
try {
Assert.notNull(requestUrl, "requestUrl不能为空");
Assert.notNull(businessJsonData, "businessJsonData不能为空");
TokenUrlDto validAccessToken = tokenManager.getValidAccessToken();
String accessToken = validAccessToken.getAccessToken();
String gatewayUrl = validAccessToken.getGatewayUrl();
Map<String, String> queryParams = new HashMap<>();
queryParams.put("access_token", accessToken);
// String requestBody = String.format("{\"data\": %s}", businessJsonData);
// 4. 使用RequestTool发起POST请求
logger.info("向YS业务接口发起请求, URL: {}, Body: {}", gatewayUrl + requestUrl, businessJsonData);
result = RequestTool.doPost(gatewayUrl + requestUrl, queryParams, businessJsonData);
logger.info("YS业务接口响应: {}", result);
} catch (Exception e) {
logger.error("调用YS业务接口异常, URL: " + requestUrl, e);
throw new RuntimeException("调用YS业务接口异常", e);
}
return result;
}
}

View File

@ -0,0 +1,78 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.util;
import com.hzya.frame.plugin.zanhuo.constant.YsConstant;
import com.hzya.frame.plugin.zanhuo.yonsuite.dto.token.AccessTokenResponse;
import com.hzya.frame.plugin.zanhuo.yonsuite.dto.token.DcUrlResult;
import com.hzya.frame.plugin.zanhuo.yonsuite.dto.token.GenericResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.HashMap;
import java.util.Map;
/**
* YS接口获取类
*
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.util
* @ProjectkangarooDataCenterV3
* @namePullYsData
* @Date2025/9/2 16:47
* @FilenamePullYsData
*/
@Component
public class GetYsToken {
Logger logger = LoggerFactory.getLogger(GetYsToken.class);
/**
* 获取aaccess_token
*
* @author liuyang
*/
public AccessTokenResponse getAccessTokenV2(DcUrlResult dcUrl) throws Exception {
// DcUrlResult dcUrl = getGateway();
String tokenUrl = dcUrl.getTokenUrl();
Map<String, String> params = new HashMap<>();
// 除签名外的其他参数
params.put("appKey", YsConstant.appKey);
params.put("timestamp", String.valueOf(System.currentTimeMillis()));
// 计算签名
String signature = SignHelper.sign(params, YsConstant.appSecret);
params.put("signature", signature);
// 请求
String requestUrl = tokenUrl + "/open-auth/selfAppAuth/getAccessToken";
GenericResponse<AccessTokenResponse> response = RequestTool.doGet(requestUrl, params, new TypeReference<GenericResponse<AccessTokenResponse>>() {
});
if (response.isSuccess()) {
return response.getData();
}
logger.error("请求开放平台接口失败code: {}, message: {}", response.getCode(), response.getMessage());
throw new RuntimeException("请求开放平台接口失败, code: " + response.getCode() + ", message: " + response.getMessage());
}
/**
* 获取租户所在数据中心域名
*
* @author liuyang
*/
public DcUrlResult getGateway() throws Exception {
try {
Map<String, String> params = new HashMap<>();
params.put("tenantId", YsConstant.tenantId);
String requestUrl = YsConstant.openApiUrl + "/open-auth/dataCenter/getGatewayAddress";
GenericResponse<DcUrlResult> response = RequestTool.doGet(requestUrl, params, new TypeReference<GenericResponse<DcUrlResult>>() {
});
return response.getData();
} catch (Exception e) {
logger.error("getGateway方法抛出异常", e);
throw new RuntimeException("getGateway方法抛出异常", e);
}
}
}

View File

@ -0,0 +1,157 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.util;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.util
* @ProjectkangarooDataCenterV3
* @nameRequestTool
* @Date2025/9/2 17:54
* @FilenameRequestTool
*/
public class RequestTool {
private static final String HEADER_CONTENT_JSON = "application/json";
private static final String DEFAULT_CHARSET = "UTF-8";
private static PoolingHttpClientConnectionManager cm = null;
private static ObjectMapper mapper = new ObjectMapper();
private static CloseableHttpClient httpClient;
/**
* 记录开放平台请求结果
*/
public static class Response {
/**
* 该请求的 http 状态码
* 200 为正常的返回结果
*/
private int status;
/**
* 请求返回消息
* status == 200 时会返回 response body 中的字符串
* status !== 200 时会返回具体的错误信息
*/
private String result;
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
}
static {
cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(500);
cm.setDefaultMaxPerRoute(50);
RequestConfig globalConfig = RequestConfig.custom().setConnectionRequestTimeout(1000) // 连接池获取连接超时
.setConnectTimeout(1000) // 连接建立超时
.setSocketTimeout(5000) // 等待响应超时
.setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
httpClient = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(globalConfig).build();
}
private static CloseableHttpClient getHttpClient() {
return httpClient;
}
public static <T> T doGet(String requestUrl, Map<String, String> paramMap, Class<T> type) throws IOException {
return mapper.readValue(doGet(requestUrl, paramMap), type);
}
public static <T> T doGet(String requestUrl, Map<String, String> paramMap, TypeReference<T> typeReference) throws IOException {
return mapper.readValue(doGet(requestUrl, paramMap), typeReference);
}
public static String doGet(String requestUrl, Map<String, String> paramMap) throws IOException {
CloseableHttpClient httpClient = getHttpClient();
StringBuilder param = new StringBuilder("?");
if (paramMap != null) {
for (Map.Entry<String, String> entry : paramMap.entrySet()) {
param.append(entry.getKey());
param.append("=");
param.append(entry.getValue());
param.append("&");
}
param.deleteCharAt(param.length() - 1);
}
HttpGet get = new HttpGet(requestUrl + param);
String responseString = httpClient.execute(get, response -> EntityUtils.toString(response.getEntity()));
get.releaseConnection();
return responseString;
}
/**
* 执行一个HTTP POST请求将JSON字符串数据发布到指定的URL
*
* @param requestUrl 请求的URL
* @param queryParams URL的查询参数 (例如 access_token)
* @param jsonBody 要发送的JSON格式的请求体
* @return 响应的字符串
* @throws IOException
*/
public static String doPost(String requestUrl, Map<String, String> queryParams, String jsonBody) throws IOException {
CloseableHttpClient httpClient = getHttpClient();
// 构造带查询参数的URL
StringBuilder urlWithParams = new StringBuilder(requestUrl);
if (queryParams != null && !queryParams.isEmpty()) {
urlWithParams.append("?");
for (Map.Entry<String, String> entry : queryParams.entrySet()) {
urlWithParams.append(entry.getKey());
urlWithParams.append("=");
urlWithParams.append(entry.getValue());
urlWithParams.append("&");
}
urlWithParams.deleteCharAt(urlWithParams.length() - 1);
}
// 创建HttpPost对象
HttpPost post = new HttpPost(urlWithParams.toString());
// 设置请求体
if (jsonBody != null) {
StringEntity entity = new StringEntity(jsonBody, StandardCharsets.UTF_8);
entity.setContentEncoding(StandardCharsets.UTF_8.name());
entity.setContentType(HEADER_CONTENT_JSON);
post.setEntity(entity);
}
// 执行请求并获取响应
String responseString = httpClient.execute(post, response -> EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8));
post.releaseConnection();
return responseString;
}
}

View File

@ -0,0 +1,52 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.util;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Map;
import java.util.TreeMap;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.util
* @ProjectkangarooDataCenterV3
* @nameSignHelper
* @Date2025/9/2 18:07
* @FilenameSignHelper
*/
public class SignHelper {
/**
* 按参数名排序后依次拼接参数名称与数值之后对该字符串使用 HmacSHA256 加签加签结果进行 base 64 返回
* @param params 请求参数 map
* @param suiteSecret 套件密钥用作 mac key
* @return 签名
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
* @throws InvalidKeyException
*/
public static String sign(Map<String, String> params, String suiteSecret) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
// use tree map to sort params by name
Map<String, String> treeMap;
if (params instanceof TreeMap) {
treeMap = params;
} else {
treeMap = new TreeMap<>(params);
}
StringBuilder stringBuilder = new StringBuilder();
for (Map.Entry<String, String> entry : treeMap.entrySet()) {
stringBuilder.append(entry.getKey()).append(entry.getValue());
}
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(suiteSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] signData = mac.doFinal(stringBuilder.toString().getBytes(StandardCharsets.UTF_8));
String base64String = Base64.getEncoder().encodeToString(signData);
return URLEncoder.encode(base64String, "UTF-8");
}
}

View File

@ -0,0 +1,92 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.util;
import com.hzya.frame.plugin.zanhuo.yonsuite.dto.token.AccessTokenResponse;
import com.hzya.frame.plugin.zanhuo.yonsuite.dto.token.DcUrlResult;
import com.hzya.frame.plugin.zanhuo.yonsuite.dto.token.TokenUrlDto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* YS Token管理器
*
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.util
* @ProjectkangarooDataCenterV3
* @nameYonSuiteTokenManager
* @Date2025/9/3 09:19
* @FilenameYonSuiteTokenManager
*/
@Component
public class YonSuiteTokenManager {
Logger logger = LoggerFactory.getLogger(GetYsToken.class);
@Autowired
private GetYsToken pullYsData;
//缓存获取到的AccessToken
private String accessToken;
//Token的过期时间戳单位毫秒
private long expiryTimestamp;
//业务接口
private String gatewayUrl;
//认证接口
private String tokenUrl;
//容错时间提前200秒刷新Token
private static final long FAULT_TOLERANCE_SECONDS = 200L;
/**
* 对外提供获取有效AccessToken的唯一入口
* 此方法是线程安全的防止多个线程同时刷新Token
*
* @return 有效的AccessToken字符串
*/
public synchronized TokenUrlDto getValidAccessToken() {
// 检查Token是否为null或者是否已过期
if (accessToken == null || System.currentTimeMillis() >= expiryTimestamp) {
logger.info("AccessToken为空或已过期准备刷新...");
refreshToken();
}
return new TokenUrlDto(accessToken, gatewayUrl, tokenUrl);
}
/**
* 内部方法负责调用API刷新Token并更新缓存和过期时间
*/
private void refreshToken() {
try {
logger.info("开始调用getAccessTokenV2接口获取新Token...");
DcUrlResult gateway = pullYsData.getGateway();
AccessTokenResponse response = pullYsData.getAccessTokenV2(gateway);
if (response != null && response.getAccessToken() != null) {
// 更新本地状态值
this.accessToken = response.getAccessToken();
this.gatewayUrl = gateway.getGatewayUrl();
this.tokenUrl = gateway.getTokenUrl();
// 计算新的过期时间戳
long expiresInSeconds = response.getExpire();
long expiresInMillis = expiresInSeconds * 1000L;
long bufferMillis = FAULT_TOLERANCE_SECONDS * 1000L;
// 新的过期时间 = 当前时间 + 接口返回的有效时长 - 容错时间
this.expiryTimestamp = System.currentTimeMillis() + expiresInMillis - bufferMillis;
logger.info("成功刷新AccessToken新的Token将在 {} 秒后过期(已包含{}秒容错时间)。", (this.expiryTimestamp - System.currentTimeMillis()) / 1000, FAULT_TOLERANCE_SECONDS);
} else {
logger.error("获取AccessToken失败API返回为空或Token为空。");
throw new RuntimeException("无法获取有效的AccessToken");
}
} catch (Exception e) {
logger.error("刷新AccessToken时发生异常", e);
throw new RuntimeException("刷新AccessToken时发生异常", e);
}
}
}

View File

@ -0,0 +1,43 @@
package com.hzya.frame.plugin.zanhuo.yonsuite.util;
import com.hzya.frame.WebappApplication;
import com.hzya.frame.plugin.zanhuo.yonsuite.dto.token.AccessTokenResponse;
import com.hzya.frame.plugin.zanhuo.yonsuite.dto.token.DcUrlResult;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @Authorliuyang
* @Packagecom.hzya.frame.plugin.zanhuo.yonsuite.util
* @ProjectkangarooDataCenterV3
* @namePullYsDataTest
* @Date2025/9/2 18:29
* @FilenamePullYsDataTest
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = WebappApplication.class)
public class PullYsDataTest {
@Autowired
private GetYsToken pullYsData;
@Test
public void getAccessTokenV2() {
try {
DcUrlResult gateway = pullYsData.getGateway();
AccessTokenResponse accessTokenV2 = pullYsData.getAccessTokenV2(gateway);
System.out.println(accessTokenV2.getAccessToken());
// System.out.println(accessTokenV2.getExpire());
//当前时间+7200秒=过期时间
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void getGateway() {
}
}