引言:数据孤岛的代价
数字化转型后,企业有了多个系统,但如果数据不通:
- 同一个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 | 业务部门,负责数据定义和质量 |
| 数据Steward | IT部门,负责数据管理和维护 |
| 数据Analyst | 分析师,负责数据分析和报告 |
5.2 数据治理流程
数据需求 ──> 数据建模 ──> 数据标准 ──> 数据开发 ──> 数据运维
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
业务提出 设计模型 制定规范 开发实现 监控维护
六、总结
6.1 核心要点
- 主数据管理:统一管理SKU、供应商、客户等核心数据
- 数据标准化:统一编码规范、数据字典、接口规范
- 数据同步:通过消息队列实现各系统数据同步
- 数据质量:建立数据质量检查和对账机制
6.2 实施建议
- 先梳理核心主数据(SKU、供应商)
- 建立统一的编码规范
- 实现主数据同步机制
- 建立数据质量监控
系列文章导航
本文是《跨境电商数字化转型指南》系列的第6篇
- 01-05. 战略篇+技术选型
- 06. 数据治理基础(本文)
- 07. 自研还是采购?