Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/mall_product

# Conflicts:
#	yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java
#	yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/activity/AppBargainActivityDetailRespVO.java
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/activity/AppBargainActivityRespVO.java
#	yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java
#	yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
#	yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
#	yudao-server/src/main/resources/application-local.yaml
This commit is contained in:
YunaiV
2023-09-28 15:18:59 +08:00
575 changed files with 1987 additions and 47700 deletions

View File

@@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.system.api.notify;
import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.api.notify.dto.NotifyTemplateReqDTO;
import cn.iocoder.yudao.module.system.service.notify.NotifySendService;
import org.springframework.stereotype.Service;
@@ -30,14 +29,4 @@ public class NotifyMessageSendApiImpl implements NotifyMessageSendApi {
reqDTO.getTemplateCode(), reqDTO.getTemplateParams());
}
@Override
public boolean validateNotifyTemplate(String orderDelivery) {
return false;
}
@Override
public void createNotifyTemplate(NotifyTemplateReqDTO templateReqDTO) {
}
}

View File

@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.system.controller.admin.auth;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
@@ -153,4 +154,4 @@ public class AuthController {
return success(authService.socialLogin(reqVO));
}
}
}

View File

@@ -41,7 +41,7 @@ public class AuthLoginReqVO {
// ========== 绑定社交登录时,需要传递如下参数 ==========
@Schema(description = "社交平台的类型,参见 SysUserSocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@InEnum(SocialTypeEnum.class)
private Integer socialType;
@@ -66,4 +66,4 @@ public class AuthLoginReqVO {
return socialType == null || StrUtil.isNotEmpty(socialState);
}
}
}

View File

@@ -32,8 +32,8 @@ public interface SmsChannelConvert {
PageResult<SmsChannelRespVO> convertPage(PageResult<SmsChannelDO> page);
List<SmsChannelProperties> convertList02(List<SmsChannelDO> list);
List<SmsChannelSimpleRespVO> convertList03(List<SmsChannelDO> list);
SmsChannelProperties convert02(SmsChannelDO channel);
}

View File

@@ -55,7 +55,7 @@ public class DictDataDO extends BaseDO {
/**
* css 样式
*/
@TableField(updateStrategy = FieldStrategy.IGNORED)
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String cssClass;
/**
* 备注

View File

@@ -6,9 +6,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
@Mapper
public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> {
@@ -21,7 +18,8 @@ public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> {
.orderByDesc(SmsChannelDO::getId));
}
@Select("SELECT COUNT(*) FROM system_sms_channel WHERE update_time > #{maxUpdateTime}")
Long selectCountByUpdateTimeGt(LocalDateTime maxTime);
default SmsChannelDO selectByCode(String code) {
return selectOne(SmsChannelDO::getCode, code);
}
}

View File

@@ -11,14 +11,14 @@ import javax.annotation.Resource;
import java.util.List;
@Component
@TenantJob // 标记多租户
public class DemoJob implements JobHandler {
@Resource
private AdminUserMapper adminUserMapper;
@Override
public String execute(String param) throws Exception {
@TenantJob // 标记多租户
public String execute(String param) {
System.out.println("当前租户:" + TenantContextHolder.getTenantId());
List<AdminUserDO> users = adminUserMapper.selectList();
return "用户数量:" + users.size();

View File

@@ -18,6 +18,7 @@ import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct;
@@ -42,6 +43,11 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_
@Validated
public class SensitiveWordServiceImpl implements SensitiveWordService {
/**
* 是否开启敏感词功能
*/
public static Boolean ENABLED = false;
/**
* 敏感词列表缓存
*/
@@ -75,6 +81,10 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
*/
@PostConstruct
public void initLocalCache() {
if (!ENABLED) {
return;
}
// 第一步:查询数据
List<SensitiveWordDO> sensitiveWords = sensitiveWordMapper.selectList();
log.info("[initLocalCache][缓存敏感词,数量为:{}]", sensitiveWords.size());
@@ -216,6 +226,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
@Override
public List<String> validateText(String text, List<String> tags) {
Assert.isTrue(ENABLED, "敏感词功能未开启,请将 ENABLED 设置为 true");
// 无标签时,默认所有
if (CollUtil.isEmpty(tags)) {
return defaultSensitiveWordTrie.validate(text);
}
@@ -233,6 +246,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
@Override
public boolean isTextValid(String text, List<String> tags) {
Assert.isTrue(ENABLED, "敏感词功能未开启,请将 ENABLED 设置为 true");
// 无标签时,默认所有
if (CollUtil.isEmpty(tags)) {
return defaultSensitiveWordTrie.isValid(text);
}

View File

@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.service.sms;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelUpdateReqVO;
@@ -13,15 +14,10 @@ import java.util.List;
* 短信渠道 Service 接口
*
* @author zzf
* @date 2021/1/25 9:24
* @since 2021/1/25 9:24
*/
public interface SmsChannelService {
/**
* 初始化短信客户端
*/
void initLocalCache();
/**
* 创建短信渠道
*
@@ -67,4 +63,20 @@ public interface SmsChannelService {
*/
PageResult<SmsChannelDO> getSmsChannelPage(SmsChannelPageReqVO pageReqVO);
/**
* 获得短信客户端
*
* @param id 编号
* @return 短信客户端
*/
SmsClient getSmsClient(Long id);
/**
* 获得短信客户端
*
* @param code 编码
* @return 短信客户端
*/
SmsClient getSmsClient(String code);
}

View File

@@ -1,7 +1,8 @@
package cn.iocoder.yudao.module.system.service.sms;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelCreateReqVO;
@@ -10,20 +11,18 @@ import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannel
import cn.iocoder.yudao.module.system.convert.sms.SmsChannelConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collections;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
@@ -37,10 +36,44 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNE
public class SmsChannelServiceImpl implements SmsChannelService {
/**
* 短信渠道列表的缓存
* {@link SmsClient} 缓存,通过它异步刷新 smsClientFactory
*/
@Getter
private volatile List<SmsChannelDO> channelCache = Collections.emptyList();
private final LoadingCache<Long, SmsClient> idClientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
new CacheLoader<Long, SmsClient>() {
@Override
public SmsClient load(Long id) {
// 查询,然后尝试刷新
SmsChannelDO channel = smsChannelMapper.selectById(id);
if (channel != null) {
SmsChannelProperties properties = SmsChannelConvert.INSTANCE.convert02(channel);
smsClientFactory.createOrUpdateSmsClient(properties);
}
return smsClientFactory.getSmsClient(id);
}
});
/**
* {@link SmsClient} 缓存,通过它异步刷新 smsClientFactory
*/
@Getter
private final LoadingCache<String, SmsClient> codeClientCache = buildAsyncReloadingCache(Duration.ofSeconds(60L),
new CacheLoader<String, SmsClient>() {
@Override
public SmsClient load(String code) {
// 查询,然后尝试刷新
SmsChannelDO channel = smsChannelMapper.selectByCode(code);
if (channel != null) {
SmsChannelProperties properties = SmsChannelConvert.INSTANCE.convert02(channel);
smsClientFactory.createOrUpdateSmsClient(properties);
}
return smsClientFactory.getSmsClient(code);
}
});
@Resource
private SmsClientFactory smsClientFactory;
@@ -51,66 +84,29 @@ public class SmsChannelServiceImpl implements SmsChannelService {
@Resource
private SmsTemplateService smsTemplateService;
@Override
@PostConstruct
public void initLocalCache() {
// 第一步:查询数据
List<SmsChannelDO> channels = smsChannelMapper.selectList();
log.info("[initLocalCache][缓存短信渠道,数量为:{}]", channels.size());
// 第二步:构建缓存:创建或更新短信 Client
List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList02(channels);
propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
this.channelCache = channels;
}
/**
* 通过定时任务轮询,刷新缓存
*
* 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
*/
@Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
public void refreshLocalCache() {
// 情况一:如果缓存里没有数据,则直接刷新缓存
if (CollUtil.isEmpty(channelCache)) {
initLocalCache();
return;
}
// 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
LocalDateTime maxTime = getMaxValue(channelCache, SmsChannelDO::getUpdateTime);
if (smsChannelMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
initLocalCache();
}
}
@Override
public Long createSmsChannel(SmsChannelCreateReqVO createReqVO) {
// 插入
SmsChannelDO smsChannel = SmsChannelConvert.INSTANCE.convert(createReqVO);
smsChannelMapper.insert(smsChannel);
// 刷新缓存
initLocalCache();
return smsChannel.getId();
SmsChannelDO channel = SmsChannelConvert.INSTANCE.convert(createReqVO);
smsChannelMapper.insert(channel);
return channel.getId();
}
@Override
public void updateSmsChannel(SmsChannelUpdateReqVO updateReqVO) {
// 校验存在
validateSmsChannelExists(updateReqVO.getId());
SmsChannelDO channel = validateSmsChannelExists(updateReqVO.getId());
// 更新
SmsChannelDO updateObj = SmsChannelConvert.INSTANCE.convert(updateReqVO);
smsChannelMapper.updateById(updateObj);
// 刷新缓存
initLocalCache();
// 清空缓存
clearCache(updateReqVO.getId(), channel.getCode());
}
@Override
public void deleteSmsChannel(Long id) {
// 校验存在
validateSmsChannelExists(id);
SmsChannelDO channel = validateSmsChannelExists(id);
// 校验是否有在使用该账号的模版
if (smsTemplateService.countByChannelId(id) > 0) {
throw exception(SMS_CHANNEL_HAS_CHILDREN);
@@ -118,14 +114,29 @@ public class SmsChannelServiceImpl implements SmsChannelService {
// 删除
smsChannelMapper.deleteById(id);
// 刷新缓存
initLocalCache();
// 清空缓存
clearCache(id, channel.getCode());
}
private void validateSmsChannelExists(Long id) {
if (smsChannelMapper.selectById(id) == null) {
/**
* 清空指定渠道编号的缓存
*
* @param id 渠道编号
* @param code 渠道编码
*/
private void clearCache(Long id, String code) {
idClientCache.invalidate(id);
if (StrUtil.isNotEmpty(code)) {
codeClientCache.invalidate(code);
}
}
private SmsChannelDO validateSmsChannelExists(Long id) {
SmsChannelDO channel = smsChannelMapper.selectById(id);
if (channel == null) {
throw exception(SMS_CHANNEL_NOT_EXISTS);
}
return channel;
}
@Override
@@ -143,4 +154,14 @@ public class SmsChannelServiceImpl implements SmsChannelService {
return smsChannelMapper.selectPage(pageReqVO);
}
@Override
public SmsClient getSmsClient(Long id) {
return idClientCache.getUnchecked(id);
}
@Override
public SmsClient getSmsClient(String code) {
return codeClientCache.getUnchecked(code);
}
}

View File

@@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
@@ -49,9 +48,6 @@ public class SmsSendServiceImpl implements SmsSendService {
@Resource
private SmsLogService smsLogService;
@Resource
private SmsClientFactory smsClientFactory;
@Resource
private SmsProducer smsProducer;
@@ -95,7 +91,6 @@ public class SmsSendServiceImpl implements SmsSendService {
// 创建发送日志。如果模板被禁用,则不发送短信,只记录日志
Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus())
&& CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus());
;
String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams);
Long sendLogId = smsLogService.createSmsLog(mobile, userId, userType, isSend, template, content, templateParams);
@@ -132,7 +127,7 @@ public class SmsSendServiceImpl implements SmsSendService {
/**
* 将参数模板,处理成有序的 KeyValue 数组
* <p>
* 原因是,部分短信平台并不是使用 key 作为参数,而是数组下标,例如说腾讯云 https://cloud.tencent.com/document/product/382/39023
* 原因是,部分短信平台并不是使用 key 作为参数,而是数组下标,例如说 <a href="https://cloud.tencent.com/document/product/382/39023">腾讯云</a>
*
* @param template 短信模板
* @param templateParams 原始参数
@@ -160,7 +155,7 @@ public class SmsSendServiceImpl implements SmsSendService {
@Override
public void doSendSms(SmsSendMessage message) {
// 获得渠道对应的 SmsClient 客户端
SmsClient smsClient = smsClientFactory.getSmsClient(message.getChannelId());
SmsClient smsClient = smsChannelService.getSmsClient(message.getChannelId());
Assert.notNull(smsClient, "短信客户端({}) 不存在", message.getChannelId());
// 发送短信
SmsCommonResult<SmsSendRespDTO> sendResult = smsClient.sendSms(message.getLogId(), message.getMobile(),
@@ -173,7 +168,7 @@ public class SmsSendServiceImpl implements SmsSendService {
@Override
public void receiveSmsStatus(String channelCode, String text) throws Throwable {
// 获得渠道对应的 SmsClient 客户端
SmsClient smsClient = smsClientFactory.getSmsClient(channelCode);
SmsClient smsClient = smsChannelService.getSmsClient(channelCode);
Assert.notNull(smsClient, "短信客户端({}) 不存在", channelCode);
// 解析内容
List<SmsReceiveRespDTO> receiveResults = smsClient.parseSmsReceiveStatus(text);