【同步】BOOT 和 CLOUD 的功能
This commit is contained in:
@@ -36,6 +36,7 @@ public class MailSendSingleToUserReqDTO {
|
||||
*/
|
||||
private List<@Email String> bccMails;
|
||||
|
||||
|
||||
/**
|
||||
* 邮件模板编号
|
||||
*/
|
||||
@@ -45,8 +46,9 @@ public class MailSendSingleToUserReqDTO {
|
||||
* 邮件模板参数
|
||||
*/
|
||||
private Map<String, Object> templateParams;
|
||||
|
||||
/**
|
||||
* 附件内容
|
||||
* 附件
|
||||
*/
|
||||
private File[] attachments;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cn.iocoder.yudao.module.system.convert.auth;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
||||
@@ -39,8 +40,6 @@ public interface AuthConvert {
|
||||
.build();
|
||||
}
|
||||
|
||||
AuthPermissionInfoRespVO.MenuVO convertTreeNode(MenuDO menu);
|
||||
|
||||
/**
|
||||
* 将菜单列表,构建成菜单树
|
||||
*
|
||||
@@ -59,9 +58,10 @@ public interface AuthConvert {
|
||||
// 构建菜单树
|
||||
// 使用 LinkedHashMap 的原因,是为了排序 。实际也可以用 Stream API ,就是太丑了。
|
||||
Map<Long, AuthPermissionInfoRespVO.MenuVO> treeNodeMap = new LinkedHashMap<>();
|
||||
menuList.forEach(menu -> treeNodeMap.put(menu.getId(), AuthConvert.INSTANCE.convertTreeNode(menu)));
|
||||
menuList.forEach(menu -> treeNodeMap.put(menu.getId(),
|
||||
BeanUtils.toBean(menu, AuthPermissionInfoRespVO.MenuVO.class)));
|
||||
// 处理父子关系
|
||||
treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(ID_ROOT)).forEach(childNode -> {
|
||||
treeNodeMap.values().stream().filter(node -> ObjUtil.notEqual(node.getParentId(), ID_ROOT)).forEach(childNode -> {
|
||||
// 获得父节点
|
||||
AuthPermissionInfoRespVO.MenuVO parentNode = treeNodeMap.get(childNode.getParentId());
|
||||
if (parentNode == null) {
|
||||
|
||||
@@ -53,8 +53,9 @@ public class MailSendMessage {
|
||||
*/
|
||||
@NotEmpty(message = "邮件内容不能为空")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 邮件附件
|
||||
* 附件
|
||||
*/
|
||||
private File[] attachments;
|
||||
|
||||
|
||||
@@ -25,24 +25,25 @@ public class MailProducer {
|
||||
/**
|
||||
* 发送 {@link MailSendMessage} 消息
|
||||
*
|
||||
* @param sendLogId 发送日志编码
|
||||
* @param toMails 接收邮件地址
|
||||
* @param ccMails 抄送邮件地址
|
||||
* @param bccMails 密送邮件地址
|
||||
* @param accountId 邮件账号编号
|
||||
* @param nickname 邮件发件人
|
||||
* @param title 邮件标题
|
||||
* @param content 邮件内容
|
||||
* @param sendLogId 发送日志编码
|
||||
* @param toMails 接收邮件地址
|
||||
* @param ccMails 抄送邮件地址
|
||||
* @param bccMails 密送邮件地址
|
||||
* @param accountId 邮件账号编号
|
||||
* @param nickname 邮件发件人
|
||||
* @param title 邮件标题
|
||||
* @param content 邮件内容
|
||||
* @param attachments 附件
|
||||
*/
|
||||
public void sendMailSendMessage(Long sendLogId,
|
||||
Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
|
||||
Long accountId, String nickname, String title, String content, File[] attachments) {
|
||||
Long accountId, String nickname, String title, String content,
|
||||
File[] attachments) {
|
||||
MailSendMessage message = new MailSendMessage()
|
||||
.setLogId(sendLogId)
|
||||
.setToMails(toMails).setCcMails(ccMails).setBccMails(bccMails)
|
||||
.setAccountId(accountId).setNickname(nickname)
|
||||
.setTitle(title).setContent(content)
|
||||
.setAttachments(attachments);
|
||||
.setTitle(title).setContent(content).setAttachments(attachments);
|
||||
applicationContext.publishEvent(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,11 +24,13 @@ public interface MailSendService {
|
||||
* @param bccMails 密送邮箱
|
||||
* @param templateCode 邮件模版编码
|
||||
* @param templateParams 邮件模版参数
|
||||
* @param attachments 附件
|
||||
* @return 发送日志编号
|
||||
*/
|
||||
default Long sendSingleMailToAdmin(Long userId,
|
||||
Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
|
||||
String templateCode, Map<String, Object> templateParams, File... attachments) {
|
||||
String templateCode, Map<String, Object> templateParams,
|
||||
File... attachments) {
|
||||
return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.ADMIN.getValue(),
|
||||
templateCode, templateParams, attachments);
|
||||
}
|
||||
@@ -42,11 +44,13 @@ public interface MailSendService {
|
||||
* @param bccMails 密送邮箱
|
||||
* @param templateCode 邮件模版编码
|
||||
* @param templateParams 邮件模版参数
|
||||
* @param attachments 附件
|
||||
* @return 发送日志编号
|
||||
*/
|
||||
default Long sendSingleMailToMember(Long userId,
|
||||
Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
|
||||
String templateCode, Map<String, Object> templateParams, File... attachments) {
|
||||
String templateCode, Map<String, Object> templateParams,
|
||||
File... attachments) {
|
||||
return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.MEMBER.getValue(),
|
||||
templateCode, templateParams, attachments);
|
||||
}
|
||||
@@ -61,11 +65,13 @@ public interface MailSendService {
|
||||
* @param userType 用户类型
|
||||
* @param templateCode 邮件模版编码
|
||||
* @param templateParams 邮件模版参数
|
||||
* @param attachments 附件
|
||||
* @return 发送日志编号
|
||||
*/
|
||||
Long sendSingleMail(Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
|
||||
Long userId, Integer userType,
|
||||
String templateCode, Map<String, Object> templateParams, File... attachments);
|
||||
String templateCode, Map<String, Object> templateParams,
|
||||
File... attachments);
|
||||
|
||||
/**
|
||||
* 执行真正的邮件发送
|
||||
|
||||
@@ -121,7 +121,7 @@ public class MailSendServiceImpl implements MailSendService {
|
||||
public void doSendMail(MailSendMessage message) {
|
||||
// 1. 创建发送账号
|
||||
MailAccountDO account = validateMailAccount(message.getAccountId());
|
||||
MailAccount mailAccount = buildMailAccount(account, message.getNickname());
|
||||
MailAccount mailAccount = buildMailAccount(account, message.getNickname());
|
||||
// 2. 发送邮件
|
||||
try {
|
||||
String messageId = MailUtil.send(mailAccount, message.getToMails(), message.getCcMails(), message.getBccMails(),
|
||||
|
||||
@@ -102,6 +102,19 @@ public class SocialClientServiceImpl implements SocialClientService {
|
||||
@Value("${yudao.wxa-subscribe-message.miniprogram-state:formal}")
|
||||
public String miniprogramState;
|
||||
|
||||
/**
|
||||
* 上传发货信息重试次数
|
||||
*/
|
||||
private static final int UPLOAD_SHIPPING_INFO_MAX_RETRIES = 5;
|
||||
/**
|
||||
* 上传发货信息重试间隔
|
||||
*/
|
||||
private static final Duration UPLOAD_SHIPPING_INFO_RETRY_INTERVAL = Duration.ofMillis(500L);
|
||||
/**
|
||||
* 微信错误码:支付单不存在
|
||||
*/
|
||||
private static final int WX_ERR_CODE_PAY_ORDER_NOT_EXIST = 10060001;
|
||||
|
||||
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
|
||||
@Autowired(required = false) // 由于 justauth.enable 配置项,可以关闭 AuthRequestFactory 的功能,所以这里只能不强制注入
|
||||
private AuthRequestFactory authRequestFactory;
|
||||
@@ -368,16 +381,34 @@ public class SocialClientServiceImpl implements SocialClientService {
|
||||
.payer(PayerBean.builder().openid(reqDTO.getOpenid()).build())
|
||||
.uploadTime(ZonedDateTime.now().format(UTC_MS_WITH_XXX_OFFSET_FORMATTER))
|
||||
.build();
|
||||
try {
|
||||
WxMaOrderShippingInfoBaseResponse response = service.getWxMaOrderShippingService().upload(request);
|
||||
if (response.getErrCode() != 0) {
|
||||
// 重试机制:解决支付回调与订单信息上传之间的时间差导致的 10060001 错误
|
||||
// 对应 ISSUE:https://gitee.com/zhijiantianya/yudao-cloud/pulls/230
|
||||
for (int attempt = 1; attempt <= UPLOAD_SHIPPING_INFO_MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
WxMaOrderShippingInfoBaseResponse response = service.getWxMaOrderShippingService().upload(request);
|
||||
// 成功,直接返回
|
||||
if (response.getErrCode() == 0) {
|
||||
log.info("[uploadWxaOrderShippingInfo][上传微信小程序发货信息成功:request({}) response({})]", request, response);
|
||||
return;
|
||||
}
|
||||
// 如果是 10060001 错误(支付单不存在)且还有重试次数,则等待后重试
|
||||
if (response.getErrCode() == WX_ERR_CODE_PAY_ORDER_NOT_EXIST && attempt < UPLOAD_SHIPPING_INFO_MAX_RETRIES) {
|
||||
log.warn("[uploadWxaOrderShippingInfo][第 {} 次尝试失败,支付单不存在,{} 后重试:request({}) response({})]",
|
||||
attempt, UPLOAD_SHIPPING_INFO_RETRY_INTERVAL, request, response);
|
||||
Thread.sleep(UPLOAD_SHIPPING_INFO_RETRY_INTERVAL.toMillis());
|
||||
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);
|
||||
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, "重试等待被中断");
|
||||
}
|
||||
log.info("[uploadWxaOrderShippingInfo][上传微信小程序发货信息成功:request({}) response({})]", request, response);
|
||||
} catch (WxErrorException ex) {
|
||||
log.error("[uploadWxaOrderShippingInfo][上传微信小程序发货信息失败:request({})]", request, ex);
|
||||
throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR, ex.getError().getErrorMsg());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
||||
@@ -284,11 +285,14 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
@Override
|
||||
public PageResult<AdminUserDO> getUserPage(UserPageReqVO reqVO) {
|
||||
// 如果有角色编号,查询角色对应的用户编号
|
||||
Set<Long> userIds = reqVO.getRoleId() != null ?
|
||||
permissionService.getUserRoleIdListByRoleId(singleton(reqVO.getRoleId())) : null;
|
||||
if (userIds != null && userIds.isEmpty()) {
|
||||
return PageResult.empty();
|
||||
Set<Long> userIds = null;
|
||||
if (reqVO.getRoleId() != null) {
|
||||
userIds = permissionService.getUserRoleIdListByRoleId(singleton(reqVO.getRoleId()));
|
||||
if (CollUtil.isEmpty(userIds)) {
|
||||
return PageResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
return userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()), userIds);
|
||||
}
|
||||
@@ -484,12 +488,15 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
// 2. 遍历,逐个创建 or 更新
|
||||
UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>())
|
||||
.updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build();
|
||||
AtomicInteger index = new AtomicInteger(1);
|
||||
importUsers.forEach(importUser -> {
|
||||
int currentIndex = index.getAndIncrement();
|
||||
// 2.1.1 校验字段是否符合要求
|
||||
try {
|
||||
ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class).setPassword(initPassword));
|
||||
} catch (ConstraintViolationException ex){
|
||||
respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage());
|
||||
} catch (ConstraintViolationException ex) {
|
||||
String key = StrUtil.blankToDefault(importUser.getUsername(), "第 " + currentIndex + " 行");
|
||||
respVO.getFailureUsernames().put(key, ex.getMessage());
|
||||
return;
|
||||
}
|
||||
// 2.1.2 校验,判断是否有不符合的原因
|
||||
|
||||
@@ -263,7 +263,7 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> mailSendService.sendSingleMail(toMails, null, null, userId,
|
||||
UserTypeEnum.ADMIN.getValue(), templateCode, templateParams, (java.io.File[]) null),
|
||||
UserTypeEnum.ADMIN.getValue(), templateCode, templateParams, (java.io.File[]) null),
|
||||
MAIL_SEND_MAIL_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@@ -280,17 +280,17 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
|
||||
// mock 方法(发送邮件)
|
||||
String messageId = randomString();
|
||||
mailUtilMock.when(() -> MailUtil.send(
|
||||
argThat(mailAccount -> {
|
||||
assertEquals("芋艿 <7685@qq.com>", mailAccount.getFrom());
|
||||
assertTrue(mailAccount.isAuth());
|
||||
assertEquals(account.getUsername(), mailAccount.getUser());
|
||||
assertArrayEquals(account.getPassword().toCharArray(), mailAccount.getPass());
|
||||
assertEquals(account.getHost(), mailAccount.getHost());
|
||||
assertEquals(account.getPort(), mailAccount.getPort());
|
||||
assertEquals(account.getSslEnable(), mailAccount.isSslEnable());
|
||||
return true;
|
||||
}), eq(message.getToMails()), eq(message.getCcMails()), eq(message.getBccMails()),
|
||||
eq(message.getTitle()), eq(message.getContent()), eq(true), eq(message.getAttachments())))
|
||||
argThat(mailAccount -> {
|
||||
assertEquals("芋艿 <7685@qq.com>", mailAccount.getFrom());
|
||||
assertTrue(mailAccount.isAuth());
|
||||
assertEquals(account.getUsername(), mailAccount.getUser());
|
||||
assertArrayEquals(account.getPassword().toCharArray(), mailAccount.getPass());
|
||||
assertEquals(account.getHost(), mailAccount.getHost());
|
||||
assertEquals(account.getPort(), mailAccount.getPort());
|
||||
assertEquals(account.getSslEnable(), mailAccount.isSslEnable());
|
||||
return true;
|
||||
}), eq(message.getToMails()), eq(message.getCcMails()), eq(message.getBccMails()),
|
||||
eq(message.getTitle()), eq(message.getContent()), eq(true), eq(message.getAttachments())))
|
||||
.thenReturn(messageId);
|
||||
|
||||
// 调用
|
||||
|
||||
Reference in New Issue
Block a user