绝大多数项目都需要涉及到地址库,我曾经负责过的电商系统,各个渠道(中间商,包括唯品、华泊渠道 )的地址库数据是不一样,比如相同地址的中文不同,或者地址编码不同,为了能够正常下单,我们建立了地址库并且通过"渠道ID"的方式去映射对应的地址,我们的地址库是TBL地址库,唯品的地址库是VIP地址库,华泊的地址库是HUABO地址库。
tbl_hua_bo_districts是TBL地址库的表名,项目的线上数据库使用的是Mongodb。
huabo_address是华泊HUABO地址库的表名。
为了匹配这些数据库的地址数据,设计抽象地址适配器类AbstractAddressPathInfo 作为地址领域类。
这个类是处理地址数据的精髓,基于最短距离的算法,去计算最匹配的地址。注意,基于规则的算法是不适用的。
import lombok.Data;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author linzihao
* 路径节点抽象类
* 用于比对地址库
*/
@Data
public abstract class AbstractAddressPathInfo {
private AbstractAddressPathInfo parent;
private List<AbstractAddressPathInfo> pathInfoList = null;
/**
* 缓存计算得到的全路径
*/
private String fullPath;
/**
* 对于编码不同的地址库,
* mongodb的id要跟mysql的id保持一致。
*
* @return
*/
public abstract Long getId();
public abstract void setId(Long id);
public abstract String getPlaceName();
public abstract void setPlaceName(String placeName);
public abstract Long getPlaceCode();
public abstract void setPlaceCode(Long placeCode);
public abstract Long getParentCode();
public abstract void setParentCode(Long parentCode);
public synchronized List<AbstractAddressPathInfo> getPath() {
if (this.pathInfoList == null) {
this.pathInfoList = new Vector<>();
AbstractAddressPathInfo p = this;
while (p != null) {
//头部插入
this.pathInfoList.add(0, p);
p = p.getParent();
}
return this.pathInfoList;
}
return this.pathInfoList;
}
public String generateFullPath() {
this.setFullPath(getPath().stream().map(AbstractAddressPathInfo::getPlaceName)
.collect(Collectors.joining()));
return this.getFullPath();
}
public static String toString(AbstractAddressPathInfo abstractAddressPathInfo) {
return abstractAddressPathInfo.getPath().stream()
.map(p -> p.getPlaceName() + ":" + p.getPlaceCode())
.collect(Collectors.joining(","));
}
/**
* 通用距离计算方法。
* 所有地址库比对都用这个算法
*
* @param district
* @param huaboAddress
* @return
*/
public static double calculateDistance(AbstractAddressPathInfo district,
AbstractAddressPathInfo huaboAddress
) {
List<AbstractAddressPathInfo> disList = district.getPath().stream()
.sorted(Comparator.comparing(AbstractAddressPathInfo::getPlaceCode))
.collect(Collectors.toList());
List<AbstractAddressPathInfo> huaboPath = huaboAddress.getPath();
String TAGS[] = new String[]{"省", "市", "区", "镇", "乡", "村", "县", "街道"};
//计算长度,尽可能匹配更长的地址
// double distance = Math.abs(disList.size() - huaboPath.size()) * Math.pow(8, disList.size() + 5);
double factor = 8;
double base = Math.pow(factor, 5);
double distance = Math.abs(disList.size() - huaboPath.size());
//比较最后一个名称
String disLastName = disList.get(disList.size() - 1).getPlaceName();
String huaboLastName = huaboPath.get(huaboPath.size() - 1).getPlaceName();
for (String tag : TAGS) {
disLastName = disLastName.replace(tag, "");
huaboLastName = huaboLastName.replace(tag, "");
}
double lastNameSim = new StrSimilarity(disLastName, huaboLastName).sim();
distance -= lastNameSim * base * Math.pow(factor, disList.size()) * Math.pow(Math.E, -distance);
//计算相同的路径节点数
List<Long> huaboCodes = huaboPath.stream()
.map(AbstractAddressPathInfo::getPlaceCode)
.collect(Collectors.toList());
for (int i = 0; i < disList.size(); i++) {
if (huaboCodes.contains(disList.get(i).getPlaceCode())) {
distance -= base * Math.pow(factor, disList.size() - i);
}
}
//计算地理名字匹配度
for (int i = 0; i < disList.size(); i++) {
String placeName = disList.get(i).getPlaceName();
for (AbstractAddressPathInfo huaboPathInfo : huaboPath) {
boolean flag = false;
for (String tag : TAGS) {
if (placeName.contains(tag) && huaboPathInfo.getPlaceName().contains(tag)) {
double nodeSim = new StrSimilarity(placeName, huaboPathInfo.getPlaceName()).sim();
distance -= nodeSim * base * Math.pow(factor, disList.size() - i);
flag = true;
break;
}
}
if (flag) {
break;
}
}
}
return distance;
}
public static <T extends AbstractAddressPathInfo> Map<Long, T> generatePathInfo(Map<Long, T> allMap) {
Map<Long, List<T>> disPathMap = allMap
.values()
.parallelStream()
.collect(Collectors.groupingByConcurrent(p -> p.getPlaceCode()));
//路径
//Map<Long, DistrictPathInfoAdapter> disPaths = new ConcurrentHashMap<>();
allMap.values().forEach(dis -> {
T p = dis;
while (p != null && p.getParentCode() != null && disPathMap.get(p.getParentCode()) != null) {
System.err.println("1------dis.code=" + dis.getPlaceCode() + ",p.code=" + p.getPlaceCode() + ",p.parentCode=" + p.getParentCode() + "");
T curr = p;
p = disPathMap.get(p.getParentCode()).stream()
//.peek(j->System.err.println(j.getPlaceCode()==null?"j.getValue() is NUll!":""+j.getPlaceCode()))
.filter(i -> !i.getPlaceCode().equals(curr.getPlaceCode()))
.findAny().orElse(null);
curr.setParent(p);
}
});
return allMap;
}
public boolean contains(AbstractAddressPathInfo other) {
List<AbstractAddressPathInfo> otherPath = other.getPath();
List<AbstractAddressPathInfo> thisPath = this.getPath();
if (otherPath.size() < thisPath.size()) {
for (int i = 0; i < otherPath.size(); i++) {
if (!thisPath.get(i).getPlaceCode().equals(otherPath.get(i).getPlaceCode())
&& !thisPath.get(i).getPlaceName().equals(otherPath.get(i).getPlaceName())) {
return false;
}
}
return true;
}
return false;
}
public static void main(String[] args) {
System.err.println(Math.pow(2, 5));
}
}
为各个渠道实现具体的适配器类,实现TBL适配器类。
/**
* @author linzihao
* 路径节点适配器类
* 用于比对地址库数据
*/
public class DistrictPathInfoAdapter extends AbstractAddressPathInfo {
private DistrictsCopy1 districtsCopy1;
public DistrictPathInfoAdapter(DistrictsCopy1 districtsCopy1) {
this.districtsCopy1 = districtsCopy1;
}
public DistrictsCopy1 getDistrictsCopy1() {
return districtsCopy1;
}
public void setDistrictsCopy1(DistrictsCopy1 districtsCopy1) {
this.districtsCopy1 = districtsCopy1;
}
@Override
public Long getId() {
return districtsCopy1.getId();
}
@Override
public void setId(Long id) {
this.districtsCopy1.setId(id);
}
@Override
public String getPlaceName() {
return districtsCopy1.getLabel();
}
@Override
public void setPlaceName(String placeName) {
districtsCopy1.setLabel(placeName);
}
@Override
public Long getPlaceCode() {
return Long.parseLong(districtsCopy1.getValue());
}
@Override
public void setPlaceCode(Long placeCode) {
districtsCopy1.setValue(placeCode + "");
}
@Override
public Long getParentCode() {
//Optional.ofNullable(districtsCopy1.getParentValue()).orElse()
return StringUtils.isEmpty(districtsCopy1.getParentValue()) ? null : Long.parseLong(districtsCopy1.getParentValue());
}
@Override
public void setParentCode(Long parentId) {
districtsCopy1.setParentValue(StringUtils.isEmpty(districtsCopy1.getParentValue()) ? null : districtsCopy1.getParentValue());
}
}
为华泊渠道实现HUABO地址的适配器类。
import com.ligeit.supply.rules.infrastructure.persistence.entity.HuaboAddressCopy1;
/**
* @author linzihao
* 路径节点适配器类
* 用于比对地址库数据
*/
public class HuaboPathInfoAdapter extends AbstractAddressPathInfo {
private HuaboAddressCopy1 huaboAddressCopy1;
public HuaboPathInfoAdapter(HuaboAddressCopy1 huaboAddressCopy1) {
this.huaboAddressCopy1 = huaboAddressCopy1;
}
public HuaboAddressCopy1 getHuaboAddressCopy1() {
return huaboAddressCopy1;
}
public void setHuaboAddressCopy1(HuaboAddressCopy1 huaboAddressCopy1) {
this.huaboAddressCopy1 = huaboAddressCopy1;
}
@Override
public Long getId() {
return huaboAddressCopy1.getId();
}
@Override
public void setId(Long id) {
huaboAddressCopy1.setId(id);
}
@Override
public String getPlaceName() {
return huaboAddressCopy1.getDivisionName();
}
@Override
public void setPlaceName(String placeName) {
huaboAddressCopy1.setDivisionName(placeName);
}
@Override
public Long getPlaceCode() {
return huaboAddressCopy1.getId();
}
@Override
public void setPlaceCode(Long placeCode) {
huaboAddressCopy1.setId(placeCode);
}
@Override
public Long getParentCode() {
return huaboAddressCopy1.getParentId();
}
@Override
public void setParentCode(Long parentId) {
huaboAddressCopy1.setParentId(parentId);
}
}
为唯品渠道实现VIP地址的适配器类。
/**
* @author linzihao
* 路径节点适配器类
* 用于比对地址库数据
*/
public class VipPathInfoAdapter extends AbstractAddressPathInfo {
private VipAddress vopAddress;
public VipPathInfoAdapter(VipAddress vopAddress) {
this.vopAddress = vopAddress;
}
public VipAddress getVopAddress() {
return vopAddress;
}
public void setVopAddress(VipAddress vopAddress) {
this.vopAddress = vopAddress;
}
@Override
public Long getId() {
return vopAddress.getId();
}
@Override
public void setId(Long id) {
vopAddress.setId(id);
}
@Override
public String getPlaceName() {
return vopAddress.getAreaName();
}
@Override
public void setPlaceName(String placeName) {
vopAddress.setAreaName(placeName);
}
@Override
public Long getPlaceCode() {
return Long.parseLong(vopAddress.getAreaCode());
}
@Override
public void setPlaceCode(Long placeCode) {
vopAddress.setAreaCode(placeCode + "");
}
@Override
public Long getParentCode() {
//Optional.ofNullable(districtsCopy1.getParentValue()).orElse()
return StringUtils.isEmpty(vopAddress.getParent()) ? null : Long.parseLong(vopAddress.getParent());
}
@Override
public void setParentCode(Long parentId) {
vopAddress.setParent(StringUtils.isEmpty(vopAddress.getParent()) ? null : vopAddress.getParent());
}
}
计算字符串的相似度算法,主要用于计算地址的名称相似度。
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @author linzihao
* 计算两个字符串的相似度
*/
public class StrSimilarity {
Map<Character, int[]> vectorMap = new HashMap<Character, int[]>();
int[] tempArray = null;
public StrSimilarity(String source, String target) {
for (Character sch : source.toCharArray()) {
if (vectorMap.containsKey(sch)) {
vectorMap.get(sch)[0]++;
}
//用将字符转化为向量
else {
tempArray = new int[2];
tempArray[0] = 1;
tempArray[1] = 0;
vectorMap.put(sch, tempArray);
}
}
for (Character tch : target.toCharArray()) {
if (vectorMap.containsKey(tch)) {
vectorMap.get(tch)[1]++;
}
//用将字符转化为向量
else {
tempArray = new int[2];
tempArray[0] = 0;
tempArray[1] = 1;
vectorMap.put(tch, tempArray);
}
}
}
// 求余弦相似度
public double sim() {
double result = 0;
result = pointMulti(vectorMap) / sqrtMulti(vectorMap);
return result;
}
// 求平方和
private double squares(Map<Character, int[]> paramMap) {
double result1 = 0;
double result2 = 0;
Set<Character> keySet = paramMap.keySet();
for (Character character : keySet) {
int temp[] = paramMap.get(character);
result1 += (temp[0] * temp[0]);
result2 += (temp[1] * temp[1]);
}
return result1 * result2;
}
// 点乘法
private double pointMulti(Map<Character, int[]> paramMap) {
double result = 0;
Set<Character> keySet = paramMap.keySet();
for (Character character : keySet) {
int temp[] = paramMap.get(character);
result += (temp[0] * temp[1]);
}
return result;
}
private double sqrtMulti(Map<Character, int[]> paramMap) {
double result = 0;
result = squares(paramMap);
result = Math.sqrt(result);
return result;
}
public static void main(String[] args) {
String s1 = "我是中国人";
String s3 = "中国人";
String s4 = "我是中国人";
String s2 = "66666";
StrSimilarity t2 = new StrSimilarity(s1, s2);
StrSimilarity t4 = new StrSimilarity(s1, s4);
StrSimilarity t3 = new StrSimilarity(s1, s3);
System.out.println("==相似度===" + t2.sim());
System.out.println("==相似度===" + t4.sim());
System.out.println("==相似度===" + t3.sim());
}
}
开发环境用的是mysql,通过Mybatics框架从mysql生成的地址Entity。
Mybatics框架生成的TBL地址Entity。
import java.io.Serializable;
import java.util.Date;
import javax.annotation.Generated;
public class DistrictsCopy1 implements Serializable {
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private Long id;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private String label;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private String value;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private String parentValue;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private String regionDepth;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private Date createdAt;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private Date updatedAt;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private Long huaboId;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private Long vopId;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private static final long serialVersionUID = 1L;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public Long getId() {
return id;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setId(Long id) {
this.id = id;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public String getLabel() {
return label;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setLabel(String label) {
this.label = label == null ? null : label.trim();
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public String getValue() {
return value;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setValue(String value) {
this.value = value == null ? null : value.trim();
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public String getParentValue() {
return parentValue;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setParentValue(String parentValue) {
this.parentValue = parentValue == null ? null : parentValue.trim();
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public String getRegionDepth() {
return regionDepth;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setRegionDepth(String regionDepth) {
this.regionDepth = regionDepth == null ? null : regionDepth.trim();
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public Date getCreatedAt() {
return createdAt;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public Date getUpdatedAt() {
return updatedAt;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public Long getHuaboId() {
return huaboId;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setHuaboId(Long huaboId) {
this.huaboId = huaboId;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public Long getVopId() {
return vopId;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setVopId(Long vopId) {
this.vopId = vopId;
}
}
Mybatics框架生成的华泊地址Entity。
import java.io.Serializable;
import javax.annotation.Generated;
public class HuaboAddressCopy1 implements Serializable {
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private Long id;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private String divisionName;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private Long parentId;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private String path;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private String fullName;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private Integer divisionlevel;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private static final long serialVersionUID = 1L;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public Long getId() {
return id;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setId(Long id) {
this.id = id;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public String getDivisionName() {
return divisionName;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setDivisionName(String divisionName) {
this.divisionName = divisionName == null ? null : divisionName.trim();
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public Long getParentId() {
return parentId;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setParentId(Long parentId) {
this.parentId = parentId;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public String getPath() {
return path;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setPath(String path) {
this.path = path == null ? null : path.trim();
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public String getFullName() {
return fullName;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setFullName(String fullName) {
this.fullName = fullName == null ? null : fullName.trim();
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public Integer getDivisionlevel() {
return divisionlevel;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setDivisionlevel(Integer divisionlevel) {
this.divisionlevel = divisionlevel;
}
}
Mybatics框架生成的唯品VIP地址Entity。
import java.io.Serializable;
import javax.annotation.Generated;
public class VipAddress implements Serializable {
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private Long id;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private String areaCode;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private String areaName;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private String parent;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private String fullPath;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private static final long serialVersionUID = 1L;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public Long getId() {
return id;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setId(Long id) {
this.id = id;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public String getAreaCode() {
return areaCode;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setAreaCode(String areaCode) {
this.areaCode = areaCode == null ? null : areaCode.trim();
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public String getAreaName() {
return areaName;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setAreaName(String areaName) {
this.areaName = areaName == null ? null : areaName.trim();
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public String getParent() {
return parent;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setParent(String parent) {
this.parent = parent == null ? null : parent.trim();
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public String getFullPath() {
return fullPath;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setFullPath(String fullPath) {
this.fullPath = fullPath == null ? null : fullPath.trim();
}
}
TBL地址库通过huabo_id与HUABO地址库建立映射的服务类实现。
/**
* huabo地址库匹配
*
* @author linzihao
*/
@Service
public class HuaboAddressServiceImpl {
{
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism",
Runtime.getRuntime().availableProcessors() * 3 + "");
}
@Autowired
private DistrictsCopy1Mapper districtsMapper;
@Autowired
private HuaboAddressCopy1Mapper huaboAddressMapper;
@Autowired
private DistrictsMapper oldDistrictMapper;
@Autowired
private MongoTemplate mongoTemplate;
public Map<Long, DistrictPathInfoAdapter> getAllDistricts() {
return districtsMapper
.select(SelectDSLCompleter.allRows())
.parallelStream()
.map(p -> new DistrictPathInfoAdapter(p))
.collect(Collectors.toConcurrentMap(DistrictPathInfoAdapter::getId, Function.identity()));
}
public Map<Long, HuaboPathInfoAdapter> getAllHuaboAddress() {
return huaboAddressMapper
.select(SelectDSLCompleter.allRows())
.parallelStream()
.map(p -> {
HuaboPathInfoAdapter huaboPathInfoAdapter = new HuaboPathInfoAdapter(p);
if (huaboPathInfoAdapter.getParentCode().longValue() == 1L) {
huaboPathInfoAdapter.setParentCode(null);
}
return huaboPathInfoAdapter;
})
.collect(Collectors.toConcurrentMap(HuaboPathInfoAdapter::getId, Function.identity()));
}
public void resetAllDistricts(Map<Long, DistrictsCopy1> allDistrictsCopyMap) {
allDistrictsCopyMap.values().parallelStream().forEach(districtsCopy1 -> {
districtsCopy1.setHuaboId(null);
districtsMapper.updateByPrimaryKey(districtsCopy1);
});
}
private Map<Long, DistrictPathInfoAdapter> updateBatchByValueAndDepth(Map<Long, DistrictPathInfoAdapter> allDistrictsCopyMap
, Map<Long, HuaboPathInfoAdapter> allHubboAddressCopy1Map) {
Map<Long, DistrictPathInfoAdapter> treatedMap = new ConcurrentHashMap<>();
//Map<Long, DistrictsCopy1> allDistrictsCopyMap = getAllDistricts();
Map<Integer, List<HuaboPathInfoAdapter>> huaboAddressCopyMap = allHubboAddressCopy1Map.values().parallelStream()
.collect(Collectors.groupingByConcurrent(p -> p.getHuaboAddressCopy1().getDivisionlevel()));
allDistrictsCopyMap.values().parallelStream().forEach(dis -> {
huaboAddressCopyMap.get(Integer.parseInt(dis.getDistrictsCopy1().getRegionDepth()) + 1)
.parallelStream()
.filter(huabo -> huabo.getId().equals(dis.getPlaceCode()))
.findAny()
.ifPresent(huabo -> {
dis.getDistrictsCopy1().setHuaboId(huabo.getId());
districtsMapper.updateByPrimaryKey(dis.getDistrictsCopy1());
treatedMap.putIfAbsent(dis.getId(), dis);
});
});
//districtsMapper.updateBatch(getAllDistricts().values().stream().collect(Collectors.toList()));
Map<Long, DistrictPathInfoAdapter> unTreatedMap = new ConcurrentHashMap<>();
allDistrictsCopyMap.forEach((k, v) -> {
if (!treatedMap.containsKey(k)) {
unTreatedMap.putIfAbsent(k, v);
}
});
return unTreatedMap;
}
/**
* 计算距离最小值,返回HuaboID
*
* @param districtPath 从当前路径节点开始的所有上层路径节点
* @param huaboAddressCopy1List 所有huabo地址
* @return 距离
*/
private Long getMinDistance(DistrictPathInfoAdapter districtPath, List<HuaboPathInfoAdapter> huaboAddressCopy1List) {
return huaboAddressCopy1List.parallelStream()
.min(Comparator.comparing(huabo -> AbstractAddressPathInfo.calculateDistance(districtPath, huabo)))
.map(huaboAddressCopy1 -> huaboAddressCopy1.getId()).orElse(null);
}
private void updateBatchByPath(Map<Long, DistrictPathInfoAdapter> allDistrictsCopyMap,
Map<Long, HuaboPathInfoAdapter> allHubboAddressCopy1Map,
Map<Long, DistrictPathInfoAdapter> untreatedMap) {
//路径
Map<Long, DistrictPathInfoAdapter> disPathMap = AbstractAddressPathInfo.generatePathInfo(allDistrictsCopyMap);
Map<Long, HuaboPathInfoAdapter> huaboPathMap = AbstractAddressPathInfo.generatePathInfo(allHubboAddressCopy1Map);
//debug2(disPathMap);
//
untreatedMap.values().parallelStream().forEach(dis -> {
//allDistrictsCopyMap.get(dis.getId()).setHuaboId(huaboId);
Long huaboId = getMinDistance(dis, huaboPathMap.values()
.parallelStream().collect(Collectors.toList()));
if (huaboId != null) {
dis.getDistrictsCopy1().setHuaboId(huaboId);
districtsMapper.updateByPrimaryKey(dis.getDistrictsCopy1());
}
});
//districtsMapper.updateBatch(getAllDistricts().values().stream().collect(Collectors.toList()));
}
public void updateBatch() {
Map<Long, DistrictPathInfoAdapter> allDistricts = getAllDistricts();
Map<Long, HuaboPathInfoAdapter> allHuaboAddress = getAllHuaboAddress();
Map<Long, DistrictPathInfoAdapter> unTreatedMap = updateBatchByValueAndDepth(allDistricts, allHuaboAddress);
//测试
//debug(unTreatedMap);
updateBatchByPath(allDistricts, allHuaboAddress, unTreatedMap);
}
public void saveToMongo() {
mongoTemplate.dropCollection(TblHuaBoDistricts.class);
Map<Long, DistrictPathInfoAdapter> allDistricts = getAllDistricts();
allDistricts.values().parallelStream().forEach(districtPathInfo -> {
TblHuaBoDistricts tblHuaBoDistricts = new TblHuaBoDistricts();
tblHuaBoDistricts.setHuaboId(districtPathInfo.getDistrictsCopy1().getHuaboId() + "");
tblHuaBoDistricts.setLabel(districtPathInfo.getDistrictsCopy1().getLabel());
tblHuaBoDistricts.setParentValue(districtPathInfo.getDistrictsCopy1().getParentValue());
tblHuaBoDistricts.setRegionDepth(districtPathInfo.getDistrictsCopy1().getRegionDepth());
tblHuaBoDistricts.setTblId(districtPathInfo.getDistrictsCopy1().getId());
tblHuaBoDistricts.setValue(districtPathInfo.getDistrictsCopy1().getValue());
tblHuaBoDistricts.setVipId(districtPathInfo.getDistrictsCopy1().getVopId() + "");
mongoTemplate.save(tblHuaBoDistricts);
});
}
//
public void saveToMongoNewData() {
mongoTemplate.dropCollection(TblHuaBoDistrictsNewData.class);
List<DistrictsCopy1> districtsCopy1s = districtsMapper
.select(SelectDSLCompleter.allRows())
.parallelStream()
.collect(Collectors.toList());
districtsCopy1s.parallelStream().forEach(districtPathInfo -> {
TblHuaBoDistrictsNewData tblHuaBoDistricts = new TblHuaBoDistrictsNewData();
tblHuaBoDistricts.setHuaboId(districtPathInfo.getHuaboId() + "");
tblHuaBoDistricts.setLabel(districtPathInfo.getLabel());
tblHuaBoDistricts.setParentValue(districtPathInfo.getParentValue());
tblHuaBoDistricts.setRegionDepth(districtPathInfo.getRegionDepth());
tblHuaBoDistricts.setTblId(districtPathInfo.getId());
tblHuaBoDistricts.setVipId(StringUtils.isEmpty(districtPathInfo.getVopId()) ?
null : String.valueOf(districtPathInfo.getVopId()));
tblHuaBoDistricts.setValue(districtPathInfo.getValue());
mongoTemplate.save(tblHuaBoDistricts);
});
}
public void debug(Map<Long, DistrictPathInfoAdapter> unTreatedMap) {
//测试
Path recordPath = Paths.get("C:\\Users\\Administrator\\Desktop\\records.txt");
List<String> recordLines = new ArrayList<>();
for (DistrictPathInfoAdapter value : unTreatedMap.values()) {
recordLines.add("id:" + value.getId()
+ " label:" + value.getPlaceName()
+ " value:" + value.getPlaceCode()
+ " parentValue:" + value.getParentCode()
+ " regionDepth:" + value.getDistrictsCopy1().getRegionDepth()
+ " huaboId:" + value.getDistrictsCopy1().getHuaboId());
}
try {
if (Files.notExists(recordPath)) {
Files.createDirectories(recordPath.getParent());
Files.createFile(recordPath);
}
Files.write(recordPath, recordLines, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
} catch (IOException exception) {
exception.printStackTrace();
}
}
public void debug2(Map<Long, DistrictPathInfoAdapter> disPathMap) {
Path recordPath = Paths.get("../test/pathTest.txt");
try {
if (Files.notExists(recordPath)) {
Files.createDirectories(recordPath.getParent());
Files.createFile(recordPath);
}
Files.write(recordPath, disPathMap.values().parallelStream().map(p -> p.getDistrictsCopy1().toString())
.collect(Collectors.toSet()), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
} catch (IOException exception) {
exception.printStackTrace();
}
}
}
TBL地址库通过vip_id与唯品VIP地址库建立映射的服务类实现。
import com.ligeit.supply.rules.biz.domain.model.address.AbstractAddressPathInfo;
import com.ligeit.supply.rules.biz.domain.model.address.DistrictPathInfoAdapter;
import com.ligeit.supply.rules.biz.domain.model.address.VipPathInfoAdapter;
import com.ligeit.supply.rules.infrastructure.persistence.entity.DistrictsCopy1Tmp;
import com.ligeit.supply.rules.infrastructure.persistence.entity.VipAddress;
import com.ligeit.supply.rules.infrastructure.persistence.entity.mongo.VipDocumentAddress;
import com.ligeit.supply.rules.infrastructure.persistence.mapper.DistrictsCopy1Mapper;
import com.ligeit.supply.rules.infrastructure.persistence.mapper.DistrictsCopy1TmpMapper;
import com.ligeit.supply.rules.infrastructure.persistence.mapper.VipAddressMapper;
import com.vip.osp.sdk.context.InvocationContext;
import com.vip.wpc.ospservice.channel.vo.WpcChannelAddressInfoVO;
import com.vip.wpc.ospservice.channel.vo.WpcChannelSelectAddressVO;
import com.vip.wpc.ospservice.vop.WpcVopOspServiceHelper;
import com.vip.wpc.ospservice.vop.request.WpcAddressSelectRequest;
import org.mybatis.dynamic.sql.delete.DeleteDSLCompleter;
import org.mybatis.dynamic.sql.select.SelectDSLCompleter;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 唯代购地址库匹配
*
* @author linzihao
*/
@Service
public class VipAddressServiceImpl {
{
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", Runtime.getRuntime().availableProcessors() * 5 + "");
}
@Autowired
private DistrictsCopy1Mapper districtsMapper;
@Autowired
private DistrictsCopy1TmpMapper districtsCopy1TmpMapper;
@Autowired
private VipAddressMapper vipAddressMapper;
@Autowired
private MongoTemplate mongoTemplate;
private WpcVopOspServiceHelper.WpcVopOspServiceClient getWebClient() {
WpcVopOspServiceHelper.WpcVopOspServiceClient client = new WpcVopOspServiceHelper.WpcVopOspServiceClient();
InvocationContext invocationContext = InvocationContext.Factory.getInstance();
invocationContext.setAppKey("a7c44686");
invocationContext.setAppSecret("E1C46F81E4A52008402DD6CCEE831CEE");
invocationContext.setAppURL("https://gw.vipapis.com/");
invocationContext.setLanguage("zh");
return client;
}
public WpcChannelSelectAddressVO requestAddress(String areaCode) {
try {
WpcVopOspServiceHelper.WpcVopOspServiceClient client = getWebClient();
WpcAddressSelectRequest request1 = new WpcAddressSelectRequest();
request1.setVopChannelId("a7c44686");
request1.setUserNumber("80015553");
if (areaCode != null && !areaCode.isEmpty()) {
request1.setAreaCode(areaCode);
}
return client.selectAddress(request1);
} catch (com.vip.osp.sdk.exception.OspException e) {
e.printStackTrace();
}
return null;
}
private VipAddress compose(WpcChannelSelectAddressVO vo, String parentAreaCode) {
WpcChannelAddressInfoVO info = vo.getInfo();
VipAddress vopAddress = new VipAddress();
vopAddress.setAreaName(info.getAreaName());
vopAddress.setAreaCode(info.getAreaCode());
vopAddress.setParent(parentAreaCode);
return vopAddress;
}
private void saveAddress(WpcChannelSelectAddressVO vo, String parentAreaCode) {
vipAddressMapper.insert(compose(vo, parentAreaCode));
}
private void viewNode(String parent, String currNode) {
WpcChannelSelectAddressVO vo = requestAddress(currNode);
saveAddress(vo, parent);
List<WpcChannelAddressInfoVO> childList = vo.getChildList();
if (childList != null && !childList.isEmpty()) {
childList.parallelStream().forEach(childAddresss -> {
viewNode(vo.getInfo().getAreaCode(), childAddresss.getAreaCode());
});
}
}
private List<VipAddress> handle(String parent, String currNode) {
List<VipAddress> vopAddressesList = new Vector<>();
WpcChannelSelectAddressVO vo = requestAddress(currNode);
if (vo == null) {
return vopAddressesList;
}
//saveAddress(vo, parent);
vopAddressesList.add(compose(vo, parent));
List<WpcChannelAddressInfoVO> childList = vo.getChildList();
if (childList != null && !childList.isEmpty()) {
childList.parallelStream().forEach(childAddresss -> {
vopAddressesList.addAll(handle(vo.getInfo().getAreaCode(), childAddresss.getAreaCode()));
});
}
System.out.println("return list ok!");
return vopAddressesList;
}
private void fullPath() {
Map<Long, VipPathInfoAdapter> vopPathMap = AbstractAddressPathInfo.generatePathInfo(getAllVopAddress());
vopPathMap.values().parallelStream().forEach(address -> {
VipAddress vopAddress = address.getVopAddress();
vopAddress.setFullPath(address.getPath().stream()
.map(AbstractAddressPathInfo::getPlaceName)
.collect(Collectors.joining(",")));
vipAddressMapper.updateByPrimaryKey(vopAddress);
});
}
/**
* 深度遍历树结构并且保存
*/
public void saveAllAddress() {
vipAddressMapper.delete(DeleteDSLCompleter.allRows());
List<VipAddress> vopAddresses = handle(null, null);
vopAddresses.parallelStream().forEach(vop -> {
vipAddressMapper.insert(vop);
});
fullPath();
}
/**
* 深度遍历树结构并且保存
*/
public void saveAllAddress2() {
vipAddressMapper.delete(DeleteDSLCompleter.allRows());
viewNode(null, null);
fullPath();
}
public Map<Long, DistrictPathInfoAdapter> getAllDistricts() {
return districtsMapper
.select(SelectDSLCompleter.allRows())
.parallelStream()
.map(p -> new DistrictPathInfoAdapter(p))
.collect(Collectors.toConcurrentMap(DistrictPathInfoAdapter::getId, Function.identity()));
}
public Map<Long, VipPathInfoAdapter> getAllVopAddress() {
return vipAddressMapper
.select(SelectDSLCompleter.allRows())
.parallelStream()
.map(p -> new VipPathInfoAdapter(p))
.collect(Collectors.toConcurrentMap(VipPathInfoAdapter::getId, Function.identity()));
}
private void setRelation(Map<Long, DistrictPathInfoAdapter> allDistrictsMap,
Map<Long, VipPathInfoAdapter> allVopAddressMap) {
Map<Long, DistrictPathInfoAdapter> disPathMap = AbstractAddressPathInfo.generatePathInfo(allDistrictsMap);
Map<Long, VipPathInfoAdapter> vopPathMap = AbstractAddressPathInfo.generatePathInfo(allVopAddressMap);
//Map<Long, Map<Long, PathInfo>> disMap = new ConcurrentHashMap<>();
//Map<Long, Map<Long, PathInfo>> vopMap = new ConcurrentHashMap<>();
//第一阶段,先把完全匹配的找出来
//第二阶段,计算距离
disPathMap.values().parallelStream().forEach(dis -> {
Long vipId = vopPathMap.values().parallelStream()
.min(Comparator.comparing(vop -> AbstractAddressPathInfo.calculateDistance(dis, vop)))
.map(VipPathInfoAdapter::getId).orElse(0L);
dis.getDistrictsCopy1().setVopId(vipId);
districtsMapper.updateByPrimaryKey(dis.getDistrictsCopy1());
});
}
public void updateBatch() {
Map<Long, DistrictPathInfoAdapter> allDistricts = getAllDistricts();
Map<Long, VipPathInfoAdapter> allVopAddress = getAllVopAddress();
setRelation(allDistricts, allVopAddress);
}
public void saveToMongoDB() {
mongoTemplate.dropCollection(VipDocumentAddress.class);
Map<Long, VipPathInfoAdapter> allVopAddress = getAllVopAddress();
allVopAddress.values().parallelStream().forEach(p -> {
VipDocumentAddress vipDocumentAddress = new VipDocumentAddress();
vipDocumentAddress.setVid(p.getId());
vipDocumentAddress.setAreaCode(p.getPlaceCode() + "");
vipDocumentAddress.setAreaName(p.getPlaceName());
vipDocumentAddress.setFullPath(p.getVopAddress().getFullPath());
vipDocumentAddress.setParent(p.getVopAddress().getParent());
mongoTemplate.save(vipDocumentAddress);
});
}
public void saveToDB(List<DistrictPathInfoAdapter> toSave) {
List<DistrictsCopy1Tmp> list = districtsCopy1TmpMapper.select(SelectDSLCompleter.allRows());
list.parallelStream().forEach(p -> districtsCopy1TmpMapper.deleteByPrimaryKey(p.getId()));
toSave.stream().forEach(p -> {
DistrictsCopy1Tmp districtsCopy1Tmp = new DistrictsCopy1Tmp();
BeanUtils.copyProperties(p.getDistrictsCopy1(), districtsCopy1Tmp);
districtsCopy1TmpMapper.insert(districtsCopy1Tmp);
});
}
public static void main(String[] args) {
}
}
执行代码建立TBL地址库和各个渠道的映射。
import com.ligeit.supply.rules.biz.external.api.vip.VipAddressExternal;
import com.ligeit.supply.rules.biz.service.impl.HuaboAddressServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
/**
* @author linzihao
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DataRulesApplication.class})
@EnableAutoConfiguration
public class HuaboAddresssTest extends AbstractTestNGSpringContextTests {
{
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", (Runtime.getRuntime().availableProcessors() * 3) + "");
}
@Autowired
private HuaboAddressServiceImpl addressService;
@Autowired
VipAddressExternal vipAddressExternal;
@Test
public void queryState() {
long startTime = System.currentTimeMillis();
addressService.updateBatch();
addressService.saveToMongo();
long endTime = System.currentTimeMillis();
System.out.println("执行时间:" + (endTime - startTime));
}
}
import com.ligeit.supply.rules.biz.external.api.vip.VipAddressExternal;
import com.ligeit.supply.rules.biz.external.api.vip.VipOrderExternal;
import com.ligeit.supply.rules.biz.service.impl.HuaboAddressServiceImpl;
import com.ligeit.supply.rules.biz.service.impl.OrderServiceImpl;
import com.ligeit.supply.rules.biz.service.impl.VipAddressMatcherServiceImpl;
import com.ligeit.supply.rules.biz.service.impl.VipAddressServiceImpl;
import com.ligeit.supply.rules.infrastructure.persistence.mapper.DistrictsCopy1TmpMapper;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
/**
* @author linzihao
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DataRulesApplication.class})
@EnableAutoConfiguration
public class VipAddressTest extends AbstractTestNGSpringContextTests {
{
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", (Runtime.getRuntime().availableProcessors() * 5) + "");
}
@Autowired
VipAddressServiceImpl vipAddressService;
@Autowired
VipApiServiceTest vopApiService;
@Autowired
VipAddressExternal vipAddressExternal;
@Autowired
VipAddressMatcherServiceImpl vipAddressMatcherService;
@Autowired
VipOrderExternal vipOrderExternal;
@Autowired
HuaboAddressServiceImpl tblAddressService;
@Autowired
DistrictsCopy1TmpMapper districtsCopy1TmpMapper;
@Autowired
OrderServiceImpl orderService;
@Test
public void queryState() {
long round1 = 0L;
long round2 = 0L;
long round3 = 0L;
long round4 = 0L;
long startTime = System.currentTimeMillis();
//vipAddressService.saveAllAddress();
long endTime = System.currentTimeMillis();
round1 = endTime - startTime;
startTime = System.currentTimeMillis();
vipAddressService.saveAllAddress2();
endTime = System.currentTimeMillis();
round2 = endTime - startTime;
startTime = System.currentTimeMillis();
vipAddressService.updateBatch();
endTime = System.currentTimeMillis();
round3 = endTime - startTime;
System.out.println("saveAllAddress1执行时间:" + (round1 / 1000) + "秒");
System.out.println("saveAllAddress2执行时间:" + (round2 / 1000) + "秒");
System.err.println("比较保存到数据库的算法快慢:" + (round1 > round2 ? "round1" : "round2"));
System.out.println("绑定地址库关系的时间" + (round3 / 1000) + "秒");//14072秒
startTime = System.currentTimeMillis();
vipAddressService.saveToMongoDB();
endTime = System.currentTimeMillis();
round4 = endTime - startTime;
System.out.println("插入mongodb的时间" + (round4 / 1000) + "秒");
}
}