Merge remote-tracking branch 'origin/master' into feature/springdoc

This commit is contained in:
xingyu
2023-01-29 16:11:51 +08:00
182 changed files with 6621 additions and 810 deletions

View File

@ -41,6 +41,7 @@
<module>yudao-spring-boot-starter-flowable</module>
<module>yudao-spring-boot-starter-captcha</module>
<module>yudao-spring-boot-starter-websocket</module>
<module>yudao-spring-boot-starter-desensitize</module>
</modules>
<artifactId>yudao-framework</artifactId>

View File

@ -41,6 +41,11 @@ public class LocalDateTimeUtils {
return LocalDateTime.of(year, mouth, day, 0, 0, 0);
}
public static LocalDateTime[] buildBetweenTime(int year1, int mouth1, int day1,
int year2, int mouth2, int day2) {
return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)};
}
/**
* 判断当前时间是否在该时间范围内
*

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>yudao-framework</artifactId>
<groupId>cn.iocoder.boot</groupId>
<version>${revision}</version>
</parent>
<artifactId>yudao-spring-boot-starter-desensitize</artifactId>
<description>脱敏组件:支持 JSON 返回数据时,将邮箱、手机等字段进行脱敏</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,32 @@
package cn.iocoder.yudao.framework.desensitize.core.base.annotation;
import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
import cn.iocoder.yudao.framework.desensitize.core.base.serializer.StringDesensitizeSerializer;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 顶级脱敏注解,自定义注解需要使用此注解
*
* @author gaibu
*/
@Documented
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside // 此注解是其他所有 jackson 注解的元注解,打上了此注解的注解表明是 jackson 注解的一部分
@JsonSerialize(using = StringDesensitizeSerializer.class) // 指定序列化器
public @interface DesensitizeBy {
/**
* 脱敏处理器
*/
@SuppressWarnings("rawtypes")
Class<? extends DesensitizationHandler> handler();
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.framework.desensitize.core.base.handler;
import java.lang.annotation.Annotation;
/**
* 脱敏处理器接口
*
* @author gaibu
*/
public interface DesensitizationHandler<T extends Annotation> {
/**
* 脱敏
*
* @param origin 原始字符串
* @param annotation 注解信息
* @return 脱敏后的字符串
*/
String desensitize(String origin, T annotation);
}

View File

@ -0,0 +1,92 @@
package cn.iocoder.yudao.framework.desensitize.core.base.serializer;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.lang.Singleton;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import lombok.Getter;
import lombok.Setter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
/**
* 脱敏序列化器
*
* 实现 JSON 返回数据时,使用 {@link DesensitizationHandler} 对声明脱敏注解的字段,进行脱敏处理。
*
* @author gaibu
*/
@SuppressWarnings("rawtypes")
public class StringDesensitizeSerializer extends StdSerializer<String> implements ContextualSerializer {
@Getter
@Setter
private DesensitizationHandler desensitizationHandler;
protected StringDesensitizeSerializer() {
super(String.class);
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) {
DesensitizeBy annotation = beanProperty.getAnnotation(DesensitizeBy.class);
if (annotation == null) {
return this;
}
// 创建一个 StringDesensitizeSerializer 对象,使用 DesensitizeBy 对应的处理器
StringDesensitizeSerializer serializer = new StringDesensitizeSerializer();
serializer.setDesensitizationHandler(Singleton.get(annotation.handler()));
return serializer;
}
@Override
@SuppressWarnings("unchecked")
public void serialize(String value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
if (StrUtil.isBlank(value)) {
gen.writeNull();
return;
}
// 获取序列化字段
Field field = getField(gen);
// 自定义处理器
DesensitizeBy[] annotations = AnnotationUtil.getCombinationAnnotations(field, DesensitizeBy.class);
if (ArrayUtil.isEmpty(annotations)) {
gen.writeString(value);
return;
}
for (Annotation annotation : field.getAnnotations()) {
if (AnnotationUtil.hasAnnotation(annotation.annotationType(), DesensitizeBy.class)) {
value = this.desensitizationHandler.desensitize(value, annotation);
gen.writeString(value);
return;
}
}
gen.writeString(value);
}
/**
* 获取字段
*
* @param generator JsonGenerator
* @return 字段
*/
private Field getField(JsonGenerator generator) {
String currentName = generator.getOutputContext().getCurrentName();
Object currentValue = generator.getCurrentValue();
Class<?> currentValueClass = currentValue.getClass();
return ReflectUtil.getField(currentValueClass, currentName);
}
}

View File

@ -0,0 +1,4 @@
/**
* 脱敏组件:支持 JSON 返回数据时,将邮箱、手机等字段进行脱敏
*/
package cn.iocoder.yudao.framework.desensitize.core;

View File

@ -0,0 +1,36 @@
package cn.iocoder.yudao.framework.desensitize.core.regex.annotation;
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import cn.iocoder.yudao.framework.desensitize.core.regex.handler.EmailDesensitizationHandler;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 邮箱脱敏注解
*
* @author gaibu
*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = EmailDesensitizationHandler.class)
public @interface EmailDesensitize {
/**
* 匹配的正则表达式
*/
String regex() default "(^.)[^@]*(@.*$)";
/**
* 替换规则,邮箱;
*
* 比如example@gmail.com 脱敏之后为 e****@gmail.com
*/
String replacer() default "$1****$2";
}

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.framework.desensitize.core.regex.annotation;
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import cn.iocoder.yudao.framework.desensitize.core.regex.handler.DefaultRegexDesensitizationHandler;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 正则脱敏注解
*
* @author gaibu
*/
@Documented
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = DefaultRegexDesensitizationHandler.class)
public @interface RegexDesensitize {
/**
* 匹配的正则表达式(默认匹配所有)
*/
String regex() default "^[\\s\\S]*$";
/**
* 替换规则,会将匹配到的字符串全部替换成 replacer
*
* 例如regex=123; replacer=******
* 原始字符串 123456789
* 脱敏后字符串 ******456789
*/
String replacer() default "******";
}

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.framework.desensitize.core.regex.handler;
import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
import java.lang.annotation.Annotation;
/**
* 正则表达式脱敏处理器抽象类,已实现通用的方法
*
* @author gaibu
*/
public abstract class AbstractRegexDesensitizationHandler<T extends Annotation>
implements DesensitizationHandler<T> {
@Override
public String desensitize(String origin, T annotation) {
String regex = getRegex(annotation);
String replacer = getReplacer(annotation);
return origin.replaceAll(regex, replacer);
}
/**
* 获取注解上的 regex 参数
*
* @param annotation 注解信息
* @return 正则表达式
*/
abstract String getRegex(T annotation);
/**
* 获取注解上的 replacer 参数
*
* @param annotation 注解信息
* @return 待替换的字符串
*/
abstract String getReplacer(T annotation);
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.framework.desensitize.core.regex.handler;
import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.RegexDesensitize;
/**
* {@link RegexDesensitize} 的正则脱敏处理器
*
* @author gaibu
*/
public class DefaultRegexDesensitizationHandler extends AbstractRegexDesensitizationHandler<RegexDesensitize> {
@Override
String getRegex(RegexDesensitize annotation) {
return annotation.regex();
}
@Override
String getReplacer(RegexDesensitize annotation) {
return annotation.replacer();
}
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.framework.desensitize.core.regex.handler;
import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.EmailDesensitize;
/**
* {@link EmailDesensitize} 的脱敏处理器
*
* @author gaibu
*/
public class EmailDesensitizationHandler extends AbstractRegexDesensitizationHandler<EmailDesensitize> {
@Override
String getRegex(EmailDesensitize annotation) {
return annotation.regex();
}
@Override
String getReplacer(EmailDesensitize annotation) {
return annotation.replacer();
}
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.BankCardDesensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 银行卡号
*
* @author gaibu
*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = BankCardDesensitization.class)
public @interface BankCardDesensitize {
/**
* 前缀保留长度
*/
int prefixKeep() default 6;
/**
* 后缀保留长度
*/
int suffixKeep() default 2;
/**
* 替换规则,银行卡号; 比如9988002866797031 脱敏之后为 998800********31
*/
String replacer() default "*";
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.CarLicenseDesensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 车牌号
*
* @author gaibu
*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = CarLicenseDesensitization.class)
public @interface CarLicenseDesensitize {
/**
* 前缀保留长度
*/
int prefixKeep() default 3;
/**
* 后缀保留长度
*/
int suffixKeep() default 1;
/**
* 替换规则,车牌号;比如粤A66666 脱敏之后为粤A6***6
*/
String replacer() default "*";
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.ChineseNameDesensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 中文名
*
* @author gaibu
*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = ChineseNameDesensitization.class)
public @interface ChineseNameDesensitize {
/**
* 前缀保留长度
*/
int prefixKeep() default 1;
/**
* 后缀保留长度
*/
int suffixKeep() default 0;
/**
* 替换规则,中文名;比如:刘子豪脱敏之后为刘**
*/
String replacer() default "*";
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.FixedPhoneDesensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 固定电话
*
* @author gaibu
*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = FixedPhoneDesensitization.class)
public @interface FixedPhoneDesensitize {
/**
* 前缀保留长度
*/
int prefixKeep() default 4;
/**
* 后缀保留长度
*/
int suffixKeep() default 2;
/**
* 替换规则,固定电话;比如01086551122 脱敏之后为 0108*****22
*/
String replacer() default "*";
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.IdCardDesensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 身份证
*
* @author gaibu
*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = IdCardDesensitization.class)
public @interface IdCardDesensitize {
/**
* 前缀保留长度
*/
int prefixKeep() default 6;
/**
* 后缀保留长度
*/
int suffixKeep() default 2;
/**
* 替换规则,身份证号码;比如530321199204074611 脱敏之后为 530321**********11
*/
String replacer() default "*";
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.MobileDesensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 手机号
*
* @author gaibu
*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = MobileDesensitization.class)
public @interface MobileDesensitize {
/**
* 前缀保留长度
*/
int prefixKeep() default 3;
/**
* 后缀保留长度
*/
int suffixKeep() default 4;
/**
* 替换规则,手机号;比如13248765917 脱敏之后为 132****5917
*/
String replacer() default "*";
}

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.PasswordDesensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 密码
*
* @author gaibu
*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = PasswordDesensitization.class)
public @interface PasswordDesensitize {
/**
* 前缀保留长度
*/
int prefixKeep() default 0;
/**
* 后缀保留长度
*/
int suffixKeep() default 0;
/**
* 替换规则,密码;
*
* 比如123456 脱敏之后为 ******
*/
String replacer() default "*";
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.DefaultDesensitizationHandler;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 滑动脱敏注解
*
* @author gaibu
*/
@Documented
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = DefaultDesensitizationHandler.class)
public @interface SliderDesensitize {
/**
* 后缀保留长度
*/
int suffixKeep() default 0;
/**
* 替换规则,会将前缀后缀保留后,全部替换成 replacer
*
* 例如prefixKeep = 1; suffixKeep = 2; replacer = "*";
* 原始字符串 123456
* 脱敏后 1***56
*/
String replacer() default "*";
/**
* 前缀保留长度
*/
int prefixKeep() default 0;
}

View File

@ -0,0 +1,78 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
import java.lang.annotation.Annotation;
/**
* 滑动脱敏处理器抽象类,已实现通用的方法
*
* @author gaibu
*/
public abstract class AbstractSliderDesensitizationHandler<T extends Annotation>
implements DesensitizationHandler<T> {
@Override
public String desensitize(String origin, T annotation) {
int prefixKeep = getPrefixKeep(annotation);
int suffixKeep = getSuffixKeep(annotation);
String replacer = getReplacer(annotation);
int length = origin.length();
// 情况一:原始字符串长度小于等于保留长度,则原始字符串全部替换
if (prefixKeep >= length || suffixKeep >= length) {
return buildReplacerByLength(replacer, length);
}
// 情况二:原始字符串长度小于等于前后缀保留字符串长度,则原始字符串全部替换
if ((prefixKeep + suffixKeep) >= length) {
return buildReplacerByLength(replacer, length);
}
// 情况三:原始字符串长度大于前后缀保留字符串长度,则替换中间字符串
int interval = length - prefixKeep - suffixKeep;
return origin.substring(0, prefixKeep) +
buildReplacerByLength(replacer, interval) +
origin.substring(prefixKeep + interval);
}
/**
* 根据长度循环构建替换符
*
* @param replacer 替换符
* @param length 长度
* @return 构建后的替换符
*/
private String buildReplacerByLength(String replacer, int length) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++) {
builder.append(replacer);
}
return builder.toString();
}
/**
* 前缀保留长度
*
* @param annotation 注解信息
* @return 前缀保留长度
*/
abstract Integer getPrefixKeep(T annotation);
/**
* 后缀保留长度
*
* @param annotation 注解信息
* @return 后缀保留长度
*/
abstract Integer getSuffixKeep(T annotation);
/**
* 替换符
*
* @param annotation 注解信息
* @return 替换符
*/
abstract String getReplacer(T annotation);
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.BankCardDesensitize;
/**
* {@link BankCardDesensitize} 的脱敏处理器
*
* @author gaibu
*/
public class BankCardDesensitization extends AbstractSliderDesensitizationHandler<BankCardDesensitize> {
@Override
Integer getPrefixKeep(BankCardDesensitize annotation) {
return annotation.prefixKeep();
}
@Override
Integer getSuffixKeep(BankCardDesensitize annotation) {
return annotation.suffixKeep();
}
@Override
String getReplacer(BankCardDesensitize annotation) {
return annotation.replacer();
}
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.CarLicenseDesensitize;
/**
* {@link CarLicenseDesensitize} 的脱敏处理器
*
* @author gaibu
*/
public class CarLicenseDesensitization extends AbstractSliderDesensitizationHandler<CarLicenseDesensitize> {
@Override
Integer getPrefixKeep(CarLicenseDesensitize annotation) {
return annotation.prefixKeep();
}
@Override
Integer getSuffixKeep(CarLicenseDesensitize annotation) {
return annotation.suffixKeep();
}
@Override
String getReplacer(CarLicenseDesensitize annotation) {
return annotation.replacer();
}
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.ChineseNameDesensitize;
/**
* {@link ChineseNameDesensitize} 的脱敏处理器
*
* @author gaibu
*/
public class ChineseNameDesensitization extends AbstractSliderDesensitizationHandler<ChineseNameDesensitize> {
@Override
Integer getPrefixKeep(ChineseNameDesensitize annotation) {
return annotation.prefixKeep();
}
@Override
Integer getSuffixKeep(ChineseNameDesensitize annotation) {
return annotation.suffixKeep();
}
@Override
String getReplacer(ChineseNameDesensitize annotation) {
return annotation.replacer();
}
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.SliderDesensitize;
/**
* {@link SliderDesensitize} 的脱敏处理器
*
* @author gaibu
*/
public class DefaultDesensitizationHandler extends AbstractSliderDesensitizationHandler<SliderDesensitize> {
@Override
Integer getPrefixKeep(SliderDesensitize annotation) {
return annotation.prefixKeep();
}
@Override
Integer getSuffixKeep(SliderDesensitize annotation) {
return annotation.suffixKeep();
}
@Override
String getReplacer(SliderDesensitize annotation) {
return annotation.replacer();
}
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.FixedPhoneDesensitize;
/**
* {@link FixedPhoneDesensitize} 的脱敏处理器
*
* @author gaibu
*/
public class FixedPhoneDesensitization extends AbstractSliderDesensitizationHandler<FixedPhoneDesensitize> {
@Override
Integer getPrefixKeep(FixedPhoneDesensitize annotation) {
return annotation.prefixKeep();
}
@Override
Integer getSuffixKeep(FixedPhoneDesensitize annotation) {
return annotation.suffixKeep();
}
@Override
String getReplacer(FixedPhoneDesensitize annotation) {
return annotation.replacer();
}
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.IdCardDesensitize;
/**
* {@link IdCardDesensitize} 的脱敏处理器
*
* @author gaibu
*/
public class IdCardDesensitization extends AbstractSliderDesensitizationHandler<IdCardDesensitize> {
@Override
Integer getPrefixKeep(IdCardDesensitize annotation) {
return annotation.prefixKeep();
}
@Override
Integer getSuffixKeep(IdCardDesensitize annotation) {
return annotation.suffixKeep();
}
@Override
String getReplacer(IdCardDesensitize annotation) {
return annotation.replacer();
}
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.MobileDesensitize;
/**
* {@link MobileDesensitize} 的脱敏处理器
*
* @author gaibu
*/
public class MobileDesensitization extends AbstractSliderDesensitizationHandler<MobileDesensitize> {
@Override
Integer getPrefixKeep(MobileDesensitize annotation) {
return annotation.prefixKeep();
}
@Override
Integer getSuffixKeep(MobileDesensitize annotation) {
return annotation.suffixKeep();
}
@Override
String getReplacer(MobileDesensitize annotation) {
return annotation.replacer();
}
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.PasswordDesensitize;
/**
* {@link PasswordDesensitize} 的码脱敏处理器
*
* @author gaibu
*/
public class PasswordDesensitization extends AbstractSliderDesensitizationHandler<PasswordDesensitize> {
@Override
Integer getPrefixKeep(PasswordDesensitize annotation) {
return annotation.prefixKeep();
}
@Override
Integer getSuffixKeep(PasswordDesensitize annotation) {
return annotation.suffixKeep();
}
@Override
String getReplacer(PasswordDesensitize annotation) {
return annotation.replacer();
}
}

View File

@ -0,0 +1,98 @@
package cn.iocoder.yudao.framework.desensitize.core;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.EmailDesensitize;
import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.RegexDesensitize;
import cn.iocoder.yudao.framework.desensitize.core.annotation.Address;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.BankCardDesensitize;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.CarLicenseDesensitize;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.ChineseNameDesensitize;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.FixedPhoneDesensitize;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.IdCardDesensitize;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.PasswordDesensitize;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.MobileDesensitize;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.SliderDesensitize;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import lombok.Data;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link DesensitizeTest} 的单元测试
*/
public class DesensitizeTest extends BaseMockitoUnitTest {
@Test
public void test() {
// 准备参数
DesensitizeDemo desensitizeDemo = new DesensitizeDemo();
desensitizeDemo.setNickname("芋道源码");
desensitizeDemo.setBankCard("9988002866797031");
desensitizeDemo.setCarLicense("粤A66666");
desensitizeDemo.setFixedPhone("01086551122");
desensitizeDemo.setIdCard("530321199204074611");
desensitizeDemo.setPassword("123456");
desensitizeDemo.setPhoneNumber("13248765917");
desensitizeDemo.setSlider1("ABCDEFG");
desensitizeDemo.setSlider2("ABCDEFG");
desensitizeDemo.setSlider3("ABCDEFG");
desensitizeDemo.setEmail("1@email.com");
desensitizeDemo.setRegex("你好,我是芋道源码");
desensitizeDemo.setAddress("北京市海淀区上地十街10号");
desensitizeDemo.setOrigin("芋道源码");
// 调用
DesensitizeDemo d = JsonUtils.parseObject(JsonUtils.toJsonString(desensitizeDemo), DesensitizeDemo.class);
// 断言
assertNotNull(d);
assertEquals("芋***", d.getNickname());
assertEquals("998800********31", d.getBankCard());
assertEquals("粤A6***6", d.getCarLicense());
assertEquals("0108*****22", d.getFixedPhone());
assertEquals("530321**********11", d.getIdCard());
assertEquals("******", d.getPassword());
assertEquals("132****5917", d.getPhoneNumber());
assertEquals("#######", d.getSlider1());
assertEquals("ABC*EFG", d.getSlider2());
assertEquals("*******", d.getSlider3());
assertEquals("1****@email.com", d.getEmail());
assertEquals("你好,我是*", d.getRegex());
assertEquals("北京市海淀区上地十街10号*", d.getAddress());
assertEquals("芋道源码", d.getOrigin());
}
@Data
public static class DesensitizeDemo {
@ChineseNameDesensitize
private String nickname;
@BankCardDesensitize
private String bankCard;
@CarLicenseDesensitize
private String carLicense;
@FixedPhoneDesensitize
private String fixedPhone;
@IdCardDesensitize
private String idCard;
@PasswordDesensitize
private String password;
@MobileDesensitize
private String phoneNumber;
@SliderDesensitize(prefixKeep = 6, suffixKeep = 1, replacer = "#")
private String slider1;
@SliderDesensitize(prefixKeep = 3, suffixKeep = 3)
private String slider2;
@SliderDesensitize(prefixKeep = 10)
private String slider3;
@EmailDesensitize
private String email;
@RegexDesensitize(regex = "芋道源码", replacer = "*")
private String regex;
@Address
private String address;
private String origin;
}
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.framework.desensitize.core.annotation;
import cn.iocoder.yudao.framework.desensitize.core.DesensitizeTest;
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import cn.iocoder.yudao.framework.desensitize.core.handler.AddressHandler;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 地址
*
* 用于 {@link DesensitizeTest} 测试使用
*
* @author gaibu
*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = AddressHandler.class)
public @interface Address {
String replacer() default "*";
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.framework.desensitize.core.handler;
import cn.iocoder.yudao.framework.desensitize.core.DesensitizeTest;
import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
import cn.iocoder.yudao.framework.desensitize.core.annotation.Address;
/**
* {@link Address} 的脱敏处理器
*
* 用于 {@link DesensitizeTest} 测试使用
*/
public class AddressHandler implements DesensitizationHandler<Address> {
@Override
public String desensitize(String origin, Address annotation) {
return origin + annotation.replacer();
}
}

View File

@ -81,22 +81,19 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
}
/**
* 逐条插入,适合量数据插入,或者对性能要求不高的场景
* <p>
* 如果大量,请使用 {@link com.baomidou.mybatisplus.extension.service.impl.ServiceImpl#saveBatch(Collection)} 方法
* 使用示例,可见 RoleMenuBatchInsertMapper、UserRoleBatchInsertMapper 类
* 批量插入,适合量数据插入
*
* @param entities 实体们
*/
default void insertBatch(Collection<T> entities) {
entities.forEach(this::insert);
Db.saveBatch(entities);
}
/**
* 批量插入,适合大量数据插入
*
* @param entities 实体们
* @param size 插入数量 Db.saveBatch 默认为1000
* @param size 插入数量 Db.saveBatch 默认为 1000
*/
default void insertBatch(Collection<T> entities, int size) {
Db.saveBatch(entities, size);

View File

@ -7,7 +7,10 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import uk.co.jemos.podam.api.PodamFactory;
import uk.co.jemos.podam.api.PodamFactoryImpl;
import uk.co.jemos.podam.common.AttributeStrategy;
import javax.validation.constraints.Email;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.Arrays;
@ -95,6 +98,10 @@ public class RandomUtils {
return RandomUtil.randomEle(CommonStatusEnum.values()).getStatus();
}
public static String randomEmail() {
return randomString() + "@qq.com";
}
@SafeVarargs
public static <T> T randomPojo(Class<T> clazz, Consumer<T>... consumers) {
T pojo = PODAM_FACTORY.manufacturePojo(clazz);

View File

@ -1 +1,4 @@
/**
* Web 框架全局异常、API 日志等
*/
package cn.iocoder.yudao.framework;

View File

@ -2,27 +2,18 @@ package cn.iocoder.yudao.framework.web.config;
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.web.core.clean.JsoupXssCleaner;
import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter;
import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
import cn.iocoder.yudao.framework.web.core.filter.XssFilter;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
import cn.iocoder.yudao.framework.web.core.json.XssStringJsonDeserializer;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@ -34,7 +25,7 @@ import javax.annotation.Resource;
import javax.servlet.Filter;
@AutoConfiguration
@EnableConfigurationProperties({WebProperties.class, XssProperties.class})
@EnableConfigurationProperties(WebProperties.class)
public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
@Resource
@ -107,15 +98,6 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
return createFilterBean(new CacheRequestBodyFilter(), WebFilterOrderEnum.REQUEST_BODY_CACHE_FILTER);
}
/**
* 创建 XssFilter Bean解决 Xss 安全问题
*/
@Bean
@ConditionalOnBean(XssCleaner.class)
public FilterRegistrationBean<XssFilter> xssFilter(XssProperties properties, PathMatcher pathMatcher, XssCleaner xssCleaner) {
return createFilterBean(new XssFilter(properties, pathMatcher, xssCleaner), WebFilterOrderEnum.XSS_FILTER);
}
/**
* 创建 DemoFilter Bean演示模式
*/
@ -125,33 +107,7 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER);
}
/**
* Xss 清理者
*
* @return XssCleaner
*/
@Bean
@ConditionalOnMissingBean(XssCleaner.class)
public XssCleaner xssCleaner() {
return new JsoupXssCleaner();
}
/**
* 注册 Jackson 的序列化器,用于处理 json 类型参数的 xss 过滤
*
* @return Jackson2ObjectMapperBuilderCustomizer
*/
@Bean
@ConditionalOnMissingBean(name = "xssJacksonCustomizer")
@ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true")
public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssCleaner xssCleaner) {
// 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer在序列化时进行处理
return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(xssCleaner));
}
private static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
public static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);
bean.setOrder(order);
return bean;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.framework.web.config;
package cn.iocoder.yudao.framework.xss.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

View File

@ -0,0 +1,60 @@
package cn.iocoder.yudao.framework.xss.config;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.xss.core.clean.JsoupXssCleaner;
import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
import cn.iocoder.yudao.framework.xss.core.filter.XssFilter;
import cn.iocoder.yudao.framework.xss.core.json.XssStringJsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import static cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration.createFilterBean;
@AutoConfiguration
@EnableConfigurationProperties(XssProperties.class)
public class YudaoXssAutoConfiguration implements WebMvcConfigurer {
/**
* Xss 清理者
*
* @return XssCleaner
*/
@Bean
@ConditionalOnMissingBean(XssCleaner.class)
public XssCleaner xssCleaner() {
return new JsoupXssCleaner();
}
/**
* 注册 Jackson 的序列化器,用于处理 json 类型参数的 xss 过滤
*
* @return Jackson2ObjectMapperBuilderCustomizer
*/
@Bean
@ConditionalOnMissingBean(name = "xssJacksonCustomizer")
@ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true")
public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssCleaner xssCleaner) {
// 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer在序列化时进行处理
return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(xssCleaner));
}
/**
* 创建 XssFilter Bean解决 Xss 安全问题
*/
@Bean
@ConditionalOnBean(XssCleaner.class)
public FilterRegistrationBean<XssFilter> xssFilter(XssProperties properties, PathMatcher pathMatcher, XssCleaner xssCleaner) {
return createFilterBean(new XssFilter(properties, pathMatcher, xssCleaner), WebFilterOrderEnum.XSS_FILTER);
}
}

View File

@ -1,11 +1,11 @@
package cn.iocoder.yudao.framework.web.core.clean;
package cn.iocoder.yudao.framework.xss.core.clean;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Safelist;
/**
* jsonp 过滤字符串
* 基于 JSONP 实现 XSS 过滤字符串
*/
public class JsoupXssCleaner implements XssCleaner {
@ -24,21 +24,6 @@ public class JsoupXssCleaner implements XssCleaner {
this.baseUri = "";
}
public JsoupXssCleaner(Safelist safelist) {
this.safelist = safelist;
this.baseUri = "";
}
public JsoupXssCleaner(String baseUri) {
this.safelist = buildSafelist();
this.baseUri = baseUri;
}
public JsoupXssCleaner(Safelist safelist, String baseUri) {
this.safelist = safelist;
this.baseUri = baseUri;
}
/**
* 构建一个 Xss 清理的 Safelist 规则
* 基于 Safelist#relaxed() 的基础上:
@ -67,7 +52,6 @@ public class JsoupXssCleaner implements XssCleaner {
// 虽然可以重写 WhiteList#isSafeAttribute 来处理但是有隐患所以暂时不支持相对路径
// WHITELIST.removeProtocols("a", "href", "ftp", "http", "https", "mailto");
// WHITELIST.removeProtocols("img", "src", "http", "https");
return relaxedSafelist;
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.framework.web.core.clean;
package cn.iocoder.yudao.framework.xss.core.clean;
/**
* html 文本中的有 Xss 风险的数据进行清理
@ -12,4 +12,5 @@ public interface XssCleaner {
* @return 清理后的 html
*/
String clean(String html);
}

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.framework.web.core.filter;
package cn.iocoder.yudao.framework.xss.core.filter;
import cn.iocoder.yudao.framework.web.config.XssProperties;
import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
import cn.iocoder.yudao.framework.xss.config.XssProperties;
import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
import lombok.AllArgsConstructor;
import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
@ -14,8 +14,6 @@ import java.io.IOException;
/**
* Xss 过滤器
* <p>
* Xss 不了解的胖友可以看看 http://www.iocoder.cn/Fight/The-new-girl-asked-me-why-AJAX-requests-are-not-secure-I-did-not-answer/
*
* @author 芋道源码
*/

View File

@ -1,6 +1,6 @@
package cn.iocoder.yudao.framework.web.core.filter;
package cn.iocoder.yudao.framework.xss.core.filter;
import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
@ -13,6 +13,7 @@ import java.util.Map;
* @author 芋道源码
*/
public class XssRequestWrapper extends HttpServletRequestWrapper {
private final XssCleaner xssCleaner;
public XssRequestWrapper(HttpServletRequest request, XssCleaner xssCleaner) {

View File

@ -1,6 +1,6 @@
package cn.iocoder.yudao.framework.web.core.json;
package cn.iocoder.yudao.framework.xss.core.json;
import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;

View File

@ -0,0 +1,6 @@
/**
* 针对 XSS 的基础封装
*
* XSS 说明https://tech.meituan.com/2018/09/27/fe-security.html
*/
package cn.iocoder.yudao.framework.xss;

View File

@ -2,4 +2,5 @@ cn.iocoder.yudao.framework.apilog.config.YudaoApiLogAutoConfiguration
cn.iocoder.yudao.framework.jackson.config.YudaoJacksonAutoConfiguration
com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration
cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration
cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration
cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration
cn.iocoder.yudao.framework.xss.config.YudaoXssAutoConfiguration