# Conflicts:
#	yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/mail/dto/MailSendSingleToUserReqDTO.java
#	yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java
#	yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java
#	yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java
#	yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImplTest.java
This commit is contained in:
YunaiV
2026-01-18 18:15:27 +08:00
42 changed files with 239 additions and 139 deletions

View File

@@ -19,7 +19,6 @@ import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
@@ -60,8 +59,6 @@ public class DeptDataPermissionRule implements DataPermissionRule {
private static final String DEPT_COLUMN_NAME = "dept_id";
private static final String USER_COLUMN_NAME = "user_id";
static final Expression EXPRESSION_NULL = new NullValue();
private final PermissionCommonApi permissionApi;
/**
@@ -136,7 +133,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
JsonUtils.toJsonString(loginUser), tableName, tableAlias, JsonUtils.toJsonString(deptDataPermission));
// throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 构建的条件为空",
// loginUser.getId(), tableName, tableAlias.getName()));
return EXPRESSION_NULL;
return new EqualsTo(null, null); // WHERE null = null可以保证返回的数据为空
}
if (deptExpression == null) {
return userExpression;

View File

@@ -19,7 +19,6 @@ import org.mockito.MockedStatic;
import java.util.Map;
import static cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRule.EXPRESSION_NULL;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.*;
@@ -150,7 +149,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
// 调用
Expression expression = rule.getExpression(tableName, tableAlias);
// 断言
assertSame(EXPRESSION_NULL, expression);
assertEquals("null = null", expression.toString());
assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
}
}

View File

@@ -204,6 +204,12 @@
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
<version>${spring-ai.version}</version>
<exclusions>
<exclusion>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations-jakarta</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<!-- 客户端 -->

View File

@@ -27,7 +27,7 @@ import java.util.List;
* @since 2024/4/14 17:35
*/
@TableName(value = "ai_chat_message", autoResultMap = true)
@KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@KeySequence("ai_chat_message_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@Builder
@NoArgsConstructor

View File

@@ -14,7 +14,7 @@ import lombok.*;
* @author 芋道源码
*/
@TableName("ai_api_key")
@KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@KeySequence("ai_api_key_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@Builder
@NoArgsConstructor

View File

@@ -51,8 +51,7 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegment
MPJLambdaWrapper<AiKnowledgeSegmentDO> wrapper = new MPJLambdaWrapperX<AiKnowledgeSegmentDO>()
.selectAs(AiKnowledgeSegmentDO::getDocumentId, AiKnowledgeSegmentProcessRespVO::getDocumentId)
.selectCount(AiKnowledgeSegmentDO::getId, "count")
.select("COUNT(CASE WHEN vector_id > '" + AiKnowledgeSegmentDO.VECTOR_ID_EMPTY
+ "' THEN 1 ELSE NULL END) AS embeddingCount")
.select("COUNT(CASE WHEN vector_id IS NOT NULL AND vector_id <> '" + AiKnowledgeSegmentDO.VECTOR_ID_EMPTY + "' THEN 1 ELSE NULL END) AS embeddingCount")
.in(AiKnowledgeSegmentDO::getDocumentId, documentIds)
.groupBy(AiKnowledgeSegmentDO::getDocumentId);
return selectJoinList(AiKnowledgeSegmentProcessRespVO.class, wrapper);

View File

@@ -14,6 +14,7 @@ import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.common.engine.api.delegate.Expression;
import java.util.List;
import java.util.Set;
@@ -56,14 +57,7 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
protected int resolveNrOfInstances(DelegateExecution execution) {
// 情况一UserTask 节点
if (execution.getCurrentFlowElement() instanceof UserTask) {
// 第一步,设置 collectionVariable 和 CollectionVariable
// 从 execution.getVariable() 读取所有任务处理人的 key
super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
// 从 execution.getVariable() 读取当前所有任务处理的人的 key
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
// 第二步,获取任务的所有处理人
// 获取任务的所有处理人
@SuppressWarnings("unchecked")
Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
if (assigneeUserIds == null) {
@@ -94,4 +88,21 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
return super.resolveNrOfInstances(execution);
}
// ========== 屏蔽解析器覆写 ==========
@Override
public void setCollectionExpression(Expression collectionExpression) {
// 保持自定义变量名,忽略解析器写入的 collection 表达式
}
@Override
public void setCollectionVariable(String collectionVariable) {
// 保持自定义变量名,忽略解析器写入的 collection 变量名
}
@Override
public void setCollectionElementVariable(String collectionElementVariable) {
// 保持自定义变量名,忽略解析器写入的单元素变量名
}
}

View File

@@ -47,14 +47,7 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
protected int resolveNrOfInstances(DelegateExecution execution) {
// 情况一UserTask 节点
if (execution.getCurrentFlowElement() instanceof UserTask) {
// 第一步,设置 collectionVariable 和 CollectionVariable
// 从 execution.getVariable() 读取所有任务处理人的 key
super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
// 从 execution.getVariable() 读取当前所有任务处理的人的 key
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
// 第二步,获取任务的所有处理人
// 获取任务的所有处理人
// 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人
@SuppressWarnings("unchecked")
Set<Long> assigneeUserIds = (Set<Long>) execution.getVariableLocal(super.collectionVariable, Set.class);

View File

@@ -31,14 +31,14 @@ public interface ErpFinancePaymentItemMapper extends BaseMapperX<ErpFinancePayme
default BigDecimal selectPaymentPriceSumByBizIdAndBizType(Long bizId, Integer bizType) {
// SQL sum 查询
List<Map<String, Object>> result = selectMaps(new QueryWrapper<ErpFinancePaymentItemDO>()
.select("SUM(payment_price) AS paymentPriceSum")
.select("SUM(payment_price) AS payment_price_sum")
.eq("biz_id", bizId)
.eq("biz_type", bizType));
// 获得数量
if (CollUtil.isEmpty(result)) {
return BigDecimal.ZERO;
}
return BigDecimal.valueOf(MapUtil.getDouble(result.get(0), "paymentPriceSum", 0D));
return BigDecimal.valueOf(MapUtil.getDouble(result.get(0), "payment_price_sum", 0D));
}
}

View File

@@ -31,14 +31,14 @@ public interface ErpFinanceReceiptItemMapper extends BaseMapperX<ErpFinanceRecei
default BigDecimal selectReceiptPriceSumByBizIdAndBizType(Long bizId, Integer bizType) {
// SQL sum 查询
List<Map<String, Object>> result = selectMaps(new QueryWrapper<ErpFinanceReceiptItemDO>()
.select("SUM(receipt_price) AS receiptPriceSum")
.select("SUM(receipt_price) AS receipt_price_sum")
.eq("biz_id", bizId)
.eq("biz_type", bizType));
// 获得数量
if (CollUtil.isEmpty(result)) {
return BigDecimal.ZERO;
}
return BigDecimal.valueOf(MapUtil.getDouble(result.get(0), "receiptPriceSum", 0D));
return BigDecimal.valueOf(MapUtil.getDouble(result.get(0), "receipt_price_sum", 0D));
}
}

View File

@@ -46,11 +46,11 @@ public interface ErpPurchaseInItemMapper extends BaseMapperX<ErpPurchaseInItemDO
}
// SQL sum 查询
List<Map<String, Object>> result = selectMaps(new QueryWrapper<ErpPurchaseInItemDO>()
.select("order_item_id, SUM(count) AS sumCount")
.select("order_item_id, SUM(count) AS sum_count")
.groupBy("order_item_id")
.in("in_id", inIds));
// 获得数量
return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sumCount"));
return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sum_count"));
}
}

View File

@@ -46,11 +46,11 @@ public interface ErpPurchaseReturnItemMapper extends BaseMapperX<ErpPurchaseRetu
}
// SQL sum 查询
List<Map<String, Object>> result = selectMaps(new QueryWrapper<ErpPurchaseReturnItemDO>()
.select("order_item_id, SUM(count) AS sumCount")
.select("order_item_id, SUM(count) AS sum_count")
.groupBy("order_item_id")
.in("return_id", returnIds));
// 获得数量
return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sumCount"));
return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sum_count"));
}
}

View File

@@ -46,11 +46,11 @@ public interface ErpSaleOutItemMapper extends BaseMapperX<ErpSaleOutItemDO> {
}
// SQL sum 查询
List<Map<String, Object>> result = selectMaps(new QueryWrapper<ErpSaleOutItemDO>()
.select("order_item_id, SUM(count) AS sumCount")
.select("order_item_id, SUM(count) AS sum_count")
.groupBy("order_item_id")
.in("out_id", outIds));
// 获得数量
return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sumCount"));
return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sum_count"));
}
}

View File

@@ -46,11 +46,11 @@ public interface ErpSaleReturnItemMapper extends BaseMapperX<ErpSaleReturnItemDO
}
// SQL sum 查询
List<Map<String, Object>> result = selectMaps(new QueryWrapper<ErpSaleReturnItemDO>()
.select("order_item_id, SUM(count) AS sumCount")
.select("order_item_id, SUM(count) AS sum_count")
.groupBy("order_item_id")
.in("return_id", returnIds));
// 获得数量
return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sumCount"));
return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sum_count"));
}
}

View File

@@ -52,13 +52,13 @@ public interface ErpStockMapper extends BaseMapperX<ErpStockDO> {
default BigDecimal selectSumByProductId(Long productId) {
// SQL sum 查询
List<Map<String, Object>> result = selectMaps(new QueryWrapper<ErpStockDO>()
.select("SUM(count) AS sumCount")
.select("SUM(count) AS sum_count")
.eq("product_id", productId));
// 获得数量
if (CollUtil.isEmpty(result)) {
return BigDecimal.ZERO;
}
return BigDecimal.valueOf(MapUtil.getDouble(result.get(0), "sumCount", 0D));
return BigDecimal.valueOf(MapUtil.getDouble(result.get(0), "sum_count", 0D));
}
}

View File

@@ -29,6 +29,6 @@ public class FileCreateReqVO {
private String type;
@Schema(description = "文件大小", example = "2048", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer size;
private Long size;
}

View File

@@ -28,7 +28,7 @@ public class FileRespVO {
private String type;
@Schema(description = "文件大小", example = "2048", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer size;
private Long size;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;

View File

@@ -52,6 +52,6 @@ public class FileDO extends BaseDO {
/**
* 文件大小
*/
private Integer size;
private Long size;
}

View File

@@ -59,9 +59,9 @@ public class DatabaseTableServiceImpl implements DatabaseTableService {
strategyConfig.addInclude(name);
} else {
// 移除工作流和定时任务前缀的表名
strategyConfig.addExclude("ACT_[\\S\\s]+|QRTZ_[\\S\\s]+|FLW_[\\S\\s]+");
strategyConfig.addExclude("ACT_[\\S\\s]+|QRTZ_[\\S\\s]+|FLW_[\\S\\s]+|act_[\\S\\s]+|qrtz_[\\S\\s]+|flw_[\\S\\s]+");
// 移除 ORACLE 相关的系统表
strategyConfig.addExclude("IMPDP_[\\S\\s]+|ALL_[\\S\\s]+|HS_[\\S\\\\s]+");
strategyConfig.addExclude("IMPDP_[\\S\\s]+|ALL_[\\S\\s]+|HS_[\\S\\s]+|impdp_[\\S\\s]+|all_[\\S\\s]+|hs_[\\S\\s]+");
strategyConfig.addExclude("[\\S\\s]+\\$[\\S\\s]+|[\\S\\s]+\\$"); // 表里不能有 $,一般有都是系统的表
}

View File

@@ -88,7 +88,7 @@ public class FileServiceImpl implements FileService {
// 3. 保存到数据库
fileMapper.insert(new FileDO().setConfigId(client.getId())
.setName(name).setPath(path).setUrl(url)
.setType(type).setSize(content.length));
.setType(type).setSize((long) content.length));
return url;
}

View File

@@ -7,7 +7,6 @@ import { http } from '@/http/http'
#else
#set ($primaryTsType = "string")
#end
/** ${table.classComment}信息 */
export interface ${simpleClassName} {
#foreach ($column in $columns)
@@ -28,7 +27,6 @@ export interface ${simpleClassName} {
#end
}
// TODO @AI`/system/operate-log/get?id=${id}` 类似这种
/** 获取${table.classComment}分页列表 */
export function get${simpleClassName}Page(params: PageParam) {
return http.get<PageResult<${simpleClassName}>>('/${table.moduleName}/${simpleClassName_strikeCase}/page', params)
@@ -36,7 +34,7 @@ export function get${simpleClassName}Page(params: PageParam) {
/** 获取${table.classComment}详情 */
export function get${simpleClassName}(id: ${primaryTsType}) {
return http.get<${simpleClassName}>('/${table.moduleName}/${simpleClassName_strikeCase}/get?id=' + id)
return http.get<${simpleClassName}>(`/${table.moduleName}/${simpleClassName_strikeCase}/get?id=${id}`)
}
/** 创建${table.classComment} */
@@ -49,8 +47,7 @@ export function update${simpleClassName}(data: ${simpleClassName}) {
return http.put<boolean>('/${table.moduleName}/${simpleClassName_strikeCase}/update', data)
}
// TODO @AI`/system/operate-log/get?id=${id}` 类似这种
/** 删除${table.classComment} */
export function delete${simpleClassName}(id: ${primaryTsType}) {
return http.delete<boolean>('/${table.moduleName}/${simpleClassName_strikeCase}/delete?id=' + id)
return http.delete<boolean>(`/${table.moduleName}/${simpleClassName_strikeCase}/delete?id=${id}`)
}

View File

@@ -294,4 +294,3 @@ function handleReset() {
emit('reset')
}
</script>

View File

@@ -107,6 +107,7 @@
</template>
<script lang="ts" setup>
import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
#set ($primaryTsType = "number")
@@ -204,7 +205,7 @@ const formRules = {
#end
#end
}
const formRef = ref()
const formRef = ref<FormInstance>()
/** 返回上一页 */
function handleBack() {

View File

@@ -144,7 +144,7 @@ public class ProductSkuServiceImpl implements ProductSkuService {
@Override
public void createSkuList(Long spuId, List<ProductSkuSaveReqVO> skuCreateReqList) {
List<ProductSkuDO> skus = BeanUtils.toBean(skuCreateReqList, ProductSkuDO.class, sku -> sku.setSpuId(spuId));
List<ProductSkuDO> skus = BeanUtils.toBean(skuCreateReqList, ProductSkuDO.class, sku -> sku.setSpuId(spuId).setSalesCount(0));
productSkuMapper.insertBatch(skus);
}

View File

@@ -109,7 +109,7 @@ public class ProductSpuServiceImpl implements ProductSpuService {
// sku 单价最低的商品的成本价格
spu.setCostPrice(getMinValue(skus, ProductSkuSaveReqVO::getCostPrice));
// skus 库存总数
spu.setStock(getSumValue(skus, ProductSkuSaveReqVO::getStock, Integer::sum));
spu.setStock(getSumValue(skus, ProductSkuSaveReqVO::getStock, Math::addExact));
// 若是 spu 已有状态则不处理
if (spu.getStatus() == null) {
spu.setStatus(ProductSpuStatusEnum.ENABLE.getStatus()); // 默认状态为上架

View File

@@ -14,6 +14,7 @@ public class BrokerageUserCreateReqVO {
private Long userId;
@Schema(description = "推广员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4587")
@NotNull(message = "推广员编号不能为空")
private Long bindUserId;
}

View File

@@ -265,8 +265,7 @@ public interface TradeOrderConvert {
ProductSpuRespDTO spu, ProductSkuRespDTO sku) {
BrokerageAddReqBO bo = new BrokerageAddReqBO().setBizId(String.valueOf(item.getId())).setSourceUserId(item.getUserId())
.setBasePrice(item.getPayPrice())
.setTitle(StrUtil.format("{}成功购买{}", user.getNickname(), item.getSpuName()))
.setFirstFixedPrice(0).setSecondFixedPrice(0);
.setTitle(StrUtil.format("{}成功购买{}", user.getNickname(), item.getSpuName()));
if (BooleanUtil.isTrue(spu.getSubCommissionType())) {
// 特殊:单独设置的佣金需要乘以购买数量。关联 https://gitee.com/yudaocode/yudao-mall-uniapp/issues/ICY7SJ
bo.setFirstFixedPrice(sku.getFirstBrokeragePrice() * item.getCount())

View File

@@ -65,11 +65,17 @@ public class TradeOrderLogAspect {
public void doAfterReturning(JoinPoint joinPoint, TradeOrderLog orderLog) {
try {
// 1.1 操作用户
Integer userType = getUserType();
Long userId = getUserId();
Integer userType = USER_TYPE.get();
if (ObjectUtil.isNull(userType)) {
userType = getUserType();
}
Long userId = USER_ID.get();
if (ObjectUtil.isNull(userId)) {
userId = getUserId();
}
// 1.2 订单信息
Long orderId = ORDER_ID.get();
if (orderId == null) { // 如果未设置,只有注解,说明不需要记录日志
if (ObjectUtil.isNull(orderId)) { // 如果未设置,只有注解,说明不需要记录日志
return;
}
Integer beforeStatus = BEFORE_STATUS.get();

View File

@@ -142,7 +142,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
*/
int calculatePrice(Integer basePrice, Integer percent, Integer fixedPrice) {
// 1. 优先使用固定佣金
if (fixedPrice != null && fixedPrice > 0) {
if (fixedPrice != null && fixedPrice >= 0) {
return ObjectUtil.defaultIfNull(fixedPrice, 0);
}
// 2. 根据比例计算佣金

View File

@@ -174,7 +174,11 @@ public class PayWalletServiceImpl implements PayWalletService {
}
// 3. 生成钱包流水
Integer afterBalance = payWallet.getBalance() - price;
// 情况一充值退款balance 在冻结时已扣updateWhenRechargeRefund 只扣 freeze_price所以 afterBalance 不变。https://t.zsxq.com/OJk9m
// 情况二消费支付updateWhenConsumption 从 balance 扣,所以 afterBalance = balance - price
Integer afterBalance = bizType == PayWalletBizTypeEnum.RECHARGE_REFUND
? payWallet.getBalance()
: payWallet.getBalance() - price;
WalletTransactionCreateReqBO bo = new WalletTransactionCreateReqBO().setWalletId(payWallet.getId())
.setPrice(-price).setBalance(afterBalance).setBizId(String.valueOf(bizId))
.setBizType(bizType.getType()).setTitle(bizType.getDescription());

View File

@@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.report.framework.jmreport.core.service;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
@@ -10,9 +12,6 @@ import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
import lombok.RequiredArgsConstructor;
import org.jeecg.modules.jmreport.api.JmReportTokenServiceI;
@@ -158,4 +157,33 @@ public class JmReportTokenServiceImpl implements JmReportTokenServiceI {
return StrUtil.toStringOrNull(loginUser.getTenantId());
}
@Override
public String[] getPermissions(String token) {
// 设置租户上下文
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
if (loginUser == null) {
return null;
}
TenantContextHolder.setTenantId(loginUser.getTenantId());
// 参见文档 https://help.jimureport.com/prodSafe/ 文档
// 适配:如果是本系统的管理员,则返回积木报表(仪表盘/大屏设计器)的所有权限指令
// 如果不处理,会碰到 https://t.zsxq.com/yzlkA 反馈的问题
Long userId = SecurityFrameworkUtils.getLoginUserId();
if (permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode())) {
return new String[]{
"drag:datasource:testConnection", // 数据库连接测试
"drag:datasource:saveOrUpate", // 数据源保存
"drag:datasource:delete", // 数据源删除
"drag:analysis:sql", // SQL解析
"drag:design:getTotalData", // 展示Online表单数据
"drag:dataset:save", // 数据集保存
"drag:dataset:delete", // 数据集删除
"onl:drag:clear:recovery", // 清空回收站
"onl:drag:page:delete" // 数据删除
};
}
return null;
}
}

View File

@@ -23,14 +23,14 @@ public class MailSendApiImpl implements MailSendApi {
public Long sendSingleMailToAdmin(MailSendSingleToUserReqDTO reqDTO) {
return mailSendService.sendSingleMailToAdmin(reqDTO.getUserId(),
reqDTO.getToMails(), reqDTO.getCcMails(), reqDTO.getBccMails(),
reqDTO.getTemplateCode(), reqDTO.getTemplateParams());
reqDTO.getTemplateCode(), reqDTO.getTemplateParams(), reqDTO.getAttachments());
}
@Override
public Long sendSingleMailToMember(MailSendSingleToUserReqDTO reqDTO) {
return mailSendService.sendSingleMailToMember(reqDTO.getUserId(),
reqDTO.getToMails(), reqDTO.getCcMails(), reqDTO.getBccMails(),
reqDTO.getTemplateCode(), reqDTO.getTemplateParams());
reqDTO.getTemplateCode(), reqDTO.getTemplateParams(), reqDTO.getAttachments());
}
}

View File

@@ -1,9 +1,10 @@
package cn.iocoder.yudao.module.system.api.mail.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.util.List;
import java.util.Map;
@@ -46,4 +47,9 @@ public class MailSendSingleToUserReqDTO {
*/
private Map<String, Object> templateParams;
/**
* 附件
*/
private File[] attachments;
}

View File

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.api.user;
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.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
@@ -33,6 +34,7 @@ public class AdminUserApiImpl implements AdminUserApi {
private DeptService deptService;
@Override
@DataPermission(enable = false) // 忽略数据权限避免因为过滤导致无法查询用户。类似https://github.com/YunaiV/ruoyi-vue-pro/issues/1051
public AdminUserRespDTO getUser(Long id) {
AdminUserDO user = userService.getUser(id);
return BeanUtils.toBean(user, AdminUserRespDTO.class);

View File

@@ -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) {

View File

@@ -1,12 +1,11 @@
package cn.iocoder.yudao.module.system.mq.message.mail;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.util.Collection;
import java.util.List;
/**
* 邮箱发送消息
@@ -55,4 +54,9 @@ public class MailSendMessage {
@NotEmpty(message = "邮件内容不能为空")
private String content;
/**
* 附件
*/
private File[] attachments;
}

View File

@@ -1,16 +1,13 @@
package cn.iocoder.yudao.module.system.mq.producer.mail;
import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.File;
import java.util.Collection;
import java.util.List;
import static java.util.Collections.singletonList;
/**
* Mail 邮件相关消息的 Producer
@@ -36,15 +33,17 @@ public class MailProducer {
* @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) {
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);
.setTitle(title).setContent(content).setAttachments(attachments);
applicationContext.publishEvent(message);
}

View File

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.service.mail;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
import java.io.File;
import java.util.Collection;
import java.util.Map;
@@ -23,13 +24,15 @@ 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) {
String templateCode, Map<String, Object> templateParams,
File... attachments) {
return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.ADMIN.getValue(),
templateCode, templateParams);
templateCode, templateParams, attachments);
}
/**
@@ -41,13 +44,15 @@ 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) {
String templateCode, Map<String, Object> templateParams,
File... attachments) {
return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.MEMBER.getValue(),
templateCode, templateParams);
templateCode, templateParams, attachments);
}
/**
@@ -60,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);
String templateCode, Map<String, Object> templateParams,
File... attachments);
/**
* 执行真正的邮件发送

View File

@@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.system.service.mail;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.mail.MailAccount;
import cn.hutool.extra.mail.MailUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
@@ -15,11 +13,14 @@ import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
import cn.iocoder.yudao.module.system.service.member.MemberService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.extra.mail.MailAccount;
import org.dromara.hutool.extra.mail.MailUtil;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.io.File;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
@@ -56,7 +57,8 @@ public class MailSendServiceImpl implements MailSendService {
@Override
public Long sendSingleMail(Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
Long userId, Integer userType,
String templateCode, Map<String, Object> templateParams) {
String templateCode, Map<String, Object> templateParams,
File... attachments) {
// 1.1 校验邮箱模版是否合法
MailTemplateDO template = validateMailTemplate(templateCode);
// 1.2 校验邮箱账号是否合法
@@ -94,7 +96,7 @@ public class MailSendServiceImpl implements MailSendService {
// 发送 MQ 消息,异步执行发送短信
if (isSend) {
mailProducer.sendMailSendMessage(sendLogId, toMailSet, ccMailSet, bccMailSet,
account.getId(), template.getNickname(), title, content);
account.getId(), template.getNickname(), title, content, attachments);
}
return sendLogId;
}
@@ -123,7 +125,7 @@ public class MailSendServiceImpl implements MailSendService {
// 2. 发送邮件
try {
String messageId = MailUtil.send(mailAccount, message.getToMails(), message.getCcMails(), message.getBccMails(),
message.getTitle(), message.getContent(), true);
message.getTitle(), message.getContent(), true, message.getAttachments());
// 3. 更新结果(成功)
mailLogService.updateMailSendResult(message.getLogId(), messageId, null);
} catch (Exception e) {
@@ -135,7 +137,7 @@ public class MailSendServiceImpl implements MailSendService {
private MailAccount buildMailAccount(MailAccountDO account, String nickname) {
String from = StrUtil.isNotEmpty(nickname) ? nickname + " <" + account.getMail() + ">" : account.getMail();
return new MailAccount().setFrom(from).setAuth(true)
.setUser(account.getUsername()).setPass(account.getPassword())
.setUser(account.getUsername()).setPass(account.getPassword().toCharArray())
.setHost(account.getHost()).setPort(account.getPort())
.setSslEnable(account.getSslEnable()).setStarttlsEnable(account.getStarttlsEnable());
}

View File

@@ -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;
@@ -372,16 +385,34 @@ public class SocialClientServiceImpl implements SocialClientService {
.payer(PayerBean.builder().openid(reqDTO.getOpenid()).build())
.uploadTime(ZonedDateTime.now().format(UTC_MS_WITH_XXX_OFFSET_FORMATTER))
.build();
// 重试机制:解决支付回调与订单信息上传之间的时间差导致的 10060001 错误
// 对应 ISSUEhttps://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) {
// 成功,直接返回
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());
}
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());
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
log.error("[uploadWxaOrderShippingInfo][重试等待被中断request({})]", request, ex);
throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR, "重试等待被中断");
}
}
}

View File

@@ -44,6 +44,7 @@ import javax.annotation.Resource;
import javax.validation.ConstraintViolationException;
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()) {
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());
String key = StrUtil.blankToDefault(importUser.getUsername(), "" + currentIndex + "");
respVO.getFailureUsernames().put(key, ex.getMessage());
return;
}
// 2.1.2 校验,判断是否有不符合的原因

View File

@@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.system.service.mail;
import cn.hutool.core.map.MapUtil;
import cn.hutool.extra.mail.*;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
@@ -14,12 +13,15 @@ import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
import cn.iocoder.yudao.module.system.service.member.MemberService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import org.assertj.core.util.Lists;
import org.dromara.hutool.extra.mail.MailAccount;
import org.dromara.hutool.extra.mail.MailUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@@ -60,7 +62,7 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
// .setFrom("奥特曼 <ydym_test@163.com>")
.setFrom("ydym_test@163.com") // 邮箱地址
.setHost("smtp.163.com").setPort(465).setSslEnable(true) // SMTP 服务器
.setAuth(true).setUser("ydym_test@163.com").setPass("WBZTEINMIFVRYSOE"); // 登录账号密码
.setAuth(true).setUser("ydym_test@163.com").setPass("WBZTEINMIFVRYSOE".toCharArray()); // 登录账号密码
String messageId = MailUtil.send(mailAccount, "7685413@qq.com", "主题", "内容", false);
System.out.println("发送结果:" + messageId);
}
@@ -106,7 +108,7 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
// 调用
Long resultMailLogId = mailSendService.sendSingleMail(toMails, ccMails, bccMails, userId,
UserTypeEnum.ADMIN.getValue(), templateCode, templateParams);
UserTypeEnum.ADMIN.getValue(), templateCode, templateParams, (File[]) null);
// 断言
assertEquals(mailLogId, resultMailLogId);
// 断言调用
@@ -114,7 +116,7 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
argThat(toMailSet -> toMailSet.contains(user.getEmail()) && toMailSet.contains("admin@test.com")),
argThat(ccMailSet -> ccMailSet.contains("cc@test.com")),
argThat(bccMailSet -> bccMailSet.contains("bcc@test.com")),
eq(account.getId()), eq(template.getNickname()), eq(title), eq(content));
eq(account.getId()), eq(template.getNickname()), eq(title), eq(content), isNull());
}
/**
@@ -156,7 +158,7 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
eq(account), eq(template), eq(content), eq(templateParams), eq(true))).thenReturn(mailLogId);
// 调用
Long resultMailLogId = mailSendService.sendSingleMail(toMails, null, null, userId, userType, templateCode, templateParams);
Long resultMailLogId = mailSendService.sendSingleMail(toMails, null, null, userId, userType, templateCode, templateParams, (java.io.File[]) null);
// 断言
assertEquals(mailLogId, resultMailLogId);
// 断言调用
@@ -164,7 +166,7 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
argThat(toMailSet -> toMailSet.contains(mail)),
argThat(Collection::isEmpty),
argThat(Collection::isEmpty),
eq(account.getId()), eq(template.getNickname()), eq(title), eq(content));
eq(account.getId()), eq(template.getNickname()), eq(title), eq(content), isNull());
}
/**
@@ -206,12 +208,12 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
eq(account), eq(template), eq(content), eq(templateParams), eq(false))).thenReturn(mailLogId);
// 调用
Long resultMailLogId = mailSendService.sendSingleMail(toMails, null, null, userId, userType, templateCode, templateParams);
Long resultMailLogId = mailSendService.sendSingleMail(toMails, null, null, userId, userType, templateCode, templateParams, (java.io.File[]) null);
// 断言
assertEquals(mailLogId, resultMailLogId);
// 断言调用
verify(mailProducer, times(0)).sendMailSendMessage(anyLong(), any(), any(), any(),
anyLong(), anyString(), anyString(), anyString());
anyLong(), anyString(), anyString(), anyString(), any());
}
@Test
@@ -261,7 +263,7 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
// 调用,并断言异常
assertServiceException(() -> mailSendService.sendSingleMail(toMails, null, null, userId,
UserTypeEnum.ADMIN.getValue(), templateCode, templateParams),
UserTypeEnum.ADMIN.getValue(), templateCode, templateParams, (java.io.File[]) null),
MAIL_SEND_MAIL_NOT_EXISTS);
}
@@ -282,13 +284,13 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
assertEquals("芋艿 <7685@qq.com>", mailAccount.getFrom());
assertTrue(mailAccount.isAuth());
assertEquals(account.getUsername(), mailAccount.getUser());
assertEquals(account.getPassword(), mailAccount.getPass());
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.getTitle()), eq(message.getContent()), eq(true), eq(message.getAttachments())))
.thenReturn(messageId);
// 调用
@@ -314,13 +316,13 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest {
assertEquals("芋艿 <7685@qq.com>", mailAccount.getFrom());
assertTrue(mailAccount.isAuth());
assertEquals(account.getUsername(), mailAccount.getUser());
assertEquals(account.getPassword(), mailAccount.getPass());
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))).thenThrow(e);
eq(message.getTitle()), eq(message.getContent()), eq(true), same(message.getAttachments()))).thenThrow(e);
// 调用
mailSendService.doSendMail(message);