【同步】BOOT 和 CLOUD 的功能

pull/206/head
YunaiV 2025-08-18 00:26:28 +08:00
parent 49572b2994
commit 8e45cf5787
19 changed files with 250 additions and 97 deletions

View File

@ -47,8 +47,15 @@ public class HttpUtils {
return builder.build();
}
private String append(String base, Map<String, ?> query, boolean fragment) {
return append(base, query, null, fragment);
public static String removeUrlQuery(String url) {
if (!StrUtil.contains(url, '?')) {
return url;
}
UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset());
// 移除 query、fragment
builder.setQuery(null);
builder.setFragment(null);
return builder.build();
}
/**

View File

@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.pojo.SortingField;
import cn.iocoder.yudao.framework.mybatis.core.enums.DbTypeEnum;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
@ -47,16 +48,36 @@ public class MyBatisUtils {
return page;
}
@SuppressWarnings("PatternVariableCanBeUsed")
public static <T> void addOrder(Wrapper<T> wrapper, Collection<SortingField> sortingFields) {
if (CollUtil.isEmpty(sortingFields)) {
return;
}
if (wrapper instanceof QueryWrapper<T>) {
QueryWrapper<T> query = (QueryWrapper<T>) wrapper;
for (SortingField sortingField : sortingFields) {
query.orderBy(true,
SortingField.ORDER_ASC.equals(sortingField.getOrder()),
StrUtil.toUnderlineCase(sortingField.getField()));
}
} else if (wrapper instanceof LambdaQueryWrapper<T>) {
// LambdaQueryWrapper 不直接支持字符串字段排序,使用 last 方法拼接 ORDER BY
LambdaQueryWrapper<T> lambdaQuery = (LambdaQueryWrapper<T>) wrapper;
StringBuilder orderBy = new StringBuilder();
for (SortingField sortingField : sortingFields) {
if (StrUtil.isNotEmpty(orderBy)) {
orderBy.append(", ");
}
orderBy.append(StrUtil.toUnderlineCase(sortingField.getField()))
.append(" ")
.append(SortingField.ORDER_ASC.equals(sortingField.getOrder()) ? "ASC" : "DESC");
}
lambdaQuery.last("ORDER BY " + orderBy);
// 另外个思路https://blog.csdn.net/m0_59084856/article/details/138450913
} else {
throw new IllegalArgumentException("Unsupported wrapper type: " + wrapper.getClass().getName());
}
}
/**

View File

@ -128,6 +128,7 @@ public class ApiEncryptFilter extends ApiRequestFilter {
*
* @param request
*/
@SuppressWarnings("PatternVariableCanBeUsed")
private ApiEncrypt getApiEncrypt(HttpServletRequest request) {
try {
HandlerExecutionChain mappingHandler = requestMappingHandlerMapping.getHandler(request);
@ -135,7 +136,8 @@ public class ApiEncryptFilter extends ApiRequestFilter {
return null;
}
Object handler = mappingHandler.getHandler();
if (handler instanceof HandlerMethod handlerMethod) {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
ApiEncrypt annotation = handlerMethod.getMethodAnnotation(ApiEncrypt.class);
if (annotation == null) {
annotation = handlerMethod.getBeanType().getAnnotation(ApiEncrypt.class);

View File

@ -8,8 +8,10 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
@Tag(name = "RPC 服务 - 文件")
@ -56,4 +58,16 @@ public interface FileApi {
@Operation(summary = "保存文件,并返回文件的访问路径")
CommonResult<String> createFile(@Valid @RequestBody FileCreateReqDTO createReqDTO);
/**
*
*
* @param url 访
* @param expirationSeconds 访
* @return
*/
@GetMapping(PREFIX + "/presigned-url")
@Operation(summary = "生成文件预签名地址,用于读取")
CommonResult<String> presignGetUrl(@NotEmpty(message = "URL 不能为空") @RequestParam("url") String url,
Integer expirationSeconds);
}

View File

@ -23,4 +23,9 @@ public class FileApiImpl implements FileApi {
createReqDTO.getDirectory(), createReqDTO.getType()));
}
@Override
public CommonResult<String> presignGetUrl(String url, Integer expirationSeconds) {
return success(fileService.presignGetUrl(url, expirationSeconds));
}
}

View File

@ -51,7 +51,7 @@ public class FileController {
}
@GetMapping("/presigned-url")
@Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")
@Operation(summary = "获取文件预签名地址(上传)", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")
@Parameters({
@Parameter(name = "name", description = "文件名称", required = true),
@Parameter(name = "directory", description = "文件目录")
@ -59,7 +59,7 @@ public class FileController {
public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(
@RequestParam("name") String name,
@RequestParam(value = "directory", required = false) String directory) {
return success(fileService.getFilePresignedUrl(name, directory));
return success(fileService.presignPutUrl(name, directory));
}
@PostMapping("/create")

View File

@ -41,7 +41,7 @@ public class AppFileController {
}
@GetMapping("/presigned-url")
@Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")
@Operation(summary = "获取文件预签名地址(上传)", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")
@Parameters({
@Parameter(name = "name", description = "文件名称", required = true),
@Parameter(name = "directory", description = "文件目录")
@ -49,7 +49,7 @@ public class AppFileController {
public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(
@RequestParam("name") String name,
@RequestParam(value = "directory", required = false) String directory) {
return success(fileService.getFilePresignedUrl(name, directory));
return success(fileService.presignPutUrl(name, directory));
}
@PostMapping("/create")

View File

@ -1,7 +1,5 @@
package cn.iocoder.yudao.module.infra.framework.file.core.client;
import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO;
/**
*
*
@ -42,13 +40,26 @@ public interface FileClient {
*/
byte[] getContent(String path) throws Exception;
// ========== 文件签名,目前仅 S3 支持 ==========
/**
*
*
*
* @param path
* @return
*/
default FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {
default String presignPutUrl(String path) {
throw new UnsupportedOperationException("不支持的操作");
}
/**
*
*
* @param url 访
* @param expirationSeconds 访
* @return
*/
default String presignGetUrl(String url, Integer expirationSeconds) {
throw new UnsupportedOperationException("不支持的操作");
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.infra.framework.file.core.client.local;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient;
import java.io.File;
@ -38,7 +39,14 @@ public class LocalFileClient extends AbstractFileClient<LocalFileClientConfig> {
@Override
public byte[] getContent(String path) {
String filePath = getFilePath(path);
try {
return FileUtil.readBytes(filePath);
} catch (IORuntimeException ex) {
if (ex.getMessage().startsWith("File not exist:")) {
return null;
}
throw ex;
}
}
private String getFilePath(String path) {

View File

@ -1,29 +0,0 @@
package cn.iocoder.yudao.module.infra.framework.file.core.client.s3;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Response DTO
*
* @author owen
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FilePresignedUrlRespDTO {
/**
* URL
*
*
*/
private String uploadUrl;
/**
* URL
*/
private String url;
}

View File

@ -1,8 +1,10 @@
package cn.iocoder.yudao.module.infra.framework.file.core.client.s3;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
@ -15,9 +17,11 @@ import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;
import java.net.URI;
import java.net.URL;
import java.time.Duration;
/**
@ -27,6 +31,8 @@ import java.time.Duration;
*/
public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
private static final Duration EXPIRATION_DEFAULT = Duration.ofHours(24);
private S3Client client;
private S3Presigner presigner;
@ -75,7 +81,7 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
// 上传文件
client.putObject(putRequest, RequestBody.fromBytes(content));
// 拼接返回路径
return config.getDomain() + "/" + path;
return presignGetUrl(path, null);
}
@Override
@ -97,23 +103,38 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
}
@Override
public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) {
Duration expiration = Duration.ofHours(24);
return new FilePresignedUrlRespDTO(getPresignedUrl(path, expiration), config.getDomain() + "/" + path);
public String presignPutUrl(String path) {
return presigner.presignPutObject(PutObjectPresignRequest.builder()
.signatureDuration(EXPIRATION_DEFAULT)
.putObjectRequest(b -> b.bucket(config.getBucket()).key(path)).build())
.url().toString();
}
/**
* URL
*
* @param path
* @param expiration
* @return URL
*/
private String getPresignedUrl(String path, Duration expiration) {
return presigner.presignPutObject(PutObjectPresignRequest.builder()
@Override
public String presignGetUrl(String url, Integer expirationSeconds) {
// 1. 将 url 转换为 path
String path = StrUtil.removePrefix(url, config.getDomain() + "/");
path = HttpUtils.removeUrlQuery(path);
// 2.1 情况一:公开访问:无需签名
// 考虑到老版本的兼容,所以必须是 config.getEnablePublicAccess() 为 false 时,才进行签名
if (!BooleanUtil.isFalse(config.getEnablePublicAccess())) {
return config.getDomain() + "/" + path;
}
// 2.2 情况二:私有访问:生成 GET 预签名 URL
String finalPath = path;
Duration expiration = expirationSeconds != null ? Duration.ofSeconds(expirationSeconds) : EXPIRATION_DEFAULT;
URL signedUrl = presigner.presignGetObject(GetObjectPresignRequest.builder()
.signatureDuration(expiration)
.putObjectRequest(b -> b.bucket(config.getBucket()).key(path))
.build()).url().toString();
.getObjectRequest(b -> b.bucket(config.getBucket()).key(finalPath)).build())
.url();
// 特殊:适配未使用 domain 返回的情况!!!
String signedUrlStr = signedUrl.toString();
if (!signedUrlStr.startsWith(config.getDomain())) {
signedUrlStr = signedUrlStr.replaceFirst(signedUrl.getProtocol() + "://" + signedUrl.getHost(), config.getDomain());
}
return signedUrlStr;
}
/**

View File

@ -3,11 +3,12 @@ package cn.iocoder.yudao.module.infra.framework.file.core.client.s3;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientConfig;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotNull;
/**
* S3
*
@ -72,6 +73,15 @@ public class S3FileClientConfig implements FileClientConfig {
@NotNull(message = "enablePathStyleAccess 不能为空")
private Boolean enablePathStyleAccess;
/**
* 访
*
* true访访
* false访 accessKey 访
*/
@NotNull(message = "是否公开访问不能为空")
private Boolean enablePublicAccess;
@SuppressWarnings("RedundantIfStatement")
@AssertTrue(message = "domain 不能为空")
@JsonIgnore

View File

@ -80,9 +80,15 @@ public class FileTypeUtils {
*/
public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {
// 设置 header 和 contentType
response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
String contentType = getMineType(content, filename);
response.setContentType(contentType);
// 设置内容显示、下载文件名https://www.cnblogs.com/wq-9/articles/12165056.html
if (StrUtil.containsIgnoreCase(contentType, "image/")) {
// 参见 https://github.com/YunaiV/ruoyi-vue-pro/issues/692 讨论
response.setHeader("Content-Disposition", "inline;filename=" + HttpUtils.encodeUtf8(filename));
} else {
response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
}
// 针对 video 的特殊处理,解决视频地址在移动端播放的兼容性问题
if (StrUtil.containsIgnoreCase(contentType, "video")) {
response.setHeader("Content-Length", String.valueOf(content.length));

View File

@ -37,14 +37,22 @@ public interface FileService {
String name, String directory, String type);
/**
*
*
*
* @param name
* @param directory
* @return
*/
FilePresignedUrlRespVO getFilePresignedUrl(@NotEmpty(message = "文件名不能为空") String name,
FilePresignedUrlRespVO presignPutUrl(@NotEmpty(message = "文件名不能为空") String name,
String directory);
/**
*
*
* @param url 访
* @param expirationSeconds 访
* @return
*/
String presignGetUrl(String url, Integer expirationSeconds);
/**
*

View File

@ -6,6 +6,7 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
@ -13,7 +14,6 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresigned
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper;
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;
import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO;
import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils;
import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Resource;
@ -126,19 +126,27 @@ public class FileServiceImpl implements FileService {
@Override
@SneakyThrows
public FilePresignedUrlRespVO getFilePresignedUrl(String name, String directory) {
public FilePresignedUrlRespVO presignPutUrl(String name, String directory) {
// 1. 生成上传的 path需要保证唯一
String path = generateUploadPath(name, directory);
// 2. 获取文件预签名地址
FileClient fileClient = fileConfigService.getMasterFileClient();
FilePresignedUrlRespDTO presignedObjectUrl = fileClient.getPresignedObjectUrl(path);
return BeanUtils.toBean(presignedObjectUrl, FilePresignedUrlRespVO.class,
object -> object.setConfigId(fileClient.getId()).setPath(path));
String uploadUrl = fileClient.presignPutUrl(path);
String visitUrl = fileClient.presignGetUrl(path, null);
return new FilePresignedUrlRespVO().setConfigId(fileClient.getId())
.setPath(path).setUploadUrl(uploadUrl).setUrl(visitUrl);
}
@Override
public String presignGetUrl(String url, Integer expirationSeconds) {
FileClient fileClient = fileConfigService.getMasterFileClient();
return fileClient.presignGetUrl(url, expirationSeconds);
}
@Override
public Long createFile(FileCreateReqVO createReqVO) {
createReqVO.setUrl(HttpUtils.removeUrlQuery(createReqVO.getUrl())); // 目的移除私有桶情况下URL 的签名参数
FileDO file = BeanUtils.toBean(createReqVO, FileDO.class);
fileMapper.insert(file);
return file.getId();

View File

@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileC
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
public class LocalFileClientTest {
@Test
@ -26,4 +28,18 @@ public class LocalFileClientTest {
client.delete(path);
}
@Test
@Disabled
public void testGetContent_notFound() {
// 创建客户端
LocalFileClientConfig config = new LocalFileClientConfig();
config.setDomain("http://127.0.0.1:48080");
config.setBasePath("/Users/yunai/file_test");
LocalFileClient client = new LocalFileClient(0L, config);
client.init();
// 上传文件
byte[] content = client.getContent(randomString());
System.out.println();
}
}

View File

@ -9,6 +9,7 @@ import jakarta.validation.Validation;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@SuppressWarnings("resource")
public class S3FileClientTest {
@Test
@ -70,6 +71,7 @@ public class S3FileClientTest {
config.setAccessSecret("kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP");
config.setBucket("ruoyi-vue-pro");
config.setDomain("http://test.yudao.iocoder.cn"); // 如果有自定义域名则可以设置。http://static.yudao.iocoder.cn
config.setEnablePathStyleAccess(false);
// 默认上海的 endpoint
config.setEndpoint("s3-cn-south-1.qiniucs.com");
@ -77,6 +79,32 @@ public class S3FileClientTest {
testExecuteUpload(config);
}
@Test
@Disabled // 七牛云存储(读私有桶),如果要集成测试,可以注释本行
public void testQiniu_privateGet() {
S3FileClientConfig config = new S3FileClientConfig();
// 配置成你自己的
// config.setAccessKey(System.getenv("QINIU_ACCESS_KEY"));
// config.setAccessSecret(System.getenv("QINIU_SECRET_KEY"));
config.setAccessKey("b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8");
config.setAccessSecret("kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP");
config.setBucket("ruoyi-vue-pro-private");
config.setDomain("http://t151glocd.hn-bkt.clouddn.com"); // 如果有自定义域名则可以设置。http://static.yudao.iocoder.cn
config.setEnablePathStyleAccess(false);
// 默认上海的 endpoint
config.setEndpoint("s3-cn-south-1.qiniucs.com");
// 校验配置
ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config);
// 创建 Client
S3FileClient client = new S3FileClient(0L, config);
client.init();
// 执行生成 URL 签名
String path = "output.png";
String presignedUrl = client.presignGetUrl(path, 300);
System.out.println(presignedUrl);
}
@Test
@Disabled // 华为云存储,如果要集成测试,可以注释本行
public void testHuaweiCloud() throws Exception {
@ -93,7 +121,7 @@ public class S3FileClientTest {
testExecuteUpload(config);
}
private void testExecuteUpload(S3FileClientConfig config) throws Exception {
private void testExecuteUpload(S3FileClientConfig config) {
// 校验配置
ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config);
// 创建 Client

View File

@ -30,7 +30,7 @@ public interface CouponConvert {
CouponRespDTO convert(CouponDO bean);
default CouponDO convert(CouponTemplateDO template, Long userId) {
CouponDO couponDO = new CouponDO()
CouponDO coupon = new CouponDO()
.setTemplateId(template.getId())
.setName(template.getName())
.setTakeType(template.getTakeType())
@ -44,13 +44,13 @@ public interface CouponConvert {
.setStatus(CouponStatusEnum.UNUSED.getStatus())
.setUserId(userId);
if (CouponTemplateValidityTypeEnum.DATE.getType().equals(template.getValidityType())) {
couponDO.setValidStartTime(template.getValidStartTime());
couponDO.setValidEndTime(template.getValidEndTime());
coupon.setValidStartTime(template.getValidStartTime());
coupon.setValidEndTime(template.getValidEndTime());
} else if (CouponTemplateValidityTypeEnum.TERM.getType().equals(template.getValidityType())) {
couponDO.setValidStartTime(LocalDateTime.now().plusDays(template.getFixedStartTerm()));
couponDO.setValidEndTime(LocalDateTime.now().plusDays(template.getFixedEndTerm()));
coupon.setValidStartTime(LocalDateTime.now().plusDays(template.getFixedStartTerm()));
coupon.setValidEndTime(coupon.getValidStartTime().plusDays(template.getFixedEndTerm()));
}
return couponDO;
return coupon;
}
CouponPageReqVO convert(AppCouponPageReqVO pageReqVO, Collection<Long> userIds);

View File

@ -42,6 +42,40 @@ public class TradeStatusSyncToWxaOrderHandler implements TradeOrderHandler {
if (ObjUtil.notEqual(order.getPayChannelCode(), PayChannelEnum.WX_LITE.getCode())) {
return;
}
// 上传订单物流信息到微信小程序
uploadWxaOrderShippingInfo(order);
}
@Override
public void afterReceiveOrder(TradeOrderDO order) {
// 注意:只有微信小程序支付的订单,才需要同步
if (ObjUtil.notEqual(order.getPayChannelCode(), PayChannelEnum.WX_LITE.getCode())) {
return;
}
PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId()).getCheckedData();
SocialWxaOrderNotifyConfirmReceiveReqDTO reqDTO = new SocialWxaOrderNotifyConfirmReceiveReqDTO()
.setTransactionId(payOrder.getChannelOrderNo())
.setReceivedTime(order.getReceiveTime());
try {
socialClientApi.notifyWxaOrderConfirmReceive(UserTypeEnum.MEMBER.getValue(), reqDTO);
} catch (Exception ex) {
log.error("[afterReceiveOrder][订单({}) 通知订单收货到微信小程序失败]", order, ex);
}
// 如果是门店自提订单,上传订单物流信息到微信小程序
// 原因是,门店自提订单没有 “afterDeliveryOrder” 阶段。可见 https://t.zsxq.com/KWD3u 反馈
if (DeliveryTypeEnum.PICK_UP.getType().equals(order.getDeliveryType())) {
uploadWxaOrderShippingInfo(order);
}
}
/**
*
*
* @param order
*/
private void uploadWxaOrderShippingInfo(TradeOrderDO order) {
PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId()).getCheckedData();
SocialWxaOrderUploadShippingInfoReqDTO reqDTO = new SocialWxaOrderUploadShippingInfoReqDTO()
.setTransactionId(payOrder.getChannelOrderNo())
@ -58,29 +92,12 @@ public class TradeStatusSyncToWxaOrderHandler implements TradeOrderHandler {
reqDTO.setLogisticsType(SocialWxaOrderUploadShippingInfoReqDTO.LOGISTICS_TYPE_VIRTUAL);
}
try {
socialClientApi.uploadWxaOrderShippingInfo(UserTypeEnum.MEMBER.getValue(), reqDTO).checkError();
socialClientApi.uploadWxaOrderShippingInfo(UserTypeEnum.MEMBER.getValue(), reqDTO);
} catch (Exception ex) {
log.error("[afterDeliveryOrder][订单({}) 上传订单物流信息到微信小程序失败]", order, ex);
}
}
@Override
public void afterReceiveOrder(TradeOrderDO order) {
// 注意:只有微信小程序支付的订单,才需要同步
if (ObjUtil.notEqual(order.getPayChannelCode(), PayChannelEnum.WX_LITE.getCode())) {
return;
}
PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId()).getCheckedData();
SocialWxaOrderNotifyConfirmReceiveReqDTO reqDTO = new SocialWxaOrderNotifyConfirmReceiveReqDTO()
.setTransactionId(payOrder.getChannelOrderNo())
.setReceivedTime(order.getReceiveTime());
try {
socialClientApi.notifyWxaOrderConfirmReceive(UserTypeEnum.MEMBER.getValue(), reqDTO).getCheckedData();
} catch (Exception ex) {
log.error("[afterReceiveOrder][订单({}) 通知订单收货到微信小程序失败]", order, ex);
}
}
// TODO @芋艿:【设置路径】 https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order-shipping/order-shipping.html#%E5%85%AD%E3%80%81%E6%B6%88%E6%81%AF%E8%B7%B3%E8%BD%AC%E8%B7%AF%E5%BE%84%E8%AE%BE%E7%BD%AE%E6%8E%A5%E5%8F%A3
}