读取DM数据库

This commit is contained in:
2026-02-27 17:06:56 +08:00
parent 0c0a295f09
commit df46812eff
12 changed files with 485 additions and 30 deletions

4
.env.example Normal file
View File

@@ -0,0 +1,4 @@
# 达梦数据库密码(仅 source=db 时需要)
# 复制为 .env 后填入真实密码,或将下面一行加入 ~/.bashrc 等
# export DM_PASSWORD=你的密码
DM_PASSWORD=

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.env
*.pyc
__pycache__/
.venv/
venv/

18
.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"configurations": [
{
"name": "linux-gcc-x64",
"includePath": [
"${workspaceFolder}/**"
],
"compilerPath": "/usr/bin/gcc",
"cStandard": "${default}",
"cppStandard": "${default}",
"intelliSenseMode": "linux-gcc-x64",
"compilerArgs": [
""
]
}
],
"version": 4
}

24
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,24 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "C/C++ Runner: Debug Session",
"type": "cppdbg",
"request": "launch",
"args": [],
"stopAtEntry": false,
"externalConsole": false,
"cwd": "/home/huangfukk/module1_3/.vscode",
"program": "/home/huangfukk/module1_3/.vscode/build/Debug/outDebug",
"MIMode": "gdb",
"miDebuggerPath": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

59
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,59 @@
{
"C_Cpp_Runner.cCompilerPath": "gcc",
"C_Cpp_Runner.cppCompilerPath": "g++",
"C_Cpp_Runner.debuggerPath": "gdb",
"C_Cpp_Runner.cStandard": "",
"C_Cpp_Runner.cppStandard": "",
"C_Cpp_Runner.msvcBatchPath": "",
"C_Cpp_Runner.useMsvc": false,
"C_Cpp_Runner.warnings": [
"-Wall",
"-Wextra",
"-Wpedantic",
"-Wshadow",
"-Wformat=2",
"-Wcast-align",
"-Wconversion",
"-Wsign-conversion",
"-Wnull-dereference"
],
"C_Cpp_Runner.msvcWarnings": [
"/W4",
"/permissive-",
"/w14242",
"/w14287",
"/w14296",
"/w14311",
"/w14826",
"/w44062",
"/w44242",
"/w14905",
"/w14906",
"/w14263",
"/w44265",
"/w14928"
],
"C_Cpp_Runner.enableWarnings": true,
"C_Cpp_Runner.warningsAsError": false,
"C_Cpp_Runner.compilerArgs": [],
"C_Cpp_Runner.linkerArgs": [],
"C_Cpp_Runner.includePaths": [],
"C_Cpp_Runner.includeSearch": [
"*",
"**/*"
],
"C_Cpp_Runner.excludeSearch": [
"**/build",
"**/build/**",
"**/.*",
"**/.*/**",
"**/.vscode",
"**/.vscode/**"
],
"C_Cpp_Runner.useAddressSanitizer": false,
"C_Cpp_Runner.useUndefinedSanitizer": false,
"C_Cpp_Runner.useLeakSanitizer": false,
"C_Cpp_Runner.showCompilationTime": false,
"C_Cpp_Runner.useLinkTimeOptimization": false,
"C_Cpp_Runner.msvcSecureNoWarnings": false
}

View File

@@ -6,13 +6,20 @@
## 目录结构 ## 目录结构
``` ```
project/ project/
├── config/
│ ├── config.yaml # 配置文件(推荐,支持注释)
│ └── config.json # 配置文件备选
├── data/ ├── data/
│ ├── raw_data_sample.json # 原始输入数据(包含噪声和错误 │ ├── raw_data_sample.json # 原始输入数据(JSON 模式
│ └── cleaned_data_final.json # 清洗后的输出数据 │ └── cleaned_data_final.json # 清洗后的输出数据
├── src/ ├── src/
── cleaner.py # 清洗运行程序 ── cleaner.py # 清洗运行程序
│ └── data_loader.py # 数据加载JSON/达梦)
├── report/ ├── report/
│ └── detailed_cleaning_report.json # 详细清洗报告 │ └── detailed_cleaning_report.json # 详细清洗报告
├── scripts/
│ └── inspect_dm_tables.py # 达梦表结构查看(部署时用)
├── .env.example # 环境变量示例(含 DM_PASSWORD
└── README.md # 项目说明文档 └── README.md # 项目说明文档
``` ```
@@ -50,15 +57,54 @@ project/
- 时间戳统一为 ISO-8601 格式 - 时间戳统一为 ISO-8601 格式
- 输出清洗后 JSON 及详细报告(含各步骤的统计与示例) - 输出清洗后 JSON 及详细报告(含各步骤的统计与示例)
## 数据源配置
程序支持两种数据源,通过 `config/config.yaml``config/config.json` 中的 `source` 切换:
| source | 适用场景 | 说明 |
|--------|--------------|------|
| `json` | 开发、无数据库 | 从 `data/raw_data_sample.json` 读取 |
| `db` | 生产、有达梦库 | 从达梦数据库指定表读取 |
### 开发阶段(当前设备无达梦)
保持 `source: json`,直接使用本地 JSON 文件即可。
### 生产阶段(部署到有达梦的设备)
`OBJECTIVE_INSTANCE` 表结构与 `raw_data_sample.json` 一致,配置中已设为 `db.table: OBJECTIVE_INSTANCE`,直接查询即可。
1. **修改配置**`source: db`,端口 `5080`、表 `OBJECTIVE_INSTANCE` 已预设;
2. **设置密码**`export DM_PASSWORD=你的密码`
3. **运行清洗**`python src/cleaner.py`
若需自定义查询(如加 WHERE 条件),可清空 `table`,在 `db.query` 中写完整 SQL。
### 密码注入与安全说明
**⚠️ 安全原则:切勿将达梦密码写入配置文件或源代码。**
达梦模式通过环境变量 `DM_PASSWORD` 注入密码,支持以下方式:
| 方式 | 说明 |
|------|------|
| **终端** | `export DM_PASSWORD=你的密码`Linux/Mac`set DM_PASSWORD=你的密码`Windows CMD |
| **PyCharm** | Run → Edit Configurations → Environment variables → 添加 `DM_PASSWORD=你的密码` |
| **.env 文件** | 复制 `.env.example``.env`,填入 `DM_PASSWORD=你的密码`,并用 `python-dotenv` 加载(需自行集成) |
`inspect_dm_tables.py` 支持两种密码来源:优先环境变量 `DM_PASSWORD`,其次脚本内 `LOCAL_PASSWORD`。若使用 `LOCAL_PASSWORD`,务必执行 `git update-index --assume-unchanged scripts/inspect_dm_tables.py` 避免误提交。
配置文件(`config.yaml` / `config.json`)中仅配置 `host``port``user``schema``table` 等,**不包含 password**。本项目源代码中不包含任何硬编码密码。
## 快速开始 ## 快速开始
### 1. 环境依赖 ### 1. 环境依赖
本项目仅依赖 Python 标准库及 NumPy/Pandas
```bash ```bash
pip install numpy pandas pip install -r requirements.txt
``` ```
核心依赖:`numpy``pandas``PyYAML`。达梦模式需额外安装 `dmPython`
### 2. 运行清洗 ### 2. 运行清洗
在项目根目录下执行: 在项目根目录下执行:
@@ -66,7 +112,7 @@ pip install numpy pandas
python src/cleaner.py python src/cleaner.py
``` ```
程序会读取 `data/raw_data_sample.json`,处理后生成: 默认从 JSON 读取,处理后生成:
- `data/cleaned_data_final.json`(清洗后的数据) - `data/cleaned_data_final.json`(清洗后的数据)
- `report/detailed_cleaning_report.json`(详细清洗报告) - `report/detailed_cleaning_report.json`(详细清洗报告)

16
config/config.json Normal file
View File

@@ -0,0 +1,16 @@
{
"source": "json",
"json": {
"input_file": "data/raw_data_sample.json",
"output_file": "data/cleaned_data_final.json",
"report_file": "report/detailed_cleaning_report.json"
},
"db": {
"host": "127.0.0.1",
"port": 5080,
"user": "SYSDBA",
"schema": "SYSDBA",
"table": "OBJECTIVE_INSTANCE",
"query": null
}
}

21
config/config.yaml Normal file
View File

@@ -0,0 +1,21 @@
# 数据清洗系统配置文件
# 通过 source 切换json开发 / db生产-达梦)
source: json # 开发用 json部署达梦时改为 db或直接改 config.json
# JSON 文件模式source=json 时生效)
json:
input_file: data/raw_data_sample.json
output_file: data/cleaned_data_final.json
report_file: report/detailed_cleaning_report.json
# 达梦数据库模式source=db 时生效)
# 密码通过环境变量 DM_PASSWORD 传入,切勿写入配置文件
db:
host: 127.0.0.1
port: 5080
user: SYSDBA
schema: SYSDBA
# OBJECTIVE_INSTANCE 表结构与 raw_data_sample.json 一致,直接查询即可
table: OBJECTIVE_INSTANCE
query: null

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
numpy>=1.20.0
pandas>=1.3.0
PyYAML>=5.4
# 达梦数据库(仅 source=db 时需要,开发阶段可不安)
# pip install dmPython

View File

@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""
在达梦数据库机器上运行,查看各表结构,用于编写 config 中的 db.query
用法: python scripts/inspect_dm_tables.py
密码获取顺序:环境变量 DM_PASSWORD > 下方 LOCAL_PASSWORD
若使用 LOCAL_PASSWORD切勿提交到 git可执行git update-index --assume-unchanged scripts/inspect_dm_tables.py
"""
import os
import sys
# 可选:在此直接填写密码(仅限本机调试,切勿提交到版本库!)
LOCAL_PASSWORD = None # 例如: "你的密码"
# 支持从项目根或 scripts 目录运行
_script_dir = os.path.dirname(os.path.abspath(__file__))
_project_root = os.path.dirname(_script_dir)
if _project_root not in sys.path:
sys.path.insert(0, _project_root)
try:
import dmPython
except ImportError:
print("请先安装: pip install dmPython")
sys.exit(1)
CONFIG = {
"user": "SYSDBA",
"password": os.environ.get("DM_PASSWORD") or LOCAL_PASSWORD,
"server": "127.0.0.1",
"port": 5080,
"schema": "SYSDBA",
}
TABLES = [
"OBJECTIVE_INSTANCE",
"METRIC_VALUE_INSTANCE",
"METRIC_PROFILE",
"ROLE_PROFILE",
"TASK_PROFILE",
"RELATION_INSTANCE",
]
def main():
if not CONFIG["password"]:
print("请设置环境变量 DM_PASSWORD 或在脚本中填写 LOCAL_PASSWORD")
sys.exit(1)
try:
conn = dmPython.connect(
user=CONFIG["user"],
password=CONFIG["password"],
server=CONFIG["server"],
port=CONFIG["port"],
schema=CONFIG["schema"],
)
except Exception as e:
print(f"连接失败: {e}")
sys.exit(1)
cursor = conn.cursor()
print("=" * 60)
print("达梦表结构(用于编写 config/db.query")
print("=" * 60)
for table in TABLES:
try:
cursor.execute(f'SELECT * FROM "{CONFIG["schema"]}"."{table}" WHERE 1=0')
cols = [d[0] for d in cursor.description]
print(f"\n{table}】 列: {cols}")
except Exception as e:
print(f"\n{table}】 查询失败: {e}")
cursor.execute(
"""
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE
FROM DBA_TAB_COLUMNS
WHERE OWNER = ?
ORDER BY TABLE_NAME, COLUMN_ID
""",
(CONFIG["schema"],),
)
rows = cursor.fetchall()
print("\n" + "=" * 60)
print("所有列详情 (TABLE_NAME | COLUMN_NAME | DATA_TYPE)")
print("=" * 60)
cur_table = None
for table, col, dtype in rows:
if table != cur_table:
cur_table = table
print(f"\n--- {table} ---")
print(f" {col}: {dtype}")
cursor.close()
conn.close()
print("\n完成。请根据上述结构在 config 中编写 db.query。")
if __name__ == "__main__":
main()

View File

@@ -3,13 +3,28 @@ import numpy as np
import pandas as pd import pandas as pd
from datetime import datetime from datetime import datetime
def _json_serializer(obj):
"""处理 Timestamp、datetime、numpy、NaN 等不可直接 JSON 序列化的类型"""
if hasattr(obj, 'isoformat'):
return obj.isoformat()
if hasattr(obj, 'item'):
return obj.item()
if isinstance(obj, float) and (obj != obj or obj == float('inf') or obj == float('-inf')):
return None # NaN, Inf -> null
raise TypeError(f'Object of type {type(obj).__name__} is not JSON serializable')
class AdvancedDataCleaner: class AdvancedDataCleaner:
def __init__(self, input_file, output_file, report_file): def __init__(self, output_file, report_file, data=None, input_file=None):
self.input_file = input_file """
支持两种初始化方式:
1. data=... 直接传入数据列表(从配置/数据库加载时使用)
2. input_file=... 传入 JSON 文件路径(兼容旧用法)
"""
self.output_file = output_file self.output_file = output_file
self.report_file = report_file self.report_file = report_file
self.data = [] self.data = data if data is not None else []
self.df = None self.input_file = input_file
# 报告结构 # 报告结构
self.report = { self.report = {
@@ -40,6 +55,7 @@ class AdvancedDataCleaner:
] ]
def load_data(self): def load_data(self):
if not self.data and self.input_file:
with open(self.input_file, 'r', encoding='utf-8') as f: with open(self.input_file, 'r', encoding='utf-8') as f:
self.data = json.load(f) self.data = json.load(f)
self.report["summary"]["total_records"] = len(self.data) self.report["summary"]["total_records"] = len(self.data)
@@ -138,34 +154,72 @@ class AdvancedDataCleaner:
self.report["details"]["standardization"].append("Coordinates normalized to 2 decimal places") self.report["details"]["standardization"].append("Coordinates normalized to 2 decimal places")
self.report["details"]["standardization"].append("Timestamps formatted to ISO-8601") self.report["details"]["standardization"].append("Timestamps formatted to ISO-8601")
# 保存数据 # 保存数据(确保输出目录存在)
import os
output_dir = os.path.dirname(self.output_file)
if output_dir:
os.makedirs(output_dir, exist_ok=True)
result_data = self.df.to_dict('records') result_data = self.df.to_dict('records')
with open(self.output_file, 'w', encoding='utf-8') as f: with open(self.output_file, 'w', encoding='utf-8') as f:
json.dump(result_data, f, ensure_ascii=False, indent=2) json.dump(result_data, f, ensure_ascii=False, indent=2, default=_json_serializer)
# 保存详细报告 # 保存详细报告(确保目录存在)
report_dir = os.path.dirname(self.report_file)
if report_dir:
os.makedirs(report_dir, exist_ok=True)
with open(self.report_file, 'w', encoding='utf-8') as f: with open(self.report_file, 'w', encoding='utf-8') as f:
json.dump(self.report, f, ensure_ascii=False, indent=2) json.dump(self.report, f, ensure_ascii=False, indent=2, default=_json_serializer)
print(f"完成!报告已生成至 {self.report_file}") print(f"完成!报告已生成至 {self.report_file}")
def load_config(project_root: str) -> dict:
"""加载 config/config.json 或 config.yamljson 优先,便于部署时只改 json"""
import os
config_dir = os.path.join(project_root, 'config')
json_path = os.path.join(config_dir, 'config.json')
yaml_path = os.path.join(config_dir, 'config.yaml')
if os.path.exists(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
return json.load(f)
if os.path.exists(yaml_path):
try:
import yaml
with open(yaml_path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
except ImportError:
pass
return {
'source': 'json',
'json': {
'input_file': 'data/raw_data_sample.json',
'output_file': 'data/cleaned_data_final.json',
'report_file': 'report/detailed_cleaning_report.json',
},
}
if __name__ == "__main__": if __name__ == "__main__":
import os import os
# 基于脚本位置计算项目根目录,保证无论从哪里运行都能正确找到文件 from data_loader import load_data as load_data_source
script_dir = os.path.dirname(os.path.abspath(__file__)) script_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(script_dir) project_root = os.path.dirname(script_dir)
input_path = os.path.join(project_root, 'data', 'raw_data_sample.json')
output_path = os.path.join(project_root, 'data', 'cleaned_data_final.json')
report_path = os.path.join(project_root, 'report', 'detailed_cleaning_report.json')
# 确保 report 目录存在 config = load_config(project_root)
os.makedirs(os.path.dirname(report_path), exist_ok=True) source = config.get('source', 'json')
print(f"数据源模式: {source}")
# 增加一个检查,防止路径错误 try:
if not os.path.exists(input_path): data, paths = load_data_source(config, project_root)
print(f"错误:找不到文件 {input_path}") except (FileNotFoundError, ValueError) as e:
print(f"当前工作目录是:{os.getcwd()}") print(f"错误:{e}")
print("请检查文件路径或确保已运行数据生成脚本。") exit(1)
else:
cleaner = AdvancedDataCleaner(input_path, output_path, report_path) os.makedirs(os.path.dirname(paths['report']), exist_ok=True)
cleaner = AdvancedDataCleaner(
output_file=paths['output'],
report_file=paths['report'],
data=data,
)
cleaner.run() cleaner.run()

101
src/data_loader.py Normal file
View File

@@ -0,0 +1,101 @@
"""
数据加载模块:支持从 JSON 文件或达梦数据库读取数据
开发阶段使用 JSON生产环境切换到达梦时只需修改 config 中的 source
"""
import json
import os
from typing import Tuple
def load_from_json(file_path: str, project_root: str = None) -> list:
"""从 JSON 文件加载数据"""
path = file_path
if project_root and not os.path.isabs(file_path):
path = os.path.join(project_root, file_path)
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
def load_from_dm(config: dict, project_root: str = None) -> list:
"""
从达梦数据库加载数据
需要安装 dmPython 及达梦客户端库,仅在 source=db 时调用
"""
try:
import dmPython
except ImportError as e:
raise RuntimeError(
"达梦数据库模式需要安装 dmPython。\n"
"请执行: pip install dmPython\n"
"并在有达梦客户端的环境运行(需配置 DM_HOME/LD_LIBRARY_PATH"
) from e
db_cfg = config.get('db', {})
password = os.environ.get('DM_PASSWORD')
if not password:
raise ValueError(
"达梦模式需要设置环境变量 DM_PASSWORD。\n"
"示例: export DM_PASSWORD=你的密码"
)
conn = dmPython.connect(
user=db_cfg.get('user', 'SYSDBA'),
password=password,
server=db_cfg.get('host', '127.0.0.1'),
port=int(db_cfg.get('port', 5080)),
schema=db_cfg.get('schema', 'SYSDBA'),
)
try:
cursor = conn.cursor()
query = (db_cfg.get('query') or '').strip()
table = db_cfg.get('table')
if query:
cursor.execute(query)
elif table:
schema = db_cfg.get('schema', 'SYSDBA')
cursor.execute(f'SELECT * FROM "{schema}"."{table}"')
else:
raise ValueError(
"达梦模式需配置 db.query自定义 SQL或 db.table。\n"
"当前达梦为规范化表,需通过 query 编写 JOIN 得到扁平化数据。"
)
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()
cursor.close()
return [dict(zip(columns, row)) for row in rows]
finally:
conn.close()
def load_data(config: dict, project_root: str) -> Tuple[list, dict]:
"""
根据配置加载数据,返回 (数据列表, 路径配置)
"""
source = config.get('source', 'json')
project_root = project_root or os.getcwd()
if source == 'json':
json_cfg = config.get('json', {})
data = load_from_json(json_cfg.get('input_file', 'data/raw_data_sample.json'), project_root)
paths = {
'output': _resolve_path(json_cfg.get('output_file', 'data/cleaned_data_final.json'), project_root),
'report': _resolve_path(json_cfg.get('report_file', 'report/detailed_cleaning_report.json'), project_root),
}
return data, paths
elif source == 'db':
data = load_from_dm(config, project_root)
json_cfg = config.get('json', {})
paths = {
'output': _resolve_path(json_cfg.get('output_file', 'data/cleaned_data_final.json'), project_root),
'report': _resolve_path(json_cfg.get('report_file', 'report/detailed_cleaning_report.json'), project_root),
}
return data, paths
else:
raise ValueError(f"不支持的 source 类型: {source},应为 json 或 db")
def _resolve_path(path: str, project_root: str) -> str:
if os.path.isabs(path):
return path
return os.path.join(project_root, path)