领域驱动DDD之供应链多渠道(华泊、唯品、京东等等)地址库匹配下单地址校验下单流程

绝大多数项目都需要涉及到地址库,我曾经负责过的电商系统,各个渠道(中间商,包括唯品、华泊渠道 )的地址库数据是不一样,比如相同地址的中文不同,或者地址编码不同,为了能够正常下单,我们建立了地址库并且通过"渠道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) + "秒");

    }

   
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值