add @ColumnMask() annotation support

This commit is contained in:
开源海哥 2023-03-23 12:33:39 +08:00
parent 2832cda73e
commit 7d9056bef6
8 changed files with 420 additions and 4 deletions

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
};
}

View File

@ -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;
}
}

View File

@ -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());
}

View File

@ -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());
}