mirror of
https://gitee.com/mybatis-flex/mybatis-flex.git
synced 2025-12-06 16:48:24 +08:00
add @ColumnMask() annotation support
This commit is contained in:
parent
2832cda73e
commit
7d9056bef6
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface ColumnMask {
|
||||
|
||||
/**
|
||||
* 脱敏方式
|
||||
*/
|
||||
String value();
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.mask;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据脱敏工厂类
|
||||
*/
|
||||
public class MaskFactory {
|
||||
|
||||
|
||||
/**
|
||||
* 脱敏处理器
|
||||
*/
|
||||
private static Map<String, MaskProcesser> processerMap = new HashMap<>();
|
||||
|
||||
|
||||
static {
|
||||
registerMaskProcesser(Masks.MOBILE, Masks.MOBILE_PROCESSER);
|
||||
registerMaskProcesser(Masks.FIXED_PHONE, Masks.FIXED_PHONE_PROCESSER);
|
||||
registerMaskProcesser(Masks.ID_CARD_NUMBER, Masks.ID_CARD_NUMBER_PROCESSER);
|
||||
registerMaskProcesser(Masks.CHINESE_NAME, Masks.CHINESE_NAME_PROCESSER);
|
||||
registerMaskProcesser(Masks.ADDRESS, Masks.ADDRESS_PROCESSER);
|
||||
registerMaskProcesser(Masks.EMAIL, Masks.EMAIL_PROCESSER);
|
||||
registerMaskProcesser(Masks.PASSWORD, Masks.PASSWORD_PROCESSER);
|
||||
registerMaskProcesser(Masks.CAR_LICENSE, Masks.CAR_LICENSE_PROCESSER);
|
||||
registerMaskProcesser(Masks.BANK_CARD_NUMBER, Masks.BANK_CARD_PROCESSER);
|
||||
}
|
||||
|
||||
|
||||
public static void registerMaskProcesser(String type, MaskProcesser processer) {
|
||||
processerMap.put(type, processer);
|
||||
}
|
||||
|
||||
|
||||
public static Object mask(String type, Object data) {
|
||||
MaskProcesser maskProcesser = processerMap.get(type);
|
||||
if (maskProcesser == null) {
|
||||
throw new IllegalStateException("Can not get mask processer for by type: " + type);
|
||||
}
|
||||
return maskProcesser.mask(data);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.mask;
|
||||
|
||||
/**
|
||||
* 数据脱敏处理器
|
||||
*/
|
||||
public interface MaskProcesser {
|
||||
|
||||
Object mask(Object data);
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.mask;
|
||||
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class MaskTypeHandler extends BaseTypeHandler<Object> {
|
||||
|
||||
private String maskType;
|
||||
|
||||
public MaskTypeHandler(String maskType) {
|
||||
this.maskType = maskType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
|
||||
ps.setString(i,parameter.toString());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
String data = rs.getString(columnName);
|
||||
return MaskFactory.mask(maskType,data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
String data = rs.getString(columnIndex);
|
||||
return MaskFactory.mask(maskType,data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
String data = cs.getString(columnIndex);
|
||||
return MaskFactory.mask(maskType,data);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,213 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.mask;
|
||||
|
||||
/**
|
||||
* 内置的数据脱敏方式
|
||||
*/
|
||||
public class Masks {
|
||||
|
||||
/**
|
||||
* 手机号脱敏
|
||||
*/
|
||||
public static final String MOBILE = "mobile";
|
||||
|
||||
/**
|
||||
* 固定电话脱敏
|
||||
*/
|
||||
public static final String FIXED_PHONE = "fixed_phone";
|
||||
|
||||
/**
|
||||
* 身份证号脱敏
|
||||
*/
|
||||
public static final String ID_CARD_NUMBER = "id_card_number";
|
||||
|
||||
/**
|
||||
* 中文名脱敏
|
||||
*/
|
||||
public static final String CHINESE_NAME = "chinese_name";
|
||||
|
||||
/**
|
||||
* 地址脱敏
|
||||
*/
|
||||
public static final String ADDRESS = "address";
|
||||
|
||||
/**
|
||||
* 邮件脱敏
|
||||
*/
|
||||
public static final String EMAIL = "email";
|
||||
|
||||
/**
|
||||
* 密码脱敏
|
||||
*/
|
||||
public static final String PASSWORD = "password";
|
||||
|
||||
/**
|
||||
* 车牌号脱敏
|
||||
*/
|
||||
public static final String CAR_LICENSE = "car_license";
|
||||
|
||||
/**
|
||||
* 银行卡号脱敏
|
||||
*/
|
||||
public static final String BANK_CARD_NUMBER = "bank_card_number";
|
||||
|
||||
|
||||
private static String createMask(int count) {
|
||||
StringBuilder mask = new StringBuilder();
|
||||
for (int i = 0; i < count; i++) {
|
||||
mask.append("*");
|
||||
}
|
||||
return mask.toString();
|
||||
}
|
||||
|
||||
|
||||
private static String mask(String needToMaskString, int keepFirstCount, int keepLastCount, int maskCount) {
|
||||
return needToMaskString.substring(0, keepFirstCount)
|
||||
+ createMask(maskCount)
|
||||
+ needToMaskString.substring(needToMaskString.length() - keepLastCount);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 手机号脱敏处理器
|
||||
* 保留前三后四,中间的为星号 "*"
|
||||
*/
|
||||
static MaskProcesser MOBILE_PROCESSER = data -> {
|
||||
if (data instanceof String && ((String) data).startsWith("1") && ((String) data).length() == 11) {
|
||||
return mask((String) data, 3, 4, 4);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 固定电话脱敏
|
||||
* 保留前三后四,中间的为星号 "*"
|
||||
*/
|
||||
static MaskProcesser FIXED_PHONE_PROCESSER = data -> {
|
||||
if (data instanceof String && ((String) data).length() > 5) {
|
||||
return mask((String) data, 3, 2, ((String) data).length() - 5);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 身份证号脱敏处理器
|
||||
* 身份证号的保留前三后四,中间的数为星号 "*"
|
||||
*/
|
||||
static MaskProcesser ID_CARD_NUMBER_PROCESSER = data -> {
|
||||
if (data instanceof String && ((String) data).length() >= 15) {
|
||||
return mask((String) data, 3, 4, ((String) data).length() - 7);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 姓名脱敏
|
||||
*/
|
||||
static MaskProcesser CHINESE_NAME_PROCESSER = data -> {
|
||||
if (data instanceof String) {
|
||||
String name = (String) data;
|
||||
if (name.length() == 2) {
|
||||
return name.charAt(0) + "*";
|
||||
} else if (name.length() == 3) {
|
||||
return name.charAt(0) + "*" + name.charAt(2);
|
||||
} else if (name.length() == 4) {
|
||||
return "**" + name.substring(2, 4);
|
||||
} else if (name.length() > 4) {
|
||||
return mask(name, 2, 1, name.length() - 3);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 地址脱敏
|
||||
*/
|
||||
static MaskProcesser ADDRESS_PROCESSER = data -> {
|
||||
if (data instanceof String) {
|
||||
String address = (String) data;
|
||||
if (address.length() > 6) {
|
||||
return mask(address, 6, 0, 3);
|
||||
} else if (address.length() > 3) {
|
||||
return mask(address, 3, 0, 3);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* email 脱敏
|
||||
*/
|
||||
static MaskProcesser EMAIL_PROCESSER = data -> {
|
||||
if (data instanceof String && ((String) data).contains("@")) {
|
||||
String fullEmail = (String) data;
|
||||
int indexOf = fullEmail.lastIndexOf("@");
|
||||
String email = fullEmail.substring(0, indexOf);
|
||||
|
||||
if (email.length() == 1) {
|
||||
return "*" + fullEmail.substring(indexOf);
|
||||
} else if (email.length() == 2) {
|
||||
return "**" + fullEmail.substring(indexOf);
|
||||
} else if (email.length() < 5) {
|
||||
return mask(email, 2, 0, email.length() - 2) + fullEmail.substring(indexOf);
|
||||
} else {
|
||||
return mask(email, 3, 0, email.length() - 3) + fullEmail.substring(indexOf);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 密码 脱敏
|
||||
*/
|
||||
static MaskProcesser PASSWORD_PROCESSER = data -> {
|
||||
if (data instanceof String ) {
|
||||
return mask((String) data, 0, 0, ((String) data).length()) ;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 车牌号 脱敏
|
||||
*/
|
||||
static MaskProcesser CAR_LICENSE_PROCESSER = data -> {
|
||||
if (data instanceof String) {
|
||||
return mask((String) data, 3, 1, ((String) data).length() - 4);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 银行卡号 脱敏
|
||||
*/
|
||||
static MaskProcesser BANK_CARD_PROCESSER = data -> {
|
||||
if (data instanceof String && ((String) data).length() >= 8) {
|
||||
return mask((String) data, 4, 4, 4);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.mybatisflex.core.table;
|
||||
|
||||
import com.mybatisflex.core.mask.MaskTypeHandler;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.TypeHandler;
|
||||
|
||||
@ -46,6 +48,9 @@ public class ColumnInfo {
|
||||
protected TypeHandler typeHandler;
|
||||
|
||||
|
||||
protected String maskType;
|
||||
|
||||
|
||||
|
||||
public String getColumn() {
|
||||
return column;
|
||||
@ -79,11 +84,29 @@ public class ColumnInfo {
|
||||
this.jdbcType = jdbcType;
|
||||
}
|
||||
|
||||
public TypeHandler getTypeHandler() {
|
||||
public TypeHandler buildTypeHandler() {
|
||||
|
||||
//优先使用自定义的 typeHandler
|
||||
if (typeHandler != null){
|
||||
return typeHandler;
|
||||
}
|
||||
//若用户未定义 typeHandler,而配置了数据脱敏,则使用脱敏的 handler 处理
|
||||
else if (StringUtil.isNotBlank(maskType)){
|
||||
typeHandler = new MaskTypeHandler(maskType);
|
||||
}
|
||||
|
||||
return typeHandler;
|
||||
}
|
||||
|
||||
public void setTypeHandler(TypeHandler typeHandler) {
|
||||
this.typeHandler = typeHandler;
|
||||
}
|
||||
|
||||
public String getMaskType() {
|
||||
return maskType;
|
||||
}
|
||||
|
||||
public void setMaskType(String maskType) {
|
||||
this.maskType = maskType;
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,7 +433,7 @@ public class TableInfo {
|
||||
ResultMapping mapping = new ResultMapping.Builder(configuration, columnInfo.getProperty(),
|
||||
columnInfo.getColumn(), columnInfo.getPropertyType())
|
||||
.jdbcType(columnInfo.getJdbcType())
|
||||
.typeHandler(columnInfo.getTypeHandler())
|
||||
.typeHandler(columnInfo.buildTypeHandler())
|
||||
.build();
|
||||
resultMappings.add(mapping);
|
||||
}
|
||||
@ -443,7 +443,7 @@ public class TableInfo {
|
||||
idInfo.getColumn(), idInfo.getPropertyType())
|
||||
.flags(CollectionUtil.newArrayList(ResultFlag.ID))
|
||||
.jdbcType(idInfo.getJdbcType())
|
||||
.typeHandler(idInfo.getTypeHandler())
|
||||
.typeHandler(idInfo.buildTypeHandler())
|
||||
.build();
|
||||
resultMappings.add(mapping);
|
||||
}
|
||||
@ -456,7 +456,7 @@ public class TableInfo {
|
||||
ColumnInfo columnInfo = columnInfoMapping.get(column);
|
||||
Object value = getPropertyValue(metaObject, columnInfo.property);
|
||||
|
||||
TypeHandler typeHandler = columnInfo.getTypeHandler();
|
||||
TypeHandler typeHandler = columnInfo.buildTypeHandler();
|
||||
if (value != null && typeHandler != null) {
|
||||
return new TypeHandlerObject(typeHandler, value, columnInfo.getJdbcType());
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
package com.mybatisflex.core.table;
|
||||
|
||||
import com.mybatisflex.annotation.Column;
|
||||
import com.mybatisflex.annotation.ColumnMask;
|
||||
import com.mybatisflex.annotation.Id;
|
||||
import com.mybatisflex.annotation.Table;
|
||||
import com.mybatisflex.core.FlexConsts;
|
||||
@ -206,6 +207,14 @@ public class TableInfos {
|
||||
columnInfo.setTypeHandler(typeHandler);
|
||||
}
|
||||
|
||||
ColumnMask columnMask = field.getAnnotation(ColumnMask.class);
|
||||
if (columnMask != null && StringUtil.isNotBlank(columnMask.value())) {
|
||||
if (String.class != field.getType()) {
|
||||
throw new IllegalStateException("@ColumnMask() only support for string type field. error: " + entityClass.getName() + "." + field.getName());
|
||||
}
|
||||
columnInfo.setMaskType(columnMask.value().trim());
|
||||
}
|
||||
|
||||
if (column != null && column.jdbcType() != JdbcType.UNDEFINED) {
|
||||
columnInfo.setJdbcType(column.jdbcType());
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user