阿奇索自动发货接入接口
Some checks failed
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
yudao-ui-admin CI / build (14.x) (push) Has been cancelled
yudao-ui-admin CI / build (16.x) (push) Has been cancelled
Some checks failed
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
yudao-ui-admin CI / build (14.x) (push) Has been cancelled
yudao-ui-admin CI / build (16.x) (push) Has been cancelled
This commit is contained in:
@@ -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<String> getCodeUrl() {
|
||||
return success(agisoAuthService.generateAuthUrl());
|
||||
}
|
||||
}
|
@@ -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<String> getCodeUrl() {
|
||||
return success(agisoService.generateAuthUrl());
|
||||
}
|
||||
}
|
@@ -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<AgisoCardPwdContentRequestVO> cardPwdContentList;
|
||||
}
|
@@ -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<AgisoCardPwdItemRequestVO> cardPwdItemList;
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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<AgisoSubOrderVO> 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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
@@ -31,5 +31,8 @@ public class AgisoResponse {
|
||||
* 详细数据(成功时返回)
|
||||
*/
|
||||
@JsonProperty("Data")
|
||||
private AgisoData data;
|
||||
private String data;
|
||||
|
||||
@JsonProperty("AllowRetry")
|
||||
private String allowRetry;
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
@@ -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 = "赵六")
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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 表
|
||||
*/
|
||||
|
@@ -44,6 +44,10 @@ public class OrderItemsDO extends BaseDO {
|
||||
* 商品规格
|
||||
*/
|
||||
private String productSpec;
|
||||
/**
|
||||
* 子订单编号
|
||||
*/
|
||||
private Long oid;
|
||||
/**
|
||||
* 购买数量
|
||||
*/
|
||||
|
@@ -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读取异常:{}");
|
||||
}
|
||||
|
@@ -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<Integer> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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<Integer> {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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<Integer> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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<Integer> {
|
||||
|
||||
//阿奇索返回平台类型参数值: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;
|
||||
}
|
||||
}
|
@@ -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"; // 替换为实际手机号
|
||||
|
@@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> params, String sellerOpenUid) throws NoSuchAlgorithmException {
|
||||
return execute(url,"POST",params,sellerOpenUid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgisoResponse doGet(String url, Map<String, String> params, String sellerOpenUid) throws NoSuchAlgorithmException {
|
||||
return execute(url,"GET",params,sellerOpenUid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgisoResponse autoShipment(List<String> 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<String, String> 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<String, String> 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));
|
||||
}
|
||||
}
|
@@ -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<String, String> params, String sellerOpenUid) throws IOException, NoSuchAlgorithmException;
|
||||
AgisoResponse execute(String url, String method, Map<String, String> params, String sellerOpenUid) throws IOException;
|
||||
|
||||
AgisoResponse doPost(String url, Map<String, String> params, String sellerOpenUid) throws NoSuchAlgorithmException;
|
||||
AgisoResponse doPost(String url, Map<String, String> params, String sellerOpenUid);
|
||||
|
||||
AgisoResponse doGet(String url, Map<String, String> params, String sellerOpenUid) throws NoSuchAlgorithmException;
|
||||
AgisoResponse doGet(String url, Map<String, String> params, String sellerOpenUid);
|
||||
|
||||
AgisoResponse autoShipment(List<String> tids, String sellerOpenUid) throws NoSuchAlgorithmException;
|
||||
AgisoResponse autoShipment(List<String> 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<String, String> params, String appSecret)
|
||||
throws NoSuchAlgorithmException;
|
@@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> params, String sellerOpenUid) {
|
||||
return execute(url,"POST",params,sellerOpenUid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgisoResponse doGet(String url, Map<String, String> params, String sellerOpenUid) {
|
||||
return execute(url,"GET",params,sellerOpenUid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行自动发货
|
||||
* @param tids 订单号
|
||||
* @param sellerOpenUid 商户开放id
|
||||
* @return 阿奇索统一响应
|
||||
*/
|
||||
@Override
|
||||
public AgisoResponse autoShipment(List<String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<OrderItemsDO> itemsDOS = itemsService.getOrderItemsByOrderId(order.getId());
|
||||
List<AgisoCardInfoRequestVO> list = new ArrayList<>();
|
||||
for (OrderItemsDO itemsDO : itemsDOS) {
|
||||
Long oid = itemsDO.getOid();
|
||||
AgisoCardInfoRequestVO requestVO = new AgisoCardInfoRequestVO();
|
||||
List<AgisoCardPwdContentRequestVO> contentRequestVOList = new ArrayList<>();
|
||||
List<AgisoCardPwdItemRequestVO> 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<AgisoSubOrderVO> 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<String, String> 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));
|
||||
}
|
||||
}
|
@@ -65,4 +65,6 @@ public interface KFCOrderService {
|
||||
Map<Long, List<OrderDO>> getPaidOrderByDate(LocalDateTime startDate, LocalDateTime endDate);
|
||||
|
||||
OrderDO getOrderByNo(String orderNo);
|
||||
|
||||
OrderDO getOrderByOutTradeNo(Long tid);
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -60,4 +60,5 @@ public interface OrderItemsService {
|
||||
*/
|
||||
PageResult<OrderItemsDO> getOrderItemsPage(OrderItemsPageReqVO pageReqVO);
|
||||
|
||||
List<OrderItemsDO> getOrderItemsByOrderId(Long orderId);
|
||||
}
|
@@ -77,4 +77,8 @@ public class OrderItemsServiceImpl implements OrderItemsService {
|
||||
return orderItemsMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OrderItemsDO> getOrderItemsByOrderId(Long orderId) {
|
||||
return orderItemsMapper.selectList(OrderItemsDO::getOrderId, orderId);
|
||||
}
|
||||
}
|
@@ -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)
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -274,5 +274,5 @@ pf4j:
|
||||
|
||||
# 阿奇索的配置
|
||||
agiso:
|
||||
appid: appid
|
||||
appsecret: appsecret
|
||||
appid: 2025080857174100113
|
||||
appsecret: euhawmh557x9tdzre4vcpnsyh7nkdrmu
|
Reference in New Issue
Block a user