四级联动 地址选择器 地址解析器 实现
(其他级别可类比参考,注:如果涉及到第五级:村级,请给village表中的code和name字段添加索引)
-
数据来源及参考:gitee.com/modood/Admi…
-
git项目名称:Administrative-divisions-of-China
-
从 上面地址的项目中分别获取province.json、city.json、area.json、street.json文件,使用Naticat导入到数据库中,然后新增自增的id字段
-
重命名后分别形成 address_component_province、address_component_city、address_component_area、address_component_street四张表
-
提取码:7133
功能一:地址选择器
- 实现填写地址信息时所用的地址选择下拉组件功能,如下图*
功能二:地址解析器
- 实现通过地址字符串 校验、解析出有效地址功能,常用于 地址自动补全、导入 场景
环境:mysql,navicat,idea,lombok插件及依赖,jdk8,jpa
yml配置文件:
wms:
core:
address:
provinceUnits: [省,自治区,市,特别行政区]
cityUnits: [地区,盟,自治州,市]
areaUnits: [县,自治县,旗,自治旗,市,区,林区,特区]
streetUnits: [乡,民族乡,镇,街道,苏木,民族苏木,区,市]
villageUnits: [村,社区,管理区]
exclude: [香港,澳门,台湾]
yml配置对应的javaBean配置:
@Data
@Component
@ConfigurationProperties(prefix = "wms.core.address")
public class AddressUnitsProperties {
/**
* 省一级行政单位集合
*/
private List<String> provinceUnits;
/**
* 地二级行政单位集合
*/
private List<String> cityUnits;
/**
* 县三级行政单位集合
*/
private List<String> areaUnits;
/**
* 乡(镇)四级行政单位集合
*/
private List<String> streetUnits;
/**
* 不支持 港澳台
*/
private List<String> exclude;
}
地址选择器入参:
@Data
@Builder
@ApiModel(value = "地址选择器入参")
@AllArgsConstructor
@NoArgsConstructor
public class AddressReqDTO {
/**
* 要查找的层级
* 省一级:1
* 市二级:2
* 县三级:3
* 镇四级:4
* @see AddressComponentEnum
*/
@ApiModelProperty(value = "要查找的层级,省:1,市:2,县:3,镇:4", required = true)
@NotNull
private Integer level;
@ApiModelProperty(value = "查找关键字")
private String keyword;
@ApiModelProperty(value = "省 编码")
private Integer provinceCode;
@ApiModelProperty(value = "市 编码")
private Integer cityCode;
@ApiModelProperty(value = "县/区 编码")
private Integer areaCode;
}
地址选择器出参:
/**
* 地址选择器响应体
*/
@Data
@Builder
@ApiModel
@AllArgsConstructor
@NoArgsConstructor
public class AddressRespDTO {
/**
* 查找层级
* 省一级:1
* 市二级:2
* 县三级:3
* 镇四级:4
* @see AddressComponentEnum
*/
private Integer level;
/**
* 省出参
*/
private List<AddressComponentProvince> provinceList;
/**
* 市出参
*/
private List<AddressComponentCity> cityList;
/**
* 县/区 出参
*/
private List<AddressComponentArea> areaList;
/**
* 乡镇/街道 出参
*/
private List<AddressComponentStreet> streetList;
}
用户输入的地址字符串被正则表达式解析后的对象:
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AddressKeywordDTO {
/**
* 省
*/
private String province;
/**
* 市
*/
private String city;
/**
* 县
*/
private String area;
/**
* 镇
*/
private String street;
}
地址解析入参:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AddressResolveReq {
/**
* 用户手输地址
*/
@NotBlank
private String address;
}
地址解析出参:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AddressDTO {
/**
* 省编码(国家行政代码)
*/
private Integer provinceCode;
/**
* 省名称
*/
private String provinceName;
/**
* 市编码(国家行政代码)
*/
private Integer cityCode;
/**
* 市名称
*/
private String cityName;
/**
* 县编码(国家行政代码)
*/
private Integer areaCode;
/**
* 县名称
*/
private String areaName;
/**
* 镇编码(国家行政代码)
*/
private Integer streetCode;
/**
* 镇名称
*/
private String streetName;
}
controller层:
@Slf4j
@Api(tags = "地址选择器")
@RestController
@RequestMapping("/address")
@RequiredArgsConstructor
public class AddressController {
private final IAddressService addressService;
@ApiOperation(value = "地址查询")
@PostMapping(value = "/addressSelector")
public ApiEntity<AddressRespDTO> addressSelector(@ApiParam("请求体") @RequestBody @Valid AddressReqDTO addressReqDTO) {
addressService.validateAddressParam(addressReqDTO);
return ApiEntity.ok(addressService.addressSelector(addressReqDTO));
}
@ApiOperation(value = "地址解析")
@PostMapping(value = "/addressResolver")
public ApiEntity<AddressRespDTO> addressSelector(@ApiParam("请求体") @RequestBody @Valid AddressReqDTO addressReqDTO) {
addressService.validateAddressParam(addressReqDTO);
return ApiEntity.ok(addressService.addressResolver(addressReqDTO));
}
}
service接口层:
public interface IAddressService {
/**
* 地址选择器入参校验
* @param addressReqDTO
*/
void validateAddressParam(AddressReqDTO addressReqDTO);
/**
* 地址选择器
*/
AddressRespDTO addressSelector(AddressReqDTO addressReqDTO);
/********************上面地址选择器相关方法,下面地址解析器相关方法********************/
/**
* 是否存在不支持地址(暂不支持港澳台)
* @param address 地址
* @return true:存在 false:不存在
*/
boolean existExcludeAddress(String address);
/**
* 根据输入的地址字符串,提取相应的 省、市、县、镇 四级行政单位地址
* @param address 地址
* @return
*/
List<Map<String, String>> addressResolution(String address);
/**
* 去除区域行政单位,得到搜索关键字
* 逻辑:只匹配替换最后的指定字符串,只对长度大于2的各级地址做截取
* @param addressKeywordDTO
*/
AddressKeywordDTO removeAdministrativeUnit(AddressKeywordDTO addressKeywordDTO);
/**
* 通过地址获取相应的数据库记录
*/
List<AddressDTO> getStreetsByAddress(AddressKeywordDTO addressKeywordDTO);
/**
* 地址解析器
* @param address 要被解析的地址
* @return 被解析后的地址
*/
AddressDTO addressResolver(String address);
}
service实现层:
@Service
@Slf4j
public class AddressServiceImpl implements IAddressService {
@Autowired
private AddressUnitsProperties addressUnitsProperties;
@Autowired
private AddressComponentProvinceRepository provinceRepository;
@Autowired
private AddressComponentCityRepository cityRepository;
@Autowired
private AddressComponentAreaRepository areaRepository;
@Autowired
private AddressComponentStreetRepository streetRepository;
/**
* 对地址组件的查询入参做校验
* @param addressReqDTO
*/
@Override
public void validateAddressParam(AddressReqDTO addressReqDTO) {
if (Objects.isNull(addressReqDTO.getLevel())){
throw new ApiException(ErrorCode.DATA_VALIDATE_ERROR);
}else if (addressReqDTO.getLevel() == AddressComponentEnum.CITY.getLevel() && Objects.isNull(addressReqDTO.getProvinceCode())){
throw new ApiException(ErrorCode.DATA_VALIDATE_ERROR);
}else if (addressReqDTO.getLevel() == AddressComponentEnum.AREA.getLevel()
&& (Objects.isNull(addressReqDTO.getProvinceCode()) || Objects.isNull(addressReqDTO.getCityCode()))){
throw new ApiException(ErrorCode.DATA_VALIDATE_ERROR);
}else if (addressReqDTO.getLevel() == AddressComponentEnum.STREET.getLevel()
&& (Objects.isNull(addressReqDTO.getProvinceCode()) || Objects.isNull(addressReqDTO.getCityCode()) || Objects.isNull(addressReqDTO.getAreaCode()))){
throw new ApiException(ErrorCode.DATA_VALIDATE_ERROR);
}
}
/**
* 地址选择器
*/
@Override
public AddressRespDTO addressSelector(AddressReqDTO addressReqDTO) {
AddressRespDTO addressRespDTO = new AddressRespDTO();
addressRespDTO.setLevel(addressReqDTO.getLevel());
if (addressReqDTO.getLevel() == AddressComponentEnum.PROVINCE.getLevel()){ //查询 省份
List<AddressComponentProvince> provinceList = StringUtils.isBlank(addressReqDTO.getKeyword())
? provinceRepository.findAll()
: provinceRepository.findByNameLike(QueryUtils.addLikeChar(addressReqDTO.getKeyword()));
addressRespDTO.setProvinceList(provinceList);
}else if (addressReqDTO.getLevel() == AddressComponentEnum.CITY.getLevel()){ //查询 城市
List<AddressComponentCity> cityList = StringUtils.isBlank(addressReqDTO.getKeyword())
? cityRepository.findByProvinceCode(addressReqDTO.getProvinceCode())
: cityRepository.findByProvinceCodeAndNameLike(addressReqDTO.getProvinceCode(),QueryUtils.addLikeChar(addressReqDTO.getKeyword()));
addressRespDTO.setCityList(cityList);
}else if (addressReqDTO.getLevel() == AddressComponentEnum.AREA.getLevel()){ //查询 区县
List<AddressComponentArea> areaList = StringUtils.isBlank(addressReqDTO.getKeyword())
? areaRepository.findByProvinceCodeAndCityCode(addressReqDTO.getProvinceCode(), addressReqDTO.getCityCode())
: areaRepository.findByProvinceCodeAndCityCodeAndNameLike(addressReqDTO.getProvinceCode(), addressReqDTO.getCityCode(),QueryUtils.addLikeChar(addressReqDTO.getKeyword()));
addressRespDTO.setAreaList(areaList);
}else if (addressReqDTO.getLevel() == AddressComponentEnum.STREET.getLevel()){ //查询 乡镇
List<AddressComponentStreet> streetList = StringUtils.isBlank(addressReqDTO.getKeyword())
? streetRepository.findByProvinceCodeAndCityCodeAndAreaCode(addressReqDTO.getProvinceCode(), addressReqDTO.getCityCode(), addressReqDTO.getAreaCode())
: streetRepository.findByProvinceCodeAndCityCodeAndAreaCodeAndNameLike(addressReqDTO.getProvinceCode(), addressReqDTO.getCityCode(), addressReqDTO.getAreaCode(),QueryUtils.addLikeChar(addressReqDTO.getKeyword()));
addressRespDTO.setStreetList(streetList);
}
return addressRespDTO;
}
/********************上面地址选择器相关方法,下面地址解析器相关方法********************/
/**
* 是否存在不支持地址(暂不支持港澳台)
* @param address 地址
* @return true:存在 false:不存在
*/
@Override
public boolean existExcludeAddress(String address) {
List<String> excludeAddressKeys = addressUnitsProperties.getExclude();
if (!CollectionUtils.isEmpty(excludeAddressKeys)){
for (String excludeAddressKey : excludeAddressKeys){
//判断是否以不支持的关键字开头
if (address.matches("^" + excludeAddressKey + ".*")){
return true;
}
}
}
return false;
}
/**
* 根据输入的地址字符串,提取相应的 省、市、县、镇 四级行政单位地址
* @param address 地址
* @return
*/
public List<Map<String, String>> addressResolution(String address) {
String regex="(?<province>.+?省|.+?自治区|.+?市|.+?特别行政区)(?<city>.+?地区|.+?盟|.+?自治州|.+?市)(?<county>.+?县|.+?自治县|.+?旗|.+?自治旗|.+?市|.+?区|.+?林区|.+?特区)(?<town>.+?乡|.+?民族乡|.+?镇|.+?街道|.+?苏木|.+?民族苏木|.+?区|.+?市)(?<village>.*?)";
Matcher m= Pattern.compile(regex).matcher(address);
String province=null,city=null,county=null,town=null,village=null;
List<Map<String,String>> table=new ArrayList<Map<String,String>>();
Map<String,String> row=null;
while(m.find()){
row=new LinkedHashMap<String,String>();
province=m.group("province");
row.put("province", province==null?"":province.trim());
city=m.group("city");
row.put("city", city==null?"":city.trim());
county=m.group("county");
row.put("area", county==null?"":county.trim());
town=m.group("town");
row.put("street", town==null?"":town.trim());
village=m.group("village");
row.put("village", village==null?"":village.trim());
table.add(row);
}
return table;
}
/**
* 去除区域行政单位,得到搜索关键字
* 逻辑:只匹配替换最后的指定字符串,只对长度大于2的各级地址做截取
* @param addressKeywordDTO
*/
public AddressKeywordDTO removeAdministrativeUnit(AddressKeywordDTO addressKeywordDTO){
int length = 2;
addressUnitsProperties.getProvinceUnits().stream().forEach(provinceUnit -> {
Optional.ofNullable(addressKeywordDTO.getProvince()).filter(provinceKeyword -> provinceKeyword.length() > length).ifPresent(provinceKeyword -> {
addressKeywordDTO.setProvince(provinceKeyword.replaceAll(provinceUnit + "$", ""));
});
});
addressUnitsProperties.getCityUnits().stream().forEach(cityUnit -> {
Optional.ofNullable(addressKeywordDTO.getCity()).filter(cityKeyword -> cityKeyword.length() > length).ifPresent(cityKeyword -> {
addressKeywordDTO.setCity(cityKeyword.replaceAll(cityUnit+"$", ""));
});
});
addressUnitsProperties.getAreaUnits().stream().forEach(areaUnit -> {
Optional.ofNullable(addressKeywordDTO.getArea()).filter(areaKeyword -> areaKeyword.length() > length).ifPresent(areaKeyword -> {
addressKeywordDTO.setArea(areaKeyword.replaceAll(areaUnit+"$", ""));
});
});
addressUnitsProperties.getStreetUnits().stream().forEach(streetUnit -> {
Optional.ofNullable(addressKeywordDTO.getStreet()).filter(streetKeyword -> streetKeyword.length() > length).ifPresent(streetKeyword -> {
addressKeywordDTO.setStreet(streetKeyword.replaceAll(streetUnit+"$", ""));
});
});
return addressKeywordDTO;
}
/**
* 通过地址获取相应的数据库记录
* @param addressKeywordDTO
* @return
*/
@Override
public List<AddressDTO> getStreetsByAddress(AddressKeywordDTO addressKeywordDTO) {
List<AddressDTO> addressDTOS = new ArrayList<>();
//省
List<AddressComponentProvince> provinceList = provinceRepository.findByNameLike(QueryUtils.addLikeChar(addressKeywordDTO.getProvince()));
if (!CollectionUtils.isEmpty(provinceList)){
//市
Map<Integer, AddressComponentProvince> provinceMap = provinceList.stream().collect(Collectors.toMap(AddressComponentProvince::getCode, Function.identity()));
List<AddressComponentCity> cityList = cityRepository.findByProvinceCodeInAndNameLike(provinceMap.keySet(), QueryUtils.addLikeChar(addressKeywordDTO.getCity()));
if (!CollectionUtils.isEmpty(cityList)){
//县
Map<Integer, AddressComponentCity> cityMap = cityList.stream().collect(Collectors.toMap(AddressComponentCity::getCode,Function.identity()));
List<AddressComponentArea> areaList = areaRepository.findByCityCodeInAndNameLike(cityMap.keySet(), QueryUtils.addLikeChar(addressKeywordDTO.getArea()));
if (!CollectionUtils.isEmpty(areaList)){
//镇
Map<Integer, AddressComponentArea> areaMap = areaList.stream().collect(Collectors.toMap(AddressComponentArea::getCode, Function.identity()));
List<AddressComponentStreet> streetList = streetRepository.findByAreaCodeInAndNameLike(areaMap.keySet(), QueryUtils.addLikeCharSuffix(addressKeywordDTO.getStreet()));
if (!CollectionUtils.isEmpty(streetList)){
//数据封装
streetList.stream().forEach(street -> {
AddressComponentProvince province = provinceMap.get(street.getProvinceCode());
AddressComponentCity city = cityMap.get(street.getCityCode());
AddressComponentArea area = areaMap.get(street.getAreaCode());
AddressDTO addressDTO = AddressDTO.builder()
.provinceCode(province.getCode()).provinceName(province.getName())
.cityCode(city.getCode()).cityName(city.getName())
.areaCode(area.getCode()).areaName(area.getName())
.streetCode(street.getCode()).streetName(street.getName()).build();
addressDTOS.add(addressDTO);
});
}
}
}
}
return addressDTOS;
}
}
/**
* 地址解析器
* @param address 要被解析的地址
* @return 被解析后的地址
*/
@Override
public AddressDTO addressResolver(String address) {
//校验 港澳台
if(existExcludeAddress(addressResolveReq.getAddress())){
log.error("暂不支持港澳台");
throw new ApiException(ErrorCode.ADDRESS_IS_EXCLUDE);
}
List<Map<String,String>> table = addressResolution(address);
if (CollectionUtils.isEmpty(table) || table.size() != 1){
log.error("无法提取有效采样地址:{}",address);
throw new ApiException(ErrorCode.ADDRESS_IS_INVALID);
}
//构建地址提取对象
AddressKeywordDTO addressKeywordDTO = AddressKeywordDTO.builder()
.province(table.get(0).get("province"))//省
.city(table.get(0).get("city"))//市
.area(table.get(0).get("area"))//区、县
.street(table.get(0).get("street")).build();//乡、镇
if (StringUtils.isBlank(addressKeywordDTO.getProvince())
|| StringUtils.isBlank(addressKeywordDTO.getCity())
|| StringUtils.isBlank(addressKeywordDTO.getArea())
|| StringUtils.isBlank(addressKeywordDTO.getStreet())){
log.error("无法提取有效采样地址:{}",address);
throw new ApiException(ErrorCode.ADDRESS_IS_INVALID);
}
//去除区域行政单位,得到搜索关键字放入数据库中查找更准确
removeAdministrativeUnit(addressKeywordDTO);
//校验 省、市、县、镇 四级行政单位 ,用来判断是否存在 唯一性、有效性
List<AddressDTO> addressDTOS = getStreetsByAddress(addressKeywordDTO);
if (CollectionUtils.isEmpty(addressDTOS)){
log.error("地址不存在:{}",address);
throw new ApiException(ErrorCode.ADDRESS_IS_NOT_EXIST);
}else if (addressDTOS.size() != 1){
log.error("无法定位到唯一地址:{}",address);
throw new ApiException(ErrorCode.ADDRESS_IS_NOT_UNIQUE);
}
return addressDTOS.get(0);
}
省一级的持久层:
@Repository
public interface AddressComponentProvinceRepository extends BaseRepository<AddressComponentProvince,Integer> {
List<AddressComponentProvince> findByNameLike(String name);
}
地市二级的持久层:
@Repository
public interface AddressComponentCityRepository extends BaseRepository<AddressComponentCity,Integer> {
List<AddressComponentCity> findByProvinceCode(Integer provinceCode);
List<AddressComponentCity> findByProvinceCodeAndNameLike(Integer provinceCode,String name);
List<AddressComponentCity> findByProvinceCodeInAndNameLike(Set<Integer> provinceCodeList, String name);
}
区、县三级的持久层:
@Repository
public interface AddressComponentAreaRepository extends BaseRepository<AddressComponentArea,Integer> {
List<AddressComponentArea> findByProvinceCodeAndCityCode(Integer provinceCode,Integer cityCode);
List<AddressComponentArea> findByProvinceCodeAndCityCodeAndNameLike(Integer provinceCode,Integer cityCode,String name);
List<AddressComponentArea> findByCityCodeInAndNameLike(Set<Integer> cityCodeList, String addLikeChar);
}
乡镇、街道四级的持久层:
@Repository
public interface AddressComponentStreetRepository extends BaseRepository<AddressComponentStreet,Integer> {
List<AddressComponentStreet> findByProvinceCodeAndCityCodeAndAreaCode(Integer provinceCode,Integer cityCode,Integer areaCode);
List<AddressComponentStreet> findByProvinceCodeAndCityCodeAndAreaCodeAndNameLike(Integer provinceCode,Integer cityCode,Integer areaCode,String name);
List<AddressComponentStreet> findByCodeIn(Collection<Integer> codes);
List<AddressComponentStreet> findByAreaCodeInAndNameLike(Set<Integer> areaCodeList, String addLikeChar);
}