Java地址脱敏实操案例
地址脱敏在实际开发中非常常见,主要目的是保护用户隐私,以下是几种常见的地址脱敏方案和完整代码实现。

基础地址脱敏方案
public class AddressDesensitization {
/**
* 地址脱敏 - 保留前几个字符和后几个字符
* @param address 原始地址
* @param frontKeep 前面保留字符数
* @param endKeep 后面保留字符数
* @return 脱敏后的地址
*/
public static String desensitizeAddress(String address, int frontKeep, int endKeep) {
if (address == null || address.isEmpty()) {
return address;
}
int length = address.length();
if (length <= frontKeep + endKeep) {
// 如果地址太短,全部用星号代替
return address.replaceAll(".", "*");
}
String front = address.substring(0, frontKeep);
String end = address.substring(length - endKeep);
String middle = address.substring(frontKeep, length - endKeep);
// 用星号替换中间部分
String maskedMiddle = middle.replaceAll(".", "*");
return front + maskedMiddle + end;
}
public static void main(String[] args) {
String address = "北京市朝阳区建国路88号SOHO现代城A座1508室";
// 保留前2个字符和后3个字符
String masked1 = desensitizeAddress(address, 2, 3);
System.out.println("脱敏结果1: " + masked1);
// 输出: 北京市**************************1508室
// 保留省市区信息
String masked2 = desensitizeAddress(address, 6, 10);
System.out.println("脱敏结果2: " + masked2);
// 输出: 北京市朝阳区**************A座1508室
}
}
智能地址脱敏(保留省市信息)
public class SmartAddressDesensitization {
// 省级行政区划关键词
private static final Set<String> PROVINCE_KEYWORDS = new HashSet<>(Arrays.asList(
"省", "自治区", "自治区", "特别行政区"
));
// 市级行政区划关键词
private static final Set<String> CITY_KEYWORDS = new HashSet<>(Arrays.asList(
"市", "区", "县", "自治州", "盟", "地区"
));
// 区县级关键词
private static final Set<String> DISTRICT_KEYWORDS = new HashSet<>(Arrays.asList(
"区", "县", "县级市", "旗", "自治旗"
));
/**
* 智能地址脱敏 - 自动识别并保留省市信息
*/
public static String smartDesensitize(String address) {
if (address == null || address.isEmpty()) {
return address;
}
// 查找省级位置
int provinceEnd = findKeywordEnd(address, PROVINCE_KEYWORDS);
if (provinceEnd <= 0) {
// 如果没有省级关键词,使用基础方法
return desensitizeAddress(address, 1, 1);
}
// 查找市级位置
int cityEnd = findKeywordEnd(address, CITY_KEYWORDS, provinceEnd);
if (cityEnd <= 0) {
cityEnd = provinceEnd;
}
// 查找区县级位置
int districtEnd = findKeywordEnd(address, DISTRICT_KEYWORDS, cityEnd);
if (districtEnd <= 0) {
districtEnd = cityEnd;
}
// 保留省市区县信息,后续地址脱敏
String keepPart = address.substring(0, districtEnd + 1);
String maskPart = address.substring(districtEnd + 1);
// 对后续地址进行脱敏
String maskedSuffix = maskAddressSuffix(maskPart);
return keepPart + maskedSuffix;
}
private static int findKeywordEnd(String text, Set<String> keywords) {
return findKeywordEnd(text, keywords, 0);
}
private static int findKeywordEnd(String text, Set<String> keywords, int startPos) {
for (String keyword : keywords) {
int index = text.indexOf(keyword, startPos);
if (index >= 0) {
return index + keyword.length() - 1;
}
}
return -1;
}
private static String maskAddressSuffix(String suffix) {
if (suffix.isEmpty()) {
return "";
}
int length = suffix.length();
if (length <= 2) {
return suffix.replaceAll(".", "*");
}
// 保留第一个和最后一个字符
String first = suffix.substring(0, 1);
String last = suffix.substring(length - 1);
String middle = suffix.substring(1, length - 1);
return first + middle.replaceAll(".", "*") + last;
}
public static void main(String[] args) {
// 测试各种地址格式
String[] testAddresses = {
"北京市朝阳区建国路88号SOHO现代城A座1508室",
"上海市浦东新区世纪大道100号上海环球金融中心",
"广东省广州市天河区天河路385号太古汇",
"浙江省杭州市西湖区西溪路556号阿里中心",
"四川省成都市武侯区天府大道北段1700号"
};
for (String address : testAddresses) {
String masked = smartDesensitize(address);
System.out.println("原始: " + address);
System.out.println("脱敏: " + masked);
System.out.println("---");
}
}
}
使用正则表达式脱敏
public class RegexAddressDesensitization {
/**
* 使用正则表达式进行地址脱敏
*/
public static String regexDesensitize(String address) {
if (address == null || address.isEmpty()) {
return address;
}
// 脱敏规则:保留省市区信息,后续地址替换为*
// 匹配省市区县的模式
String pattern = "(.{0,10}(省|自治区|市|区|县|自治州|旗))(.+)";
Pattern r = Pattern.compile(pattern);
Matcher m = r.matcher(address);
if (m.find()) {
String location = m.group(1);
String detail = m.group(3);
String maskedDetail = detail.replaceAll(".", "*");
return location + maskedDetail;
}
// 如果没有匹配到省市信息,使用通用脱敏
return desensitizeAddress(address, 2, 2);
}
/**
* 通用地址脱敏工具方法
*/
public static String desensitizeAddress(String address, int frontKeep, int endKeep) {
if (address == null || address.isEmpty()) {
return address;
}
int length = address.length();
if (length <= frontKeep + endKeep) {
return address.replaceAll(".", "*");
}
String front = address.substring(0, frontKeep);
String end = address.substring(length - endKeep);
String middle = address.substring(frontKeep, length - endKeep);
String maskedMiddle = middle.replaceAll(".", "*");
return front + maskedMiddle + end;
}
public static void main(String[] args) {
String[] testAddresses = {
"北京市朝阳区建国路88号SOHO现代城",
"上海市浦东新区张江高科技园区",
"广东省深圳市南山区科技园南区",
"浙江省杭州市西湖区文三路478号"
};
for (String address : testAddresses) {
String masked = regexDesensitize(address);
System.out.println("原始: " + address);
System.out.println("脱敏: " + masked);
System.out.println("---");
}
}
}
完整的生产级脱敏工具类
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;
public final class AddressMaskingUtil {
private AddressMaskingUtil() {
// 工具类禁止实例化
}
// 配置常量
private static final Set<String> PROVINCE_KEYWORDS = new HashSet<>(Arrays.asList(
"省", "自治区", "特别行政区"
));
private static final Set<String> CITY_KEYWORDS = new HashSet<>(Arrays.asList(
"市", "自治州", "盟", "地区"
));
private static final Set<String> DISTRICT_KEYWORDS = new HashSet<>(Arrays.asList(
"区", "县", "县级市", "旗", "自治旗", "林区", "特区"
));
public enum MaskingStrategy {
FULL_MASK, // 完全掩码
PRESERVE_PROVINCE, // 保留省级
PRESERVE_CITY, // 保留市级
PRESERVE_DISTRICT, // 保留区县级
CUSTOM // 自定义
}
/**
* 地址脱敏主方法
* @param address 原始地址
* @param strategy 脱敏策略
* @return 脱敏后的地址
*/
public static String mask(String address, MaskingStrategy strategy) {
if (address == null || address.isEmpty()) {
return address;
}
switch (strategy) {
case FULL_MASK:
return fullMask(address);
case PRESERVE_PROVINCE:
return preserveProvince(address);
case PRESERVE_CITY:
return preserveCity(address);
case PRESERVE_DISTRICT:
return preserveDistrict(address);
case CUSTOM:
return customMask(address);
default:
return fullMask(address);
}
}
/**
* 完全掩码
*/
private static String fullMask(String address) {
return address.replaceAll(".", "*");
}
/**
* 保留省级信息
*/
private static String preserveProvince(String address) {
int endIndex = findProvinceEnd(address);
if (endIndex >= 0) {
return address.substring(0, endIndex + 1) +
maskRest(address, endIndex + 1);
}
return fullMask(address);
}
/**
* 保留市级信息
*/
private static String preserveCity(String address) {
int provinceEnd = findProvinceEnd(address);
int cityEnd = findCityEnd(address, provinceEnd + 1);
if (cityEnd >= 0) {
return address.substring(0, cityEnd + 1) +
maskRest(address, cityEnd + 1);
} else if (provinceEnd >= 0) {
return address.substring(0, provinceEnd + 1) +
maskRest(address, provinceEnd + 1);
}
return fullMask(address);
}
/**
* 保留区县级信息
*/
private static String preserveDistrict(String address) {
int provinceEnd = findProvinceEnd(address);
int cityEnd = findCityEnd(address, provinceEnd + 1);
int districtEnd = findDistrictEnd(address, cityEnd + 1);
if (districtEnd >= 0) {
return address.substring(0, districtEnd + 1) +
maskRest(address, districtEnd + 1);
} else if (cityEnd >= 0) {
return address.substring(0, cityEnd + 1) +
maskRest(address, cityEnd + 1);
} else if (provinceEnd >= 0) {
return address.substring(0, provinceEnd + 1) +
maskRest(address, provinceEnd + 1);
}
return fullMask(address);
}
/**
* 自定义脱敏(示例:保留前几个和后几个字符)
*/
private static String customMask(String address) {
if (address.length() <= 4) {
return fullMask(address);
}
return address.substring(0, 2) +
address.substring(2, address.length() - 2).replaceAll(".", "*") +
address.substring(address.length() - 2);
}
/**
* 掩码剩余部分(保留首尾字符)
*/
private static String maskRest(String address, int startIndex) {
if (startIndex >= address.length()) {
return "";
}
String rest = address.substring(startIndex);
if (rest.length() <= 2) {
return rest.replaceAll(".", "*");
}
return rest.substring(0, 1) +
rest.substring(1, rest.length() - 1).replaceAll(".", "*") +
rest.substring(rest.length() - 1);
}
/**
* 查找省级关键词结束位置
*/
private static int findProvinceEnd(String address) {
for (String keyword : PROVINCE_KEYWORDS) {
int index = address.indexOf(keyword);
if (index >= 0) {
return index + keyword.length() - 1;
}
}
// 查找直辖市(北京、上海、天津、重庆)
if (address.startsWith("北京") || address.startsWith("上海") ||
address.startsWith("天津") || address.startsWith("重庆")) {
// 直辖市后面通常接"市"
int shiIndex = address.indexOf("市", 2);
if (shiIndex >= 0 && shiIndex < 3) {
return shiIndex;
}
}
return -1;
}
/**
* 查找市级关键词结束位置
*/
private static int findCityEnd(String address, int fromIndex) {
for (String keyword : CITY_KEYWORDS) {
int index = address.indexOf(keyword, fromIndex);
if (index >= 0) {
return index + keyword.length() - 1;
}
}
return -1;
}
/**
* 查找区县级关键词结束位置
*/
private static int findDistrictEnd(String address, int fromIndex) {
for (String keyword : DISTRICT_KEYWORDS) {
int index = address.indexOf(keyword, fromIndex);
if (index >= 0) {
return index + keyword.length() - 1;
}
}
return -1;
}
public static void main(String[] args) {
String address = "北京市朝阳区建国路88号SOHO现代城A座1508室";
System.out.println("原始地址: " + address);
System.out.println("完全掩码: " + mask(address, MaskingStrategy.FULL_MASK));
System.out.println("保留省级: " + mask(address, MaskingStrategy.PRESERVE_PROVINCE));
System.out.println("保留市级: " + mask(address, MaskingStrategy.PRESERVE_CITY));
System.out.println("保留区县: " + mask(address, MaskingStrategy.PRESERVE_DISTRICT));
System.out.println("自定义: " + mask(address, MaskingStrategy.CUSTOM));
// 测试其他地址格式
String[] addresses = {
"上海市浦东新区世纪大道100号",
"广东省广州市天河区天河路385号",
"四川省成都市武侯区人民南路四段11号"
};
System.out.println("\n--- 多地址测试 ---");
for (String addr : addresses) {
System.out.println("原始: " + addr);
System.out.println("脱敏: " + mask(addr, MaskingStrategy.PRESERVE_DISTRICT));
System.out.println("---");
}
}
}
使用示例
public class AddressMaskingDemo {
public static void main(String[] args) {
// 1. 基础脱敏
String addr1 = AddressDesensitization.desensitizeAddress("北京市朝阳区建国路88号", 2, 3);
System.out.println("基础脱敏: " + addr1);
// 2. 智能脱敏
String addr2 = SmartAddressDesensitization.smartDesensitize("广东省深圳市南山区科技园南区A栋");
System.out.println("智能脱敏: " + addr2);
// 3. 工具类脱敏
String addr3 = AddressMaskingUtil.mask("四川省成都市武侯区人民南路四段11号",
AddressMaskingUtil.MaskingStrategy.PRESERVE_DISTRICT);
System.out.println("工具类脱敏: " + addr3);
}
}
-
脱敏策略选择:
- 完全掩码:适用于不需要显示任何地址信息
- 保留省市:适用于需要显示大致的地区信息
- 保留区县:适用于需要显示较详细的区域信息
- 自定义:根据需要保留特定部分
-
边界情况处理:
- 空地址或null
- 短地址(少于保留字符数)
- 不同的地址格式和命名习惯
-
性能优化:
- 缓存常用脱敏结果
- 预编译正则表达式
- 避免在循环中频繁创建Pattern对象
-
安全性考虑:
- 确保脱敏后无法还原原始地址
- 不同场景使用不同的脱敏强度
- 结合业务需求调整脱敏规则
根据你的具体业务需求,可以选择合适的脱敏方案或组合使用。