引言:数据孤岛的代价

数字化转型后,企业有了多个系统,但如果数据不通:

  • 同一个SKU,各系统编码不一样
  • 同一个客户,各系统信息不一致
  • 想做数据分析,要从多个系统导出再合并

数据治理的目标:建立统一的数据标准,让数据在各系统间自由流转。


一、主数据管理(MDM)

1.1 什么是主数据

主数据(Master Data):企业核心业务实体的基础数据,具有:

  • 跨系统共享
  • 相对稳定
  • 唯一标识

跨境电商的核心主数据

主数据说明使用系统
商品主数据SKU信息OMS、WMS、ERP
供应商主数据供应商信息ERP、WMS
客户主数据买家信息OMS、CRM
仓库主数据仓库、库位信息WMS、OMS
承运商主数据物流商信息TMS、OMS

1.2 商品主数据设计

-- 商品主数据表
CREATE TABLE t_sku_master (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    sku_id VARCHAR(32) NOT NULL COMMENT 'SKU编码(唯一)',
    sku_name VARCHAR(256) NOT NULL COMMENT 'SKU名称',
    sku_name_en VARCHAR(256) COMMENT '英文名称',

    -- 分类信息
    category_id VARCHAR(32) COMMENT '分类ID',
    category_name VARCHAR(128) COMMENT '分类名称',
    brand VARCHAR(64) COMMENT '品牌',

    -- 规格信息
    spec VARCHAR(256) COMMENT '规格描述',
    color VARCHAR(32) COMMENT '颜色',
    size VARCHAR(32) COMMENT '尺寸',

    -- 物理属性
    weight DECIMAL(10,2) COMMENT '重量(g)',
    length DECIMAL(10,2) COMMENT '长(cm)',
    width DECIMAL(10,2) COMMENT '宽(cm)',
    height DECIMAL(10,2) COMMENT '高(cm)',

    -- 条码
    barcode VARCHAR(64) COMMENT '商品条码',
    upc VARCHAR(32) COMMENT 'UPC码',
    ean VARCHAR(32) COMMENT 'EAN码',

    -- 采购信息
    default_supplier_id VARCHAR(32) COMMENT '默认供应商',
    purchase_price DECIMAL(12,4) COMMENT '采购价',
    moq INT COMMENT '最小起订量',

    -- 销售信息
    retail_price DECIMAL(12,2) COMMENT '零售价',
    currency VARCHAR(8) DEFAULT 'USD' COMMENT '币种',

    -- 库存参数
    safety_stock INT DEFAULT 0 COMMENT '安全库存',
    lead_time_days INT DEFAULT 7 COMMENT '采购周期(天)',

    -- 状态
    status VARCHAR(16) DEFAULT 'ACTIVE' COMMENT '状态',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    UNIQUE KEY uk_sku_id (sku_id),
    KEY idx_barcode (barcode),
    KEY idx_category (category_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品主数据表';

-- 渠道SKU映射表
CREATE TABLE t_sku_channel_mapping (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    sku_id VARCHAR(32) NOT NULL COMMENT '内部SKU编码',
    channel VARCHAR(32) NOT NULL COMMENT '渠道',
    channel_sku_id VARCHAR(64) NOT NULL COMMENT '渠道SKU编码',
    channel_sku_name VARCHAR(256) COMMENT '渠道SKU名称',
    asin VARCHAR(32) COMMENT 'Amazon ASIN',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,

    UNIQUE KEY uk_channel_sku (channel, channel_sku_id),
    KEY idx_sku_id (sku_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='渠道SKU映射表';

1.3 供应商主数据设计

-- 供应商主数据表
CREATE TABLE t_supplier_master (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    supplier_id VARCHAR(32) NOT NULL COMMENT '供应商编码',
    supplier_name VARCHAR(128) NOT NULL COMMENT '供应商名称',
    supplier_type VARCHAR(32) COMMENT '类型:MANUFACTURER/TRADER',

    -- 联系信息
    contact_name VARCHAR(64) COMMENT '联系人',
    contact_phone VARCHAR(32) COMMENT '联系电话',
    contact_email VARCHAR(128) COMMENT '邮箱',
    address VARCHAR(256) COMMENT '地址',

    -- 结算信息
    payment_terms VARCHAR(64) COMMENT '账期:COD/NET30/NET60',
    bank_name VARCHAR(128) COMMENT '开户行',
    bank_account VARCHAR(64) COMMENT '银行账号',
    tax_id VARCHAR(32) COMMENT '税号',

    -- 评级
    rating VARCHAR(8) DEFAULT 'B' COMMENT '评级:A/B/C/D',
    cooperation_years INT DEFAULT 0 COMMENT '合作年限',

    -- 状态
    status VARCHAR(16) DEFAULT 'ACTIVE',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,

    UNIQUE KEY uk_supplier_id (supplier_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商主数据表';

1.4 主数据管理服务

@Service
public class MasterDataService {

    /**
     * 创建SKU主数据
     */
    @Transactional
    public SkuMaster createSku(SkuMasterRequest request) {
        // 1. 生成SKU编码
        String skuId = generateSkuId(request.getCategoryId());

        // 2. 校验唯一性
        if (skuMasterRepository.existsBySkuId(skuId)) {
            throw new DuplicateSkuException(skuId);
        }

        // 3. 创建主数据
        SkuMaster sku = new SkuMaster();
        BeanUtils.copyProperties(request, sku);
        sku.setSkuId(skuId);
        sku.setStatus("ACTIVE");
        skuMasterRepository.save(sku);

        // 4. 同步到各系统
        syncToDownstreamSystems(sku);

        return sku;
    }

    /**
     * 同步到下游系统
     */
    private void syncToDownstreamSystems(SkuMaster sku) {
        // 发布主数据变更事件
        MasterDataChangeEvent event = new MasterDataChangeEvent();
        event.setDataType("SKU");
        event.setDataId(sku.getSkuId());
        event.setChangeType("CREATE");
        event.setData(sku);

        eventPublisher.publish("master-data-change", event);
    }

    /**
     * 生成SKU编码
     * 规则:分类前缀 + 6位序号
     * 示例:EL000001(电子类)
     */
    private String generateSkuId(String categoryId) {
        String prefix = getCategoryPrefix(categoryId);
        int seq = sequenceService.nextVal("SKU_SEQ");
        return prefix + String.format("%06d", seq);
    }
}

二、数据标准化

2.1 编码规范

SKU编码规范

规则说明示例
长度8-12位EL000001
前缀分类标识EL=电子, CL=服装
序号6位数字000001
唯一性全局唯一-

订单号编码规范

规则说明示例
长度16-20位SO20240129000001
前缀业务类型SO=销售订单, PO=采购订单
日期8位日期20240129
序号6位序号000001
@Service
public class CodeGeneratorService {

    /**
     * 生成订单号
     */
    public String generateOrderNo(String prefix) {
        String date = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
        int seq = sequenceService.nextVal(prefix + "_" + date);
        return prefix + date + String.format("%06d", seq);
    }

    /**
     * 生成SKU编码
     */
    public String generateSkuCode(String categoryPrefix) {
        int seq = sequenceService.nextVal("SKU_SEQ");
        return categoryPrefix + String.format("%06d", seq);
    }
}

2.2 数据字典

-- 数据字典表
CREATE TABLE t_data_dictionary (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    dict_type VARCHAR(64) NOT NULL COMMENT '字典类型',
    dict_code VARCHAR(32) NOT NULL COMMENT '字典编码',
    dict_name VARCHAR(128) NOT NULL COMMENT '字典名称',
    dict_name_en VARCHAR(128) COMMENT '英文名称',
    parent_code VARCHAR(32) COMMENT '父级编码',
    sort_order INT DEFAULT 0 COMMENT '排序',
    status VARCHAR(16) DEFAULT 'ACTIVE',
    remark VARCHAR(256) COMMENT '备注',

    UNIQUE KEY uk_type_code (dict_type, dict_code),
    KEY idx_parent (parent_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据字典表';

-- 示例数据
INSERT INTO t_data_dictionary (dict_type, dict_code, dict_name, dict_name_en) VALUES
-- 订单状态
('ORDER_STATUS', 'PENDING', '待处理', 'Pending'),
('ORDER_STATUS', 'PROCESSING', '处理中', 'Processing'),
('ORDER_STATUS', 'SHIPPED', '已发货', 'Shipped'),
('ORDER_STATUS', 'COMPLETED', '已完成', 'Completed'),
('ORDER_STATUS', 'CANCELLED', '已取消', 'Cancelled'),

-- 渠道
('CHANNEL', 'AMAZON', '亚马逊', 'Amazon'),
('CHANNEL', 'EBAY', 'eBay', 'eBay'),
('CHANNEL', 'SHOPIFY', 'Shopify', 'Shopify'),

-- 仓库类型
('WAREHOUSE_TYPE', 'DOMESTIC', '国内仓', 'Domestic'),
('WAREHOUSE_TYPE', 'OVERSEAS', '海外仓', 'Overseas'),
('WAREHOUSE_TYPE', 'FBA', 'FBA仓', 'FBA');

2.3 接口规范

统一响应格式

@Data
public class ApiResponse<T> {
    private int code;           // 状态码:0=成功,其他=失败
    private String message;     // 消息
    private T data;             // 数据
    private long timestamp;     // 时间戳

    public static <T> ApiResponse<T> success(T data) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setCode(0);
        response.setMessage("success");
        response.setData(data);
        response.setTimestamp(System.currentTimeMillis());
        return response;
    }

    public static <T> ApiResponse<T> error(int code, String message) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setCode(code);
        response.setMessage(message);
        response.setTimestamp(System.currentTimeMillis());
        return response;
    }
}

错误码规范

错误码范围说明
0成功
10000-19999通用错误
20000-29999订单相关错误
30000-39999库存相关错误
40000-49999财务相关错误

三、数据同步策略

3.1 同步模式

模式实时性复杂度适用场景
同步调用实时强一致性要求
消息队列秒级最终一致性
定时同步分钟级批量数据
CDC秒级数据库同步

3.2 主数据同步

/**
 * 主数据同步服务
 * 主数据变更时,同步到所有下游系统
 */
@Service
public class MasterDataSyncService {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 发布主数据变更事件
     */
    public void publishChange(String dataType, String dataId, String changeType, Object data) {
        MasterDataChangeEvent event = new MasterDataChangeEvent();
        event.setEventId(UUID.randomUUID().toString());
        event.setDataType(dataType);
        event.setDataId(dataId);
        event.setChangeType(changeType);
        event.setData(data);
        event.setTimestamp(System.currentTimeMillis());

        rocketMQTemplate.convertAndSend("master-data-change", event);
    }
}

/**
 * OMS系统消费主数据变更
 */
@Component
@RocketMQMessageListener(topic = "master-data-change", consumerGroup = "oms-consumer")
public class OmsMasterDataConsumer implements RocketMQListener<MasterDataChangeEvent> {

    @Override
    public void onMessage(MasterDataChangeEvent event) {
        switch (event.getDataType()) {
            case "SKU":
                syncSkuToOms(event);
                break;
            case "WAREHOUSE":
                syncWarehouseToOms(event);
                break;
        }
    }

    private void syncSkuToOms(MasterDataChangeEvent event) {
        SkuMaster sku = JSON.parseObject(JSON.toJSONString(event.getData()), SkuMaster.class);
        // 同步到OMS本地表
        omsSkuRepository.saveOrUpdate(sku);
    }
}

3.3 交易数据同步

/**
 * 订单数据同步
 * OMS -> ERP(财务核算)
 */
@Service
public class OrderDataSyncService {

    /**
     * 订单完成时,同步到ERP
     */
    @EventListener
    public void onOrderCompleted(OrderCompletedEvent event) {
        Order order = event.getOrder();

        // 构建ERP销售单
        ErpSalesOrder erpOrder = new ErpSalesOrder();
        erpOrder.setOrderNo(order.getOrderId());
        erpOrder.setOrderDate(order.getOrderTime());
        erpOrder.setCustomerId(order.getBuyerId());
        erpOrder.setTotalAmount(order.getTotalAmount());
        erpOrder.setCurrency(order.getCurrency());

        // 同步到ERP
        erpClient.createSalesOrder(erpOrder);
    }
}

四、数据质量监控

4.1 数据质量维度

维度说明检查方法
完整性必填字段是否有值非空检查
准确性数据是否正确格式校验、范围校验
一致性各系统数据是否一致对账
及时性数据是否及时更新时效检查
唯一性是否有重复数据去重检查

4.2 数据质量检查

@Service
public class DataQualityService {

    /**
     * SKU数据质量检查
     */
    @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点
    public void checkSkuDataQuality() {
        List<DataQualityIssue> issues = new ArrayList<>();

        // 1. 完整性检查
        List<SkuMaster> incompleteSkus = skuMasterRepository.findByWeightIsNull();
        for (SkuMaster sku : incompleteSkus) {
            issues.add(new DataQualityIssue("SKU", sku.getSkuId(), "COMPLETENESS", "重量为空"));
        }

        // 2. 准确性检查
        List<SkuMaster> invalidSkus = skuMasterRepository.findByWeightLessThan(0);
        for (SkuMaster sku : invalidSkus) {
            issues.add(new DataQualityIssue("SKU", sku.getSkuId(), "ACCURACY", "重量为负数"));
        }

        // 3. 一致性检查(与WMS对比)
        List<SkuMaster> allSkus = skuMasterRepository.findAll();
        for (SkuMaster sku : allSkus) {
            WmsSku wmsSku = wmsClient.getSku(sku.getSkuId());
            if (wmsSku != null && !sku.getSkuName().equals(wmsSku.getSkuName())) {
                issues.add(new DataQualityIssue("SKU", sku.getSkuId(), "CONSISTENCY", "名称与WMS不一致"));
            }
        }

        // 4. 保存问题记录
        dataQualityIssueRepository.saveAll(issues);

        // 5. 告警
        if (!issues.isEmpty()) {
            alertService.send("数据质量问题: " + issues.size() + "条");
        }
    }
}

4.3 数据对账

@Service
public class DataReconciliationService {

    /**
     * 库存对账:OMS vs WMS
     */
    @Scheduled(cron = "0 0 3 * * ?")
    public void reconcileInventory() {
        List<ReconciliationResult> results = new ArrayList<>();

        List<String> skuIds = skuMasterRepository.findAllSkuIds();
        for (String skuId : skuIds) {
            // OMS可售库存
            int omsQty = omsInventoryService.getAvailableQty(skuId);
            // WMS实物库存
            int wmsQty = wmsClient.getTotalQty(skuId);

            if (omsQty != wmsQty) {
                ReconciliationResult result = new ReconciliationResult();
                result.setDataType("INVENTORY");
                result.setDataId(skuId);
                result.setSourceValue(String.valueOf(omsQty));
                result.setTargetValue(String.valueOf(wmsQty));
                result.setDifference(omsQty - wmsQty);
                results.add(result);
            }
        }

        // 保存对账结果
        reconciliationResultRepository.saveAll(results);

        // 差异告警
        List<ReconciliationResult> significantDiffs = results.stream()
            .filter(r -> Math.abs(r.getDifference()) > 10)
            .collect(Collectors.toList());

        if (!significantDiffs.isEmpty()) {
            alertService.send("库存对账差异: " + significantDiffs.size() + "个SKU");
        }
    }
}

五、数据治理组织

5.1 角色与职责

角色职责
数据Owner业务部门,负责数据定义和质量
数据StewardIT部门,负责数据管理和维护
数据Analyst分析师,负责数据分析和报告

5.2 数据治理流程

数据需求 ──> 数据建模 ──> 数据标准 ──> 数据开发 ──> 数据运维
    │           │           │           │           │
    ▼           ▼           ▼           ▼           ▼
 业务提出    设计模型    制定规范    开发实现    监控维护

六、总结

6.1 核心要点

  1. 主数据管理:统一管理SKU、供应商、客户等核心数据
  2. 数据标准化:统一编码规范、数据字典、接口规范
  3. 数据同步:通过消息队列实现各系统数据同步
  4. 数据质量:建立数据质量检查和对账机制

6.2 实施建议

  • 先梳理核心主数据(SKU、供应商)
  • 建立统一的编码规范
  • 实现主数据同步机制
  • 建立数据质量监控

系列文章导航

本文是《跨境电商数字化转型指南》系列的第6篇

  • 01-05. 战略篇+技术选型
  • 06. 数据治理基础(本文)
  • 07. 自研还是采购?