bash
C:\Users\13032>docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0b5e8081298a mysql:8.0.41 "docker-entrypoint.s..." 5 hours ago Exited (255) 2 minutes ago 0.0.0.0:3306->3306/tcp, 33060/tcp my-mysql
e7d9691151ab qdrant/qdrant:v1.16 "./entrypoint.sh" 30 hours ago Up 2 minutes 0.0.0.0:6333-6334->6333-6334/tcp, [::]:6333-6334->6333-6334/tcp qdrant
ddcab439807c kibana:8.19.10 "/bin/tini -- /usr/l..." 2 days ago Up 2 minutes 0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp kibana
b85631b340d8 elasticsearch:8.19.10 "/bin/tini -- /usr/l..." 2 days ago Up 2 minutes 0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp, 0.0.0.0:9300->9300/tcp, [::]:9300->9300/tcp elasticsearch
C:\Users\13032>docker restart my-mysql
my-mysql
C:\Users\13032>docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0b5e8081298a mysql:8.0.41 "docker-entrypoint.s..." 5 hours ago Up 6 seconds 0.0.0.0:3306->3306/tcp, [::]:3306->3306/tcp my-mysql
e7d9691151ab qdrant/qdrant:v1.16 "./entrypoint.sh" 30 hours ago Up 2 minutes 0.0.0.0:6333-6334->6333-6334/tcp, [::]:6333-6334->6333-6334/tcp qdrant
ddcab439807c kibana:8.19.10 "/bin/tini -- /usr/l..." 2 days ago Up 2 minutes 0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp kibana
b85631b340d8 elasticsearch:8.19.10 "/bin/tini -- /usr/l..." 2 days ago Up 2 minutes 0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp, 0.0.0.0:9300->9300/tcp, [::]:9300->9300/tcp elasticsearch
C:\Users\13032>docker run -d --name bge-large-zh-cpu -p 8080:80 -v "E:/笔记2026项目/项目一_问数/day/资料/1_windows_docker安装/embedding/bge-large-zh-v1.5":/models/bge-large-zh-v1.5 ghcr.io/huggingface/text-embeddings-inference:cpu-latest --model-id /models/bge-large-zh-v1.5 --auto-truncate
b935afed7fa41f1dd919cf65c80c64bfa3eaa96129a35a9ad6da80c47c9f8a1f
C:\Users\13032>docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b935afed7fa4 ghcr.io/huggingface/text-embeddings-inference:cpu-latest "text-embeddings-rou..." 25 seconds ago Up 24 seconds 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp bge-large-zh-cpu
0b5e8081298a mysql:8.0.41 "docker-entrypoint.s..." 5 hours ago Up 20 minutes 0.0.0.0:3306->3306/tcp, [::]:3306->3306/tcp my-mysql
e7d9691151ab qdrant/qdrant:v1.16 "./entrypoint.sh" 30 hours ago Up 23 minutes 0.0.0.0:6333-6334->6333-6334/tcp, [::]:6333-6334->6333-6334/tcp qdrant
ddcab439807c kibana:8.19.10 "/bin/tini -- /usr/l..." 2 days ago Up 23 minutes 0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp kibana
b85631b340d8 elasticsearch:8.19.10 "/bin/tini -- /usr/l..." 2 days ago Up 23 minutes 0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp, 0.0.0.0:9300->9300/tcp, [::]:9300->9300/tcp elasticsearch
C:\Users\13032>
一,meta_config_test.yaml
bash
tables: #表格信息
- name: dim_region #真实的表名
role: dim # 表的角色 或者类型
description: 地区维度表,用于描述订单发生的地理区域信息 # 这个表 对应的业务含义的说明
columns:
- name: region_id # 真实字段的名字
role: primary_key # 字段的角色 维度(只要是维度表中的),度量 (小数 类的数值)
description: 地区唯一标识 # 字段的 业务含义
alias: [地区ID,区域ID] # 字段同义词 别名
sync: false # 该字段取值是否需要同步到 ES 建立 全文索引
- name: province # 真实字段的名字
role: dimension # 字段的角色 维度(只要是维度表中的),度量 (小数 类的数值)
description: 订单所属省份名称 # 字段的 业务含义
alias: [ 省份,省,所在身份 ] # 字段同义词 别名
sync: true # 该字段取值是否需要同步到 ES 建立 全文索引
- name: region_name # 真实字段的名字
role: dimension # 字段的角色 维度(只要是维度表中的),度量 (小数 类的数值)
description: 订单所属的大区名称,如华东,华南等 # 字段的 业务含义
alias: [ 地区,区域,大区 ] # 字段同义词 别名
sync: true # 该字段取值是否需要同步到 ES 建立 全文索引
- name: country # 真实字段的名字
role: dimension # 字段的角色 维度(只要是维度表中的),度量 (小数 类的数值)
description: 地区所属国家的名称 # 字段的 业务含义
alias: [ 国家,国家名称 ] # 字段同义词 别名
sync: true # 该字段取值是否需要同步到 ES 建立 全文索引
# ==============================================================
metrics: # 指标信息
- name: GMV # 指标名称,如, GMV AOV
description: 全称Gross Merchandise value,表示所有订单的成交金额总和 # 指标的业务含义描述 与 计算口径说明
relevant_columns: # 指标相关的字段 ,这个指标所需要的字段
- fact_order.order_amount
alias: [成交总额,订单总额] # 指标同义词,这个指标的别名 其他叫法
# 把所有的订单金额 求和
# -------------------================================================================
- name: AOV # 指标名称,如, GMV AOV
description: 全称Average Order value,表示所有订单的成交金额总的平均值 # 指标的业务含义描述 与 计算口径说明
relevant_columns: # 指标相关的字段 ,这个指标所需要的字段
# - fact_order.order_quantity
# 是上面的 还是下面的 ????
- fact_order.order_amount
- fact_order.order_id
alias: [ 平均单价,平均订单金额 ] # 指标同义词,这个指标的别名 其他叫法
# 我是这样理解的 :把所有的订单 金额 求和 ,然后除以 订单数量 ,这样的理解方式 合不合理呢 ????
1_mysql_demo.py:
python
# mysql_demo.py
import json
from dataclasses import dataclass
from typing import Optional, List, Dict, Any
import mysql.connector
from mysql.connector import Error
import uuid
# ========== 数据模型定义 ==========
@dataclass
class ColumnConfig:
name: str
role: str
description: str
alias: List[str]
sync: bool
def to_dict(self):
return {
"name": self.name,
"role": self.role,
"description": self.description,
"alias": self.alias,
"sync": self.sync
}
@classmethod
def from_dict(cls, data):
return cls(
name=data["name"],
role=data["role"],
description=data["description"],
alias=data.get("alias", []),
sync=data.get("sync", False)
)
@dataclass
class TableConfig:
name: str
role: str
description: str
columns: List[ColumnConfig]
def to_dict(self):
return {
"name": self.name,
"role": self.role,
"description": self.description,
"columns": [col.to_dict() for col in self.columns]
}
@classmethod
def from_dict(cls, data):
return cls(
name=data["name"],
role=data["role"],
description=data["description"],
columns=[ColumnConfig.from_dict(col) for col in data.get("columns", [])]
)
@dataclass
class MetricConfig:
name: str
description: str
relevant_columns: List[str]
alias: List[str]
def to_dict(self):
return {
"name": self.name,
"description": self.description,
"relevant_columns": self.relevant_columns,
"alias": self.alias
}
@classmethod
def from_dict(cls, data):
return cls(
name=data["name"],
description=data["description"],
relevant_columns=data.get("relevant_columns", []),
alias=data.get("alias", [])
)
@dataclass
class MetaConfig:
tables: Optional[List[TableConfig]] = None
metrics: Optional[List[MetricConfig]] = None
def to_dict(self):
result = {}
if self.tables:
result["tables"] = [table.to_dict() for table in self.tables]
if self.metrics:
result["metrics"] = [metric.to_dict() for metric in self.metrics]
return result
@classmethod
def from_dict(cls, data):
tables = None
metrics = None
if "tables" in data:
tables = [TableConfig.from_dict(table) for table in data["tables"]]
if "metrics" in data:
metrics = [MetricConfig.from_dict(metric) for metric in data["metrics"]]
return cls(tables=tables, metrics=metrics)
# ========== MySQL 仓储实现 ==========
class MySQLMetaRepository:
def __init__(self, host: str, port: int, user: str, password: str, database: str):
self.connection_params = {
'host': host,
'port': port,
'user': user,
'password': password,
'database': database
}
self._init_tables()
def _get_connection(self):
return mysql.connector.connect(**self.connection_params)
def _init_tables(self):
"""初始化元数据表"""
conn = self._get_connection()
cursor = conn.cursor()
# 创建元数据主表
cursor.execute("""
CREATE TABLE IF NOT EXISTS meta_config (
id VARCHAR(255) PRIMARY KEY,
config_data JSON NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
""")
# 创建表名索引表(用于快速搜索)
cursor.execute("""
CREATE TABLE IF NOT EXISTS meta_table_index (
id VARCHAR(255) PRIMARY KEY,
table_name VARCHAR(255) NOT NULL,
meta_config_id VARCHAR(255) NOT NULL,
FOREIGN KEY (meta_config_id) REFERENCES meta_config(id) ON DELETE CASCADE,
INDEX idx_table_name (table_name)
)
""")
# 创建指标名索引表
cursor.execute("""
CREATE TABLE IF NOT EXISTS meta_metric_index (
id VARCHAR(255) PRIMARY KEY,
metric_name VARCHAR(255) NOT NULL,
meta_config_id VARCHAR(255) NOT NULL,
FOREIGN KEY (meta_config_id) REFERENCES meta_config(id) ON DELETE CASCADE,
INDEX idx_metric_name (metric_name)
)
""")
conn.commit()
cursor.close()
conn.close()
print("✅ MySQL表初始化完成")
def _generate_id(self) -> str:
return str(uuid.uuid4())
# ========== CRUD 操作 ==========
def create(self, entity: MetaConfig) -> str:
"""创建元数据配置,返回ID"""
entity_id = self._generate_id()
config_data = json.dumps(entity.to_dict(), ensure_ascii=False)
conn = self._get_connection()
cursor = conn.cursor()
try:
# 插入主数据
cursor.execute(
"INSERT INTO meta_config (id, config_data) VALUES (%s, %s)",
(entity_id, config_data)
)
# 插入表索引
if entity.tables:
for table in entity.tables:
cursor.execute(
"INSERT INTO meta_table_index (id, table_name, meta_config_id) VALUES (%s, %s, %s)",
(self._generate_id(), table.name, entity_id)
)
# 插入指标索引
if entity.metrics:
for metric in entity.metrics:
cursor.execute(
"INSERT INTO meta_metric_index (id, metric_name, meta_config_id) VALUES (%s, %s, %s)",
(self._generate_id(), metric.name, entity_id)
)
conn.commit()
print(f"✅ MySQL创建成功,ID: {entity_id}")
return entity_id
except Error as e:
conn.rollback()
print(f"❌ MySQL创建失败: {e}")
raise e
finally:
cursor.close()
conn.close()
def get_by_id(self, id: str) -> Optional[MetaConfig]:
"""根据ID获取配置"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute("SELECT config_data FROM meta_config WHERE id = %s", (id,))
result = cursor.fetchone()
cursor.close()
conn.close()
if result:
data = json.loads(result[0])
return MetaConfig.from_dict(data)
return None
def get_all(self) -> List[MetaConfig]:
"""获取所有配置"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute("SELECT config_data FROM meta_config")
results = cursor.fetchall()
cursor.close()
conn.close()
return [MetaConfig.from_dict(json.loads(row[0])) for row in results]
def update(self, id: str, entity: MetaConfig) -> bool:
"""更新配置"""
conn = self._get_connection()
cursor = conn.cursor()
try:
# 检查是否存在
cursor.execute("SELECT id FROM meta_config WHERE id = %s", (id,))
if not cursor.fetchone():
print(f"❌ ID {id} 不存在")
return False
# 更新主数据
config_data = json.dumps(entity.to_dict(), ensure_ascii=False)
cursor.execute(
"UPDATE meta_config SET config_data = %s WHERE id = %s",
(config_data, id)
)
# 删除旧的索引
cursor.execute("DELETE FROM meta_table_index WHERE meta_config_id = %s", (id,))
cursor.execute("DELETE FROM meta_metric_index WHERE meta_config_id = %s", (id,))
# 插入新的索引
if entity.tables:
for table in entity.tables:
cursor.execute(
"INSERT INTO meta_table_index (id, table_name, meta_config_id) VALUES (%s, %s, %s)",
(self._generate_id(), table.name, id)
)
if entity.metrics:
for metric in entity.metrics:
cursor.execute(
"INSERT INTO meta_metric_index (id, metric_name, meta_config_id) VALUES (%s, %s, %s)",
(self._generate_id(), metric.name, id)
)
conn.commit()
print(f"✅ MySQL更新成功,ID: {id}")
return True
except Error as e:
conn.rollback()
print(f"❌ MySQL更新失败: {e}")
return False
finally:
cursor.close()
conn.close()
def delete(self, id: str) -> bool:
"""删除配置"""
conn = self._get_connection()
cursor = conn.cursor()
try:
cursor.execute("DELETE FROM meta_config WHERE id = %s", (id,))
affected = cursor.rowcount
conn.commit()
if affected > 0:
print(f"✅ MySQL删除成功,ID: {id}")
return True
else:
print(f"❌ ID {id} 不存在")
return False
except Error as e:
conn.rollback()
print(f"❌ MySQL删除失败: {e}")
return False
finally:
cursor.close()
conn.close()
def search_by_table(self, table_name: str) -> List[MetaConfig]:
"""根据表名搜索"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT DISTINCT mc.config_data
FROM meta_config mc
JOIN meta_table_index mti ON mc.id = mti.meta_config_id
WHERE mti.table_name LIKE %s
""", (f"%{table_name}%",))
results = cursor.fetchall()
cursor.close()
conn.close()
return [MetaConfig.from_dict(json.loads(row[0])) for row in results]
def search_by_metric(self, metric_name: str) -> List[MetaConfig]:
"""根据指标名搜索"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT DISTINCT mc.config_data
FROM meta_config mc
JOIN meta_metric_index mmi ON mc.id = mmi.meta_config_id
WHERE mmi.metric_name LIKE %s
""", (f"%{metric_name}%",))
results = cursor.fetchall()
cursor.close()
conn.close()
return [MetaConfig.from_dict(json.loads(row[0])) for row in results]
# ========== 演示函数 ==========
def mysql_demo():
print("=" * 60)
print("🔷 MySQL 元数据管理演示")
print("=" * 60)
# 1. 初始化
repo = MySQLMetaRepository(
host="127.0.0.1",
port=3306,
user="root",
password="happy123", # 请修改为你的密码
database="demo_meta_db"
)
# 2. 准备测试数据
print("\n📦 准备测试数据...")
columns = [
ColumnConfig(
name="region_id",
role="primary_key",
description="地区唯一标识",
alias=["地区ID", "区域ID"],
sync=False
),
ColumnConfig(
name="province",
role="dimension",
description="订单所属省份名称",
alias=["省份", "省"],
sync=True
),
ColumnConfig(
name="region_name",
role="dimension",
description="订单所属的大区名称,如华东,华南等",
alias=["地区", "区域", "大区"],
sync=True
),
ColumnConfig(
name="country",
role="dimension",
description="地区所属国家的名称",
alias=["国家", "国家名称"],
sync=True
)
]
tables = [
TableConfig(
name="dim_region",
role="dim",
description="地区维度表,用于描述订单发生的地理区域信息",
columns=columns
)
]
metrics = [
MetricConfig(
name="GMV",
description="全称Gross Merchandise value,表示所有订单的成交金额总和",
relevant_columns=["fact_order.order_amount"],
alias=["成交总额", "订单总额"]
),
MetricConfig(
name="AOV",
description="全称Average Order value,表示所有订单的成交金额总的平均值",
relevant_columns=["fact_order.order_amount", "fact_order.order_id"],
alias=["平均单价", "平均订单金额"]
)
]
meta_config = MetaConfig(tables=tables, metrics=metrics)
# 3. 创建
print("\n📝 创建配置...")
config_id = repo.create(meta_config)
# 4. 查询
print("\n🔍 根据ID查询...")
retrieved = repo.get_by_id(config_id)
if retrieved:
print(f" 查询成功!")
print(f" 表数量: {len(retrieved.tables) if retrieved.tables else 0}")
print(f" 指标数量: {len(retrieved.metrics) if retrieved.metrics else 0}")
# 5. 按表名搜索
print("\n🔍 按表名搜索 'dim_region'...")
results = repo.search_by_table("dim_region")
print(f" 找到 {len(results)} 个配置")
# 6. 按指标名搜索
print("\n🔍 按指标名搜索 'GMV'...")
results = repo.search_by_metric("GMV")
print(f" 找到 {len(results)} 个配置")
# 7. 更新
print("\n📝 更新配置...")
# 添加一个新指标
new_metrics = metrics + [
MetricConfig(
name="订单量",
description="所有订单的总数量",
relevant_columns=["fact_order.order_id"],
alias=["订单总数"]
)
]
updated_config = MetaConfig(tables=tables, metrics=new_metrics)
repo.update(config_id, updated_config)
print(" 更新成功!")
# 8. 获取所有
print("\n📋 获取所有配置...")
all_configs = repo.get_all()
print(f" 共有 {len(all_configs)} 个配置")
# 9. 删除
print("\n🗑️ 删除配置...")
repo.delete(config_id)
print(" 删除成功!")
print("\n" + "=" * 60)
print("✅ MySQL演示完成!")
print("=" * 60)
if __name__ == "__main__":
mysql_demo()