mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
Merge branch 'v6-dev' of gitee.com:dromara/hutool into v6-dev
This commit is contained in:
commit
41fbaa8073
@ -16,8 +16,9 @@
|
||||
|
||||
package org.dromara.hutool.core.data;
|
||||
|
||||
import org.dromara.hutool.core.data.masking.MaskingManager;
|
||||
import org.dromara.hutool.core.data.masking.MaskingType;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.text.CharUtil;
|
||||
|
||||
/**
|
||||
* 数据脱敏(Data Masking)工具类,对某些敏感信息(比如,身份证号、手机号、卡号、姓名、地址、邮箱等 )屏蔽敏感数据。<br>
|
||||
@ -44,74 +45,6 @@ import org.dromara.hutool.core.text.CharUtil;
|
||||
*/
|
||||
public class MaskingUtil {
|
||||
|
||||
/**
|
||||
* 支持的脱敏类型枚举
|
||||
*
|
||||
* @author dazer and neusoft and qiaomu
|
||||
*/
|
||||
public enum MaskingType {
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
USER_ID,
|
||||
/**
|
||||
* 中文名
|
||||
*/
|
||||
CHINESE_NAME,
|
||||
/**
|
||||
* 身份证号
|
||||
*/
|
||||
ID_CARD,
|
||||
/**
|
||||
* 座机号
|
||||
*/
|
||||
FIXED_PHONE,
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
MOBILE_PHONE,
|
||||
/**
|
||||
* 地址
|
||||
*/
|
||||
ADDRESS,
|
||||
/**
|
||||
* 电子邮件
|
||||
*/
|
||||
EMAIL,
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
PASSWORD,
|
||||
/**
|
||||
* 中国大陆车牌,包含普通车辆、新能源车辆
|
||||
*/
|
||||
CAR_LICENSE,
|
||||
/**
|
||||
* 银行卡
|
||||
*/
|
||||
BANK_CARD,
|
||||
/**
|
||||
* IPv4地址
|
||||
*/
|
||||
IPV4,
|
||||
/**
|
||||
* IPv6地址
|
||||
*/
|
||||
IPV6,
|
||||
/**
|
||||
* 定义了一个first_mask的规则,只显示第一个字符。
|
||||
*/
|
||||
FIRST_MASK,
|
||||
/**
|
||||
* 清空为null
|
||||
*/
|
||||
CLEAR_TO_NULL,
|
||||
/**
|
||||
* 清空为""
|
||||
*/
|
||||
CLEAR_TO_EMPTY
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏,使用默认的脱敏策略
|
||||
* <pre>
|
||||
@ -134,60 +67,8 @@ public class MaskingUtil {
|
||||
* @author dazer and neusoft and qiaomu
|
||||
* @since 5.6.2
|
||||
*/
|
||||
public static String masking(final CharSequence str, final MaskingType maskingType) {
|
||||
if (StrUtil.isBlank(str)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
String newStr = String.valueOf(str);
|
||||
switch (maskingType) {
|
||||
case USER_ID:
|
||||
newStr = String.valueOf(userId());
|
||||
break;
|
||||
case CHINESE_NAME:
|
||||
newStr = chineseName(String.valueOf(str));
|
||||
break;
|
||||
case ID_CARD:
|
||||
newStr = idCardNum(String.valueOf(str), 1, 2);
|
||||
break;
|
||||
case FIXED_PHONE:
|
||||
newStr = fixedPhone(String.valueOf(str));
|
||||
break;
|
||||
case MOBILE_PHONE:
|
||||
newStr = mobilePhone(String.valueOf(str));
|
||||
break;
|
||||
case ADDRESS:
|
||||
newStr = address(String.valueOf(str), 8);
|
||||
break;
|
||||
case EMAIL:
|
||||
newStr = email(String.valueOf(str));
|
||||
break;
|
||||
case PASSWORD:
|
||||
newStr = password(String.valueOf(str));
|
||||
break;
|
||||
case CAR_LICENSE:
|
||||
newStr = carLicense(String.valueOf(str));
|
||||
break;
|
||||
case BANK_CARD:
|
||||
newStr = bankCard(String.valueOf(str));
|
||||
break;
|
||||
case IPV4:
|
||||
newStr = ipv4(String.valueOf(str));
|
||||
break;
|
||||
case IPV6:
|
||||
newStr = ipv6(String.valueOf(str));
|
||||
break;
|
||||
case FIRST_MASK:
|
||||
newStr = firstMask(String.valueOf(str));
|
||||
break;
|
||||
case CLEAR_TO_EMPTY:
|
||||
newStr = clear();
|
||||
break;
|
||||
case CLEAR_TO_NULL:
|
||||
newStr = clearToNull();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return newStr;
|
||||
public static String masking(final MaskingType maskingType, final CharSequence str) {
|
||||
return MaskingManager.getInstance().masking(maskingType.name(), str);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -219,6 +100,16 @@ public class MaskingUtil {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
|
||||
*
|
||||
* @param fullName 姓名
|
||||
* @return 脱敏后的姓名
|
||||
*/
|
||||
public static String chineseName(final CharSequence fullName) {
|
||||
return firstMask(fullName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义了一个first_mask的规则,只显示第一个字符。<br>
|
||||
* 脱敏前:123456789;脱敏后:1********。
|
||||
@ -226,21 +117,8 @@ public class MaskingUtil {
|
||||
* @param str 字符串
|
||||
* @return 脱敏后的字符串
|
||||
*/
|
||||
public static String firstMask(final String str) {
|
||||
if (StrUtil.isBlank(str)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
return StrUtil.hide(str, 1, str.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
|
||||
*
|
||||
* @param fullName 姓名
|
||||
* @return 脱敏后的姓名
|
||||
*/
|
||||
public static String chineseName(final String fullName) {
|
||||
return firstMask(fullName);
|
||||
public static String firstMask(final CharSequence str) {
|
||||
return MaskingManager.EMPTY.firstMask(str);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -251,20 +129,8 @@ public class MaskingUtil {
|
||||
* @param end 保留:后面的end位数;从1开始
|
||||
* @return 脱敏后的身份证
|
||||
*/
|
||||
public static String idCardNum(final String idCardNum, final int front, final int end) {
|
||||
//身份证不能为空
|
||||
if (StrUtil.isBlank(idCardNum)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
//需要截取的长度不能大于身份证号长度
|
||||
if ((front + end) > idCardNum.length()) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
//需要截取的不能小于0
|
||||
if (front < 0 || end < 0) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
return StrUtil.hide(idCardNum, front, idCardNum.length() - end);
|
||||
public static String idCardNum(final CharSequence idCardNum, final int front, final int end) {
|
||||
return MaskingManager.EMPTY.idCardNum(idCardNum, front, end);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -273,11 +139,8 @@ public class MaskingUtil {
|
||||
* @param num 固定电话
|
||||
* @return 脱敏后的固定电话;
|
||||
*/
|
||||
public static String fixedPhone(final String num) {
|
||||
if (StrUtil.isBlank(num)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
return StrUtil.hide(num, 4, num.length() - 2);
|
||||
public static String fixedPhone(final CharSequence num) {
|
||||
return MaskingManager.EMPTY.fixedPhone(num);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -286,11 +149,8 @@ public class MaskingUtil {
|
||||
* @param num 移动电话;
|
||||
* @return 脱敏后的移动电话;
|
||||
*/
|
||||
public static String mobilePhone(final String num) {
|
||||
if (StrUtil.isBlank(num)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
return StrUtil.hide(num, 3, num.length() - 4);
|
||||
public static String mobilePhone(final CharSequence num) {
|
||||
return MaskingManager.EMPTY.mobilePhone(num);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -300,12 +160,8 @@ public class MaskingUtil {
|
||||
* @param sensitiveSize 敏感信息长度
|
||||
* @return 脱敏后的家庭地址
|
||||
*/
|
||||
public static String address(final String address, final int sensitiveSize) {
|
||||
if (StrUtil.isBlank(address)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
final int length = address.length();
|
||||
return StrUtil.hide(address, length - sensitiveSize, length);
|
||||
public static String address(final CharSequence address, final int sensitiveSize) {
|
||||
return MaskingManager.EMPTY.address(address, sensitiveSize);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -314,28 +170,23 @@ public class MaskingUtil {
|
||||
* @param email 邮箱
|
||||
* @return 脱敏后的邮箱
|
||||
*/
|
||||
public static String email(final String email) {
|
||||
if (StrUtil.isBlank(email)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
final int index = StrUtil.indexOf(email, '@');
|
||||
if (index <= 1) {
|
||||
return email;
|
||||
}
|
||||
return StrUtil.hide(email, 1, index);
|
||||
public static String email(final CharSequence email) {
|
||||
return MaskingManager.EMPTY.email(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【密码】密码的全部字符都用*代替,比如:******
|
||||
* 【密码】密码的全部字符都用*代替,比如:******<br>
|
||||
* 密码位数不能被猜测,因此固定10位
|
||||
*
|
||||
* @param password 密码
|
||||
* @return 脱敏后的密码
|
||||
*/
|
||||
public static String password(final String password) {
|
||||
public static String password(final CharSequence password) {
|
||||
if (StrUtil.isBlank(password)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
return StrUtil.repeat('*', password.length());
|
||||
// 密码位数不能被猜测,因此固定10位
|
||||
return StrUtil.repeat('*', 10);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -349,18 +200,8 @@ public class MaskingUtil {
|
||||
* @param carLicense 完整的车牌号
|
||||
* @return 脱敏后的车牌
|
||||
*/
|
||||
public static String carLicense(String carLicense) {
|
||||
if (StrUtil.isBlank(carLicense)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
// 普通车牌
|
||||
if (carLicense.length() == 7) {
|
||||
carLicense = StrUtil.hide(carLicense, 3, 6);
|
||||
} else if (carLicense.length() == 8) {
|
||||
// 新能源车牌
|
||||
carLicense = StrUtil.hide(carLicense, 3, 7);
|
||||
}
|
||||
return carLicense;
|
||||
public static String carLicense(final CharSequence carLicense) {
|
||||
return MaskingManager.EMPTY.carLicense(carLicense);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -371,29 +212,8 @@ public class MaskingUtil {
|
||||
* @return 脱敏之后的银行卡号
|
||||
* @since 5.6.3
|
||||
*/
|
||||
public static String bankCard(String bankCardNo) {
|
||||
if (StrUtil.isBlank(bankCardNo)) {
|
||||
return bankCardNo;
|
||||
}
|
||||
bankCardNo = StrUtil.cleanBlank(bankCardNo);
|
||||
if (bankCardNo.length() < 9) {
|
||||
return bankCardNo;
|
||||
}
|
||||
|
||||
final int length = bankCardNo.length();
|
||||
final int endLength = length % 4 == 0 ? 4 : length % 4;
|
||||
final int midLength = length - 4 - endLength;
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
|
||||
buf.append(bankCardNo, 0, 4);
|
||||
for (int i = 0; i < midLength; ++i) {
|
||||
if (i % 4 == 0) {
|
||||
buf.append(CharUtil.SPACE);
|
||||
}
|
||||
buf.append('*');
|
||||
}
|
||||
buf.append(CharUtil.SPACE).append(bankCardNo, length - endLength, length);
|
||||
return buf.toString();
|
||||
public static String bankCard(final CharSequence bankCardNo) {
|
||||
return MaskingManager.EMPTY.bankCard(bankCardNo);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -402,8 +222,8 @@ public class MaskingUtil {
|
||||
* @param ipv4 IPv4地址
|
||||
* @return 脱敏后的地址
|
||||
*/
|
||||
public static String ipv4(final String ipv4) {
|
||||
return StrUtil.subBefore(ipv4, '.', false) + ".*.*.*";
|
||||
public static String ipv4(final CharSequence ipv4) {
|
||||
return MaskingManager.EMPTY.ipv4(ipv4);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -412,7 +232,7 @@ public class MaskingUtil {
|
||||
* @param ipv6 IPv6地址
|
||||
* @return 脱敏后的地址
|
||||
*/
|
||||
public static String ipv6(final String ipv6) {
|
||||
return StrUtil.subBefore(ipv6, ':', false) + ":*:*:*:*:*:*:*";
|
||||
public static String ipv6(final CharSequence ipv6) {
|
||||
return MaskingManager.EMPTY.ipv6(ipv6);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Hutool Team and hutool.cn
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.dromara.hutool.core.data.masking;
|
||||
|
||||
/**
|
||||
* 脱敏处理器,用于自定义脱敏规则
|
||||
*
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MaskingHandler {
|
||||
|
||||
/**
|
||||
* 处理传入的数据字符串,经过脱敏逻辑后,返回处理后的值
|
||||
*
|
||||
* @param value 待处理的值
|
||||
* @return 处理后的值
|
||||
*/
|
||||
String handle(CharSequence value);
|
||||
}
|
@ -0,0 +1,345 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Hutool Team and hutool.cn
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.dromara.hutool.core.data.masking;
|
||||
|
||||
import org.dromara.hutool.core.text.CharUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static org.dromara.hutool.core.data.masking.MaskingType.*;
|
||||
|
||||
/**
|
||||
* 脱敏管理器,用于管理所有脱敏处理器,使用方式有三种:
|
||||
* <ul>
|
||||
* <li>全局默认:使用{@link MaskingManager#getInstance()},带有预定义的脱敏方法</li>
|
||||
* <li>自定义默认:使用{@link MaskingManager#ofDefault(char)},可以自定义脱敏字符,带有预定义的脱敏方法</li>
|
||||
* <li>自定义:使用{@link #MaskingManager(Map, char)}构造,不带有默认规则</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class MaskingManager {
|
||||
|
||||
/**
|
||||
* 默认的脱敏字符:*
|
||||
*/
|
||||
public static final char DEFAULT_MASK_CHAR = '*';
|
||||
|
||||
/**
|
||||
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
|
||||
*/
|
||||
private static class SingletonHolder {
|
||||
/**
|
||||
* 静态初始化器,由JVM来保证线程安全
|
||||
*/
|
||||
private static final MaskingManager INSTANCE = registerDefault(DEFAULT_MASK_CHAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 空脱敏管理器,用于不处理任何数据的情况
|
||||
*/
|
||||
public static final MaskingManager EMPTY = new MaskingManager(null);
|
||||
|
||||
/**
|
||||
* 获得单例的 MaskingManager
|
||||
*
|
||||
* @return MaskingManager
|
||||
*/
|
||||
public static MaskingManager getInstance() {
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认的脱敏管理器,通过给定的脱敏字符,提供默认的脱敏规则
|
||||
*
|
||||
* @param maskChar 脱敏字符,默认为*
|
||||
* @return 默认的脱敏管理器
|
||||
*/
|
||||
public static MaskingManager ofDefault(final char maskChar) {
|
||||
return registerDefault(maskChar);
|
||||
}
|
||||
|
||||
private final Map<String, MaskingHandler> handlerMap;
|
||||
private final char maskChar;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param handlerMap 脱敏处理器Map,如果应用于单例,则需要传入线程安全的Map
|
||||
*/
|
||||
public MaskingManager(final Map<String, MaskingHandler> handlerMap) {
|
||||
this(handlerMap, DEFAULT_MASK_CHAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param handlerMap 脱敏处理器Map,如果应用于单例,则需要传入线程安全的Map
|
||||
* @param maskChar 默认的脱敏字符,默认为*
|
||||
*/
|
||||
public MaskingManager(final Map<String, MaskingHandler> handlerMap, final char maskChar) {
|
||||
this.handlerMap = handlerMap;
|
||||
this.maskChar = maskChar;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个脱敏处理器
|
||||
*
|
||||
* @param type 类型
|
||||
* @param handler 脱敏处理器
|
||||
* @return this
|
||||
*/
|
||||
public MaskingManager register(final String type, final MaskingHandler handler) {
|
||||
this.handlerMap.put(type, handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏处理<br>
|
||||
* 如果没有指定的脱敏处理器,则返回{@code null}
|
||||
*
|
||||
* @param type 类型
|
||||
* @param value 待脱敏值
|
||||
* @return 脱敏后的值
|
||||
*/
|
||||
public String masking(String type, final CharSequence value) {
|
||||
if (StrUtil.isEmpty(type)) {
|
||||
type = CLEAR_TO_NULL.name();
|
||||
}
|
||||
final MaskingHandler handler = handlerMap.get(type);
|
||||
return null == handler ? null : handler.handle(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认的脱敏处理器注册
|
||||
*
|
||||
* @param maskChar 默认的脱敏字符,默认为*
|
||||
* @return 默认的脱敏处理器注册
|
||||
*/
|
||||
private static MaskingManager registerDefault(final char maskChar) {
|
||||
final MaskingManager manager = new MaskingManager(
|
||||
new ConcurrentHashMap<>(15, 1), maskChar);
|
||||
|
||||
manager.register(USER_ID.name(), (str) -> "0");
|
||||
manager.register(CHINESE_NAME.name(), manager::firstMask);
|
||||
manager.register(ID_CARD.name(), (str) -> manager.idCardNum(str, 1, 2));
|
||||
manager.register(FIXED_PHONE.name(), manager::fixedPhone);
|
||||
manager.register(MOBILE_PHONE.name(), manager::mobilePhone);
|
||||
manager.register(ADDRESS.name(), (str) -> manager.address(str, 8));
|
||||
manager.register(EMAIL.name(), manager::email);
|
||||
manager.register(PASSWORD.name(), manager::password);
|
||||
manager.register(CAR_LICENSE.name(), manager::carLicense);
|
||||
manager.register(BANK_CARD.name(), manager::bankCard);
|
||||
manager.register(IPV4.name(), manager::ipv4);
|
||||
manager.register(IPV6.name(), manager::ipv6);
|
||||
manager.register(FIRST_MASK.name(), manager::firstMask);
|
||||
manager.register(CLEAR_TO_EMPTY.name(), (str) -> StrUtil.EMPTY);
|
||||
manager.register(CLEAR_TO_NULL.name(), (str) -> null);
|
||||
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义了一个first_mask的规则,只显示第一个字符。<br>
|
||||
* 脱敏前:123456789;脱敏后:1********。
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return 脱敏后的字符串
|
||||
*/
|
||||
public String firstMask(final CharSequence str) {
|
||||
if (StrUtil.isBlank(str)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
return StrUtil.replaceByCodePoint(str, 1, str.length(), maskChar);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【身份证号】前1位 和后2位
|
||||
*
|
||||
* @param idCardNum 身份证
|
||||
* @param front 保留:前面的front位数;从1开始
|
||||
* @param end 保留:后面的end位数;从1开始
|
||||
* @return 脱敏后的身份证
|
||||
*/
|
||||
public String idCardNum(final CharSequence idCardNum, final int front, final int end) {
|
||||
//身份证不能为空
|
||||
if (StrUtil.isBlank(idCardNum)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
//需要截取的长度不能大于身份证号长度
|
||||
if ((front + end) > idCardNum.length()) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
//需要截取的不能小于0
|
||||
if (front < 0 || end < 0) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
return StrUtil.replaceByCodePoint(idCardNum, front, idCardNum.length() - end, maskChar);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【固定电话 前四位,后两位
|
||||
*
|
||||
* @param num 固定电话
|
||||
* @return 脱敏后的固定电话;
|
||||
*/
|
||||
public String fixedPhone(final CharSequence num) {
|
||||
if (StrUtil.isBlank(num)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
return StrUtil.replaceByCodePoint(num, 4, num.length() - 2, maskChar);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【手机号码】前三位,后4位,其他隐藏,比如135****2210
|
||||
*
|
||||
* @param num 移动电话;
|
||||
* @return 脱敏后的移动电话;
|
||||
*/
|
||||
public String mobilePhone(final CharSequence num) {
|
||||
if (StrUtil.isBlank(num)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
return StrUtil.replaceByCodePoint(num, 3, num.length() - 4, maskChar);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
|
||||
*
|
||||
* @param address 家庭住址
|
||||
* @param sensitiveSize 敏感信息长度
|
||||
* @return 脱敏后的家庭地址
|
||||
*/
|
||||
public String address(final CharSequence address, final int sensitiveSize) {
|
||||
if (StrUtil.isBlank(address)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
final int length = address.length();
|
||||
return StrUtil.replaceByCodePoint(address, length - sensitiveSize, length, maskChar);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【电子邮箱】邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com
|
||||
*
|
||||
* @param email 邮箱
|
||||
* @return 脱敏后的邮箱
|
||||
*/
|
||||
public String email(final CharSequence email) {
|
||||
if (StrUtil.isBlank(email)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
final int index = StrUtil.indexOf(email, '@');
|
||||
if (index <= 1) {
|
||||
return email.toString();
|
||||
}
|
||||
return StrUtil.replaceByCodePoint(email, 1, index, maskChar);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【密码】密码的全部字符都用定义的脱敏字符(如*)代替,比如:******<br>
|
||||
* 密码位数不能被猜测,因此固定10位
|
||||
*
|
||||
* @param password 密码
|
||||
* @return 脱敏后的密码
|
||||
*/
|
||||
public String password(final CharSequence password) {
|
||||
if (StrUtil.isBlank(password)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
// 密码位数不能被猜测,因此固定10位
|
||||
return StrUtil.repeat(maskChar, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【中国车牌】车牌中间用脱敏字符(如*)代替
|
||||
* eg1:null -》 ""
|
||||
* eg1:"" -》 ""
|
||||
* eg3:苏D40000 -》 苏D4***0
|
||||
* eg4:陕A12345D -》 陕A1****D
|
||||
* eg5:京A123 -》 京A123 如果是错误的车牌,不处理
|
||||
*
|
||||
* @param carLicense 完整的车牌号
|
||||
* @return 脱敏后的车牌
|
||||
*/
|
||||
public String carLicense(CharSequence carLicense) {
|
||||
if (StrUtil.isBlank(carLicense)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
// 普通车牌
|
||||
if (carLicense.length() == 7) {
|
||||
carLicense = StrUtil.replaceByCodePoint(carLicense, 3, 6, maskChar);
|
||||
} else if (carLicense.length() == 8) {
|
||||
// 新能源车牌
|
||||
carLicense = StrUtil.replaceByCodePoint(carLicense, 3, 7, maskChar);
|
||||
}
|
||||
return carLicense.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 银行卡号脱敏
|
||||
* eg: 1101 **** **** **** 3256
|
||||
*
|
||||
* @param bankCardNo 银行卡号
|
||||
* @return 脱敏之后的银行卡号
|
||||
*/
|
||||
public String bankCard(CharSequence bankCardNo) {
|
||||
if (StrUtil.isBlank(bankCardNo)) {
|
||||
return StrUtil.toStringOrNull(bankCardNo);
|
||||
}
|
||||
bankCardNo = StrUtil.cleanBlank(bankCardNo);
|
||||
if (bankCardNo.length() < 9) {
|
||||
return bankCardNo.toString();
|
||||
}
|
||||
|
||||
final int length = bankCardNo.length();
|
||||
final int endLength = length % 4 == 0 ? 4 : length % 4;
|
||||
final int midLength = length - 4 - endLength;
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
|
||||
buf.append(bankCardNo, 0, 4);
|
||||
for (int i = 0; i < midLength; ++i) {
|
||||
if (i % 4 == 0) {
|
||||
buf.append(CharUtil.SPACE);
|
||||
}
|
||||
buf.append(maskChar);
|
||||
}
|
||||
buf.append(CharUtil.SPACE).append(bankCardNo, length - endLength, length);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* IPv4脱敏,如:脱敏前:192.0.2.1;脱敏后:192.*.*.*。
|
||||
*
|
||||
* @param ipv4 IPv4地址
|
||||
* @return 脱敏后的地址
|
||||
*/
|
||||
public String ipv4(final CharSequence ipv4) {
|
||||
return StrUtil.subBefore(ipv4, '.', false) + StrUtil.repeat("." + maskChar, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* IPv6脱敏,如:脱敏前:2001:0db8:86a3:08d3:1319:8a2e:0370:7344;脱敏后:2001:*:*:*:*:*:*:*
|
||||
*
|
||||
* @param ipv6 IPv6地址
|
||||
* @return 脱敏后的地址
|
||||
*/
|
||||
public String ipv6(final CharSequence ipv6) {
|
||||
return StrUtil.subBefore(ipv6, ':', false) + StrUtil.repeat(":" + maskChar, 7);
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Hutool Team and hutool.cn
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.dromara.hutool.core.data.masking;
|
||||
|
||||
/**
|
||||
* 支持的脱敏类型枚举
|
||||
*
|
||||
* @author dazer and neusoft and qiaomu
|
||||
*/
|
||||
public enum MaskingType {
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
USER_ID,
|
||||
/**
|
||||
* 中文名
|
||||
*/
|
||||
CHINESE_NAME,
|
||||
/**
|
||||
* 身份证号
|
||||
*/
|
||||
ID_CARD,
|
||||
/**
|
||||
* 座机号
|
||||
*/
|
||||
FIXED_PHONE,
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
MOBILE_PHONE,
|
||||
/**
|
||||
* 地址
|
||||
*/
|
||||
ADDRESS,
|
||||
/**
|
||||
* 电子邮件
|
||||
*/
|
||||
EMAIL,
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
PASSWORD,
|
||||
/**
|
||||
* 中国大陆车牌,包含普通车辆、新能源车辆
|
||||
*/
|
||||
CAR_LICENSE,
|
||||
/**
|
||||
* 银行卡
|
||||
*/
|
||||
BANK_CARD,
|
||||
/**
|
||||
* IPv4地址
|
||||
*/
|
||||
IPV4,
|
||||
/**
|
||||
* IPv6地址
|
||||
*/
|
||||
IPV6,
|
||||
/**
|
||||
* 定义了一个first_mask的规则,只显示第一个字符。
|
||||
*/
|
||||
FIRST_MASK,
|
||||
/**
|
||||
* 清空为null
|
||||
*/
|
||||
CLEAR_TO_NULL,
|
||||
/**
|
||||
* 清空为""
|
||||
*/
|
||||
CLEAR_TO_EMPTY
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Hutool Team and hutool.cn
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 数据脱敏,提供各种数据类型(字符串、数字等)的脱敏方法。
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package org.dromara.hutool.core.data.masking;
|
@ -58,7 +58,8 @@ public class SyncInputStream extends FilterInputStream {
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步数据到内存
|
||||
* 同步数据到内存,同步后关闭原流
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public SyncInputStream sync() {
|
||||
@ -76,18 +77,19 @@ public class SyncInputStream extends FilterInputStream {
|
||||
* @return bytes
|
||||
*/
|
||||
public byte[] readBytes() {
|
||||
final FastByteArrayOutputStream bytesOut = new FastByteArrayOutputStream(length > 0 ? (int)length : 1024);
|
||||
final FastByteArrayOutputStream bytesOut = new FastByteArrayOutputStream(length > 0 ? (int) length : 1024);
|
||||
final long length = copyTo(bytesOut, null);
|
||||
return length > 0 ? bytesOut.toByteArray() : new byte[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 将流的内容拷贝到输出流
|
||||
* @param out 输出流
|
||||
* 将流的内容拷贝到输出流,拷贝结束后关闭输入流
|
||||
*
|
||||
* @param out 输出流
|
||||
* @param streamProgress 进度条
|
||||
* @return 拷贝长度
|
||||
*/
|
||||
public long copyTo(final OutputStream out, final StreamProgress streamProgress){
|
||||
public long copyTo(final OutputStream out, final StreamProgress streamProgress) {
|
||||
long copyLength = -1;
|
||||
try {
|
||||
copyLength = IoUtil.copy(this.in, out, IoUtil.DEFAULT_BUFFER_SIZE, this.length, streamProgress);
|
||||
@ -96,7 +98,7 @@ public class SyncInputStream extends FilterInputStream {
|
||||
throw e;
|
||||
}
|
||||
// 忽略读取流中的EOF错误
|
||||
}finally {
|
||||
} finally {
|
||||
// 读取结束
|
||||
IoUtil.closeQuietly(in);
|
||||
}
|
||||
|
@ -16,9 +16,13 @@
|
||||
|
||||
package org.dromara.hutool.core.data;
|
||||
|
||||
import org.dromara.hutool.core.data.masking.MaskingManager;
|
||||
import org.dromara.hutool.core.data.masking.MaskingType;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* 脱敏工具类 MaskingUtil 安全测试
|
||||
*
|
||||
@ -29,94 +33,108 @@ public class MaskingUtilTest {
|
||||
|
||||
@Test
|
||||
public void maskingTest() {
|
||||
Assertions.assertEquals("", MaskingUtil.masking("100", MaskingUtil.MaskingType.CLEAR_TO_EMPTY));
|
||||
Assertions.assertNull(MaskingUtil.masking("100", MaskingUtil.MaskingType.CLEAR_TO_NULL));
|
||||
assertEquals("", MaskingUtil.masking(MaskingType.CLEAR_TO_EMPTY, "100"));
|
||||
Assertions.assertNull(MaskingUtil.masking(MaskingType.CLEAR_TO_NULL, "100"));
|
||||
|
||||
Assertions.assertEquals("0", MaskingUtil.masking("100", MaskingUtil.MaskingType.USER_ID));
|
||||
Assertions.assertEquals("段**", MaskingUtil.masking("段正淳", MaskingUtil.MaskingType.CHINESE_NAME));
|
||||
Assertions.assertEquals("5***************1X", MaskingUtil.masking("51343620000320711X", MaskingUtil.MaskingType.ID_CARD));
|
||||
Assertions.assertEquals("0915*****79", MaskingUtil.masking("09157518479", MaskingUtil.MaskingType.FIXED_PHONE));
|
||||
Assertions.assertEquals("180****1999", MaskingUtil.masking("18049531999", MaskingUtil.MaskingType.MOBILE_PHONE));
|
||||
Assertions.assertEquals("北京市海淀区马********", MaskingUtil.masking("北京市海淀区马连洼街道289号", MaskingUtil.MaskingType.ADDRESS));
|
||||
Assertions.assertEquals("d*************@gmail.com.cn", MaskingUtil.masking("duandazhi-jack@gmail.com.cn", MaskingUtil.MaskingType.EMAIL));
|
||||
Assertions.assertEquals("**********", MaskingUtil.masking("1234567890", MaskingUtil.MaskingType.PASSWORD));
|
||||
assertEquals("0", MaskingUtil.masking(MaskingType.USER_ID, "100"));
|
||||
assertEquals("段**", MaskingUtil.masking(MaskingType.CHINESE_NAME, "段正淳"));
|
||||
assertEquals("5***************1X", MaskingUtil.masking(MaskingType.ID_CARD, "51343620000320711X"));
|
||||
assertEquals("0915*****79", MaskingUtil.masking(MaskingType.FIXED_PHONE, "09157518479"));
|
||||
assertEquals("180****1999", MaskingUtil.masking(MaskingType.MOBILE_PHONE, "18049531999"));
|
||||
assertEquals("北京市海淀区马********", MaskingUtil.masking(MaskingType.ADDRESS, "北京市海淀区马连洼街道289号"));
|
||||
assertEquals("d*************@gmail.com.cn", MaskingUtil.masking(MaskingType.EMAIL, "duandazhi-jack@gmail.com.cn"));
|
||||
assertEquals("**********", MaskingUtil.masking(MaskingType.PASSWORD, "1234567890"));
|
||||
assertEquals("**********", MaskingUtil.masking(MaskingType.PASSWORD, "123"));
|
||||
assertEquals("1101 **** **** **** 3256", MaskingUtil.masking(MaskingType.BANK_CARD, "11011111222233333256"));
|
||||
assertEquals("6227 **** **** **** 123", MaskingUtil.masking(MaskingType.BANK_CARD, "6227880100100105123"));
|
||||
assertEquals("192.*.*.*", MaskingUtil.masking(MaskingType.IPV4, "192.168.1.1"));
|
||||
assertEquals("2001:*:*:*:*:*:*:*", MaskingUtil.masking(MaskingType.IPV6, "2001:0db8:86a3:08d3:1319:8a2e:0370:7344"));
|
||||
}
|
||||
|
||||
Assertions.assertEquals("0", MaskingUtil.masking("100", MaskingUtil.MaskingType.USER_ID));
|
||||
Assertions.assertEquals("段**", MaskingUtil.masking("段正淳", MaskingUtil.MaskingType.CHINESE_NAME));
|
||||
Assertions.assertEquals("5***************1X", MaskingUtil.masking("51343620000320711X", MaskingUtil.MaskingType.ID_CARD));
|
||||
Assertions.assertEquals("0915*****79", MaskingUtil.masking("09157518479", MaskingUtil.MaskingType.FIXED_PHONE));
|
||||
Assertions.assertEquals("180****1999", MaskingUtil.masking("18049531999", MaskingUtil.MaskingType.MOBILE_PHONE));
|
||||
Assertions.assertEquals("北京市海淀区马********", MaskingUtil.masking("北京市海淀区马连洼街道289号", MaskingUtil.MaskingType.ADDRESS));
|
||||
Assertions.assertEquals("d*************@gmail.com.cn", MaskingUtil.masking("duandazhi-jack@gmail.com.cn", MaskingUtil.MaskingType.EMAIL));
|
||||
Assertions.assertEquals("**********", MaskingUtil.masking("1234567890", MaskingUtil.MaskingType.PASSWORD));
|
||||
Assertions.assertEquals("1101 **** **** **** 3256", MaskingUtil.masking("11011111222233333256", MaskingUtil.MaskingType.BANK_CARD));
|
||||
Assertions.assertEquals("6227 **** **** **** 123", MaskingUtil.masking("6227880100100105123", MaskingUtil.MaskingType.BANK_CARD));
|
||||
Assertions.assertEquals("192.*.*.*", MaskingUtil.masking("192.168.1.1", MaskingUtil.MaskingType.IPV4));
|
||||
Assertions.assertEquals("2001:*:*:*:*:*:*:*", MaskingUtil.masking("2001:0db8:86a3:08d3:1319:8a2e:0370:7344", MaskingUtil.MaskingType.IPV6));
|
||||
@Test
|
||||
public void maskingWithMaskCharTest() {
|
||||
final MaskingManager manager = MaskingManager.ofDefault('#');
|
||||
|
||||
assertEquals("", manager.masking(MaskingType.CLEAR_TO_EMPTY.name(), "100"));
|
||||
Assertions.assertNull(manager.masking(MaskingType.CLEAR_TO_NULL.name(), "100"));
|
||||
|
||||
assertEquals("0", manager.masking(MaskingType.USER_ID.name(), "100"));
|
||||
assertEquals("段##", manager.masking(MaskingType.CHINESE_NAME.name(), "段正淳"));
|
||||
assertEquals("5###############1X", manager.masking(MaskingType.ID_CARD.name(), "51343620000320711X"));
|
||||
assertEquals("0915#####79", manager.masking(MaskingType.FIXED_PHONE.name(), "09157518479"));
|
||||
assertEquals("180####1999", manager.masking(MaskingType.MOBILE_PHONE.name(), "18049531999"));
|
||||
assertEquals("北京市海淀区马########", manager.masking(MaskingType.ADDRESS.name(), "北京市海淀区马连洼街道289号"));
|
||||
assertEquals("d#############@gmail.com.cn", manager.masking(MaskingType.EMAIL.name(), "duandazhi-jack@gmail.com.cn"));
|
||||
assertEquals("##########", manager.masking(MaskingType.PASSWORD.name(), "1234567890"));
|
||||
assertEquals("##########", manager.masking(MaskingType.PASSWORD.name(), "123"));
|
||||
assertEquals("1101 #### #### #### 3256", manager.masking(MaskingType.BANK_CARD.name(), "11011111222233333256"));
|
||||
assertEquals("6227 #### #### #### 123", manager.masking(MaskingType.BANK_CARD.name(), "6227880100100105123"));
|
||||
assertEquals("192.#.#.#", manager.masking(MaskingType.IPV4.name(), "192.168.1.1"));
|
||||
assertEquals("2001:#:#:#:#:#:#:#", manager.masking(MaskingType.IPV6.name(), "2001:0db8:86a3:08d3:1319:8a2e:0370:7344"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userIdTest() {
|
||||
Assertions.assertEquals(Long.valueOf(0L), MaskingUtil.userId());
|
||||
assertEquals(Long.valueOf(0L), MaskingUtil.userId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chineseNameTest() {
|
||||
Assertions.assertEquals("段**", MaskingUtil.chineseName("段正淳"));
|
||||
assertEquals("段**", MaskingUtil.chineseName("段正淳"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void idCardNumTest() {
|
||||
Assertions.assertEquals("5***************1X", MaskingUtil.idCardNum("51343620000320711X", 1, 2));
|
||||
assertEquals("5***************1X", MaskingUtil.idCardNum("51343620000320711X", 1, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fixedPhoneTest() {
|
||||
Assertions.assertEquals("0915*****79", MaskingUtil.fixedPhone("09157518479"));
|
||||
assertEquals("0915*****79", MaskingUtil.fixedPhone("09157518479"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mobilePhoneTest() {
|
||||
Assertions.assertEquals("180****1999", MaskingUtil.mobilePhone("18049531999"));
|
||||
assertEquals("180****1999", MaskingUtil.mobilePhone("18049531999"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addressTest() {
|
||||
Assertions.assertEquals("北京市海淀区马连洼街*****", MaskingUtil.address("北京市海淀区马连洼街道289号", 5));
|
||||
Assertions.assertEquals("***************", MaskingUtil.address("北京市海淀区马连洼街道289号", 50));
|
||||
Assertions.assertEquals("北京市海淀区马连洼街道289号", MaskingUtil.address("北京市海淀区马连洼街道289号", 0));
|
||||
Assertions.assertEquals("北京市海淀区马连洼街道289号", MaskingUtil.address("北京市海淀区马连洼街道289号", -1));
|
||||
assertEquals("北京市海淀区马连洼街*****", MaskingUtil.address("北京市海淀区马连洼街道289号", 5));
|
||||
assertEquals("***************", MaskingUtil.address("北京市海淀区马连洼街道289号", 50));
|
||||
assertEquals("北京市海淀区马连洼街道289号", MaskingUtil.address("北京市海淀区马连洼街道289号", 0));
|
||||
assertEquals("北京市海淀区马连洼街道289号", MaskingUtil.address("北京市海淀区马连洼街道289号", -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emailTest() {
|
||||
Assertions.assertEquals("d********@126.com", MaskingUtil.email("duandazhi@126.com"));
|
||||
Assertions.assertEquals("d********@gmail.com.cn", MaskingUtil.email("duandazhi@gmail.com.cn"));
|
||||
Assertions.assertEquals("d*************@gmail.com.cn", MaskingUtil.email("duandazhi-jack@gmail.com.cn"));
|
||||
assertEquals("d********@126.com", MaskingUtil.email("duandazhi@126.com"));
|
||||
assertEquals("d********@gmail.com.cn", MaskingUtil.email("duandazhi@gmail.com.cn"));
|
||||
assertEquals("d*************@gmail.com.cn", MaskingUtil.email("duandazhi-jack@gmail.com.cn"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void passwordTest() {
|
||||
Assertions.assertEquals("**********", MaskingUtil.password("1234567890"));
|
||||
assertEquals("**********", MaskingUtil.password("1234567890"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void carLicenseTest() {
|
||||
Assertions.assertEquals("", MaskingUtil.carLicense(null));
|
||||
Assertions.assertEquals("", MaskingUtil.carLicense(""));
|
||||
Assertions.assertEquals("苏D4***0", MaskingUtil.carLicense("苏D40000"));
|
||||
Assertions.assertEquals("陕A1****D", MaskingUtil.carLicense("陕A12345D"));
|
||||
Assertions.assertEquals("京A123", MaskingUtil.carLicense("京A123"));
|
||||
assertEquals("", MaskingUtil.carLicense(null));
|
||||
assertEquals("", MaskingUtil.carLicense(""));
|
||||
assertEquals("苏D4***0", MaskingUtil.carLicense("苏D40000"));
|
||||
assertEquals("陕A1****D", MaskingUtil.carLicense("陕A12345D"));
|
||||
assertEquals("京A123", MaskingUtil.carLicense("京A123"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bankCardTest() {
|
||||
Assertions.assertNull(MaskingUtil.bankCard(null));
|
||||
Assertions.assertEquals("", MaskingUtil.bankCard(""));
|
||||
Assertions.assertEquals("1234 **** **** **** **** 9", MaskingUtil.bankCard("1234 2222 3333 4444 6789 9"));
|
||||
Assertions.assertEquals("1234 **** **** **** **** 91", MaskingUtil.bankCard("1234 2222 3333 4444 6789 91"));
|
||||
Assertions.assertEquals("1234 **** **** **** 6789", MaskingUtil.bankCard("1234 2222 3333 4444 6789"));
|
||||
Assertions.assertEquals("1234 **** **** **** 678", MaskingUtil.bankCard("1234 2222 3333 4444 678"));
|
||||
assertEquals("", MaskingUtil.bankCard(""));
|
||||
assertEquals("1234 **** **** **** **** 9", MaskingUtil.bankCard("1234 2222 3333 4444 6789 9"));
|
||||
assertEquals("1234 **** **** **** **** 91", MaskingUtil.bankCard("1234 2222 3333 4444 6789 91"));
|
||||
assertEquals("1234 **** **** **** 6789", MaskingUtil.bankCard("1234 2222 3333 4444 6789"));
|
||||
assertEquals("1234 **** **** **** 678", MaskingUtil.bankCard("1234 2222 3333 4444 678"));
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -19,9 +19,7 @@ package org.dromara.hutool.crypto.symmetric;
|
||||
import org.dromara.hutool.core.codec.binary.Base64;
|
||||
import org.dromara.hutool.core.codec.binary.HexUtil;
|
||||
import org.dromara.hutool.core.util.RandomUtil;
|
||||
import org.dromara.hutool.crypto.KeyUtil;
|
||||
import org.dromara.hutool.crypto.Mode;
|
||||
import org.dromara.hutool.crypto.Padding;
|
||||
import org.dromara.hutool.crypto.*;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -148,4 +146,10 @@ public class AESTest {
|
||||
final String decryptStr = aes.decryptStr(encrypt);
|
||||
Assertions.assertEquals(phone, decryptStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
void issue3766Test() {
|
||||
Assertions.assertThrows(CryptoException.class, ()->
|
||||
SecureUtil.aes("8888888888888888".getBytes()).decryptStr("哈哈"));
|
||||
}
|
||||
}
|
||||
|
@ -82,13 +82,21 @@ public interface Response extends Closeable {
|
||||
InputStream bodyStream();
|
||||
|
||||
/**
|
||||
* 获取响应体,包含服务端返回的内容和Content-Type信息
|
||||
* 同步<br>
|
||||
* 如果为异步状态,则暂时不读取服务器中响应的内容,而是持有Http链接的{@link InputStream}。<br>
|
||||
* 当调用此方法时,异步状态转为同步状态,此时从Http链接流中读取body内容并暂存在内容(内存)中。如果已经是同步状态,则不进行任何操作。
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
Response sync();
|
||||
|
||||
/**
|
||||
* 获取响应体,包含服务端返回的内容和Content-Type信息<br>
|
||||
* 如果为HEAD、CONNECT、TRACE等方法无响应体,则返回{@code null}
|
||||
*
|
||||
* @return {@link ResponseBody}
|
||||
*/
|
||||
default ResponseBody body() {
|
||||
return new ResponseBody(this, bodyStream(), false, true);
|
||||
}
|
||||
ResponseBody body();
|
||||
|
||||
/**
|
||||
* 获取响应主体
|
||||
@ -112,7 +120,7 @@ public interface Response extends Closeable {
|
||||
*/
|
||||
default byte[] bodyBytes() {
|
||||
try (final ResponseBody body = body()) {
|
||||
return body.getBytes();
|
||||
return null == body ? null : body.getBytes();
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
@ -44,6 +44,16 @@ public class ResponseBody implements HttpBody, Closeable {
|
||||
*/
|
||||
private final SyncInputStream bodyStream;
|
||||
|
||||
/**
|
||||
* 构造,不读取响应体,忽略响应体EOF错误
|
||||
*
|
||||
* @param response 响应体
|
||||
* @param in HTTP主体响应流
|
||||
*/
|
||||
public ResponseBody(final Response response, final InputStream in) {
|
||||
this(response, in, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
|
@ -21,6 +21,7 @@ import org.apache.http.HttpHost;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
@ -75,11 +76,14 @@ public class HttpClient4Engine extends AbstractClientEngine {
|
||||
initEngine();
|
||||
|
||||
final HttpUriRequest request = buildRequest(message);
|
||||
final CloseableHttpResponse response;
|
||||
try {
|
||||
return this.engine.execute(request, response -> new HttpClient4Response(response, message.charset()));
|
||||
//return this.engine.execute(request, response -> new HttpClient4Response(response, message));
|
||||
response = this.engine.execute(request);
|
||||
} catch (final IOException e) {
|
||||
throw new HttpException(e);
|
||||
}
|
||||
return new HttpClient4Response(response, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -24,13 +24,15 @@ import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.core.io.IORuntimeException;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.lang.wrapper.SimpleWrapper;
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
import org.dromara.hutool.http.HttpException;
|
||||
import org.dromara.hutool.http.HttpUtil;
|
||||
import org.dromara.hutool.http.client.Request;
|
||||
import org.dromara.hutool.http.client.Response;
|
||||
import org.dromara.hutool.http.client.body.ResponseBody;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
@ -52,18 +54,20 @@ public class HttpClient4Response extends SimpleWrapper<HttpResponse> implements
|
||||
* 请求时的默认编码
|
||||
*/
|
||||
private final Charset requestCharset;
|
||||
private final ResponseBody body;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 通过传入一个请求时的编码,当无法获取响应内容的编码时,默认使用响应时的编码
|
||||
*
|
||||
* @param rawRes {@link HttpResponse}
|
||||
* @param requestCharset 请求时的编码
|
||||
* @param rawRes {@link HttpResponse}
|
||||
* @param message 请求消息
|
||||
*/
|
||||
public HttpClient4Response(final HttpResponse rawRes, final Charset requestCharset) {
|
||||
public HttpClient4Response(final HttpResponse rawRes, final Request message) {
|
||||
super(rawRes);
|
||||
this.entity = rawRes.getEntity();
|
||||
this.requestCharset = requestCharset;
|
||||
this.requestCharset = message.charset();
|
||||
this.body = message.method().isIgnoreBody() ? null : new ResponseBody(this, bodyStream());
|
||||
}
|
||||
|
||||
|
||||
@ -112,6 +116,21 @@ public class HttpClient4Response extends SimpleWrapper<HttpResponse> implements
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient4Response sync() {
|
||||
final ResponseBody body = this.body;
|
||||
if(null != body){
|
||||
body.sync();
|
||||
}
|
||||
IoUtil.closeIfPossible(this.raw);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseBody body() {
|
||||
return this.body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String bodyStr() throws HttpException {
|
||||
try {
|
||||
@ -124,10 +143,8 @@ public class HttpClient4Response extends SimpleWrapper<HttpResponse> implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if(this.raw instanceof Closeable){
|
||||
((Closeable) this.raw).close();
|
||||
}
|
||||
public void close() {
|
||||
IoUtil.closeIfPossible(this.raw);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -29,6 +29,7 @@ import org.apache.hc.client5.http.impl.classic.HttpClients;
|
||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
|
||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
@ -81,11 +82,14 @@ public class HttpClient5Engine extends AbstractClientEngine {
|
||||
initEngine();
|
||||
|
||||
final ClassicHttpRequest request = buildRequest(message);
|
||||
final ClassicHttpResponse response;
|
||||
try {
|
||||
return this.engine.execute(request, (response -> new HttpClient5Response(response, message.charset())));
|
||||
//return this.engine.execute(request, (response -> new HttpClient5Response(response, message)));
|
||||
response = this.engine.executeOpen(null, request, null);
|
||||
} catch (final IOException e) {
|
||||
throw new HttpException(e);
|
||||
}
|
||||
return new HttpClient5Response(response, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -16,28 +16,27 @@
|
||||
|
||||
package org.dromara.hutool.http.client.engine.httpclient5;
|
||||
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
import org.dromara.hutool.core.io.IORuntimeException;
|
||||
import org.apache.hc.core5.http.ParseException;
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.core.io.IORuntimeException;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.lang.wrapper.SimpleWrapper;
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
import org.dromara.hutool.http.HttpException;
|
||||
import org.dromara.hutool.http.HttpUtil;
|
||||
import org.dromara.hutool.http.client.Request;
|
||||
import org.dromara.hutool.http.client.Response;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.ParseException;
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||
import org.dromara.hutool.http.client.body.ResponseBody;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* HttpClient响应包装<br>
|
||||
@ -52,18 +51,20 @@ public class HttpClient5Response extends SimpleWrapper<ClassicHttpResponse> impl
|
||||
* 请求时的默认编码
|
||||
*/
|
||||
private final Charset requestCharset;
|
||||
private final ResponseBody body;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 通过传入一个请求时的编码,当无法获取响应内容的编码时,默认使用响应时的编码
|
||||
*
|
||||
* @param rawRes {@link CloseableHttpResponse}
|
||||
* @param requestCharset 请求时的编码
|
||||
* @param rawRes {@link CloseableHttpResponse}
|
||||
* @param message 请求消息
|
||||
*/
|
||||
public HttpClient5Response(final ClassicHttpResponse rawRes, final Charset requestCharset) {
|
||||
public HttpClient5Response(final ClassicHttpResponse rawRes, final Request message) {
|
||||
super(rawRes);
|
||||
this.entity = rawRes.getEntity();
|
||||
this.requestCharset = requestCharset;
|
||||
this.requestCharset = message.charset();
|
||||
this.body = message.method().isIgnoreBody() ? null : new ResponseBody(this, bodyStream());
|
||||
}
|
||||
|
||||
|
||||
@ -112,6 +113,21 @@ public class HttpClient5Response extends SimpleWrapper<ClassicHttpResponse> impl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient5Response sync() {
|
||||
final ResponseBody body = this.body;
|
||||
if(null != body){
|
||||
body.sync();
|
||||
}
|
||||
IoUtil.closeQuietly(this.raw);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseBody body() {
|
||||
return this.body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String bodyStr() throws HttpException {
|
||||
try {
|
||||
|
@ -26,7 +26,6 @@ import org.dromara.hutool.http.HttpException;
|
||||
import org.dromara.hutool.http.HttpUtil;
|
||||
import org.dromara.hutool.http.client.ClientConfig;
|
||||
import org.dromara.hutool.http.client.Request;
|
||||
import org.dromara.hutool.http.client.Response;
|
||||
import org.dromara.hutool.http.client.body.HttpBody;
|
||||
import org.dromara.hutool.http.client.engine.AbstractClientEngine;
|
||||
import org.dromara.hutool.http.meta.HeaderName;
|
||||
@ -65,18 +64,7 @@ public class JdkClientEngine extends AbstractClientEngine {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response send(final Request message) {
|
||||
return send(message, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送请求
|
||||
*
|
||||
* @param message 请求消息
|
||||
* @param isAsync 是否异步,异步不会立即读取响应内容
|
||||
* @return {@link Response}
|
||||
*/
|
||||
public JdkHttpResponse send(final Request message, final boolean isAsync) {
|
||||
public JdkHttpResponse send(final Request message) {
|
||||
final JdkHttpConnection conn = buildConn(message);
|
||||
try {
|
||||
doSend(conn, message);
|
||||
@ -86,7 +74,7 @@ public class JdkClientEngine extends AbstractClientEngine {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
||||
return sendRedirectIfPossible(conn, message, isAsync);
|
||||
return sendRedirectIfPossible(conn, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -173,11 +161,10 @@ public class JdkClientEngine extends AbstractClientEngine {
|
||||
/**
|
||||
* 调用转发,如果需要转发返回转发结果,否则返回{@code null}
|
||||
*
|
||||
* @param conn {@link JdkHttpConnection}}
|
||||
* @param isAsync 最终请求是否异步
|
||||
* @param conn {@link JdkHttpConnection}}
|
||||
* @return {@link JdkHttpResponse},无转发返回 {@code null}
|
||||
*/
|
||||
private JdkHttpResponse sendRedirectIfPossible(final JdkHttpConnection conn, final Request message, final boolean isAsync) {
|
||||
private JdkHttpResponse sendRedirectIfPossible(final JdkHttpConnection conn, final Request message) {
|
||||
// 手动实现重定向
|
||||
if (message.maxRedirects() > 0) {
|
||||
final int code;
|
||||
@ -203,14 +190,14 @@ public class JdkClientEngine extends AbstractClientEngine {
|
||||
|
||||
if (conn.redirectCount < message.maxRedirects()) {
|
||||
conn.redirectCount++;
|
||||
return send(message, isAsync);
|
||||
return send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 最终页面
|
||||
return new JdkHttpResponse(conn, this.cookieManager, true, message.charset(), isAsync, message.method().isIgnoreBody());
|
||||
return new JdkHttpResponse(conn, this.cookieManager, message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,6 +21,7 @@ import org.dromara.hutool.core.io.stream.EmptyInputStream;
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
import org.dromara.hutool.http.HttpException;
|
||||
import org.dromara.hutool.http.HttpUtil;
|
||||
import org.dromara.hutool.http.client.Request;
|
||||
import org.dromara.hutool.http.client.Response;
|
||||
import org.dromara.hutool.http.client.body.ResponseBody;
|
||||
import org.dromara.hutool.http.meta.HeaderName;
|
||||
@ -42,6 +43,10 @@ import java.util.Map;
|
||||
*/
|
||||
public class JdkHttpResponse implements Response, Closeable {
|
||||
|
||||
/**
|
||||
* 持有连接对象
|
||||
*/
|
||||
protected JdkHttpConnection httpConnection;
|
||||
/**
|
||||
* 请求时的默认编码
|
||||
*/
|
||||
@ -50,26 +55,14 @@ public class JdkHttpResponse implements Response, Closeable {
|
||||
* Cookie管理器
|
||||
*/
|
||||
private final JdkCookieManager cookieManager;
|
||||
/**
|
||||
* 响应内容体,{@code null} 表示无内容
|
||||
*/
|
||||
private ResponseBody body;
|
||||
/**
|
||||
* 响应头
|
||||
*/
|
||||
private Map<String, List<String>> headers;
|
||||
|
||||
/**
|
||||
* 是否忽略响应读取时可能的EOF异常。<br>
|
||||
* 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。<br>
|
||||
* 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。
|
||||
* 响应内容体,{@code null} 表示无内容
|
||||
*/
|
||||
private final boolean ignoreEOFError;
|
||||
|
||||
/**
|
||||
* 持有连接对象
|
||||
*/
|
||||
protected JdkHttpConnection httpConnection;
|
||||
private ResponseBody body;
|
||||
/**
|
||||
* 响应状态码
|
||||
*/
|
||||
@ -80,22 +73,15 @@ public class JdkHttpResponse implements Response, Closeable {
|
||||
*
|
||||
* @param httpConnection {@link JdkHttpConnection}
|
||||
* @param cookieManager Cookie管理器
|
||||
* @param ignoreEOFError 是否忽略响应读取时可能的EOF异常
|
||||
* @param requestCharset 编码,从请求编码中获取默认编码
|
||||
* @param isAsync 是否异步
|
||||
* @param isIgnoreBody 是否忽略读取响应体
|
||||
* @param message 请求消息
|
||||
*/
|
||||
protected JdkHttpResponse(final JdkHttpConnection httpConnection,
|
||||
final JdkCookieManager cookieManager,
|
||||
final boolean ignoreEOFError,
|
||||
final Charset requestCharset,
|
||||
final boolean isAsync,
|
||||
final boolean isIgnoreBody) {
|
||||
final Request message) {
|
||||
this.httpConnection = httpConnection;
|
||||
this.cookieManager = cookieManager;
|
||||
this.ignoreEOFError = ignoreEOFError;
|
||||
this.requestCharset = requestCharset;
|
||||
init(isAsync, isIgnoreBody);
|
||||
this.requestCharset = message.charset();
|
||||
init(message.method().isIgnoreBody());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,18 +114,13 @@ public class JdkHttpResponse implements Response, Closeable {
|
||||
return ObjUtil.defaultIfNull(Response.super.charset(), requestCharset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步<br>
|
||||
* 如果为异步状态,则暂时不读取服务器中响应的内容,而是持有Http链接的{@link InputStream}。<br>
|
||||
* 当调用此方法时,异步状态转为同步状态,此时从Http链接流中读取body内容并暂存在内容中。如果已经是同步状态,则不进行任何操作。
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
public JdkHttpResponse sync() {
|
||||
if (null != this.body) {
|
||||
this.body.sync();
|
||||
}
|
||||
close();
|
||||
// 关闭连接
|
||||
this.httpConnection.closeQuietly();
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -250,9 +231,10 @@ public class JdkHttpResponse implements Response, Closeable {
|
||||
* 3、持有Http流,并不关闭流
|
||||
* </pre>
|
||||
*
|
||||
* @param isIgnoreBody 是否忽略消息体
|
||||
* @throws HttpException IO异常
|
||||
*/
|
||||
private void init(final boolean isAsync, final boolean isIgnoreBody) throws HttpException {
|
||||
private void init(final boolean isIgnoreBody) throws HttpException {
|
||||
// 获取响应状态码
|
||||
try {
|
||||
this.status = httpConnection.getCode();
|
||||
@ -272,13 +254,13 @@ public class JdkHttpResponse implements Response, Closeable {
|
||||
}
|
||||
|
||||
// 存储服务端设置的Cookie信息
|
||||
if(null != this.cookieManager){
|
||||
if (null != this.cookieManager) {
|
||||
this.cookieManager.saveFromResponse(this.httpConnection, this.headers);
|
||||
}
|
||||
|
||||
// 获取响应内容流
|
||||
if (!isIgnoreBody) {
|
||||
this.body = new ResponseBody(this, new JdkHttpInputStream(this), isAsync, this.ignoreEOFError);
|
||||
this.body = new ResponseBody(this, new JdkHttpInputStream(this));
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------- Private method end
|
||||
|
@ -71,7 +71,7 @@ public class OkHttpEngine extends AbstractClientEngine {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
||||
return new OkHttpResponse(response, message.charset());
|
||||
return new OkHttpResponse(response, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -16,19 +16,20 @@
|
||||
|
||||
package org.dromara.hutool.http.client.engine.okhttp;
|
||||
|
||||
import kotlin.Pair;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.ResponseBody;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.io.stream.EmptyInputStream;
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
import org.dromara.hutool.http.GlobalCompressStreamRegister;
|
||||
import org.dromara.hutool.http.HttpUtil;
|
||||
import org.dromara.hutool.http.client.Request;
|
||||
import org.dromara.hutool.http.client.Response;
|
||||
import org.dromara.hutool.http.client.body.ResponseBody;
|
||||
import org.dromara.hutool.http.meta.HeaderName;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* OkHttp3的{@link okhttp3.Response} 响应包装
|
||||
@ -42,14 +43,16 @@ public class OkHttpResponse implements Response {
|
||||
* 请求时的默认编码
|
||||
*/
|
||||
private final Charset requestCharset;
|
||||
private final ResponseBody body;
|
||||
|
||||
/**
|
||||
* @param rawRes {@link okhttp3.Response}
|
||||
* @param requestCharset 请求时的默认编码
|
||||
* @param message 请求对象
|
||||
*/
|
||||
public OkHttpResponse(final okhttp3.Response rawRes, final Charset requestCharset) {
|
||||
public OkHttpResponse(final okhttp3.Response rawRes, final Request message) {
|
||||
this.rawRes = rawRes;
|
||||
this.requestCharset = requestCharset;
|
||||
this.requestCharset = message.charset();
|
||||
this.body = message.method().isIgnoreBody() ? null : new ResponseBody(this, bodyStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -74,7 +77,7 @@ public class OkHttpResponse implements Response {
|
||||
|
||||
@Override
|
||||
public InputStream bodyStream() {
|
||||
final ResponseBody body = rawRes.body();
|
||||
final okhttp3.ResponseBody body = rawRes.body();
|
||||
if(null == body){
|
||||
return EmptyInputStream.INSTANCE;
|
||||
}
|
||||
@ -84,10 +87,22 @@ public class OkHttpResponse implements Response {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if(null != this.rawRes){
|
||||
rawRes.close();
|
||||
public OkHttpResponse sync() {
|
||||
if (null != this.body) {
|
||||
this.body.sync();
|
||||
}
|
||||
IoUtil.closeQuietly(this.rawRes);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseBody body() {
|
||||
return this.body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
IoUtil.closeQuietly(this.rawRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Hutool Team and hutool.cn
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.dromara.hutool.http.client;
|
||||
|
||||
import org.dromara.hutool.http.HttpUtil;
|
||||
import org.dromara.hutool.http.client.engine.ClientEngine;
|
||||
import org.dromara.hutool.http.client.engine.ClientEngineFactory;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class Issue3765Test {
|
||||
|
||||
public static void main(String[] args) {
|
||||
HttpUtil.createServer(8888)
|
||||
.setRoot("d:/test/www")
|
||||
.start();
|
||||
}
|
||||
@Test
|
||||
@Disabled
|
||||
void downloadTest() {
|
||||
final String url = "http://localhost:8888/a.mp3";
|
||||
final ClientEngine engine = ClientEngineFactory.createEngine("httpclient4");
|
||||
Request.of(url)
|
||||
.send(engine)
|
||||
.body().write("d:/test/");
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user