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

# Conflicts:
#	yudao-server/src/main/resources/application-local.yaml
This commit is contained in:
YunaiV 2024-05-04 09:15:58 +08:00
commit 213a6aa0fc
14 changed files with 110 additions and 46 deletions

View File

@ -23,7 +23,7 @@ public class YudaoDataSourceAutoConfiguration {
* 创建 DruidAdRemoveFilter 过滤器过滤 common.js 的广告
*/
@Bean
@ConditionalOnProperty(name = "spring.datasource.druid.web-stat-filter.enabled", havingValue = "true")
@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true")
public FilterRegistrationBean<DruidAdRemoveFilter> druidAdRemoveFilterFilter(DruidStatProperties properties) {
// 获取 druid web 监控页面的参数
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();

View File

@ -5,7 +5,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
import cn.iocoder.yudao.framework.common.pojo.SortingField;
import cn.iocoder.yudao.framework.mybatis.core.enums.SqlConstants;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@ -20,6 +22,7 @@ import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
* MyBatis Plus BaseMapper 的基础上拓展提供更多的能力
@ -147,6 +150,11 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
* @param entities 实体们
*/
default Boolean insertBatch(Collection<T> entities) {
// 特殊SQL Server 批量插入后获取 id 会报错因此通过循环处理
if (Objects.equals(SqlConstants.DB_TYPE, DbType.SQL_SERVER)) {
entities.forEach(this::insert);
return CollUtil.isNotEmpty(entities);
}
return Db.saveBatch(entities);
}
@ -157,6 +165,11 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
* @param size 插入数量 Db.saveBatch 默认为 1000
*/
default Boolean insertBatch(Collection<T> entities, int size) {
// 特殊SQL Server 批量插入后获取 id 会报错因此通过循环处理
if (Objects.equals(SqlConstants.DB_TYPE, DbType.SQL_SERVER)) {
entities.forEach(this::insert);
return CollUtil.isNotEmpty(entities);
}
return Db.saveBatch(entities, size);
}

View File

@ -43,7 +43,7 @@ public class ApiAccessLogInterceptor implements HandlerInterceptor {
log.info("[preHandle][开始请求 URL({}) 无参数]", request.getRequestURI());
} else {
log.info("[preHandle][开始请求 URL({}) 参数({})]", request.getRequestURI(),
StrUtil.nullToDefault(requestBody, queryString.toString()));
StrUtil.blankToDefault(requestBody, queryString.toString()));
}
// 计时
StopWatch stopWatch = new StopWatch();

View File

@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
@ -32,6 +33,7 @@ import org.springframework.web.servlet.NoHandlerFoundException;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
@ -45,6 +47,11 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
@Slf4j
public class GlobalExceptionHandler {
/**
* 忽略的 ServiceException 错误提示避免打印过多 logger
*/
public static final Set<String> IGNORE_ERROR_MESSAGES = SetUtils.asSet("无效的刷新令牌");
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
private final String applicationName;
@ -199,7 +206,10 @@ public class GlobalExceptionHandler {
*/
@ExceptionHandler(value = ServiceException.class)
public CommonResult<?> serviceExceptionHandler(ServiceException ex) {
log.info("[serviceExceptionHandler]", ex);
if (!IGNORE_ERROR_MESSAGES.contains(ex.getMessage())) {
// 不包含的时候才进行打印避免 ex 堆栈过多
log.info("[serviceExceptionHandler]", ex);
}
return CommonResult.error(ex.getCode(), ex.getMessage());
}

View File

@ -66,7 +66,7 @@ public class CrmBusinessSaveReqVO {
private Long contactId; // 使用场景联系人详情添加商机时如果需要关联两者需要传递 contactId 字段
@Schema(description = "产品列表")
private List<BusinessProduct> businessProducts;
private List<BusinessProduct> products;
@Schema(description = "产品列表")
@Data

View File

@ -89,7 +89,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
success = CRM_BUSINESS_CREATE_SUCCESS)
public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
// 1.1 校验产品项的有效性
List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(createReqVO.getBusinessProducts());
List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(createReqVO.getProducts());
// 1.2 校验关联字段
validateRelationDataExists(createReqVO);
@ -130,7 +130,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
// 1.1 校验存在
CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
// 1.2 校验产品项的有效性
List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getBusinessProducts());
List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getProducts());
// 1.3 校验关联字段
validateRelationDataExists(updateReqVO);

View File

@ -0,0 +1,5 @@
### 请求 /infra/job/sync 接口 => 成功
POST {{baseUrl}}/infra/job/sync
Content-Type: application/json
tenant-id: {{adminTenentId}}
Authorization: Bearer {{token}}

View File

@ -90,6 +90,14 @@ public class JobController {
return success(true);
}
@PostMapping("/sync")
@Operation(summary = "同步定时任务")
@PreAuthorize("@ss.hasPermission('infra:job:create')")
public CommonResult<Boolean> syncJob() throws SchedulerException {
jobService.syncJob();
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得定时任务")
@Parameter(name = "id", description = "编号", required = true, example = "1024")

View File

@ -45,6 +45,13 @@ public interface JobService {
*/
void triggerJob(Long id) throws SchedulerException;
/**
* 同步定时任务
*
* 目的自己存储的 Job 信息强制同步到 Quartz
*/
void syncJob() throws SchedulerException;
/**
* 删除定时任务
*

View File

@ -12,11 +12,15 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.job.JobDO;
import cn.iocoder.yudao.module.infra.dal.mysql.job.JobMapper;
import cn.iocoder.yudao.module.infra.enums.job.JobStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.containsAny;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
@ -28,6 +32,7 @@ import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
*/
@Service
@Validated
@Slf4j
public class JobServiceImpl implements JobService {
@Resource
@ -129,6 +134,26 @@ public class JobServiceImpl implements JobService {
schedulerManager.triggerJob(job.getId(), job.getHandlerName(), job.getHandlerParam());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void syncJob() throws SchedulerException {
// 1. 查询 Job 配置
List<JobDO> jobList = jobMapper.selectList();
// 2. 遍历处理
for (JobDO job : jobList) {
// 2.1 先删除再创建
schedulerManager.deleteJob(job.getHandlerName());
schedulerManager.addJob(job.getId(), job.getHandlerName(), job.getHandlerParam(), job.getCronExpression(),
job.getRetryCount(), job.getRetryInterval());
// 2.2 如果 status 为暂停则需要暂停
if (Objects.equals(job.getStatus(), JobStatusEnum.STOP.getStatus())) {
schedulerManager.pauseJob(job.getHandlerName());
}
log.info("[syncJob][id({}) handlerName({}) 同步完成]", job.getId(), job.getHandlerName());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteJob(Long id) throws SchedulerException {

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.report.framework.jmreport.config;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
import cn.iocoder.yudao.module.report.framework.jmreport.core.service.JmReportTokenServiceImpl;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import org.jeecg.modules.jmreport.api.JmReportTokenServiceI;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
@ -19,8 +20,10 @@ public class JmReportConfiguration {
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public JmReportTokenServiceI jmReportTokenService(OAuth2TokenApi oAuth2TokenApi, SecurityProperties securityProperties) {
return new JmReportTokenServiceImpl(oAuth2TokenApi, securityProperties);
public JmReportTokenServiceI jmReportTokenService(OAuth2TokenApi oAuth2TokenApi,
PermissionApi permissionApi,
SecurityProperties securityProperties) {
return new JmReportTokenServiceImpl(oAuth2TokenApi, permissionApi, securityProperties);
}
}

View File

@ -11,12 +11,13 @@ import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import cn.iocoder.yudao.module.system.api.permission.RoleApi;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.jeecg.modules.jmreport.api.JmReportTokenServiceI;
import org.springframework.http.HttpHeaders;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
@ -37,6 +38,7 @@ public class JmReportTokenServiceImpl implements JmReportTokenServiceI {
private static final String AUTHORIZATION_FORMAT = SecurityFrameworkUtils.AUTHORIZATION_BEARER + " %s";
private final OAuth2TokenApi oauth2TokenApi;
private final PermissionApi permissionApi;
private final SecurityProperties securityProperties;
@ -130,9 +132,12 @@ public class JmReportTokenServiceImpl implements JmReportTokenServiceI {
}
@Override
public String[] getRoles(String s) {
// 暂时不用实现因为不用 JmReport 的角色
return null;
public String[] getRoles(String token) {
// 参见文档 https://help.jeecg.com/jimureport/prodSafe.html 文档
// 适配如果是本系统的管理员则转换成 jimu 报表的管理员
Long userId = SecurityFrameworkUtils.getLoginUserId();
return permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode())
? new String[]{"admin"} : null;
}
@Override

View File

@ -40,17 +40,14 @@ spring:
primary: master
datasource:
master:
name: ruoyi-vue-pro
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=CTT&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
username: root
password: 3WLiVUBEwTbvAfsh
password: 123456
slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改
name: ruoyi-vue-pro
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&serverTimezone=CTT&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.jdbc.Driver
lazy: true # 开启懒加载,保证启动速度
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
username: root
password: 3WLiVUBEwTbvAfsh
password: 123456
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
data:

View File

@ -39,39 +39,30 @@ spring:
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
# validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效, 不同数据库有所不同, 建议mysql以外的先注释掉
validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
test-while-idle: true
test-on-borrow: false
test-on-return: false
primary: master
datasource:
master:
# url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@//127.0.0.1:1521/XEPDB1 # Oracle 连接的示例
# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer 连接的示例
url: jdbc:dm://localhost:5236 # DM 连接的示例
# username: root
# password: 123456
# username: sa # SQLServer 连接的示例
# password: Yudao@2024 # SQLServer 连接的示例
username: SYSDBA # DM 连接的示例
password: SYSDBA001 # DM 连接的示例
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer 连接的示例
# url: jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO # DM 连接的示例
username: root
password: 123456
# username: sa # SQL Server 连接的示例
# password: Yudao@2024 # SQL Server 连接的示例
# username: SYSDBA # DM 连接的示例
# password: SYSDBA001 # DM 连接的示例
slave: # 模拟从库,可根据自己需要修改
lazy: true # 开启懒加载,保证启动速度
# url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@//127.0.0.1:1521/XEPDB1 # Oracle 连接的示例
# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro # SQLServer 连接的示例
# url: jdbc:dm://10.211.55.4:5236?schema=RUOYI_VUE_PRO # DM 连接的示例
# username: root
# password: 123456
username: sa # SQLServer 连接的示例
password: Yudao@2024 # SQLServer 连接的示例
# username: SYSDBA # DM 连接的示例
# password: SYSDBA # DM 连接的示例
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: 123456
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
data: