【同步】BOOT 和 CLOUD 的功能(system)
parent
f57f0c551c
commit
3e5e60ce96
|
|
@ -67,7 +67,7 @@ public class TenantController {
|
||||||
@Operation(summary = "使用域名,获得租户信息", description = "登录界面,根据用户的域名,获得租户信息")
|
@Operation(summary = "使用域名,获得租户信息", description = "登录界面,根据用户的域名,获得租户信息")
|
||||||
@Parameter(name = "website", description = "域名", required = true, example = "www.iocoder.cn")
|
@Parameter(name = "website", description = "域名", required = true, example = "www.iocoder.cn")
|
||||||
public CommonResult<TenantRespVO> getTenantByWebsite(
|
public CommonResult<TenantRespVO> getTenantByWebsite(
|
||||||
@RequestParam("website") @Pattern(regexp = "^[a-zA-Z0-9.-]+$", message = "网站域名格式不正确") String website) {
|
@RequestParam("website") @Pattern(regexp = "^[a-zA-Z0-9.-]+(:\\d{1,5})?$", message = "网站域名格式不正确") String website) {
|
||||||
TenantDO tenant = tenantService.getTenantByWebsite(website);
|
TenantDO tenant = tenantService.getTenantByWebsite(website);
|
||||||
if (tenant == null || CommonStatusEnum.isDisable(tenant.getStatus())) {
|
if (tenant == null || CommonStatusEnum.isDisable(tenant.getStatus())) {
|
||||||
return success(null);
|
return success(null);
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public class AppTenantController {
|
||||||
@Operation(summary = "使用域名,获得租户信息", description = "根据用户的域名,获得租户信息")
|
@Operation(summary = "使用域名,获得租户信息", description = "根据用户的域名,获得租户信息")
|
||||||
@Parameter(name = "website", description = "域名", required = true, example = "www.iocoder.cn")
|
@Parameter(name = "website", description = "域名", required = true, example = "www.iocoder.cn")
|
||||||
public CommonResult<AppTenantRespVO> getTenantByWebsite(
|
public CommonResult<AppTenantRespVO> getTenantByWebsite(
|
||||||
@RequestParam("website") @Pattern(regexp = "^[a-zA-Z0-9.-]+$", message = "网站域名格式不正确") String website) {
|
@RequestParam("website") @Pattern(regexp = "^[a-zA-Z0-9.-]+(:\\d{1,5})?$", message = "网站域名格式不正确") String website) {
|
||||||
TenantDO tenant = tenantService.getTenantByWebsite(website);
|
TenantDO tenant = tenantService.getTenantByWebsite(website);
|
||||||
if (tenant == null || CommonStatusEnum.isDisable(tenant.getStatus())) {
|
if (tenant == null || CommonStatusEnum.isDisable(tenant.getStatus())) {
|
||||||
return success(null);
|
return success(null);
|
||||||
|
|
|
||||||
|
|
@ -303,7 +303,7 @@ public class PermissionServiceImpl implements PermissionService {
|
||||||
CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
|
CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
|
||||||
// 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。
|
// 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。
|
||||||
// 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉
|
// 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉
|
||||||
CollUtil.addAll(result.getDeptIds(), userDeptId.get());
|
CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptId.get());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// 情况三,DEPT_ONLY
|
// 情况三,DEPT_ONLY
|
||||||
|
|
@ -313,9 +313,14 @@ public class PermissionServiceImpl implements PermissionService {
|
||||||
}
|
}
|
||||||
// 情况四,DEPT_DEPT_AND_CHILD
|
// 情况四,DEPT_DEPT_AND_CHILD
|
||||||
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
|
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
|
||||||
CollUtil.addAll(result.getDeptIds(), deptService.getChildDeptIdListFromCache(userDeptId.get()));
|
Long deptId = userDeptId.get();
|
||||||
|
// 用户未设置部门,直接跳过;否则 getChildDeptIdListFromCache 走缓存注解会因 null key 报错
|
||||||
|
if (deptId == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CollUtil.addAll(result.getDeptIds(), deptService.getChildDeptIdListFromCache(deptId));
|
||||||
// 添加本身部门编号
|
// 添加本身部门编号
|
||||||
CollUtil.addAll(result.getDeptIds(), userDeptId.get());
|
result.getDeptIds().add(deptId);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// 情况五,SELF
|
// 情况五,SELF
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import cn.binarywang.wx.miniapp.constant.WxMaConstants;
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.thread.ThreadUtil;
|
||||||
import cn.hutool.core.util.DesensitizedUtil;
|
import cn.hutool.core.util.DesensitizedUtil;
|
||||||
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.hutool.core.util.ReflectUtil;
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
|
@ -103,13 +104,9 @@ public class SocialClientServiceImpl implements SocialClientService {
|
||||||
public String miniprogramState;
|
public String miniprogramState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传发货信息重试次数
|
* 上传发货信息重试间隔(毫秒),数组长度即重试次数;总等待最坏 1 + 2 + 4 = 7 秒,覆盖微信支付回调的常见延迟
|
||||||
*/
|
*/
|
||||||
private static final int UPLOAD_SHIPPING_INFO_MAX_RETRIES = 5;
|
private static final long[] UPLOAD_SHIPPING_INFO_RETRY_BACKOFF_MILLIS = {1000, 2000, 4000};
|
||||||
/**
|
|
||||||
* 上传发货信息重试间隔
|
|
||||||
*/
|
|
||||||
private static final Duration UPLOAD_SHIPPING_INFO_RETRY_INTERVAL = Duration.ofMillis(500L);
|
|
||||||
/**
|
/**
|
||||||
* 微信错误码:支付单不存在
|
* 微信错误码:支付单不存在
|
||||||
*/
|
*/
|
||||||
|
|
@ -383,31 +380,23 @@ public class SocialClientServiceImpl implements SocialClientService {
|
||||||
.build();
|
.build();
|
||||||
// 重试机制:解决支付回调与订单信息上传之间的时间差导致的 10060001 错误
|
// 重试机制:解决支付回调与订单信息上传之间的时间差导致的 10060001 错误
|
||||||
// 对应 ISSUE:https://gitee.com/zhijiantianya/yudao-cloud/pulls/230
|
// 对应 ISSUE:https://gitee.com/zhijiantianya/yudao-cloud/pulls/230
|
||||||
for (int attempt = 1; attempt <= UPLOAD_SHIPPING_INFO_MAX_RETRIES; attempt++) {
|
// 注意:wx-java 的 upload 内部对 errCode != 0 直接抛 WxErrorException,所以重试判断必须基于异常的 errorCode
|
||||||
|
int maxAttempts = UPLOAD_SHIPPING_INFO_RETRY_BACKOFF_MILLIS.length + 1;
|
||||||
|
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||||
try {
|
try {
|
||||||
WxMaOrderShippingInfoBaseResponse response = service.getWxMaOrderShippingService().upload(request);
|
WxMaOrderShippingInfoBaseResponse response = service.getWxMaOrderShippingService().upload(request);
|
||||||
// 成功,直接返回
|
log.info("[uploadWxaOrderShippingInfo][上传微信小程序发货信息成功:request({}) response({})]", request, response);
|
||||||
if (response.getErrCode() == 0) {
|
return;
|
||||||
log.info("[uploadWxaOrderShippingInfo][上传微信小程序发货信息成功:request({}) response({})]", request, response);
|
} catch (WxErrorException ex) {
|
||||||
return;
|
if (ex.getError().getErrorCode() == WX_ERR_CODE_PAY_ORDER_NOT_EXIST && attempt < maxAttempts) {
|
||||||
}
|
long delayMillis = UPLOAD_SHIPPING_INFO_RETRY_BACKOFF_MILLIS[attempt - 1];
|
||||||
// 如果是 10060001 错误(支付单不存在)且还有重试次数,则等待后重试
|
log.warn("[uploadWxaOrderShippingInfo][第 {} 次尝试失败,支付单不存在,{} ms 后重试:request({})]",
|
||||||
if (response.getErrCode() == WX_ERR_CODE_PAY_ORDER_NOT_EXIST && attempt < UPLOAD_SHIPPING_INFO_MAX_RETRIES) {
|
attempt, delayMillis, request, ex);
|
||||||
log.warn("[uploadWxaOrderShippingInfo][第 {} 次尝试失败,支付单不存在,{} 后重试:request({}) response({})]",
|
ThreadUtil.sleep(delayMillis);
|
||||||
attempt, UPLOAD_SHIPPING_INFO_RETRY_INTERVAL, request, response);
|
|
||||||
Thread.sleep(UPLOAD_SHIPPING_INFO_RETRY_INTERVAL.toMillis());
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// 其他错误或重试次数用尽,抛出异常
|
|
||||||
log.error("[uploadWxaOrderShippingInfo][上传微信小程序发货信息失败:request({}) response({})]", request, response);
|
|
||||||
throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR, response.getErrMsg());
|
|
||||||
} catch (WxErrorException ex) {
|
|
||||||
log.error("[uploadWxaOrderShippingInfo][上传微信小程序发货信息失败:request({})]", request, ex);
|
log.error("[uploadWxaOrderShippingInfo][上传微信小程序发货信息失败:request({})]", request, ex);
|
||||||
throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR, ex.getError().getErrorMsg());
|
throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR, ex.getError().getErrorMsg());
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
log.error("[uploadWxaOrderShippingInfo][重试等待被中断:request({})]", request, ex);
|
|
||||||
throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR, "重试等待被中断");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -440,6 +440,34 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetDeptDataPermission_DeptCustom_userDeptIdNull() {
|
||||||
|
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||||
|
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||||
|
.thenReturn(permissionService);
|
||||||
|
|
||||||
|
// 准备参数
|
||||||
|
Long userId = 1L;
|
||||||
|
// mock 用户的角色编号
|
||||||
|
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
|
||||||
|
// mock 获得用户的角色
|
||||||
|
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope())
|
||||||
|
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||||
|
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
|
||||||
|
// mock 部门的返回:用户未设置部门
|
||||||
|
when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO()); // deptId 为 null
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||||
|
// 断言:角色配置的可见部门仍正常加入,但 null 不进集合
|
||||||
|
assertFalse(result.getAll());
|
||||||
|
assertFalse(result.getSelf());
|
||||||
|
assertEquals(roleDO.getDataScopeDeptIds().size(), result.getDeptIds().size());
|
||||||
|
assertTrue(CollUtil.containsAll(result.getDeptIds(), roleDO.getDataScopeDeptIds()));
|
||||||
|
assertFalse(result.getDeptIds().contains(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetDeptDataPermission_DeptOnly() {
|
public void testGetDeptDataPermission_DeptOnly() {
|
||||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||||
|
|
@ -500,6 +528,33 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetDeptDataPermission_DeptAndChild_userDeptIdNull() {
|
||||||
|
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||||
|
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||||
|
.thenReturn(permissionService);
|
||||||
|
|
||||||
|
// 准备参数
|
||||||
|
Long userId = 1L;
|
||||||
|
// mock 用户的角色编号
|
||||||
|
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
|
||||||
|
// mock 获得用户的角色
|
||||||
|
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope())
|
||||||
|
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||||
|
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
|
||||||
|
// mock 部门的返回:用户未设置部门
|
||||||
|
when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO()); // deptId 为 null
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||||
|
// 断言:deptId 为 null,整段跳过;deptIds 为空,子部门查询不被触发
|
||||||
|
assertFalse(result.getAll());
|
||||||
|
assertFalse(result.getSelf());
|
||||||
|
assertTrue(CollUtil.isEmpty(result.getDeptIds()));
|
||||||
|
verify(deptService, never()).getChildDeptIdListFromCache(any());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetDeptDataPermission_Self() {
|
public void testGetDeptDataPermission_Self() {
|
||||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
package cn.iocoder.yudao.module.system.service.social;
|
package cn.iocoder.yudao.module.system.service.social;
|
||||||
|
|
||||||
|
import cn.binarywang.wx.miniapp.api.WxMaOrderShippingService;
|
||||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||||
import cn.binarywang.wx.miniapp.api.WxMaUserService;
|
import cn.binarywang.wx.miniapp.api.WxMaUserService;
|
||||||
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
|
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
|
||||||
|
import cn.binarywang.wx.miniapp.bean.shop.request.shipping.WxMaOrderShippingInfoUploadRequest;
|
||||||
|
import cn.binarywang.wx.miniapp.bean.shop.response.WxMaOrderShippingInfoBaseResponse;
|
||||||
import cn.hutool.core.util.ReflectUtil;
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||||
|
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderUploadShippingInfoReqDTO;
|
||||||
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;
|
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;
|
||||||
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;
|
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;
|
||||||
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
|
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
|
||||||
|
|
@ -18,6 +22,7 @@ import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties;
|
||||||
import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
|
import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import me.chanjar.weixin.common.bean.WxJsapiSignature;
|
import me.chanjar.weixin.common.bean.WxJsapiSignature;
|
||||||
|
import me.chanjar.weixin.common.error.WxError;
|
||||||
import me.chanjar.weixin.common.error.WxErrorException;
|
import me.chanjar.weixin.common.error.WxErrorException;
|
||||||
import me.chanjar.weixin.mp.api.WxMpService;
|
import me.chanjar.weixin.mp.api.WxMpService;
|
||||||
import me.zhyd.oauth.config.AuthConfig;
|
import me.zhyd.oauth.config.AuthConfig;
|
||||||
|
|
@ -26,6 +31,7 @@ import me.zhyd.oauth.model.AuthUser;
|
||||||
import me.zhyd.oauth.request.AuthDefaultRequest;
|
import me.zhyd.oauth.request.AuthDefaultRequest;
|
||||||
import me.zhyd.oauth.request.AuthRequest;
|
import me.zhyd.oauth.request.AuthRequest;
|
||||||
import me.zhyd.oauth.utils.AuthStateUtils;
|
import me.zhyd.oauth.utils.AuthStateUtils;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.MockedStatic;
|
import org.mockito.MockedStatic;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
@ -468,4 +474,100 @@ public class SocialClientServiceImplTest extends BaseDbUnitTest {
|
||||||
assertPojoEquals(dbSocialClient, pageResult.getList().get(0));
|
assertPojoEquals(dbSocialClient, pageResult.getList().get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =================== 微信小程序订单发货 ===================
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setUpUploadShippingBackoff() {
|
||||||
|
// 测试场景下把退避数组的每一项改为 1ms,避免拖慢用例(数组引用是 final 但元素可改)
|
||||||
|
long[] backoff = (long[]) ReflectUtil.getFieldValue(SocialClientServiceImpl.class,
|
||||||
|
"UPLOAD_SHIPPING_INFO_RETRY_BACKOFF_MILLIS");
|
||||||
|
for (int i = 0; i < backoff.length; i++) {
|
||||||
|
backoff[i] = 1L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUploadWxaOrderShippingInfo_success() throws WxErrorException {
|
||||||
|
// 准备参数
|
||||||
|
Integer userType = randomPojo(UserTypeEnum.class).getValue();
|
||||||
|
SocialWxaOrderUploadShippingInfoReqDTO reqDTO = randomUploadShippingReqDTO();
|
||||||
|
// mock 方法:首次调用就成功
|
||||||
|
WxMaOrderShippingService shippingService = mockWxMaOrderShippingService();
|
||||||
|
when(shippingService.upload(any(WxMaOrderShippingInfoUploadRequest.class)))
|
||||||
|
.thenReturn(new WxMaOrderShippingInfoBaseResponse());
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
socialClientService.uploadWxaOrderShippingInfo(userType, reqDTO);
|
||||||
|
// 断言:仅调用 1 次,无重试
|
||||||
|
verify(shippingService, times(1)).upload(any(WxMaOrderShippingInfoUploadRequest.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUploadWxaOrderShippingInfo_retryThenSuccess() throws WxErrorException {
|
||||||
|
// 准备参数
|
||||||
|
Integer userType = randomPojo(UserTypeEnum.class).getValue();
|
||||||
|
SocialWxaOrderUploadShippingInfoReqDTO reqDTO = randomUploadShippingReqDTO();
|
||||||
|
// mock 方法:首次抛 10060001,第二次成功
|
||||||
|
WxMaOrderShippingService shippingService = mockWxMaOrderShippingService();
|
||||||
|
when(shippingService.upload(any(WxMaOrderShippingInfoUploadRequest.class)))
|
||||||
|
.thenThrow(buildWxErrorException(10060001))
|
||||||
|
.thenReturn(new WxMaOrderShippingInfoBaseResponse());
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
socialClientService.uploadWxaOrderShippingInfo(userType, reqDTO);
|
||||||
|
// 断言:上传调用了 2 次,触发了 1 次重试
|
||||||
|
verify(shippingService, times(2)).upload(any(WxMaOrderShippingInfoUploadRequest.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUploadWxaOrderShippingInfo_retryExhausted() throws WxErrorException {
|
||||||
|
// 准备参数
|
||||||
|
Integer userType = randomPojo(UserTypeEnum.class).getValue();
|
||||||
|
SocialWxaOrderUploadShippingInfoReqDTO reqDTO = randomUploadShippingReqDTO();
|
||||||
|
// mock 方法:始终抛 10060001
|
||||||
|
WxMaOrderShippingService shippingService = mockWxMaOrderShippingService();
|
||||||
|
when(shippingService.upload(any(WxMaOrderShippingInfoUploadRequest.class)))
|
||||||
|
.thenThrow(buildWxErrorException(10060001));
|
||||||
|
|
||||||
|
// 调用并断言:重试用尽抛业务异常
|
||||||
|
assertServiceException(() -> socialClientService.uploadWxaOrderShippingInfo(userType, reqDTO),
|
||||||
|
SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR);
|
||||||
|
// 断言:共 4 次尝试(1 次首发 + 3 次重试)
|
||||||
|
verify(shippingService, times(4)).upload(any(WxMaOrderShippingInfoUploadRequest.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUploadWxaOrderShippingInfo_otherErrorNoRetry() throws WxErrorException {
|
||||||
|
// 准备参数
|
||||||
|
Integer userType = randomPojo(UserTypeEnum.class).getValue();
|
||||||
|
SocialWxaOrderUploadShippingInfoReqDTO reqDTO = randomUploadShippingReqDTO();
|
||||||
|
// mock 方法:抛非 10060001 错误(如 access_token 失效)
|
||||||
|
WxMaOrderShippingService shippingService = mockWxMaOrderShippingService();
|
||||||
|
when(shippingService.upload(any(WxMaOrderShippingInfoUploadRequest.class)))
|
||||||
|
.thenThrow(buildWxErrorException(40001));
|
||||||
|
|
||||||
|
// 调用并断言:立即抛业务异常,无重试
|
||||||
|
assertServiceException(() -> socialClientService.uploadWxaOrderShippingInfo(userType, reqDTO),
|
||||||
|
SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR);
|
||||||
|
verify(shippingService, times(1)).upload(any(WxMaOrderShippingInfoUploadRequest.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 构造一个发货上传请求 */
|
||||||
|
private SocialWxaOrderUploadShippingInfoReqDTO randomUploadShippingReqDTO() {
|
||||||
|
return randomPojo(SocialWxaOrderUploadShippingInfoReqDTO.class,
|
||||||
|
o -> o.setLogisticsType(SocialWxaOrderUploadShippingInfoReqDTO.LOGISTICS_TYPE_EXPRESS));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** mock 出 wxMaService.getWxMaOrderShippingService() 的返回值并返回该 mock */
|
||||||
|
private WxMaOrderShippingService mockWxMaOrderShippingService() {
|
||||||
|
WxMaOrderShippingService shippingService = mock(WxMaOrderShippingService.class);
|
||||||
|
when(wxMaService.getWxMaOrderShippingService()).thenReturn(shippingService);
|
||||||
|
return shippingService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 构造指定 errorCode 的 WxErrorException */
|
||||||
|
private WxErrorException buildWxErrorException(int errorCode) {
|
||||||
|
return new WxErrorException(WxError.builder().errorCode(errorCode).errorMsg("mock error").build());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue