【同步】BOOT 和 CLOUD 的功能

pull/203/MERGE
YunaiV 2025-08-16 19:13:10 +08:00
parent 66824310c1
commit b4df6f93cb
19 changed files with 587 additions and 52 deletions

View File

@ -17,6 +17,8 @@ public interface WebFilterOrderEnum {
int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500;
int API_ENCRYPT_FILTER = REQUEST_BODY_CACHE_FILTER + 1;
// OrderedRequestContextFilter 默认为 -105用于国际化上下文等等
int TENANT_CONTEXT_FILTER = - 104; // 需要保证在 ApiAccessLogFilter 前面

View File

@ -25,16 +25,16 @@ public class CommonResult<T> implements Serializable {
* @see ErrorCode#getCode()
*/
private Integer code;
/**
*
*/
private T data;
/**
*
*
* @see ErrorCode#getMsg() ()
*/
private String msg;
/**
*
*/
private T data;
/**
* result

View File

@ -11,12 +11,12 @@ import java.util.List;
@Data
public final class PageResult<T> implements Serializable {
@Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED)
private List<T> list;
@Schema(description = "总量", requiredMode = Schema.RequiredMode.REQUIRED)
private Long total;
@Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED)
private List<T> list;
public PageResult() {
}

View File

@ -0,0 +1,70 @@
package cn.iocoder.yudao.framework.encrypt.config;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
/**
* HTTP API
*
* @author
*/
@ConfigurationProperties(prefix = "yudao.api-encrypt")
@Validated
@Data
public class ApiEncryptProperties {
/**
*
*/
@NotNull(message = "是否开启不能为空")
private Boolean enable;
/**
*
*
* 1.
* 2.
*/
@NotEmpty(message = "请求头(响应头)名称不能为空")
private String header = "X-Api-Encrypt";
/**
* /
*
*
*
* 1. {@link cn.hutool.crypto.symmetric.SymmetricAlgorithm#AES}
* 2. {@link cn.hutool.crypto.symmetric.SM4#ALGORITHM_NAME}
*
* 1. {@link cn.hutool.crypto.asymmetric.AsymmetricAlgorithm#RSA}
* 2. {@link cn.hutool.crypto.asymmetric.SM2}
*
* @see <a href="https://help.aliyun.com/zh/ssl-certificate/what-are-a-public-key-and-a-private-key"></a>
*/
@NotEmpty(message = "对称加密算法不能为空")
private String algorithm;
/**
*
*
*
* 1.
* 2.
*/
@NotEmpty(message = "请求的解密密钥不能为空")
private String requestKey;
/**
*
*
*
* 1.
* 2.
*/
@NotEmpty(message = "响应的加密密钥不能为空")
private String responseKey;
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.framework.encrypt.config;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.encrypt.core.filter.ApiEncryptFilter;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import static cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration.createFilterBean;
@AutoConfiguration
@Slf4j
@EnableConfigurationProperties(ApiEncryptProperties.class)
@ConditionalOnProperty(prefix = "yudao.api-encrypt", name = "enable", havingValue = "true")
public class YudaoApiEncryptAutoConfiguration {
@Bean
public FilterRegistrationBean<ApiEncryptFilter> apiEncryptFilter(WebProperties webProperties,
ApiEncryptProperties apiEncryptProperties,
RequestMappingHandlerMapping requestMappingHandlerMapping,
GlobalExceptionHandler globalExceptionHandler) {
ApiEncryptFilter filter = new ApiEncryptFilter(webProperties, apiEncryptProperties,
requestMappingHandlerMapping, globalExceptionHandler);
return createFilterBean(filter, WebFilterOrderEnum.API_ENCRYPT_FILTER);
}
}

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.framework.encrypt.core.annotation;
import java.lang.annotation.*;
/**
* HTTP API
*/
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiEncrypt {
/**
* true
*/
boolean request() default true;
/**
* true
*/
boolean response() default true;
}

View File

@ -0,0 +1,86 @@
package cn.iocoder.yudao.framework.encrypt.core.filter;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.AsymmetricDecryptor;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.symmetric.SymmetricDecryptor;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* {@link HttpServletRequestWrapper}
*
* @author
*/
public class ApiDecryptRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public ApiDecryptRequestWrapper(HttpServletRequest request,
SymmetricDecryptor symmetricDecryptor,
AsymmetricDecryptor asymmetricDecryptor) throws IOException {
super(request);
// 读取 body允许 HEX、BASE64 传输
String requestBody = StrUtil.utf8Str(
IoUtil.readBytes(request.getInputStream(), false));
// 解密 body
body = symmetricDecryptor != null ? symmetricDecryptor.decrypt(requestBody)
: asymmetricDecryptor.decrypt(requestBody, KeyType.PrivateKey);
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
@Override
public int getContentLength() {
return body.length;
}
@Override
public long getContentLengthLong() {
return body.length;
}
@Override
public ServletInputStream getInputStream() {
ByteArrayInputStream stream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() {
return stream.read();
}
@Override
public int available() {
return body.length;
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}

View File

@ -0,0 +1,152 @@
package cn.iocoder.yudao.framework.encrypt.core.filter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.AsymmetricDecryptor;
import cn.hutool.crypto.asymmetric.AsymmetricEncryptor;
import cn.hutool.crypto.symmetric.SymmetricDecryptor;
import cn.hutool.crypto.symmetric.SymmetricEncryptor;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.encrypt.config.ApiEncryptProperties;
import cn.iocoder.yudao.framework.encrypt.core.annotation.ApiEncrypt;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.io.IOException;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
/**
* API {@link ApiEncrypt}
*
* 1.
* 2.
*
* 使 SpringMVC RequestBodyAdvice ResponseBodyAdvice
* 访 HTTP API
*
* @author
*/
@Slf4j
public class ApiEncryptFilter extends ApiRequestFilter {
private final ApiEncryptProperties apiEncryptProperties;
private final RequestMappingHandlerMapping requestMappingHandlerMapping;
private final GlobalExceptionHandler globalExceptionHandler;
private final SymmetricDecryptor requestSymmetricDecryptor;
private final AsymmetricDecryptor requestAsymmetricDecryptor;
private final SymmetricEncryptor responseSymmetricEncryptor;
private final AsymmetricEncryptor responseAsymmetricEncryptor;
public ApiEncryptFilter(WebProperties webProperties,
ApiEncryptProperties apiEncryptProperties,
RequestMappingHandlerMapping requestMappingHandlerMapping,
GlobalExceptionHandler globalExceptionHandler) {
super(webProperties);
this.apiEncryptProperties = apiEncryptProperties;
this.requestMappingHandlerMapping = requestMappingHandlerMapping;
this.globalExceptionHandler = globalExceptionHandler;
if (StrUtil.equalsIgnoreCase(apiEncryptProperties.getAlgorithm(), "AES")) {
this.requestSymmetricDecryptor = SecureUtil.aes(StrUtil.utf8Bytes(apiEncryptProperties.getRequestKey()));
this.requestAsymmetricDecryptor = null;
this.responseSymmetricEncryptor = SecureUtil.aes(StrUtil.utf8Bytes(apiEncryptProperties.getResponseKey()));
this.responseAsymmetricEncryptor = null;
} else if (StrUtil.equalsIgnoreCase(apiEncryptProperties.getAlgorithm(), "RSA")) {
this.requestSymmetricDecryptor = null;
this.requestAsymmetricDecryptor = SecureUtil.rsa(apiEncryptProperties.getRequestKey(), null);
this.responseSymmetricEncryptor = null;
this.responseAsymmetricEncryptor = SecureUtil.rsa(null, apiEncryptProperties.getResponseKey());
} else {
// 补充说明:如果要支持 SM2、SM4 等算法,可在此处增加对应实例的创建,并添加相应的 Maven 依赖即可。
throw new IllegalArgumentException("不支持的加密算法:" + apiEncryptProperties.getAlgorithm());
}
}
@Override
@SuppressWarnings("NullableProblems")
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
// 获取 @ApiEncrypt 注解
ApiEncrypt apiEncrypt = getApiEncrypt(request);
boolean requestEnable = apiEncrypt != null && apiEncrypt.request();
boolean responseEnable = apiEncrypt != null && apiEncrypt.response();
String encryptHeader = request.getHeader(apiEncryptProperties.getHeader());
if (!requestEnable && !responseEnable && StrUtil.isBlank(encryptHeader)) {
chain.doFilter(request, response);
return;
}
// 1. 解密请求
if (ObjectUtils.equalsAny(HttpMethod.valueOf(request.getMethod()),
HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE)) {
try {
if (StrUtil.isNotBlank(encryptHeader)) {
request = new ApiDecryptRequestWrapper(request,
requestSymmetricDecryptor, requestAsymmetricDecryptor);
} else if (requestEnable) {
throw invalidParamException("请求未包含加密标头,请检查是否正确配置了加密标头");
}
} catch (Exception ex) {
CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
ServletUtils.writeJSON(response, result);
return;
}
}
// 2. 执行过滤器链
if (responseEnable) {
// 特殊仅包装最后执行。目的Response 内容可以被重复读取!!!
response = new ApiEncryptResponseWrapper(response);
}
chain.doFilter(request, response);
// 3. 加密响应(真正执行)
if (responseEnable) {
((ApiEncryptResponseWrapper) response).encrypt(apiEncryptProperties,
responseSymmetricEncryptor, responseAsymmetricEncryptor);
}
}
/**
* @ApiEncrypt
*
* @param request
*/
private ApiEncrypt getApiEncrypt(HttpServletRequest request) {
try {
HandlerExecutionChain mappingHandler = requestMappingHandlerMapping.getHandler(request);
if (mappingHandler == null) {
return null;
}
Object handler = mappingHandler.getHandler();
if (handler instanceof HandlerMethod handlerMethod) {
ApiEncrypt annotation = handlerMethod.getMethodAnnotation(ApiEncrypt.class);
if (annotation == null) {
annotation = handlerMethod.getBeanType().getAnnotation(ApiEncrypt.class);
}
return annotation;
}
} catch (Exception e) {
log.error("[getApiEncrypt][url({}/{}) 获取 @ApiEncrypt 注解失败]",
request.getRequestURI(), request.getMethod(), e);
}
return null;
}
}

View File

@ -0,0 +1,109 @@
package cn.iocoder.yudao.framework.encrypt.core.filter;
import cn.hutool.crypto.asymmetric.AsymmetricEncryptor;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.symmetric.SymmetricEncryptor;
import cn.iocoder.yudao.framework.encrypt.config.ApiEncryptProperties;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
/**
* {@link HttpServletResponseWrapper}
*
* @author
*/
public class ApiEncryptResponseWrapper extends HttpServletResponseWrapper {
private final ByteArrayOutputStream byteArrayOutputStream;
private final ServletOutputStream servletOutputStream;
private final PrintWriter printWriter;
public ApiEncryptResponseWrapper(HttpServletResponse response) {
super(response);
this.byteArrayOutputStream = new ByteArrayOutputStream();
this.servletOutputStream = this.getOutputStream();
this.printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream));
}
public void encrypt(ApiEncryptProperties properties,
SymmetricEncryptor symmetricEncryptor,
AsymmetricEncryptor asymmetricEncryptor) throws IOException {
// 1.1 清空 body
HttpServletResponse response = (HttpServletResponse) this.getResponse();
response.resetBuffer();
// 1.2 获取 body
this.flushBuffer();
byte[] body = byteArrayOutputStream.toByteArray();
// 2. 加密 body
String encryptedBody = symmetricEncryptor != null ? symmetricEncryptor.encryptBase64(body)
: asymmetricEncryptor.encryptBase64(body, KeyType.PublicKey);
response.getWriter().write(encryptedBody);
// 3. 添加加密 header 标识
this.addHeader(properties.getHeader(), "true");
// 特殊特殊https://juejin.cn/post/6867327674675625992
this.addHeader("Access-Control-Expose-Headers", properties.getHeader());
}
@Override
public PrintWriter getWriter() {
return printWriter;
}
@Override
public void flushBuffer() throws IOException {
if (servletOutputStream != null) {
servletOutputStream.flush();
}
if (printWriter != null) {
printWriter.flush();
}
}
@Override
public void reset() {
byteArrayOutputStream.reset();
}
@Override
public ServletOutputStream getOutputStream() {
return new ServletOutputStream() {
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
@Override
public void write(int b) {
byteArrayOutputStream.write(b);
}
@Override
@SuppressWarnings("NullableProblems")
public void write(byte[] b) throws IOException {
byteArrayOutputStream.write(b);
}
@Override
@SuppressWarnings("NullableProblems")
public void write(byte[] b, int off, int len) {
byteArrayOutputStream.write(b, off, len);
}
};
}
}

View File

@ -0,0 +1,4 @@
/**
* HTTP API Request Response
*/
package cn.iocoder.yudao.framework.encrypt;

View File

@ -1,14 +1,13 @@
package cn.iocoder.yudao.framework.web.core.filter;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
@ -29,12 +28,22 @@ public class CacheRequestBodyWrapper extends HttpServletRequestWrapper {
}
@Override
public BufferedReader getReader() throws IOException {
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
public int getContentLength() {
return body.length;
}
@Override
public long getContentLengthLong() {
return body.length;
}
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
// 返回 ServletInputStream
return new ServletInputStream() {

View File

@ -4,4 +4,5 @@ cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration
cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration
cn.iocoder.yudao.framework.apilog.config.YudaoApiLogRpcAutoConfiguration
cn.iocoder.yudao.framework.xss.config.YudaoXssAutoConfiguration
cn.iocoder.yudao.framework.banner.config.YudaoBannerAutoConfiguration
cn.iocoder.yudao.framework.banner.config.YudaoBannerAutoConfiguration
cn.iocoder.yudao.framework.encrypt.config.YudaoApiEncryptAutoConfiguration

View File

@ -14,7 +14,7 @@ public interface LogRecordConstants {
String CRM_CLUE_CREATE_SUB_TYPE = "创建线索";
String CRM_CLUE_CREATE_SUCCESS = "创建了线索{{#clue.name}}";
String CRM_CLUE_UPDATE_SUB_TYPE = "更新线索";
String CRM_CLUE_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReq}}";
String CRM_CLUE_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReqVO}}";
String CRM_CLUE_DELETE_SUB_TYPE = "删除线索";
String CRM_CLUE_DELETE_SUCCESS = "删除了线索【{{#clueName}}】";
String CRM_CLUE_TRANSFER_SUB_TYPE = "转移线索";

View File

@ -275,6 +275,10 @@ public class MenuServiceImpl implements MenuService {
if (menu == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同 id 的菜单
if (id == null) {
return;
}
if (!menu.getId().equals(id)) {
throw exception(MENU_COMPONENT_NAME_DUPLICATE);
}

View File

@ -158,6 +158,13 @@ yudao:
enable: false
exclude-urls: # 如下 url仅仅是为了演示去掉配置也没关系
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
api-encrypt:
enable: true # 是否开启 API 加密
algorithm: AES # 加密算法,支持 AES、RSA 等
request-key: 52549111389893486934626385991395 # 【AES 案例】请求加密的秘钥,,必须 16、24、32 位
response-key: 96103715984234343991809655248883 # 【AES 案例】响应加密的秘钥AES 案例,必须 16、24、32 位
# request-key: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKWzasimcZ1icsWDPVdTXcZs1DkOWjI+m9bTQU8aOqflnomkr6QO1WWeSHBHzuJGsTlV/ZY2pFfq/NstKC94hBjx7yioYJvzb2bKN/Uy4j5nM3iCF//u0RiFkkY8j0Bt/EWoFTOb6RHf8cHIAjbYYtP3pYzbpCIwryfe0g//KIuzAgMBAAECgYADDjZrYcpZjR2xr7RbXmGtzYbyUGXwZEAqa3XaWBD51J2iSyOkAlQEDjGmxGQ3vvb4qDHHadWI+3/TKNeDXJUO+xTVJrnismK5BsHyC6dfxlIK/5BAuknryTca/3UoA1yomS9ZlF3Q0wcecaDoEnSmZEaTrp9T3itPAz4KnGjv5QJBAN5mNcfu6iJ5ktNvEdzqcxkKwbXb9Nq1SLnmTvt+d5TPX7eQ9fCwtOfVu5iBLhhZzb5PJ7pSN3Zt6rl5/jPOGv0CQQC+vETX9oe1wbxZSv6/RBGy0Xow6GndbJwvd89PcAJ2h+OJXWtg/rRHB3t9EQm7iis0XbZTapj19E4U6l8EibhvAkEA1CvYpRwmHKu1SqdM+GBnW/2qHlBwwXJvpoK02TOm674HR/4w0+YRQJfkd7LOAgcyxJuJgDTNmtt0MmzS+iNoFQJAMVSUIZ77XoDq69U/qcw7H5qaFcgmiUQr6QL9tTftCyb+LGri+MUnby96OtCLSdvkbLjIDS8GvKYhA7vSM2RDNQJBAKGyVVnFFIrbK3yuwW71yvxQEGoGxlgvZSezZ4vGgqTxrr9HvAtvWLwR6rpe6ybR/x8uUtoW7NRBWgpiIFwjvY4= # 【RSA 案例】请求解密的私钥
# response-key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDh/CHyBcS/zEfVyINVA7+c9Xxl0CPdxPMK1OIjxaLy/7BLfbkoEpI8onQtjuzfpuxCraDem9bu3BMF0pMH95HytI3Vi0kGjaV+WLIalwgc2w37oA2sbsmKzQOP7SDLO5s2QJNAD7kVwd+Q5rqaLu2MO0xVv+0IUJhn83hClC0L5wIDAQAB # 【RSA 案例】响应加密的公钥
swagger:
title: 管理后台
description: 提供管理员管理的所有功能

View File

@ -26,7 +26,6 @@ import jakarta.validation.Validation;
import jakarta.validation.Validator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;

View File

@ -105,12 +105,40 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
}
@Test
public void testValidateDeptExists_notFound() {
public void testDeleteDeptList_success() {
// mock 数据
DeptDO deptDO1 = randomPojo(DeptDO.class);
deptMapper.insert(deptDO1);
DeptDO deptDO2 = randomPojo(DeptDO.class);
deptMapper.insert(deptDO2);
// 准备参数
Long id = randomLongId();
List<Long> ids = Arrays.asList(deptDO1.getId(), deptDO2.getId());
// 调用
deptService.deleteDeptList(ids);
// 校验数据不存在了
assertNull(deptMapper.selectById(deptDO1.getId()));
assertNull(deptMapper.selectById(deptDO2.getId()));
}
@Test
public void testDeleteDeptList_exitsChildren() {
// mock 数据
DeptDO parentDept = randomPojo(DeptDO.class);
deptMapper.insert(parentDept);
DeptDO childrenDeptDO = randomPojo(DeptDO.class, o -> {
o.setParentId(parentDept.getId());
o.setStatus(randomCommonStatus());
});
deptMapper.insert(childrenDeptDO);
DeptDO anotherDept = randomPojo(DeptDO.class);
deptMapper.insert(anotherDept);
// 准备参数(包含有子部门的 parentDept
List<Long> ids = Arrays.asList(parentDept.getId(), anotherDept.getId());
// 调用, 并断言异常
assertServiceException(() -> deptService.validateDeptExists(id), DEPT_NOT_FOUND);
assertServiceException(() -> deptService.deleteDeptList(ids), DEPT_EXITS_CHILDREN);
}
@Test

View File

@ -33,11 +33,11 @@
</dependency>
<!-- 会员中心。默认注释,保证编译速度 -->
<!-- <dependency>-->
<!-- <groupId>cn.iocoder.cloud</groupId>-->
<!-- <artifactId>yudao-module-member-server</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-member-server</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据报表。默认注释,保证编译速度 -->
<!-- <dependency>-->
@ -46,17 +46,17 @@
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<!-- 工作流。默认注释,保证编译速度 -->
<!-- <dependency>-->
<!-- <groupId>cn.iocoder.cloud</groupId>-->
<!-- <artifactId>yudao-module-bpm-server</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-bpm-server</artifactId>
<version>${revision}</version>
</dependency>
<!-- 支付服务。默认注释,保证编译速度 -->
<!-- <dependency>-->
<!-- <groupId>cn.iocoder.cloud</groupId>-->
<!-- <artifactId>yudao-module-pay-server</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-pay-server</artifactId>
<version>${revision}</version>
</dependency>
<!-- 微信公众号模块。默认注释,保证编译速度 -->
<!-- <dependency>-->
@ -66,26 +66,26 @@
<!-- </dependency>-->
<!-- 商城相关模块。默认注释,保证编译速度-->
<!-- <dependency>-->
<!-- <groupId>cn.iocoder.cloud</groupId>-->
<!-- <artifactId>yudao-module-product-server</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>cn.iocoder.cloud</groupId>-->
<!-- <artifactId>yudao-module-promotion-server</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>cn.iocoder.cloud</groupId>-->
<!-- <artifactId>yudao-module-trade-server</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>cn.iocoder.cloud</groupId>-->
<!-- <artifactId>yudao-module-statistics-server</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-product-server</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-promotion-server</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-trade-server</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-statistics-server</artifactId>
<version>${revision}</version>
</dependency>
<!-- CRM 相关模块。默认注释,保证编译速度 -->
<!-- <dependency>-->

View File

@ -269,6 +269,13 @@ yudao:
security:
permit-all_urls:
- /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录
api-encrypt:
enable: true # 是否开启 API 加密
algorithm: AES # 加密算法,支持 AES、RSA 等
request-key: 52549111389893486934626385991395 # 【AES 案例】请求加密的秘钥,,必须 16、24、32 位
response-key: 96103715984234343991809655248883 # 【AES 案例】响应加密的秘钥AES 案例,必须 16、24、32 位
# request-key: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKWzasimcZ1icsWDPVdTXcZs1DkOWjI+m9bTQU8aOqflnomkr6QO1WWeSHBHzuJGsTlV/ZY2pFfq/NstKC94hBjx7yioYJvzb2bKN/Uy4j5nM3iCF//u0RiFkkY8j0Bt/EWoFTOb6RHf8cHIAjbYYtP3pYzbpCIwryfe0g//KIuzAgMBAAECgYADDjZrYcpZjR2xr7RbXmGtzYbyUGXwZEAqa3XaWBD51J2iSyOkAlQEDjGmxGQ3vvb4qDHHadWI+3/TKNeDXJUO+xTVJrnismK5BsHyC6dfxlIK/5BAuknryTca/3UoA1yomS9ZlF3Q0wcecaDoEnSmZEaTrp9T3itPAz4KnGjv5QJBAN5mNcfu6iJ5ktNvEdzqcxkKwbXb9Nq1SLnmTvt+d5TPX7eQ9fCwtOfVu5iBLhhZzb5PJ7pSN3Zt6rl5/jPOGv0CQQC+vETX9oe1wbxZSv6/RBGy0Xow6GndbJwvd89PcAJ2h+OJXWtg/rRHB3t9EQm7iis0XbZTapj19E4U6l8EibhvAkEA1CvYpRwmHKu1SqdM+GBnW/2qHlBwwXJvpoK02TOm674HR/4w0+YRQJfkd7LOAgcyxJuJgDTNmtt0MmzS+iNoFQJAMVSUIZ77XoDq69U/qcw7H5qaFcgmiUQr6QL9tTftCyb+LGri+MUnby96OtCLSdvkbLjIDS8GvKYhA7vSM2RDNQJBAKGyVVnFFIrbK3yuwW71yvxQEGoGxlgvZSezZ4vGgqTxrr9HvAtvWLwR6rpe6ybR/x8uUtoW7NRBWgpiIFwjvY4= # 【RSA 案例】请求解密的私钥
# response-key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDh/CHyBcS/zEfVyINVA7+c9Xxl0CPdxPMK1OIjxaLy/7BLfbkoEpI8onQtjuzfpuxCraDem9bu3BMF0pMH95HytI3Vi0kGjaV+WLIalwgc2w37oA2sbsmKzQOP7SDLO5s2QJNAD7kVwd+Q5rqaLu2MO0xVv+0IUJhn83hClC0L5wIDAQAB # 【RSA 案例】响应加密的公钥
websocket:
enable: true # websocket的开关
path: /infra/ws # 路径