diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/AgisoAuthCallbackController.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/AgisoAuthCallbackController.java deleted file mode 100644 index 32a059ca1d..0000000000 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/AgisoAuthCallbackController.java +++ /dev/null @@ -1,50 +0,0 @@ -package cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.kfc.service.agiso.AgisoAuthService; -import io.swagger.v3.oas.annotations.Operation; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import javax.annotation.security.PermitAll; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -/** - * Agiso开放平台授权回调控制器 - * 用于接收授权后返回的code等参数,进而换取AccessToken - */ -@RestController -@RequestMapping("/agiso") -public class AgisoAuthCallbackController { - - @Resource - private AgisoAuthService agisoAuthService; - /** - * 接收授权回调 - * 平台会在用户授权后重定向到该接口,并携带code、state等参数 - * - * @param code 授权码(用于换取AccessToken) - * @param state 状态参数(与请求时传入的值一致,用于校验请求合法性) - * @param error 错误信息(授权失败时返回,如用户拒绝授权) - * @return 处理结果 - */ - @GetMapping("/callback/authorize") - @Operation(summary = "阿奇索code回调接口") - @PermitAll - public String handleAuthCallback( - @RequestParam(value = "code", required = false) String code, - @RequestParam(value = "state", required = false) String state, - @RequestParam(value = "error", required = false) String error) { - return agisoAuthService.authAndGetAccessToken(code,state,error); - } - - @GetMapping("/get/code-url") - @Operation(summary = "生成阿奇索code认证url") - public CommonResult getCodeUrl() { - return success(agisoAuthService.generateAuthUrl()); - } -} \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/AgisoController.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/AgisoController.java new file mode 100644 index 0000000000..8afd38505e --- /dev/null +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/AgisoController.java @@ -0,0 +1,60 @@ +package cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.kfc.service.agiso.AgisoService; +import io.swagger.v3.oas.annotations.Operation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.annotation.security.PermitAll; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +/** + * Agiso开放平台授权回调控制器 + * 用于接收授权后返回的code等参数,进而换取AccessToken + */ +@RestController +@RequestMapping("/agiso") +@Slf4j +public class AgisoController { + + @Resource + private AgisoService agisoService; + /** + * 接收授权回调 + * 平台会在用户授权后重定向到该接口,并携带code、state等参数 + * + * @param code 授权码(用于换取AccessToken) + * @param state 状态参数(与请求时传入的值一致,用于校验请求合法性) + * @param error 错误信息(授权失败时返回,如用户拒绝授权) + * @return 处理结果 + */ + @GetMapping("/callback/authorize") + @Operation(summary = "阿奇索code回调接口") + @PermitAll + public String handleAuthCallback(@RequestParam(value = "code", required = false) String code, + @RequestParam(value = "state", required = false) String state, + @RequestParam(value = "error", required = false) String error) { + return agisoService.authAndGetAccessToken(code,state,error); + } + + @PostMapping("/notify/order-message") + @Operation(summary = "接收下单消息推送接口") + @PermitAll + public String handlePurchaseOrderPush(@RequestParam("fromPlatform") String fromPlatform, + @RequestParam("timestamp") Long timestamp, + @RequestParam("aopic") Integer aopic, + @RequestParam("sign") String sign, + @RequestParam("json") String json) { + log.info("收到采购下单推送消息,平台:{},订单内容:{}", fromPlatform, json); + return agisoService.handlePurchaseOrderPush(fromPlatform, timestamp, aopic, json, sign); + } + + @GetMapping("/get/code-url") + @Operation(summary = "生成阿奇索code认证url") + public CommonResult getCodeUrl() { + return success(agisoService.generateAuthUrl()); + } +} \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoCardInfoRequestVO.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoCardInfoRequestVO.java new file mode 100644 index 0000000000..773e4ef719 --- /dev/null +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoCardInfoRequestVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.util.List; + +/** + * 卡密信息主实体类 + */ +@Data +public class AgisoCardInfoRequestVO { + + /** + * 子订单编号 + * 【必填】 + */ + @NotBlank(message = "子订单编号Oid不能为空") + @JsonProperty("Oid") + private Long oid; + + /** + * 附言(使用说明) + * 非必填 + */ + @JsonProperty("Additional") + private String additional; + + /** + * 卡密信息 + * 非必填,长度不能超过5000字符 + */ + @JsonProperty("CardPwdContent") + @Size(max = 5000, message = "卡密信息长度不能超过5000字符") + private List cardPwdContentList; +} \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoCardPwdContentRequestVO.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoCardPwdContentRequestVO.java new file mode 100644 index 0000000000..94bf9c1815 --- /dev/null +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoCardPwdContentRequestVO.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import java.util.List; + +/** + * 卡密内容详情 + */ +@Data +public class AgisoCardPwdContentRequestVO { + + /** + * 卡号显示类型 + * 0:文本;1:条形码;2:二维码;3:相册网址;4:条形码+二维码 + */ + @JsonProperty("CardNoShowType") + @Min(value = 0, message = "卡号显示类型必须在0-4之间") + @Max(value = 4, message = "卡号显示类型必须在0-4之间") + private Integer cardNoShowType; + + /** + * 卡密显示类型 + * 0:文本;1:条形码;2:二维码;3:相册网址;4:条形码+二维码 + */ + @JsonProperty("PwdShowType") + @Min(value = 0, message = "卡密显示类型必须在0-4之间") + @Max(value = 4, message = "卡密显示类型必须在0-4之间") + private Integer pwdShowType; + + /** + * 卡密标题 + */ + @JsonProperty("Title") + private String title; + + /** + * 卡号前缀 + * 非必填 + */ + @JsonProperty("PrefixCardNo") + private String prefixCardNo; + + /** + * 卡密前缀 + * 非必填 + */ + @JsonProperty("PrefixPwd") + private String prefixPwd; + + /** + * 卡号密码数组 + */ + @JsonProperty("CardPwdArr") + private List cardPwdItemList; +} \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoCardPwdItemRequestVO.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoCardPwdItemRequestVO.java new file mode 100644 index 0000000000..b53bbf98fc --- /dev/null +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoCardPwdItemRequestVO.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.time.LocalDateTime; + +/** + * 单个卡密项 + */ +@Data +public class AgisoCardPwdItemRequestVO { + + /** + * 卡号 + * 【必填】 + */ + @NotBlank(message = "卡号不能为空") + @JsonProperty("c") + private String cardNo; + + /** + * 卡密 + * 【必填】 + */ + @NotBlank(message = "卡密不能为空") + @JsonProperty("p") + private String password; + + /** + * 到期时间 + * 非必填,格式:yyyy-MM-dd HH:mm:ss + */ + @JsonProperty("d") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime expireTime; +} \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoOrderDetailVO.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoOrderDetailVO.java new file mode 100644 index 0000000000..e3f29ba0e1 --- /dev/null +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoOrderDetailVO.java @@ -0,0 +1,104 @@ +package cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import java.util.List; + +/** + * 阿奇索订单详情完整实体类 + * 包含订单主信息及子订单列表 + */ +@Data +public class AgisoOrderDetailVO { + + /** + * 买家留言 + */ + @JsonProperty("BuyerMessage") + private String buyerMessage; + + /** + * 买家昵称(可能包含脱敏) + */ + @JsonProperty("BuyerNick") + private String buyerNick; + + /** + * 订单创建时间 + */ + @JsonProperty("Created") + private String created; + + /** + * 商品总数 + */ + @JsonProperty("Num") + private Integer num; + + /** + * 子订单列表 + */ + @JsonProperty("Orders") + private List orders; + + /** + * 扩展卡扩展价格使用金额 + */ + @JsonProperty("ExpandCardExpandPriceUsed") + private String expandCardExpandPriceUsed; + + /** + * 实付金额 + */ + @JsonProperty("Payment") + private String payment; + + /** + * 支付时间 + */ + @JsonProperty("PayTime") + private String payTime; + + /** + * 商品单价 + */ + @JsonProperty("Price") + private String price; + + /** + * 收货人手机号(可能包含脱敏) + */ + @JsonProperty("ReceiverMobile") + private String receiverMobile; + + /** + * 收货人姓名(可能包含脱敏) + */ + @JsonProperty("ReceiverName") + private String receiverName; + + /** + * 卖家昵称(可能包含脱敏) + */ + @JsonProperty("SellerNick") + private String sellerNick; + + /** + * 订单状态 + */ + @JsonProperty("Status") + private String status; + + /** + * 主订单编号 + */ + @JsonProperty("Tid") + private Long tid; + + /** + * 订单总金额 + */ + @JsonProperty("TotalFee") + private String totalFee; +} + \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoOrderPushVO.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoOrderPushVO.java new file mode 100644 index 0000000000..a1ea268b9a --- /dev/null +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoOrderPushVO.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 阿奇索采购订单推送消息实体类 + * 对应推送JSON中的订单详情数据 + */ +@Data +public class AgisoOrderPushVO { + + /** + * 订单编号 + * 对应JSON中的"Tid"字段 + */ + @JsonProperty("Tid") + private Long tid; + + /** + * 订单状态 + * 例如:WAIT_SELLER_SEND_GOODS(等待卖家发货)、WAIT_BUYER_CONFIRM_GOODS(等待买家确认收货) + * 对应JSON中的"Status"字段 + */ + @JsonProperty("Status") + private String status; + + /** + * 卖家昵称 + * 对应JSON中的"SellerNick"字段 + */ + @JsonProperty("SellerNick") + private String sellerNick; + + /** + * 卖家ID(平台唯一标识) + * 对应JSON中的"SellerOpenUid"字段 + */ + @JsonProperty("SellerOpenUid") + private String sellerOpenUid; + + /** + * 买家昵称(可能包含脱敏处理,如"碎**") + * 对应JSON中的"BuyerNick"字段 + */ + @JsonProperty("BuyerNick") + private String buyerNick; + + /** + * 买家ID(平台唯一标识) + * 对应JSON中的"BuyerOpenUid"字段 + */ + @JsonProperty("BuyerOpenUid") + private String buyerOpenUid; + + /** + * 支付金额(字符串形式,保留两位小数) + * 对应JSON中的"Payment"字段 + */ + @JsonProperty("Payment") + private String payment; + + /** + * 交易类型 + * 例如:fixed(一口价) + * 对应JSON中的"Type"字段 + */ + @JsonProperty("Type") + private String type; +} \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoResponse.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoResponse.java index 0d4d015818..66ea0a1520 100644 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoResponse.java +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoResponse.java @@ -31,5 +31,8 @@ public class AgisoResponse { * 详细数据(成功时返回) */ @JsonProperty("Data") - private AgisoData data; + private String data; + + @JsonProperty("AllowRetry") + private String allowRetry; } \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoSubOrderVO.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoSubOrderVO.java new file mode 100644 index 0000000000..afb208835f --- /dev/null +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/agisoproxy/vo/AgisoSubOrderVO.java @@ -0,0 +1,79 @@ +package cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 阿奇索子订单信息实体类 + * 对应订单详情中的子订单数据 + */ +@Data +public class AgisoSubOrderVO { + + /** + * 子订单商品数量 + */ + @JsonProperty("Num") + private Integer num; + + /** + * 商品数字ID + */ + @JsonProperty("NumIid") + private Long numIid; + + /** + * 子订单编号 + */ + @JsonProperty("Oid") + private Long oid; + + /** + * 商家外部商品ID + */ + @JsonProperty("OuterIid") + private String outerIid; + + /** + * 子订单实付金额 + */ + @JsonProperty("Payment") + private String payment; + + /** + * 子订单商品单价 + */ + @JsonProperty("Price") + private String price; + + /** + * 商品SKU属性名称 + */ + @JsonProperty("SkuPropertiesName") + private String skuPropertiesName; + + /** + * 商品标题 + */ + @JsonProperty("Title") + private String title; + + /** + * 子订单总金额 + */ + @JsonProperty("TotalFee") + private String totalFee; + + /** + * 子订单扩展卡扩展价格使用金额 + */ + @JsonProperty("ExpandCardExpandPriceUsedSuborder") + private String expandCardExpandPriceUsedSuborder; + + /** + * 定制订单信息 + */ + @JsonProperty("Customization") + private String customization; +} + \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/order/vo/OrderSaveReqVO.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/order/vo/OrderSaveReqVO.java index 5b3fbd5d12..a38f5cd940 100644 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/order/vo/OrderSaveReqVO.java +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/order/vo/OrderSaveReqVO.java @@ -24,18 +24,24 @@ public class OrderSaveReqVO { private String orderNo; @Schema(description = "商户ID,关联 merchants 表", requiredMode = Schema.RequiredMode.REQUIRED, example = "7304") - @NotNull(message = "商户ID,关联 merchants 表不能为空") +// @NotNull(message = "商户ID,关联 merchants 表不能为空") private Long merchantId; @Schema(description = "平台ID,关联 platforms 表", requiredMode = Schema.RequiredMode.REQUIRED, example = "28720") @NotNull(message = "平台ID,关联 platforms 表不能为空") private Long platformId; + @Schema(description = "商户开放id") + private String sellerOpenUid; + + @Schema(description = "外部订单号,如:淘宝,京东等") + private Long outTradeNo; + @Schema(description = "订单类型") private Integer orderType; @Schema(description = "买家ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "19247") - @NotEmpty(message = "买家ID不能为空") +// @NotEmpty(message = "买家ID不能为空") private String buyerId; @Schema(description = "买家姓名", example = "赵六") diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/orderitems/vo/OrderItemsSaveReqVO.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/orderitems/vo/OrderItemsSaveReqVO.java index fe9f9f48a0..9f6d66794a 100644 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/orderitems/vo/OrderItemsSaveReqVO.java +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/controller/admin/orderitems/vo/OrderItemsSaveReqVO.java @@ -29,6 +29,9 @@ public class OrderItemsSaveReqVO { @Schema(description = "商品规格") private String productSpec; + @Schema(description = "子订单编号") + private Long oid; + @Schema(description = "购买数量", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull(message = "购买数量不能为空") private Integer quantity; diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/convert/OrderConvert.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/convert/OrderConvert.java new file mode 100644 index 0000000000..7321abb622 --- /dev/null +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/convert/OrderConvert.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.kfc.convert; + +import cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo.AgisoOrderDetailVO; +import cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo.AgisoOrderPushVO; +import cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo.AgisoSubOrderVO; +import cn.iocoder.yudao.module.kfc.controller.admin.orderitems.vo.OrderItemsSaveReqVO; +import cn.iocoder.yudao.module.kfc.dal.dataobject.order.OrderDO; +import cn.iocoder.yudao.module.kfc.enums.order.OrderStatusEnum; +import cn.iocoder.yudao.module.kfc.enums.platform.PlatformTypeEnum; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.module.kfc.utils.NoGenerator.generate; + +@Mapper( + componentModel = "spring", + unmappedTargetPolicy = ReportingPolicy.IGNORE // 忽略未映射字段 +) +public interface OrderConvert { + + OrderConvert INSTANCE = Mappers.getMapper(OrderConvert.class); + + default OrderDO convert(AgisoOrderPushVO agisoOrderPushVO, String fromPlatform, AgisoOrderDetailVO orderDetailVO){ + OrderDO orderDO = new OrderDO(); + orderDO.setId(null) + .setOutTradeNo(agisoOrderPushVO.getTid()) + .setActualAmount(BigDecimal.valueOf(Double.parseDouble(agisoOrderPushVO.getPayment()))) + .setBuyerName(agisoOrderPushVO.getBuyerNick()) + .setSellerOpenUid(agisoOrderPushVO.getSellerOpenUid()) + .setOrderNo(generate()) + .setBuyerId(agisoOrderPushVO.getBuyerOpenUid()) + .setBuyerPhone(orderDetailVO.getReceiverMobile()) + .setTotalAmount(BigDecimal.valueOf(Double.parseDouble(orderDetailVO.getTotalFee()))) + .setOrderStatus(OrderStatusEnum.UNDELIVERED.getStatus()) + .setRemark(orderDetailVO.getBuyerMessage()) + .setPaymentTime(LocalDateTime.now()); + if (fromPlatform.startsWith("Tb")) + orderDO.setPlatformId(PlatformTypeEnum.TB.getType().longValue()); + else orderDO.setPlatformId(PlatformTypeEnum.OTHER.getType().longValue()); + return orderDO; + } + + default OrderItemsSaveReqVO convert(AgisoSubOrderVO agisoSubOrderVO,Long orderId){ + OrderItemsSaveReqVO orderItemsSaveReqVO = new OrderItemsSaveReqVO(); + orderItemsSaveReqVO.setId(null) + .setOrderId(orderId) + .setOid(agisoSubOrderVO.getOid()) + .setProductId(agisoSubOrderVO.getNumIid()) + .setProductName(agisoSubOrderVO.getTitle()) + .setQuantity(agisoSubOrderVO.getNum()) + .setUnitPrice(BigDecimal.valueOf(Double.parseDouble(agisoSubOrderVO.getPrice()))) + .setSubtotal(BigDecimal.valueOf(Double.parseDouble(agisoSubOrderVO.getTotalFee()))); + return orderItemsSaveReqVO; + } +} diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/dal/dataobject/order/OrderDO.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/dal/dataobject/order/OrderDO.java index ab4ba3454b..6d30ef954e 100644 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/dal/dataobject/order/OrderDO.java +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/dal/dataobject/order/OrderDO.java @@ -34,10 +34,18 @@ public class OrderDO extends BaseDO { * 订单编号,业务唯一标识 */ private String orderNo; + /** + * 外部平台订单号 如:淘宝,京东等 + */ + private Long outTradeNo; /** * 商户ID,关联 merchants 表 */ private Long merchantId; + /** + * 商户openID + */ + private String sellerOpenUid; /** * 平台ID,关联 platforms 表 */ diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/dal/dataobject/orderitems/OrderItemsDO.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/dal/dataobject/orderitems/OrderItemsDO.java index fd6ce83050..42c1b2ff33 100644 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/dal/dataobject/orderitems/OrderItemsDO.java +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/dal/dataobject/orderitems/OrderItemsDO.java @@ -44,6 +44,10 @@ public class OrderItemsDO extends BaseDO { * 商品规格 */ private String productSpec; + /** + * 子订单编号 + */ + private Long oid; /** * 购买数量 */ diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/ErrorCodeConstants.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/ErrorCodeConstants.java index 5b5859f55e..57f9b17675 100644 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/ErrorCodeConstants.java +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/ErrorCodeConstants.java @@ -58,4 +58,6 @@ public interface ErrorCodeConstants { ErrorCode USER_AUTH_FAILED = new ErrorCode(2_001_013_004,"用户授权失败"); ErrorCode INTERFACE_RETURNED_FAILURE = new ErrorCode(2_001_013_005,"接口返回失败,错误码:{},错误信息:{}"); ErrorCode REQUEST_FAILED = new ErrorCode(2_001_013_006,"HTTP请求失败,状态码:{}"); + + ErrorCode JSON_READ_FAILED = new ErrorCode(2_001_014_000,"JSON读取异常:{}"); } diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/order/AldsTypeEnum.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/order/AldsTypeEnum.java new file mode 100644 index 0000000000..5d3455d5c9 --- /dev/null +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/order/AldsTypeEnum.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.kfc.enums.order; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * 发货结果-类型 + * + * @author kfc + */ +@RequiredArgsConstructor +@Getter +public enum AldsTypeEnum implements ArrayValuable { + + PAY_SUCCESS(1, "付款后发货结果"), + CONFIRM_RECEIPT(2, "确认收货赠送结果"), + PRAISE(4, "好评后赠送结果"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(AldsTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型值 + */ + private final Integer type; + /** + * 类型名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + +} diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/order/AopicEnum.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/order/AopicEnum.java new file mode 100644 index 0000000000..f2f8d61be5 --- /dev/null +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/order/AopicEnum.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.kfc.enums.order; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Objects; + +/** + * 发货结果-类型 + * + * @author kfc + */ +@RequiredArgsConstructor +@Getter +public enum AopicEnum implements ArrayValuable { + + BUYER_TOOK_A_PICTURE(1, "买家拍下"), + BUYER_PAID(2, "买家付款"), + SELLER_MODIFIES_NOTES(524288, "卖家修改备注"), + AUTO_SHIPMENT_SUCCESSFUL(2048, "自动发货成功"), + SELLER_SHIPPING(1048576, "卖家发货"), + BUYER_CONFIRMS_RECEIPT(4, "买家确认收货"), + BOTH_SIDES_HAVE_COMMENTED(16, "双方已评"), + REFUND_CREATION(256, "退款创建"), + REFUNDS_CLOSED(32768, "退款关闭"), + REFUND_SUCCESSFUL(65536, "退款成功"), + SELLER_AGREES_TO_RETURN_THE_PRODUCT(131072, "卖家同意退货"), + SELLER_REFUSED_TO_RETURN_THE_ITEM(262144, "卖家拒绝退货"), + BUYER_PAID_NEW(2097152, "新版买家付款"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(AopicEnum::getType).toArray(Integer[]::new); + + /** + * 类型值 + */ + private final Integer type; + /** + * 类型名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static AopicEnum valueOf(Integer type) { + return Arrays.stream(values()).filter(v -> Objects.equals(v.getType(), type)).findFirst().orElse(null); + } + + +} diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/order/CardShowTypeEnum.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/order/CardShowTypeEnum.java new file mode 100644 index 0000000000..a116528a86 --- /dev/null +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/order/CardShowTypeEnum.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.kfc.enums.order; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * 发货结果-类型 + * + * @author kfc + */ +@RequiredArgsConstructor +@Getter +public enum CardShowTypeEnum implements ArrayValuable { + + TEXT(0, "文本"), + BAR_CODE(1, "条形码"), + QR_CODE(2, "二维码"), + ALBUM_URL(3, "相册网址"), + BAR_QR(4, "条形码+二维码"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(CardShowTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型值 + */ + private final Integer type; + /** + * 类型名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + +} diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/platform/PlatformTypeEnum.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/platform/PlatformTypeEnum.java new file mode 100644 index 0000000000..139d80272d --- /dev/null +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/enums/platform/PlatformTypeEnum.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.kfc.enums.platform; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +@RequiredArgsConstructor +@Getter +public enum PlatformTypeEnum implements ArrayValuable { + + //阿奇索返回平台类型参数值:TbAcs,TbAlds,TbArs,Print,Acpr,PddAlds,AldsIdle,AldsJd,AldsDoudian,AldsKwai,AldsYouzan,AldsWeidian,AldsWxVideoShop,AldsXhs + + TB(1, "淘宝"), + JD(2, "京东"), + XY(3, "闲鱼"), + KW(4, "快手"), + DY(5, "抖音"), + OTHER(6, "其他"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(PlatformTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型值 + */ + private final Integer type; + /** + * 类型名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static PlatformTypeEnum valueOf(Integer type) { + for (PlatformTypeEnum enumInstance : PlatformTypeEnum.values()) { + if (enumInstance.getType().equals(type)) { + return enumInstance; + } + } + return null; + } +} diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agentorder/KFCAutomatedHeadlessOrder.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agentorder/KFCAutomatedHeadlessOrder.java index 883096fc10..a96adc9d9a 100644 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agentorder/KFCAutomatedHeadlessOrder.java +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agentorder/KFCAutomatedHeadlessOrder.java @@ -19,10 +19,10 @@ import java.util.Random; public class KFCAutomatedHeadlessOrder { private static final Logger logger = LoggerFactory.getLogger(KFCAutomatedHeadlessOrder.class); - private WebDriver driver; - private WebDriverWait wait; - private Actions actions; - private Random random; + private final WebDriver driver; + private final WebDriverWait wait; + private final Actions actions; + private final Random random; // 配置信息 private final String phoneNumber = "18881628029"; // 替换为实际手机号 diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agiso/AgisoAuthServiceImpl.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agiso/AgisoAuthServiceImpl.java deleted file mode 100644 index f937528c6d..0000000000 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agiso/AgisoAuthServiceImpl.java +++ /dev/null @@ -1,254 +0,0 @@ -package cn.iocoder.yudao.module.kfc.service.agiso; - -import cn.hutool.core.util.RandomUtil; -import cn.iocoder.yudao.module.kfc.config.AgisoConfig; -import cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo.AgisoData; -import cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo.AgisoResponse; -import cn.iocoder.yudao.module.kfc.utils.HttpUtils; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.extern.slf4j.Slf4j; -import okhttp3.*; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.kfc.enums.ErrorCodeConstants.*; -import static software.amazon.awssdk.http.HttpStatusCode.BAD_REQUEST; - -@Service -@Slf4j -public class AgisoAuthServiceImpl implements AgisoAuthService { - - @Resource - private AgisoConfig agisoConfig; - - // Redis中存储state的前缀,过期时间10分钟 - private static final String STATE_REDIS_KEY_PREFIX = "agiso:auth:state:"; - private static final String TOKEN_REDIS_KEY_PREFIX = "agiso:token:"; - private static final long STATE_EXPIRE_MINUTES = 10; - private static final String AUTO_SHIPMENTS_URL = "http://gw.api.agiso.com/alds/Trade/AldsProcessTrades"; - - @Resource - private RedisTemplate redisTemplate; - - @Resource - private OkHttpClient okHttpClient; - - @Resource - private ObjectMapper objectMapper; - - /** - * 生成授权链接 - */ - @Override - public String generateAuthUrl() { - // 1. 生成随机state - String state = RandomUtil.randomNumbers(4); - // 2. 存储state到Redis,用于回调校验 - redisTemplate.opsForValue() - .set(STATE_REDIS_KEY_PREFIX + state, "VALID", STATE_EXPIRE_MINUTES, TimeUnit.MINUTES); - // 3. 拼接授权URL - return "https://alds.agiso.com/authorize.aspx?appId=" + agisoConfig.getAppid() + "&state=" + state; - } - - /** - * 处理授权回调并获取AccessToken - */ - @Override - public String authAndGetAccessToken(String code, String state, String error) { - // 1. 校验state - String redisKey = STATE_REDIS_KEY_PREFIX + state; - if (!redisTemplate.hasKey(redisKey)) { - log.warn("授权回调失败,state校验不通过,state={}", state); - throw exception(STATE_NOT_VALID); - } - // 校验通过后删除state,防止重复使用 - redisTemplate.delete(redisKey); - - // 2. 处理授权失败场景 - if (error != null) { - log.warn("用户授权失败,error={}", error); - throw exception(USER_AUTH_FAILED); - } - - // 3. 处理授权成功场景 - if (code != null) { - - //异步执行,不阻塞回调线程 - CompletableFuture.runAsync(() -> { - try { - String accessToken = generateAccessToken(code, state); - log.info("授权成功,AccessToken已存储到redis"); - } catch (Exception e) { - log.error("换取AccessToken失败,code={}", code, e); - } - }); - } - return "success"; - } - - /** - * 用授权码code换取AccessToken - */ - private String generateAccessToken(String code, String state) throws IOException { - // 1. 构建请求URL - HttpUrl url = Objects.requireNonNull(HttpUrl.parse("https://alds.agiso.com/auth/token")).newBuilder() - .addQueryParameter("code", code) - .addQueryParameter("appId", agisoConfig.getAppid()) - .addQueryParameter("secret", agisoConfig.getAppsecret()) - .addQueryParameter("state", state) // 使用回调的state,保持一致 - .build(); - log.debug("请求AccessToken的URL:{}", url); - - // 2. 创建请求 - Request request = new Request.Builder() - .url(url) - .addHeader("Accept", "application/json") - .build(); - - // 3. 执行请求 - try (Response response = okHttpClient.newCall(request).execute()) { - // 4. 检查HTTP响应状态 - if (!response.isSuccessful()) { - throw exception(REQUEST_FAILED, response.code()); - } - - // 5. 读取响应体 - String responseBody = response.body() != null ? response.body().string() : ""; - log.debug("获取AccessToken的响应:{}", responseBody); - if (responseBody.isEmpty()) { - throw exception(RESPONSE_IS_EMPTY); - } - - // 6. 解析响应 - AgisoResponse tokenResponse = objectMapper.readValue(responseBody, AgisoResponse.class); - if (!tokenResponse.isSuccess()) { - throw exception(INTERFACE_RETURNED_FAILURE,tokenResponse.getErrorCode(),tokenResponse.getErrorMsg()); - } - - // 7. 提取Token和相关信息 - AgisoData data = tokenResponse.getData(); - if (data == null || data.getToken() == null || data.getToken().isEmpty()) { - throw exception(TOKEN_IS_EMPTY); - } - - // 8. 缓存Token - String cacheKey = TOKEN_REDIS_KEY_PREFIX + data.getSellerOpenUid(); - redisTemplate.opsForValue() - .set(cacheKey, data.getToken(), data.getExpiresIn(), TimeUnit.SECONDS); - log.info("AccessToken缓存成功,商家ID:{},过期时间:{}秒", data.getSellerOpenUid(), data.getExpiresIn()); - - return data.getToken(); - } - } - - @Override - public AgisoResponse execute(String url, String method, Map params, String sellerOpenUid) throws NoSuchAlgorithmException { - - if (params == null) params = new HashMap<>(); - - //从redis获取accessToken - String accessToken = redisTemplate.opsForValue().get(TOKEN_REDIS_KEY_PREFIX + sellerOpenUid); - if (accessToken == null) throw exception(TOKEN_AUTH_FAILED); - - //构建公共请求参数 - long timestamp = System.currentTimeMillis() / 1000; - params.put("timestamp", Long.toString(timestamp)); - params.put("sign",sign(params,agisoConfig.getAppsecret())); - - //构建公共请求头 - Map headers = new HashMap<>(); - headers.put("Authorization", "Bearer " + accessToken); - headers.put("ApiVersion", "1"); - //执行请求 - return HttpUtils.execute(url,method, headers, params); - } - - @Override - public AgisoResponse doPost(String url, Map params, String sellerOpenUid) throws NoSuchAlgorithmException { - return execute(url,"POST",params,sellerOpenUid); - } - - @Override - public AgisoResponse doGet(String url, Map params, String sellerOpenUid) throws NoSuchAlgorithmException { - return execute(url,"GET",params,sellerOpenUid); - } - - @Override - public AgisoResponse autoShipment(List tids, String sellerOpenUid) throws NoSuchAlgorithmException { - // 校验参数 - - if (tids == null || tids.isEmpty()) { - log.warn("自动发货失败,订单ID列表为空"); - AgisoResponse emptyResponse = new AgisoResponse(); - emptyResponse.setSuccess(false); - emptyResponse.setErrorCode(BAD_REQUEST); - emptyResponse.setErrorMsg("订单ID列表不能为空"); - return emptyResponse; - } - - // 拼接订单ID字符串 - String tidsStr = String.join(",", tids); - log.debug("自动发货处理,订单ID列表:{},商家标识:{}", tidsStr, sellerOpenUid); - - // 构建请求参数 - Map params = new HashMap<>(); - params.put("tids", tidsStr); - - // 调用POST请求方法 - return doPost(AUTO_SHIPMENTS_URL, params, sellerOpenUid); - } - - /** - * 阿奇索签名算法 - * @param params 业务参数 - * @param appSecret appsecret - * @return 签名字符串 - * @throws NoSuchAlgorithmException 未找到算法异常 - */ - @Override - public String sign(Map params, String appSecret) throws NoSuchAlgorithmException { - String[] keys = params.keySet().toArray(new String[0]); - Arrays.sort(keys); - - StringBuilder query = new StringBuilder(); - query.append(appSecret); - for (String key : keys) { - String value = params.get(key); - query.append(key).append(value); - } - query.append(appSecret); - - byte[] md5byte = encryptMD5(query.toString()); - - return byte2hex(md5byte); - } - - // byte数组转成16进制字符串 - private String byte2hex(byte[] bytes) { - StringBuilder sign = new StringBuilder(); - for (byte aByte : bytes) { - String hex = Integer.toHexString(aByte & 0xFF); - if (hex.length() == 1) { - sign.append("0"); - } - sign.append(hex.toLowerCase()); - } - return sign.toString(); - } - - // Md5摘要 - private byte[] encryptMD5(String data) throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - return md5.digest(data.getBytes(StandardCharsets.UTF_8)); - } -} \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agiso/AgisoAuthService.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agiso/AgisoService.java similarity index 56% rename from yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agiso/AgisoAuthService.java rename to yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agiso/AgisoService.java index e125d770f6..3c0ec8bb76 100644 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agiso/AgisoAuthService.java +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agiso/AgisoService.java @@ -2,24 +2,33 @@ package cn.iocoder.yudao.module.kfc.service.agiso; import cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo.AgisoResponse; +import javax.validation.constraints.NotEmpty; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Map; -public interface AgisoAuthService { +public interface AgisoService { String generateAuthUrl(); String authAndGetAccessToken(String code, String state, String error); - AgisoResponse execute(String url, String method, Map params, String sellerOpenUid) throws IOException, NoSuchAlgorithmException; + AgisoResponse execute(String url, String method, Map params, String sellerOpenUid) throws IOException; - AgisoResponse doPost(String url, Map params, String sellerOpenUid) throws NoSuchAlgorithmException; + AgisoResponse doPost(String url, Map params, String sellerOpenUid); - AgisoResponse doGet(String url, Map params, String sellerOpenUid) throws NoSuchAlgorithmException; + AgisoResponse doGet(String url, Map params, String sellerOpenUid); - AgisoResponse autoShipment(List tids, String sellerOpenUid) throws NoSuchAlgorithmException; + AgisoResponse autoShipment(List tids, String sellerOpenUid) ; + + AgisoResponse getTradeDetail(@NotEmpty String tid, String sellerOpenUid) ; + + AgisoResponse createDeliveryResult(String tid, Integer aldsType, String content, String sellerOpenUid); + + String handlePurchaseOrderPush(String fromPlatform, Long timestamp, Integer aopic, String json, String sign); + + void sendWWMsg(Long tid, String msg, String sellerOpenUid); String sign(Map params, String appSecret) throws NoSuchAlgorithmException; diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agiso/AgisoServiceImpl.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agiso/AgisoServiceImpl.java new file mode 100644 index 0000000000..ea27bc99f8 --- /dev/null +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/agiso/AgisoServiceImpl.java @@ -0,0 +1,513 @@ +package cn.iocoder.yudao.module.kfc.service.agiso; + +import cn.hutool.core.util.RandomUtil; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.kfc.config.AgisoConfig; +import cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo.*; +import cn.iocoder.yudao.module.kfc.controller.admin.order.vo.OrderSaveReqVO; +import cn.iocoder.yudao.module.kfc.controller.admin.orderitems.vo.OrderItemsSaveReqVO; +import cn.iocoder.yudao.module.kfc.convert.OrderConvert; +import cn.iocoder.yudao.module.kfc.dal.dataobject.order.OrderDO; +import cn.iocoder.yudao.module.kfc.dal.dataobject.orderitems.OrderItemsDO; +import cn.iocoder.yudao.module.kfc.enums.order.AldsTypeEnum; +import cn.iocoder.yudao.module.kfc.enums.order.AopicEnum; +import cn.iocoder.yudao.module.kfc.enums.order.CardShowTypeEnum; +import cn.iocoder.yudao.module.kfc.enums.order.OrderStatusEnum; +import cn.iocoder.yudao.module.kfc.service.order.KFCOrderService; +import cn.iocoder.yudao.module.kfc.service.orderitems.OrderItemsService; +import cn.iocoder.yudao.module.kfc.utils.AgisoHttpUtils; +import cn.iocoder.yudao.module.kfc.utils.AssertUtils; +import com.alibaba.fastjson.JSONObject; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import javax.validation.constraints.NotEmpty; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.kfc.enums.ErrorCodeConstants.*; +import static software.amazon.awssdk.http.HttpStatusCode.BAD_REQUEST; + +@Service +@Slf4j +@Validated +public class AgisoServiceImpl implements AgisoService { + + // Redis中存储state的前缀,过期时间10分钟 + private static final String STATE_REDIS_KEY_PREFIX = "agiso:auth:state:"; + private static final String TOKEN_REDIS_KEY_PREFIX = "agiso:token:"; + private static final long STATE_EXPIRE_MINUTES = 10; + + //自行自动发货url + private static final String AUTO_SHIPMENTS_URL = "http://gw.api.agiso.com/alds/Trade/AldsProcessTrades"; + //交易订单详情url + private static final String TRADE_DETAIL_URL = "http://gw.api.agiso.com/alds/Trade/TradeInfo"; + //创建发货结果url + private static final String DELIVERY_RESULT_URL = "http://gw.api.agiso.com/alds/Trade/CreateOrderResult"; + //发送旺旺消息url + private static final String WW_SEND_URL = "http://gw.api.agiso.com/alds/WwMsg/Send"; + //订单状态 + private static final String ORDER_WAIT_STATUS = "WAIT_SELLER_SEND_GOODS"; + + private static final OrderConvert convert =OrderConvert.INSTANCE; + + @Resource + private AgisoConfig agisoConfig; + + @Resource + private RedisTemplate redisTemplate; + + @Resource + private OkHttpClient okHttpClient; + + @Resource + private ObjectMapper objectMapper; + + @Resource + private KFCOrderService kfcOrderService; + + @Resource + private OrderItemsService itemsService; + + /** + * 生成授权链接 + */ + @Override + public String generateAuthUrl() { + // 1. 生成随机state + String state = RandomUtil.randomNumbers(4); + // 2. 存储state到Redis,用于回调校验 + redisTemplate.opsForValue() + .set(STATE_REDIS_KEY_PREFIX + state, "VALID", STATE_EXPIRE_MINUTES, TimeUnit.MINUTES); + // 3. 拼接授权URL + return "https://alds.agiso.com/authorize.aspx?appId=" + agisoConfig.getAppid() + "&state=" + state; + } + + /** + * 处理授权回调并获取AccessToken + */ + @Override + public String authAndGetAccessToken(String code, String state, String error) { + // 1. 校验state + String redisKey = STATE_REDIS_KEY_PREFIX + state; + if (!redisTemplate.hasKey(redisKey)) { + log.warn("授权回调失败,state校验不通过,state={}", state); + throw exception(STATE_NOT_VALID); + } + // 校验通过后删除state,防止重复使用 + redisTemplate.delete(redisKey); + + // 2. 处理授权失败场景 + if (error != null) { + log.warn("用户授权失败,error={}", error); + throw exception(USER_AUTH_FAILED); + } + + // 3. 处理授权成功场景 + if (code != null) { + + //异步执行,不阻塞回调线程 + CompletableFuture.runAsync(() -> { + try { + String accessToken = generateAccessToken(code, state); + log.info("授权成功,AccessToken已存储到redis"); + } catch (Exception e) { + log.error("换取AccessToken失败,code={}", code, e); + } + }); + } + return "success"; + } + + /** + * 用授权码code换取AccessToken + */ + private String generateAccessToken(String code, String state) throws IOException { + // 1. 构建请求URL + HttpUrl url = Objects.requireNonNull(HttpUrl.parse("https://alds.agiso.com/auth/token")).newBuilder() + .addQueryParameter("code", code) + .addQueryParameter("appId", agisoConfig.getAppid()) + .addQueryParameter("secret", agisoConfig.getAppsecret()) + .addQueryParameter("state", state) // 使用回调的state,保持一致 + .build(); + log.debug("请求AccessToken的URL:{}", url); + + // 2. 创建请求 + Request request = new Request.Builder() + .url(url) + .addHeader("Accept", "application/json") + .build(); + + // 3. 执行请求 + try (Response response = okHttpClient.newCall(request).execute()) { + // 4. 检查HTTP响应状态 + if (!response.isSuccessful()) { + throw exception(REQUEST_FAILED, response.code()); + } + + // 5. 读取响应体 + String responseBody = response.body() != null ? response.body().string() : ""; + log.debug("获取AccessToken的响应:{}", responseBody); + if (responseBody.isEmpty()) { + throw exception(RESPONSE_IS_EMPTY); + } + + // 6. 解析响应 + AgisoResponse tokenResponse = objectMapper.readValue(responseBody, AgisoResponse.class); + if (!tokenResponse.isSuccess()) { + throw exception(INTERFACE_RETURNED_FAILURE,tokenResponse.getErrorCode(),tokenResponse.getErrorMsg()); + } + + // 7. 提取Token和相关信息 + String responseData = tokenResponse.getData(); + AgisoData data = objectMapper.readValue(responseData, AgisoData.class); + if (data == null || data.getToken() == null || data.getToken().isEmpty()) { + throw exception(TOKEN_IS_EMPTY); + } + + // 8. 缓存Token + String cacheKey = TOKEN_REDIS_KEY_PREFIX + data.getSellerOpenUid(); + redisTemplate.opsForValue() + .set(cacheKey, data.getToken(), data.getExpiresIn(), TimeUnit.SECONDS); + log.info("AccessToken缓存成功,商家ID:{},过期时间:{}秒", data.getSellerOpenUid(), data.getExpiresIn()); + + return data.getToken(); + } + } + + @Override + public AgisoResponse execute(String url, String method, Map params, String sellerOpenUid) { + + if (params == null) params = new HashMap<>(); + + //从redis获取accessToken + String accessToken = redisTemplate.opsForValue().get(TOKEN_REDIS_KEY_PREFIX + sellerOpenUid); + if (accessToken == null) throw exception(TOKEN_AUTH_FAILED); + + //构建公共请求参数 + long timestamp = System.currentTimeMillis() / 1000; + params.put("timestamp", Long.toString(timestamp)); + params.put("sign",sign(params,agisoConfig.getAppsecret())); + + //构建公共请求头 + Map headers = new HashMap<>(); + headers.put("Authorization", "Bearer " + accessToken); + headers.put("ApiVersion", "1"); + //执行请求 + return AgisoHttpUtils.execute(url,method, headers, params); + } + + @Override + public AgisoResponse doPost(String url, Map params, String sellerOpenUid) { + return execute(url,"POST",params,sellerOpenUid); + } + + @Override + public AgisoResponse doGet(String url, Map params, String sellerOpenUid) { + return execute(url,"GET",params,sellerOpenUid); + } + + /** + * 执行自动发货 + * @param tids 订单号 + * @param sellerOpenUid 商户开放id + * @return 阿奇索统一响应 + */ + @Override + public AgisoResponse autoShipment(List tids, String sellerOpenUid) { + + // 校验参数 + if (tids == null || tids.isEmpty()) { + log.warn("自动发货失败,订单ID列表为空"); + AgisoResponse emptyResponse = new AgisoResponse(); + emptyResponse.setSuccess(false); + emptyResponse.setErrorCode(BAD_REQUEST); + emptyResponse.setErrorMsg("订单ID列表不能为空"); + return emptyResponse; + } + + // 拼接订单ID字符串 + String tidsStr = String.join(",", tids); + log.debug("自动发货处理,订单ID列表:{},商家标识:{}", tidsStr, sellerOpenUid); + + // 构建请求参数 + Map params = new HashMap<>(); + params.put("tids", tidsStr); + + // 调用POST请求方法 + return doPost(AUTO_SHIPMENTS_URL, params, sellerOpenUid); + } + + /** + * 获取订单详情 + * @param tid 淘宝订单编号 + * @param sellerOpenUid 商户开放id + * @return 统一响应 + */ + @Override + public AgisoResponse getTradeDetail(@NotEmpty String tid, String sellerOpenUid) { + Map params = new HashMap<>(); + params.put("tid", tid); + return doPost(TRADE_DETAIL_URL,params,sellerOpenUid); + } + + /** + * 创建发货结果 + * @param tid 订单编号 + * @param aldsType 发货结果类型 + * @param content 发货结果内容 + * @param sellerOpenUid 商户开放id + * @return 阿奇索统一响应 + */ + @Override + public AgisoResponse createDeliveryResult(String tid, Integer aldsType, String content, String sellerOpenUid) { + Map params = new HashMap<>(); + params.put("tid", tid); + params.put("aldsType", String.valueOf(aldsType)); + params.put("content", content); + return doPost(DELIVERY_RESULT_URL,params,sellerOpenUid); + } + + /** + * 处理采购下单等通知的推送消息 + * 接收并验证推送内容,完成去重判断后返回处理结果 + * @param fromPlatform 平台标识 + * @param timestamp 时间戳 + * @param aopic 推送类型 + * @param json 推送消息内容(JSON字符串) + * @param sign 签名 + * @return 处理结果(成功返回"success",失败返回错误信息) + */ + @Override + public String handlePurchaseOrderPush(String fromPlatform, Long timestamp, Integer aopic, String json, String sign) { + try { + // 1. 校验参数完整性 + if (fromPlatform == null || timestamp == null || aopic == null || json == null || sign == null) { + log.warn("推送消息参数不完整,fromPlatform={}, timestamp={}, aopic={}", fromPlatform, timestamp, aopic); + return "参数不完整"; + } + + // 2. 验证签名 + boolean signValid = verifyPushSign(json, timestamp, sign); + if (!signValid) { + log.warn("推送消息签名验证失败,sign={}, json={}, timestamp={}", sign, json, timestamp); + return "签名无效"; + } + AopicEnum aopicEnum = AopicEnum.valueOf(aopic); + switch (aopicEnum) { + case BUYER_PAID: + case BUYER_PAID_NEW: + handleBuyerPaid(fromPlatform, json); + break; + //todo 其他类型待实现 + case REFUNDS_CLOSED: + case REFUND_CREATION: + case SELLER_SHIPPING: + case REFUND_SUCCESSFUL: + case BUYER_TOOK_A_PICTURE: + case SELLER_MODIFIES_NOTES: + case BUYER_CONFIRMS_RECEIPT: + case AUTO_SHIPMENT_SUCCESSFUL: + case BOTH_SIDES_HAVE_COMMENTED: + case SELLER_REFUSED_TO_RETURN_THE_ITEM: + case SELLER_AGREES_TO_RETURN_THE_PRODUCT: + default: + break; + } + + return "success"; + } catch (Exception e) { + log.error("处理采购下单推送消息失败", e); + return "处理失败:" + e.getMessage(); + } + } + + private void handleBuyerPaid(String fromPlatform, String json) throws JsonProcessingException { + // 3. 解析JSON消息内容 + AgisoOrderPushVO agisoOrderPushVO = objectMapper.readValue(json, AgisoOrderPushVO.class); + Long tid = agisoOrderPushVO.getTid(); // 订单编号 + String status = agisoOrderPushVO.getStatus(); // 订单状态 + String sellerOpenUid = agisoOrderPushVO.getSellerOpenUid();//商家开放id + //3.1异步去存储订单到我们的数据库并推送选择门店消息 + CompletableFuture.runAsync(() -> { + String orderNo = saveOrderDetail(fromPlatform, tid, agisoOrderPushVO); + //在这里给客户推送链接选择门店并提交订单 + String msg =String.format("下单成功,请点击下方链接选择门店:https://www.cdsrh.top/kfc/kfc/store?code=[%s]", orderNo); + sendWWMsg(tid, msg, sellerOpenUid); + }); + + // 4. 去重判断(基于Redis存储已处理的订单状态) + String redisDedupKey = "agiso:push:dedup:" + tid + ":" + status; + if (redisTemplate.hasKey(redisDedupKey)) { + log.info("推送消息已处理(去重),Tid={}, Status={}", tid, status); + return; + } + + // 5. 业务处理 + log.info("处理新的采购下单通知,平台={}, 订单号={}, 状态={}, 内容={}", + fromPlatform, tid, status, json); + + // 若状态为"等待卖家发货",异步去执行自动发货逻辑 + if (ORDER_WAIT_STATUS.equals(status)) { + CompletableFuture.runAsync(()->{ + if (sellerOpenUid != null) { + try { + AgisoResponse agisoResponse = autoShipment(Collections.singletonList(tid.toString()), sellerOpenUid); + if (agisoResponse.isSuccess()) { + //去更新数据库 + OrderDO order = kfcOrderService.getOrderByOutTradeNo(tid); + if (order != null) { + order.setOrderStatus(OrderStatusEnum.DELIVERED.getStatus()); + kfcOrderService.updateOrder(BeanUtils.toBean(order, OrderSaveReqVO.class)); + } + log.info("订单{}自动发货触发成功", tid); + }else log.error("订单{}自动发货触发失败,错误码:{},错误信息:{}", tid, agisoResponse.getErrorCode(), agisoResponse.getErrorMsg()); + } catch (Exception e) { + log.error("订单{}自动发货触发失败", tid, e); + } + } + }); + } + + // 6. 标记为已处理 + redisTemplate.opsForValue().set(redisDedupKey, "PROCESSED", 24, TimeUnit.HOURS); + } + + @Override + public void sendWWMsg(Long tid, String msg, String sellerOpenUid) { + Map params = new HashMap<>(); + params.put("tid", tid.toString()); + params.put("msg",msg); + doPost(WW_SEND_URL,params, sellerOpenUid); + } + + @Deprecated + private void handleDeliveryResult(Long tid, String sellerOpenUid) { + OrderDO order = kfcOrderService.getOrderByOutTradeNo(tid); + AssertUtils.notNull(order,ORDER_NOT_EXISTS); + List itemsDOS = itemsService.getOrderItemsByOrderId(order.getId()); + List list = new ArrayList<>(); + for (OrderItemsDO itemsDO : itemsDOS) { + Long oid = itemsDO.getOid(); + AgisoCardInfoRequestVO requestVO = new AgisoCardInfoRequestVO(); + List contentRequestVOList = new ArrayList<>(); + List itemRequestVOList = new ArrayList<>(); + + AgisoCardPwdContentRequestVO contentRequestVO = new AgisoCardPwdContentRequestVO(); + contentRequestVO.setCardNoShowType(CardShowTypeEnum.TEXT.getType()) + .setPwdShowType(CardShowTypeEnum.TEXT.getType()) + .setCardPwdItemList(itemRequestVOList) + .setTitle("取餐码"); + requestVO.setOid(oid).setCardPwdContentList(contentRequestVOList); + list.add(requestVO); + } + String content = JSONObject.toJSONString(list); + createDeliveryResult(String.valueOf(tid), AldsTypeEnum.PAY_SUCCESS.getType(), content,sellerOpenUid); + } + + /** + * 存储订单到数据库 + * @param fromPlatform 平台标识 + * @param tid 淘宝订单编号 + * @param agisoOrderPushVO 阿奇索自动推送订单数据 + */ + private String saveOrderDetail(String fromPlatform, Long tid, AgisoOrderPushVO agisoOrderPushVO) { + OrderDO orderDO = kfcOrderService.getOrderByOutTradeNo(tid); + if (orderDO == null) { + AgisoResponse tradeDetail = getTradeDetail(String.valueOf(tid), agisoOrderPushVO.getSellerOpenUid()); + if (tradeDetail != null && tradeDetail.isSuccess()) { + String data = tradeDetail.getData(); + AgisoOrderDetailVO orderDetailVO; + try { + orderDetailVO = objectMapper.readValue(data, AgisoOrderDetailVO.class); + OrderDO orderConvert =convert.convert(agisoOrderPushVO, fromPlatform,orderDetailVO); + Long orderId = kfcOrderService.createOrder(BeanUtils.toBean(orderConvert, OrderSaveReqVO.class)); + List orders = orderDetailVO.getOrders(); + orders.forEach(order -> { + OrderItemsSaveReqVO itemsSaveReqVO = convert.convert(order, orderId); + itemsService.createOrderItems(itemsSaveReqVO); + }); + OrderDO order = kfcOrderService.getOrder(orderId); + return order.getOrderNo(); + } catch (JsonProcessingException e) { + throw exception(JSON_READ_FAILED,e.getMessage()); + } + } + } + return null; + } + + /** + * 验证推送消息的签名 + * @param json 推送的JSON内容 + * @param timestamp 时间戳 + * @param sign 待验证的签名 + * @return 签名是否有效 + */ + private boolean verifyPushSign(String json, Long timestamp, String sign) { + String source = agisoConfig.getAppsecret() + + "json" + json + + "timestamp" + timestamp + + agisoConfig.getAppsecret(); + + byte[] md5Bytes = encryptMD5(source); + String calculatedSign = byte2hex(md5Bytes); + return calculatedSign.equalsIgnoreCase(sign); + } + + /** + * 阿奇索签名算法 + * @param params 业务参数 + * @param appSecret appsecret + * @return 签名字符串 + */ + @Override + public String sign(Map params, String appSecret) { + String[] keys = params.keySet().toArray(new String[0]); + Arrays.sort(keys); + + StringBuilder query = new StringBuilder(); + query.append(appSecret); + for (String key : keys) { + String value = params.get(key); + query.append(key).append(value); + } + query.append(appSecret); + + byte[] md5byte = encryptMD5(query.toString()); + + return byte2hex(md5byte); + } + + // byte数组转成16进制字符串 + private String byte2hex(byte[] bytes) { + StringBuilder sign = new StringBuilder(); + for (byte aByte : bytes) { + String hex = Integer.toHexString(aByte & 0xFF); + if (hex.length() == 1) { + sign.append("0"); + } + sign.append(hex.toLowerCase()); + } + return sign.toString(); + } + + // Md5摘要 + @SneakyThrows + private byte[] encryptMD5(String data) { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + return md5.digest(data.getBytes(StandardCharsets.UTF_8)); + } +} \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/order/KFCOrderService.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/order/KFCOrderService.java index 8752959f74..ebedab3808 100644 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/order/KFCOrderService.java +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/order/KFCOrderService.java @@ -65,4 +65,6 @@ public interface KFCOrderService { Map> getPaidOrderByDate(LocalDateTime startDate, LocalDateTime endDate); OrderDO getOrderByNo(String orderNo); + + OrderDO getOrderByOutTradeNo(Long tid); } \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/order/KFCOrderServiceImpl.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/order/KFCOrderServiceImpl.java index 87cb26fd7f..75a77e40de 100644 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/order/KFCOrderServiceImpl.java +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/order/KFCOrderServiceImpl.java @@ -93,4 +93,9 @@ public class KFCOrderServiceImpl implements KFCOrderService { public OrderDO getOrderByNo(String orderNo) { return KFCOrderMapper.selectOne(OrderDO::getOrderNo, orderNo); } + + @Override + public OrderDO getOrderByOutTradeNo(Long tid) { + return KFCOrderMapper.selectOne(OrderDO::getOutTradeNo, tid); + } } \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/orderitems/OrderItemsService.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/orderitems/OrderItemsService.java index f1a3b60a52..f5c1092565 100644 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/orderitems/OrderItemsService.java +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/orderitems/OrderItemsService.java @@ -60,4 +60,5 @@ public interface OrderItemsService { */ PageResult getOrderItemsPage(OrderItemsPageReqVO pageReqVO); + List getOrderItemsByOrderId(Long orderId); } \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/orderitems/OrderItemsServiceImpl.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/orderitems/OrderItemsServiceImpl.java index 4d7e9bca68..21bac05b9f 100644 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/orderitems/OrderItemsServiceImpl.java +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/service/orderitems/OrderItemsServiceImpl.java @@ -77,4 +77,8 @@ public class OrderItemsServiceImpl implements OrderItemsService { return orderItemsMapper.selectPage(pageReqVO); } + @Override + public List getOrderItemsByOrderId(Long orderId) { + return orderItemsMapper.selectList(OrderItemsDO::getOrderId, orderId); + } } \ No newline at end of file diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/utils/HttpUtils.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/utils/AgisoHttpUtils.java similarity index 97% rename from yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/utils/HttpUtils.java rename to yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/utils/AgisoHttpUtils.java index b692e31c86..061dd425de 100644 --- a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/utils/HttpUtils.java +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/utils/AgisoHttpUtils.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.kfc.utils; -import cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo.AgisoData; import cn.iocoder.yudao.module.kfc.controller.admin.agisoproxy.vo.AgisoResponse; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; @@ -12,7 +11,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; @Slf4j -public class HttpUtils { +public class AgisoHttpUtils { // 单例OkHttpClient实例 private static final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) diff --git a/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/utils/NoGenerator.java b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/utils/NoGenerator.java new file mode 100644 index 0000000000..eaa6dae954 --- /dev/null +++ b/yudao-module-kfc/src/main/java/cn/iocoder/yudao/module/kfc/utils/NoGenerator.java @@ -0,0 +1,81 @@ +package cn.iocoder.yudao.module.kfc.utils; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; + +public class NoGenerator { + private static final Random RANDOM = new Random(); + private static final AtomicInteger SEQUENCE = new AtomicInteger(1000); + private static final String DEFAULT_BUSINESS_CODE = "SQ-KFC"; // 默认业务标识 + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); + + // 私有构造函数,防止实例化 + private NoGenerator() {} + + /** + * 生成默认格式的订单编号:业务标识+日期+随机数 + * 例如:ORD20230615123456789 + */ + public static String generate() { + return generate(DEFAULT_BUSINESS_CODE); + } + + /** + * 生成带业务标识的订单编号 + * 例如:BUS20230615123456789 + */ + public static String generate(String businessCode) { + String datePart = formatDate(Instant.now()); + String randomPart = String.format("%09d", RANDOM.nextInt(1000000000)); + return businessCode + datePart + randomPart; + } + + /** + * 生成带业务标识和时间戳的订单编号 + * 例如:BUS20230615143025123456 + */ + public static String generateWithTimestamp(String businessCode) { + String datePart = formatDate(Instant.now()); + String timestampPart = String.valueOf(System.currentTimeMillis() % 1000000000); + return businessCode + datePart + String.format("%09d", Long.parseLong(timestampPart)); + } + + /** + * 生成带业务标识和递增序列的订单编号 + * 例如:BUS20230615000001 + */ + public static synchronized String generateWithSequence(String businessCode) { + String datePart = formatDate(Instant.now()); + int seq = SEQUENCE.incrementAndGet(); + if (seq > 9999) { // 达到最大值重置 + SEQUENCE.set(1000); + seq = 1000; + } + return businessCode + datePart + seq; + } + + /** + * 生成基于UUID的订单编号 + * 例如:550e8400-e29b-41d4-a716-446655440000 + */ + public static String generateUUID() { + return java.util.UUID.randomUUID().toString().replace("-", ""); + } + + /** + * 格式化日期为yyyymmdd格式 + */ + private static String formatDate(Instant instant) { + if (instant == null) { + return null; + } + LocalDate localDate = instant.atZone(ZoneId.systemDefault()).toLocalDate(); + // 使用预定义的格式化器 + return localDate.format(FORMATTER); + } + +} diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index f204e0404b..1fbbad681e 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -274,5 +274,5 @@ pf4j: # 阿奇索的配置 agiso: - appid: appid - appsecret: appsecret \ No newline at end of file + appid: 2025080857174100113 + appsecret: euhawmh557x9tdzre4vcpnsyh7nkdrmu \ No newline at end of file