Files
yunmei 098125164d fix: 对齐技术架构补齐传输 mTLS 与结构化 JSON 日志(NFR 9 章)
架构核对发现 2 处差距,本次补齐:
- Kafka 上行支持 SSL/SASL_SSL 双向 mTLS(8.2 服务间 mTLS 边缘网关↔总线)
- 网关日志支持结构化 JSON 输出(NFR 可维护:统一日志规范)
- 配置示例与 README 验收口径同步更新
2026-08-04 15:46:09 +08:00

124 lines
5.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""数据总线命名与分区模板化 —— 换行业只改配置,内核零改动(PRD 5.2 / Issue #4)。
模板(template)是行业复制的唯一粒度,所有外部资源命名均由
「模板名 + 点位维度」推导:
- Kafka topic :`{topic_prefix}.{device_id}.points`(与 edge-gateway 上行一致);
分区 = hash(device_id) % num_partitions,保证单设备分区内有序;
- TDengine :超级表 `{tpl}_points`、每测点子表 `{tpl}_pt_{point_id}`;
- PostgreSQL :schema `tpl_{tpl}`,表名 `{schema}.{table}`;
- MinIO :桶 `{template}-artifacts`,对象键 `features/{model_id}/{...}`。
命名清洗规则:
- Kafka topic / MinIO 桶:小写 + 保留 `-` / `_` / `.` 之外替换为 `_`;
- SQL 标识符(TDengine / PostgreSQL):额外把 `-` 替换为 `_`,避免引号转义。
"""
from __future__ import annotations
import hashlib
import re
from typing import List, Optional
# 保留字符集(Kafka topic / MinIO 桶名均允许小写字母、数字、- _ .)
_KEEP = re.compile(r"[^a-z0-9_.-]+")
# SQL 标识符额外排除 `-`(TDengine / PostgreSQL 不加引号时不允许)
_SQL_KEEP = re.compile(r"[^a-z0-9_.]+")
def sanitize(name: str) -> str:
"""通用命名清洗:小写 + 非安全字符替换为 `_`。"""
s = (name or "").strip().lower()
s = _KEEP.sub("_", s)
s = s.strip("._") or "tpl"
return s
def sanitize_sql(name: str) -> str:
"""SQL 标识符清洗:`-` 与其余非安全字符替换为 `_`。"""
s = sanitize(name)
s = _SQL_KEEP.sub("_", s)
return s.strip("._") or "tpl"
def _sql_str(value: str) -> str:
"""SQL 字符串字面量转义(单引号加倍)。"""
return "'" + str(value).replace("'", "''") + "'"
class TemplateNaming:
"""按模板推导全部外部资源命名(Kafka / TDengine / PostgreSQL / MinIO)。"""
def __init__(
self,
template: str,
topic_prefix: Optional[str] = None,
num_partitions: int = 12,
bucket_suffix: str = "artifacts",
):
self.template = sanitize(template)
# SQL 标识符用下划线形态(避免 `-` 需加引号)
self.tpl_sql = sanitize_sql(self.template)
self.topic_prefix = sanitize(topic_prefix) if topic_prefix else self.template
self.num_partitions = max(1, int(num_partitions))
self.bucket_suffix = sanitize_sql(bucket_suffix)
# ------------------------------------------------------------------
# Kafka:topic 命名 / 分区策略(子任务 #28)
# ------------------------------------------------------------------
def topic(self, device_id: str) -> str:
"""上行 topic:`{topic_prefix}.{device_id}.points`(对齐 edge-gateway)。"""
return f"{self.topic_prefix}.{sanitize(device_id)}.points"
def partition(self, device_id: str, num_partitions: Optional[int] = None) -> int:
"""分区策略:按 device_id 一致性哈希 → 单设备分区内严格有序。"""
n = num_partitions or self.num_partitions
digest = hashlib.md5(sanitize(device_id).encode("utf-8")).hexdigest()
return int(digest[:8], 16) % n
def partitions(self, device_ids: List[str], num_partitions: Optional[int] = None) -> dict:
"""设备 → 分区映射(模板配置台预览用)。"""
n = num_partitions or self.num_partitions
return {d: self.partition(d, n) for d in device_ids}
# ------------------------------------------------------------------
# TDengine:超级表 + 每测点子表(子任务 #29)
# ------------------------------------------------------------------
def stable(self) -> str:
"""时序超级表:`{tpl}_points`。"""
return f"{self.tpl_sql}_points"
def subtable(self, point_id: str) -> str:
"""测点子表:`{tpl}_pt_{point_id}`(点位维度,字典驱动自动生成)。"""
return f"{self.tpl_sql}_pt_{sanitize_sql(point_id)}"
# ------------------------------------------------------------------
# PostgreSQL:schema 与表(子任务 #30)
# ------------------------------------------------------------------
def pg_schema(self) -> str:
"""关系 schema:`tpl_{tpl}`。"""
return f"tpl_{self.tpl_sql}"
def pg_table(self, table: str) -> str:
"""`{schema}.{table}` 限定名。"""
return f"{self.pg_schema()}.{sanitize_sql(table)}"
# ------------------------------------------------------------------
# MinIO:对象桶与对象键(子任务 #31)
# ------------------------------------------------------------------
def bucket(self) -> str:
"""对象存储桶:`{template}-artifacts`(S3 桶名允许 `-`)。"""
return f"{self.template}-{self.bucket_suffix}"
def snapshot_key(self, model_id: str, date: str, seq: int) -> str:
"""特征快照对象键:`features/{model_id}/{date}/{seq:06d}.jsonl`。"""
return f"features/{sanitize_sql(model_id)}/{date}/{int(seq):06d}.jsonl"
def model_artifact_key(self, model_id: str, version: str) -> str:
"""模型 artifact 对象键:`models/{model_id}/{version}/model.bin`。"""
return f"models/{sanitize_sql(model_id)}/{sanitize_sql(version)}/model.bin"
# ------------------------------------------------------------------
def _sql_str(self, value: str) -> str:
return _sql_str(value)