Four – level linkage address selector address resolver implementation

(Add index to code and name in village table if there is a level 5: village)

  • Data source and reference: gitee.com/modood/Admi…

  • Git Projects: Administrative Responsible-of-China

  • Json, city. Json, area.json, street. Json files from the above address project, import them into the database using Naticat, and add the id field

  • After renaming, four tables are formed: ADDRESS_COMPONent_province, ADDRESS_COMPONent_CITY, ADDRESS_COMPONent_AREA, and ADDRESS_COMPONent_street

  • Link: pan.baidu.com/s/1Rb15oUBz…

  • Extraction code: 7133

Function 1: Address selector

  • Realize the address selection drop-down component function when filling in the address information, as shown in the following figure *

Function 2: Address resolver

  • The function is used to verify and resolve valid addresses through address strings, which is often used in automatic address completion and import scenarios

Environment: mysql navicat, idea, lombok plug-ins, and depend on jdk8, jpa

Yml configuration file:

WMS: Core: Address: provinceUnits: [Province, Autonomous Region, City, SAR]cityUnits: [region, league, autonomous prefecture, city]areaUnits: [County, autonomous County, banner, autonomous banner, city, district, forest district, special Zone]streetUnits[Township, ethnic township, town, sub-district, Sumu, ethnic Sumu, district, city]villageUnits: [Village, community, Administrative district]exclude: [Hong Kong, Macau, Taiwan]Copy the code

JavaBean configuration corresponding to YML configuration:

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

    /** * does not support Hong Kong, Macao and Taiwan */
    private List<String> exclude;
}
Copy the code

Address selector input parameter:

@Data
@Builder
@ApiModel(value = "Address selector entry")
@AllArgsConstructor
@NoArgsConstructor
public class AddressReqDTO {

    * Province level: 1 * City level: 2 * County level: 3 * Town level: 4 *@see AddressComponentEnum* /
    @ApiModelProperty(value = "Search level: Province: 1, city: 2, county: 3, town: 4", required = true)
    @NotNull
    private Integer level;

    @ApiModelProperty(value = "Find keywords")
    private String keyword;

    @ApiModelProperty(value = "Save code")
    private Integer provinceCode;

    @ApiModelProperty(value = "Market code")
    private Integer cityCode;

    @ApiModelProperty(value = "County/District Code")
    private Integer areaCode;

}
Copy the code

Address selector output parameter:

/** * address selector response body */
@Data
@Builder
@ApiModel
@AllArgsConstructor
@NoArgsConstructor
public class AddressRespDTO {

    * Provincial level: 1 * City level 2 * County level 3 * Town level 4 *@see AddressComponentEnum* /
    private Integer level;

    /** ** */
    private List<AddressComponentProvince> provinceList;

    /** ** ** */
    private List<AddressComponentCity> cityList;

    /**
     * 县/区 出参
     */
    private List<AddressComponentArea> areaList;

    /**
     * 乡镇/街道 出参
     */
    private List<AddressComponentStreet> streetList;
}
Copy the code

The address string entered by the user is parsed by the regular expression:

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AddressKeywordDTO {

    / * * / * * province
    private String province;

    / * * * * / city
    private String city;

    / * * / * * county
    private String area;

    / * * * * / town
    private String street;
}
Copy the code

Address resolution input parameter:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AddressResolveReq {

    /** * The user manually enters the address */
    @NotBlank
    private String address;

}
Copy the code

Address resolution parameter:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AddressDTO {

    /** ** ** /
    private Integer provinceCode;

    /** ** Province name */
    private String provinceName;

    /** ** city code */
    private Integer cityCode;

    /** ** city name */
    private String cityName;

    /** ** county code */
    private Integer areaCode;

    /** ** county name */
    private String areaName;

    /** ** town code */
    private Integer streetCode;

    /** ** Town name */
    private String streetName;
}
Copy the code

The controller layer:

@Slf4j
@Api(tags = "Address selector")
@RestController
@RequestMapping("/address")
@RequiredArgsConstructor
public class AddressController {

    private final IAddressService addressService;

    @ApiOperation(value = "Address Query")
    @PostMapping(value = "/addressSelector")
    public ApiEntity<AddressRespDTO> addressSelector(@ApiParam("Request body") @RequestBody @Valid AddressReqDTO addressReqDTO) {
        addressService.validateAddressParam(addressReqDTO);
        return ApiEntity.ok(addressService.addressSelector(addressReqDTO));
    }
    
    @ApiOperation(value = "Address resolution")
    @PostMapping(value = "/addressResolver")
    public ApiEntity<AddressRespDTO> addressSelector(@ApiParam("Request body") @RequestBody @Valid AddressReqDTO addressReqDTO) {
        addressService.validateAddressParam(addressReqDTO);
        returnApiEntity.ok(addressService.addressResolver(addressReqDTO)); }}Copy the code

Service interface layer:

public interface IAddressService {

    /** * address selector input verification *@param addressReqDTO* /
    void validateAddressParam(AddressReqDTO addressReqDTO);
    
    /** * address picker */
    AddressRespDTO addressSelector(AddressReqDTO addressReqDTO);


    / * * * * * * * * * * * * * * * * * * * * address selector relevant methods above, the following address parser relevant methods * * * * * * * * * * * * * * * * * * * * /


    /** * Whether there is an unsupported address (currently not supported) *@param Address address *@return True: exists. False: does not exist
    boolean existExcludeAddress(String address);

    /** * According to the input address string, extract the corresponding provincial, city, county, town administrative unit address *@param Address address *@return* /
    List<Map<String.String>> addressResolution(String address);

    /** * if the length of the address is greater than 2, it will only be truncated@param addressKeywordDTO* /
    AddressKeywordDTO removeAdministrativeUnit(AddressKeywordDTO addressKeywordDTO);

    /** * get the corresponding database record */ by address
    List<AddressDTO> getStreetsByAddress(AddressKeywordDTO addressKeywordDTO);
    
    /** * address resolver *@param Address Indicates the address to be resolved *@return Parsed address */
    AddressDTO addressResolver(String address);
}
Copy the code

Service implementation layer:

@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;
    
    /** * verify the address component query input parameter *@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 newApiException(ErrorCode.DATA_VALIDATE_ERROR); }}/** * address picker */
    @Override
    public AddressRespDTO addressSelector(AddressReqDTO addressReqDTO) {
        AddressRespDTO addressRespDTO = new AddressRespDTO();
        addressRespDTO.setLevel(addressReqDTO.getLevel());

        if (addressReqDTO.getLevel() == AddressComponentEnum.PROVINCE.getLevel()){ // Query the province
            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()){ // Query the cityList<AddressComponentCity> cityList = StringUtils.isBlank(addressReqDTO.getKeyword()) ? cityRepository.findByProvinceCode(addressReqDTO.getProvinceCode()) : cityRepository.findByProvinceCodeAndNameLike(addressReqDTO.getProvinceCode(),QueryUtils.addLikeChar(addressReqDTO.getKey word())); addressRespDTO.setCityList(cityList); }else if (addressReqDTO.getLevel() == AddressComponentEnum.AREA.getLevel()){ // Query the county
            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()){ // Query the township
            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;
    }


/ * * * * * * * * * * * * * * * * * * * * address selector relevant methods above, the following address parser relevant methods * * * * * * * * * * * * * * * * * * * * /


    /** * Whether there is an unsupported address (currently not supported) *@param Address address *@return True: exists. False: does not exist
    @Override
    public boolean existExcludeAddress(String address) {
        List<String> excludeAddressKeys = addressUnitsProperties.getExclude();
        if(! CollectionUtils.isEmpty(excludeAddressKeys)){for (String excludeAddressKey : excludeAddressKeys){
                // Determine whether to start with an unsupported keyword
                if (address.matches("^" + excludeAddressKey + ". *")) {return true; }}}return false;
    }

    /** * According to the input address string, extract the corresponding provincial, city, county, town administrative unit address *@param Address address *@return* /
    public List<Map<String.String>> addressResolution(String address) {
        String regex="(? 
      
       .+? Province |. +? Autonomous region |. +? The city |. +? Special Administrative Region)(? 
       
        .+? Regional |. +? Au |. +? Autonomous prefecture |. +? (city)? 
        
         .+? County |. +? Autonomous county |. +? Flag |. +? The first-hand |. +? The city |. +? District |. +? Forest |. +? (dc)? 
         
          .+? Home |. +? Ethnic townships |. +? Town |. +? Street |. +? Wood |. +? National wood |. +? District |. +? (city)? 
          
           .*?) "
          
         
        
       
      ;
        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;
    }

    /** * if the length of the address is greater than 2, it will only be truncated@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;
    }

    /** * obtain the corresponding database record * from the address@param addressKeywordDTO
     * @return* /
    @Override
    public List<AddressDTO> getStreetsByAddress(AddressKeywordDTO addressKeywordDTO) {
        List<AddressDTO> addressDTOS = new ArrayList<>();

        / / province
        List<AddressComponentProvince> provinceList = provinceRepository.findByNameLike(QueryUtils.addLikeChar(addressKeywordDTO.getProvince()));

        if(! CollectionUtils.isEmpty(provinceList)){/ / the city
            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)){/ / county
                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)){// Data encapsulationstreetList.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); }); }}}}returnaddressDTOS; }}/** * address resolver *@param Address Indicates the address to be resolved *@return Parsed address */
@Override
public AddressDTO addressResolver(String address) {
    // Check Hong Kong, Macao and Taiwan
    if(existExcludeAddress(addressResolveReq.getAddress())){
        log.error("No support for Hong Kong, Macao and Taiwan.");
        throw new ApiException(ErrorCode.ADDRESS_IS_EXCLUDE);
    }

    List<Map<String.String>> table = addressResolution(address);
    if(CollectionUtils.isEmpty(table) || table.size() ! =1){
        log.error("Unable to extract valid sampling address :{}",address);
        throw new ApiException(ErrorCode.ADDRESS_IS_INVALID);
    }

    // Build the address extraction object
    AddressKeywordDTO addressKeywordDTO = AddressKeywordDTO.builder()
            .province(table.get(0).get("province"))/ / province
            .city(table.get(0).get("city"))/ / the city
            .area(table.get(0).get("area"))/ / district/county
            .street(table.get(0).get("street")).build();/ / township and town

    if (StringUtils.isBlank(addressKeywordDTO.getProvince())
            || StringUtils.isBlank(addressKeywordDTO.getCity())
            || StringUtils.isBlank(addressKeywordDTO.getArea())
            || StringUtils.isBlank(addressKeywordDTO.getStreet())){
        log.error("Unable to extract valid sampling address :{}",address);
        throw new ApiException(ErrorCode.ADDRESS_IS_INVALID);
    }

    // Remove the regional administrative unit, get the search keyword into the database to find more accurate
    removeAdministrativeUnit(addressKeywordDTO);

    // Check provincial, city, county and town administrative units to determine whether there is uniqueness and validity
    List<AddressDTO> addressDTOS = getStreetsByAddress(addressKeywordDTO);

    if (CollectionUtils.isEmpty(addressDTOS)){
        log.error("Address does not exist :{}",address);
        throw new ApiException(ErrorCode.ADDRESS_IS_NOT_EXIST);
    }else if(addressDTOS.size() ! =1){
        log.error("Cannot locate unique address: {}",address);
        throw new ApiException(ErrorCode.ADDRESS_IS_NOT_UNIQUE);
    }
    return addressDTOS.get(0);
}
Copy the code

Persistence layer at provincial level:

@Repository
public interface AddressComponentProvinceRepository extends BaseRepository<AddressComponentProvince,Integer> {

    List<AddressComponentProvince> findByNameLike(String name);

}
Copy the code

Durability layer of prefectural level:

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

Durability layer at district and county levels:

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

Durable layer of township and street level:

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