chore: reinitialize project with vite architecture

This commit is contained in:
beabigegg
2026-02-08 08:30:48 +08:00
commit b56e80381b
264 changed files with 75752 additions and 0 deletions

View File

@@ -0,0 +1,344 @@
"""
生成 MES 数据库参考文档
用于报表开发参考
"""
import json
from pathlib import Path
from datetime import datetime
# 读取表结构信息
ROOT_DIR = Path(__file__).resolve().parent.parent
DATA_FILE = ROOT_DIR / 'data' / 'table_schema_info.json'
with open(DATA_FILE, 'r', encoding='utf-8') as f:
table_info = json.load(f)
# 表用途描述(根据表名推断)
TABLE_DESCRIPTIONS = {
'DW_MES_CONTAINER': '容器/批次主檔 - 目前在製容器狀態、數量與流程資訊',
'DW_MES_HOLDRELEASEHISTORY': 'Hold/Release 歷史表 - 批次停工與解除紀錄',
'DW_MES_JOB': '設備維修工單表 - 維修工單的當前狀態與流程',
'DW_MES_LOTREJECTHISTORY': '批次不良/報廢歷史表 - 不良原因與數量',
'DW_MES_LOTWIPDATAHISTORY': '在製數據採集歷史表 - 製程量測/參數紀錄',
'DW_MES_LOTWIPHISTORY': '在製流轉歷史表 - 批次進出站與流程軌跡',
'DW_MES_MAINTENANCE': '設備保養/維護紀錄表 - 保養計畫與點檢數據',
'DW_MES_PARTREQUESTORDER': '維修用料請求表 - 維修/設備零件請領',
'DW_MES_PJ_COMBINEDASSYLOTS': '併批紀錄表 - 合批/合併批次關聯與數量資訊',
'DW_MES_RESOURCESTATUS': '設備狀態變更歷史表 - 狀態切換與原因',
'DW_MES_RESOURCESTATUS_SHIFT': '設備狀態班次彙總表 - 班次級狀態/工時',
'DW_MES_WIP': '在製品現況表(含歷史累積)- 當前 WIP 狀態/數量',
'DW_MES_HM_LOTMOVEOUT': '批次出站事件歷史表 - 出站/移出交易',
'DW_MES_JOBTXNHISTORY': '維修工單交易歷史表 - 工單狀態變更紀錄',
'DW_MES_LOTMATERIALSHISTORY': '批次物料消耗歷史表 - 用料與批次關聯',
'DW_MES_RESOURCE': '資源表 - 設備/載具等資源基本資料OBJECTCATEGORY=ASSEMBLY 時RESOURCENAME 為設備編號)'
}
# 常见字段说明
COMMON_FIELD_NOTES = {
'ID': '唯一标识符',
'NAME': '名称',
'STATUS': '状态',
'TIMESTAMP': '时间戳',
'CREATEDATE': '创建日期',
'UPDATEDATE': '更新日期',
'LOTID': '批次ID',
'CONTAINERID': '容器ID',
'RESOURCEID': '资源ID',
'EQUIPMENTID': '设备ID',
'OPERATIONID': '工序ID',
'JOBID': '工单ID',
'PRODUCTID': '产品ID',
'CUSTOMERID': '客户ID',
'QTY': '数量',
'QUANTITY': '数量'
}
def generate_markdown():
"""生成 Markdown 文档"""
md = []
# 标题和简介
md.append("# MES 数据库报表开发参考文档\n")
md.append(f"**生成时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
md.append("---\n")
# 目录
md.append("## 目录\n")
md.append("1. [数据库连接信息](#数据库连接信息)")
md.append("2. [数据库概览](#数据库概览)")
md.append("3. [表结构详细说明](#表结构详细说明)")
md.append("4. [报表开发注意事项](#报表开发注意事项)")
md.append("5. [常用查询示例](#常用查询示例)\n")
md.append("---\n")
# 1. 数据库连接信息
md.append("## 数据库连接信息\n")
md.append("### 连接参数\n")
md.append("| 参数 | 值 |")
md.append("|------|------|")
md.append("| 数据库类型 | Oracle Database 19c Enterprise Edition |")
md.append("| 主机地址 | 請參考 .env 檔案 (DB_HOST) |")
md.append("| 端口 | 請參考 .env 檔案 (DB_PORT) |")
md.append("| 服务名 | 請參考 .env 檔案 (DB_SERVICE) |")
md.append("| 用户名 | 請參考 .env 檔案 (DB_USER) |")
md.append("| 密码 | 請參考 .env 檔案 (DB_PASSWORD) |\n")
md.append("### Python 连接示例\n")
md.append("```python")
md.append("import os")
md.append("import oracledb")
md.append("from dotenv import load_dotenv")
md.append("")
md.append("# 載入環境變數")
md.append("load_dotenv()")
md.append("")
md.append("# 连接配置 (從環境變數讀取)")
md.append("DB_CONFIG = {")
md.append(" 'user': os.getenv('DB_USER'),")
md.append(" 'password': os.getenv('DB_PASSWORD'),")
md.append(" 'dsn': f\"(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST={os.getenv('DB_HOST')})(PORT={os.getenv('DB_PORT')})))(CONNECT_DATA=(SERVICE_NAME={os.getenv('DB_SERVICE')})))\"")
md.append("}")
md.append("")
md.append("# 建立连接")
md.append("connection = oracledb.connect(**DB_CONFIG)")
md.append("cursor = connection.cursor()")
md.append("")
md.append("# 执行查询")
md.append("cursor.execute('SELECT * FROM DW_MES_WIP WHERE ROWNUM <= 10')")
md.append("results = cursor.fetchall()")
md.append("")
md.append("# 关闭连接")
md.append("cursor.close()")
md.append("connection.close()")
md.append("```\n")
md.append("### JDBC 连接字符串\n")
md.append("```")
md.append("jdbc:oracle:thin:@${DB_HOST}:${DB_PORT}:${DB_SERVICE}")
md.append("```\n")
# 2. 数据库概览
md.append("---\n")
md.append("## 数据库概览\n")
md.append("### 表统计信息\n")
md.append("| # | 表名 | 用途 | 数据量 |")
md.append("|---|------|------|--------|")
for idx, (table_name, info) in enumerate(sorted(table_info.items()), 1):
if 'error' not in info:
row_count = f"{info['row_count']:,}"
description = TABLE_DESCRIPTIONS.get(table_name, '待补充')
md.append(f"| {idx} | `{table_name}` | {description} | {row_count} |")
md.append("")
# 计算总数据量
total_rows = sum(info['row_count'] for info in table_info.values() if 'error' not in info)
md.append(f"**总数据量**: {total_rows:,}\n")
# 3. 表结构详细说明
md.append("---\n")
md.append("## 表结构详细说明\n")
for table_name in sorted(table_info.keys()):
info = table_info[table_name]
if 'error' in info:
continue
md.append(f"### {table_name}\n")
# 表说明
md.append(f"**用途**: {TABLE_DESCRIPTIONS.get(table_name, '待补充')}\n")
md.append(f"**数据量**: {info['row_count']:,}\n")
if info.get('table_comment'):
md.append(f"**表注释**: {info['table_comment']}\n")
# 字段列表
md.append("#### 字段列表\n")
md.append("| # | 字段名 | 数据类型 | 长度 | 可空 | 说明 |")
md.append("|---|--------|----------|------|------|------|")
schema = info.get('schema', [])
for col in schema:
col_num = col['column_id']
col_name = col['column_name']
# 构建数据类型显示
if col['data_type'] in ['VARCHAR2', 'CHAR']:
data_type = f"{col['data_type']}({col['data_length']})"
elif col['data_type'] == 'NUMBER' and col['data_precision']:
if col['data_scale']:
data_type = f"NUMBER({col['data_precision']},{col['data_scale']})"
else:
data_type = f"NUMBER({col['data_precision']})"
else:
data_type = col['data_type']
nullable = "" if col['nullable'] == 'Y' else ""
# 获取字段说明
column_comments = info.get('column_comments', {})
comment = column_comments.get(col_name, '')
# 如果没有注释,尝试从常见字段说明中获取
if not comment:
for key, value in COMMON_FIELD_NOTES.items():
if key in col_name:
comment = value
break
md.append(f"| {col_num} | `{col_name}` | {data_type} | {col.get('data_length', '-')} | {nullable} | {comment} |")
md.append("")
# 索引信息
indexes = info.get('indexes', [])
if indexes:
md.append("#### 索引\n")
md.append("| 索引名 | 类型 | 字段 |")
md.append("|--------|------|------|")
for idx_info in indexes:
idx_type = "唯一索引" if idx_info[1] == 'UNIQUE' else "普通索引"
md.append(f"| `{idx_info[0]}` | {idx_type} | {idx_info[2]} |")
md.append("")
md.append("---\n")
# 4. 报表开发注意事项
md.append("## 报表开发注意事项\n")
md.append("### 性能优化建议\n")
md.append("1. **大数据量表查询优化**")
md.append(" - 以下表数据量较大,查询时务必添加时间范围限制:")
large_tables = [(name, info['row_count']) for name, info in table_info.items()
if 'error' not in info and info['row_count'] > 10000000]
large_tables.sort(key=lambda x: x[1], reverse=True)
for table_name, count in large_tables:
md.append(f" - `{table_name}`: {count:,}")
md.append("")
md.append("2. **索引使用**")
md.append(" - 查询时尽量使用已建立索引的字段作为查询条件")
md.append(" - 避免在索引字段上使用函数,会导致索引失效")
md.append("")
md.append("3. **连接池配置**")
md.append(" - 建议使用连接池管理数据库连接")
md.append(" - 推荐连接池大小5-10 个连接")
md.append("")
md.append("4. **查询超时设置**")
md.append(" - 建议设置查询超时时间为 30-60 秒")
md.append(" - 避免长时间运行的查询影响系统性能")
md.append("")
md.append("### 数据时效性\n")
md.append("- **实时数据表**: `DW_MES_WIP`(含歷史累積), `DW_MES_RESOURCESTATUS`")
md.append("- **历史数据表**: 带有 `HISTORY` 后缀的表")
md.append("- **主数据表**: `DW_MES_RESOURCE`, `DW_MES_CONTAINER`")
md.append("")
md.append("### 常用时间字段\n")
md.append("大多数历史表包含以下时间相关字段:")
md.append("- `CREATEDATE` / `CREATETIMESTAMP`: 记录创建时间")
md.append("- `UPDATEDATE` / `UPDATETIMESTAMP`: 记录更新时间")
md.append("- `TRANSACTIONDATE`: 交易发生时间")
md.append("")
md.append("### 数据权限\n")
md.append("- 當前帳號為唯讀帳號 (詳見 .env 中的 DB_USER)")
md.append("- 仅可执行 SELECT 查询")
md.append("- 无法进行 INSERT, UPDATE, DELETE 操作")
md.append("")
# 5. 常用查询示例
md.append("---\n")
md.append("## 常用查询示例\n")
md.append("### 1. 查询当前在制品数量\n")
md.append("```sql")
md.append("SELECT COUNT(*) as WIP_COUNT")
md.append("FROM DW_MES_WIP")
md.append("WHERE CURRENTSTATUSID IS NOT NULL;")
md.append("```\n")
md.append("### 2. 查询设备状态统计\n")
md.append("```sql")
md.append("SELECT")
md.append(" CURRENTSTATUSID,")
md.append(" COUNT(*) as COUNT")
md.append("FROM DW_MES_RESOURCESTATUS")
md.append("GROUP BY CURRENTSTATUSID")
md.append("ORDER BY COUNT DESC;")
md.append("```\n")
md.append("### 3. 查询最近 7 天的批次历史\n")
md.append("```sql")
md.append("SELECT *")
md.append("FROM DW_MES_LOTWIPHISTORY")
md.append("WHERE CREATEDATE >= SYSDATE - 7")
md.append("ORDER BY CREATEDATE DESC;")
md.append("```\n")
md.append("### 4. 查询工单完成情况\n")
md.append("```sql")
md.append("SELECT")
md.append(" JOBID,")
md.append(" JOBSTATUS,")
md.append(" COUNT(*) as COUNT")
md.append("FROM DW_MES_JOB")
md.append("GROUP BY JOBID, JOBSTATUS")
md.append("ORDER BY JOBID;")
md.append("```\n")
md.append("### 5. 按日期统计生产数量\n")
md.append("```sql")
md.append("SELECT")
md.append(" TRUNC(CREATEDATE) as PRODUCTION_DATE,")
md.append(" COUNT(*) as LOT_COUNT")
md.append("FROM DW_MES_HM_LOTMOVEOUT")
md.append("WHERE CREATEDATE >= SYSDATE - 30")
md.append("GROUP BY TRUNC(CREATEDATE)")
md.append("ORDER BY PRODUCTION_DATE DESC;")
md.append("```\n")
md.append("### 6. 联表查询示例(批次与容器)\n")
md.append("```sql")
md.append("SELECT")
md.append(" w.LOTID,")
md.append(" w.CONTAINERNAME,")
md.append(" c.CURRENTSTATUSID,")
md.append(" c.CUSTOMERID")
md.append("FROM DW_MES_WIP w")
md.append("LEFT JOIN DW_MES_CONTAINER c ON w.CONTAINERID = c.CONTAINERID")
md.append("WHERE w.CREATEDATE >= SYSDATE - 1")
md.append("ORDER BY w.CREATEDATE DESC;")
md.append("```\n")
md.append("---\n")
md.append("## 附录\n")
md.append("### 文档更新记录\n")
md.append(f"- {datetime.now().strftime('%Y-%m-%d')}: 初始版本创建")
md.append("")
md.append("### 联系方式\n")
md.append("如有疑问或需要补充信息,请联系数据库管理员。\n")
return '\n'.join(md)
if __name__ == "__main__":
print("Generating documentation...")
markdown_content = generate_markdown()
output_file = ROOT_DIR / 'docs' / 'MES_Database_Reference.md'
with open(output_file, 'w', encoding='utf-8') as f:
f.write(markdown_content)
print(f"[OK] Documentation generated: {output_file}")

261
tools/query_table_schema.py Normal file
View File

@@ -0,0 +1,261 @@
"""
查询 MES 表结构信息脚本
用于生成报表开发参考文档
"""
import sys
import io
import os
import json
import argparse
from pathlib import Path
import oracledb
# 设置 UTF-8 编码输出
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
# Load .env file
try:
from dotenv import load_dotenv
env_path = Path(__file__).resolve().parent.parent / '.env'
load_dotenv(env_path)
except ImportError:
pass
# 数据库连接信息 (从环境变量读取,必须在 .env 中设置)
DB_HOST = os.getenv('DB_HOST', '')
DB_PORT = os.getenv('DB_PORT', '1521')
DB_SERVICE = os.getenv('DB_SERVICE', '')
DB_USER = os.getenv('DB_USER', '')
DB_PASSWORD = os.getenv('DB_PASSWORD', '')
DB_CONFIG = {
'user': DB_USER,
'password': DB_PASSWORD,
'dsn': f'(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST={DB_HOST})(PORT={DB_PORT})))(CONNECT_DATA=(SERVICE_NAME={DB_SERVICE})))'
}
# MES 表列表(預設清單)
MES_TABLES = [
'DW_MES_CONTAINER',
'DW_MES_HOLDRELEASEHISTORY',
'DW_MES_JOB',
'DW_MES_LOTREJECTHISTORY',
'DW_MES_LOTWIPDATAHISTORY',
'DW_MES_LOTWIPHISTORY',
'DW_MES_MAINTENANCE',
'DW_MES_PARTREQUESTORDER',
'DW_MES_PJ_COMBINEDASSYLOTS',
'DW_MES_RESOURCESTATUS',
'DW_MES_RESOURCESTATUS_SHIFT',
'DW_MES_WIP',
'DW_MES_HM_LOTMOVEOUT',
'DW_MES_JOBTXNHISTORY',
'DW_MES_LOTMATERIALSHISTORY',
'DW_MES_RESOURCE'
]
def get_table_schema(cursor, table_name, owner=None):
"""获取表的结构信息"""
query = """
SELECT
COLUMN_NAME,
DATA_TYPE,
DATA_LENGTH,
DATA_PRECISION,
DATA_SCALE,
NULLABLE,
DATA_DEFAULT,
COLUMN_ID
FROM ALL_TAB_COLUMNS
WHERE TABLE_NAME = :table_name
"""
if owner:
query += " AND OWNER = :owner ORDER BY COLUMN_ID"
cursor.execute(query, table_name=table_name, owner=owner)
else:
query += " ORDER BY COLUMN_ID"
cursor.execute(query, table_name=table_name)
columns = cursor.fetchall()
schema = []
for col in columns:
col_info = {
'column_name': col[0],
'data_type': col[1],
'data_length': col[2],
'data_precision': col[3],
'data_scale': col[4],
'nullable': col[5],
'default_value': col[6],
'column_id': col[7]
}
schema.append(col_info)
return schema
def get_table_comments(cursor, table_name, owner=None):
"""获取表和列的注释"""
# 获取表注释
table_query = """
SELECT COMMENTS
FROM ALL_TAB_COMMENTS
WHERE TABLE_NAME = :table_name
"""
if owner:
table_query += " AND OWNER = :owner"
cursor.execute(table_query, table_name=table_name, owner=owner)
else:
cursor.execute(table_query, table_name=table_name)
table_comment = cursor.fetchone()
# 获取列注释
col_query = """
SELECT COLUMN_NAME, COMMENTS
FROM ALL_COL_COMMENTS
WHERE TABLE_NAME = :table_name
"""
if owner:
col_query += " AND OWNER = :owner ORDER BY COLUMN_NAME"
cursor.execute(col_query, table_name=table_name, owner=owner)
else:
col_query += " ORDER BY COLUMN_NAME"
cursor.execute(col_query, table_name=table_name)
column_comments = {row[0]: row[1] for row in cursor.fetchall()}
return table_comment[0] if table_comment else None, column_comments
def get_table_indexes(cursor, table_name, owner=None):
"""获取表的索引信息"""
query = """
SELECT
i.INDEX_NAME,
i.UNIQUENESS,
LISTAGG(ic.COLUMN_NAME, ', ') WITHIN GROUP (ORDER BY ic.COLUMN_POSITION) as COLUMNS
FROM ALL_INDEXES i
JOIN ALL_IND_COLUMNS ic ON i.INDEX_NAME = ic.INDEX_NAME AND i.TABLE_NAME = ic.TABLE_NAME
WHERE i.TABLE_NAME = :table_name
GROUP BY i.INDEX_NAME, i.UNIQUENESS
ORDER BY i.INDEX_NAME
"""
if owner:
query = query.replace(
"WHERE i.TABLE_NAME = :table_name",
"WHERE i.TABLE_NAME = :table_name AND i.TABLE_OWNER = :owner",
)
cursor.execute(query, table_name=table_name, owner=owner)
else:
cursor.execute(query, table_name=table_name)
return cursor.fetchall()
def get_sample_data(cursor, table_name, owner=None, limit=5):
"""获取表的示例数据"""
try:
if owner:
cursor.execute(f"SELECT * FROM {owner}.{table_name} WHERE ROWNUM <= {limit}")
else:
cursor.execute(f"SELECT * FROM {table_name} WHERE ROWNUM <= {limit}")
columns = [col[0] for col in cursor.description]
rows = cursor.fetchall()
return columns, rows
except Exception as e:
return None, str(e)
def main():
"""主函数"""
parser = argparse.ArgumentParser(description="Query Oracle table/view schema information")
parser.add_argument(
"--schema",
help="Schema/owner to scan (e.g. DWH). If set, scans all TABLE/VIEW in that schema.",
)
parser.add_argument(
"--output",
help="Output JSON path (default: data/table_schema_info.json)",
default=None,
)
args = parser.parse_args()
print("Connecting to database...")
connection = oracledb.connect(**DB_CONFIG)
cursor = connection.cursor()
all_table_info = {}
owner = args.schema.strip().upper() if args.schema else None
if owner:
cursor.execute(
"""
SELECT OBJECT_NAME
FROM ALL_OBJECTS
WHERE OWNER = :owner
AND OBJECT_TYPE IN ('TABLE', 'VIEW')
ORDER BY OBJECT_NAME
""",
owner=owner,
)
table_list = [row[0] for row in cursor.fetchall()]
else:
table_list = MES_TABLES
print(f"\nQuerying schema information for {len(table_list)} objects...\n")
for idx, table_name in enumerate(table_list, 1):
print(f"[{idx}/{len(table_list)}] Processing {table_name}...")
try:
# 获取表结构
schema = get_table_schema(cursor, table_name, owner=owner)
# 获取注释
table_comment, column_comments = get_table_comments(cursor, table_name, owner=owner)
# 获取索引
indexes = get_table_indexes(cursor, table_name, owner=owner)
# 获取行数
if owner:
cursor.execute(f"SELECT COUNT(*) FROM {owner}.{table_name}")
else:
cursor.execute(f"SELECT COUNT(*) FROM {table_name}")
row_count = cursor.fetchone()[0]
# 获取示例数据
sample_columns, sample_data = get_sample_data(cursor, table_name, owner=owner, limit=3)
all_table_info[table_name] = {
'owner': owner,
'table_comment': table_comment,
'row_count': row_count,
'schema': schema,
'column_comments': column_comments,
'indexes': indexes,
'sample_columns': sample_columns,
'sample_data': sample_data
}
except Exception as e:
print(f" Error: {str(e)}")
all_table_info[table_name] = {'error': str(e)}
# 保存到 JSON 文件
if args.output:
output_file = Path(args.output)
else:
output_file = Path(__file__).resolve().parent.parent / 'data' / 'table_schema_info.json'
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(all_table_info, f, ensure_ascii=False, indent=2, default=str)
print(f"\n[OK] Schema information saved to {output_file}")
cursor.close()
connection.close()
print("[OK] Connection closed")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,152 @@
"""
Oracle Database Connection Test Script
测试连接到 DWDB 数据库并验证 MES 表访问权限
"""
import sys
import io
import os
import oracledb
from datetime import datetime
from pathlib import Path
# 设置 UTF-8 编码输出
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
# Load .env file
try:
from dotenv import load_dotenv
env_path = Path(__file__).resolve().parent.parent / '.env'
load_dotenv(env_path)
except ImportError:
pass
# 数据库连接信息 (从环境变量读取,必须在 .env 中设置)
DB_HOST = os.getenv('DB_HOST', '')
DB_PORT = os.getenv('DB_PORT', '1521')
DB_SERVICE = os.getenv('DB_SERVICE', '')
DB_USER = os.getenv('DB_USER', '')
DB_PASSWORD = os.getenv('DB_PASSWORD', '')
DB_CONFIG = {
'user': DB_USER,
'password': DB_PASSWORD,
'dsn': f'(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST={DB_HOST})(PORT={DB_PORT})))(CONNECT_DATA=(SERVICE_NAME={DB_SERVICE})))'
}
# MES 表列表
MES_TABLES = [
'DW_MES_CONTAINER',
'DW_MES_HOLDRELEASEHISTORY',
'DW_MES_JOB',
'DW_MES_LOTREJECTHISTORY',
'DW_MES_LOTWIPDATAHISTORY',
'DW_MES_LOTWIPHISTORY',
'DW_MES_MAINTENANCE',
'DW_MES_PARTREQUESTORDER',
'DW_MES_PJ_COMBINEDASSYLOTS',
'DW_MES_RESOURCESTATUS',
'DW_MES_RESOURCESTATUS_SHIFT',
'DW_MES_WIP',
'DW_MES_HM_LOTMOVEOUT',
'DW_MES_JOBTXNHISTORY',
'DW_MES_LOTMATERIALSHISTORY',
'DW_MES_RESOURCE'
]
def test_connection():
"""测试数据库连接"""
print("=" * 60)
print("Oracle Database Connection Test")
print("=" * 60)
print(f"Test Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Host: {DB_HOST}:{DB_PORT}")
print(f"Service Name: {DB_SERVICE}")
print(f"User: {DB_USER}")
print("=" * 60)
try:
# 尝试连接数据库
print("\n[1/3] Attempting to connect to database...")
connection = oracledb.connect(**DB_CONFIG)
print("[OK] Connection successful!")
# 获取数据库版本信息
print("\n[2/3] Retrieving database version...")
cursor = connection.cursor()
cursor.execute("SELECT * FROM v$version WHERE banner LIKE 'Oracle%'")
version = cursor.fetchone()
if version:
print(f"[OK] Database Version: {version[0]}")
# 测试每个表的访问权限
print("\n[3/3] Testing access to MES tables...")
print("-" * 60)
accessible_tables = []
inaccessible_tables = []
for table_name in MES_TABLES:
try:
# 尝试查询表的行数
cursor.execute(f"SELECT COUNT(*) FROM {table_name}")
count = cursor.fetchone()[0]
print(f"[OK] {table_name:<35} - {count:,} rows")
accessible_tables.append(table_name)
except oracledb.DatabaseError as e:
error_obj, = e.args
print(f"[FAIL] {table_name:<35} - Error: {error_obj.message}")
inaccessible_tables.append((table_name, error_obj.message))
# 汇总结果
print("\n" + "=" * 60)
print("Test Summary")
print("=" * 60)
print(f"Total tables tested: {len(MES_TABLES)}")
print(f"Accessible tables: {len(accessible_tables)}")
print(f"Inaccessible tables: {len(inaccessible_tables)}")
if inaccessible_tables:
print("\nInaccessible Tables:")
for table, error in inaccessible_tables:
print(f" - {table}: {error}")
# 关闭连接
cursor.close()
connection.close()
print("\n[OK] Connection closed successfully")
return len(inaccessible_tables) == 0
except oracledb.DatabaseError as e:
error_obj, = e.args
print(f"\n[FAIL] Database Error: {error_obj.message}")
print(f" Error Code: {error_obj.code}")
return False
except Exception as e:
print(f"\n[FAIL] Unexpected Error: {str(e)}")
return False
def main():
"""主函数"""
try:
success = test_connection()
print("\n" + "=" * 60)
if success:
print("[SUCCESS] All tests passed successfully!")
else:
print("[WARNING] Some tests failed. Please check the output above.")
print("=" * 60)
except KeyboardInterrupt:
print("\n\nTest interrupted by user.")
except Exception as e:
print(f"\n\nFatal error: {str(e)}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,194 @@
#!/usr/bin/env python3
"""
Generate a list of accessible TABLE/VIEW objects under a specific owner (default: DWH)
and update docs/Oracle_Authorized_Objects.md.
"""
import os
import sys
from collections import Counter
from datetime import datetime
from pathlib import Path
import oracledb
def load_env() -> None:
"""Load .env if available (best-effort)."""
try:
from dotenv import load_dotenv # type: ignore
env_path = Path(__file__).resolve().parent.parent / ".env"
load_dotenv(env_path)
return
except Exception:
pass
env_path = Path(__file__).resolve().parent.parent / ".env"
if not env_path.exists():
return
for line in env_path.read_text().splitlines():
if not line or line.strip().startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
os.environ.setdefault(key.strip(), value.strip())
def get_connection():
host = os.getenv("DB_HOST", "")
port = os.getenv("DB_PORT", "1521")
service = os.getenv("DB_SERVICE", "")
user = os.getenv("DB_USER", "")
password = os.getenv("DB_PASSWORD", "")
dsn = (
"(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)"
f"(HOST={host})(PORT={port})))(CONNECT_DATA=(SERVICE_NAME={service})))"
)
return oracledb.connect(user=user, password=password, dsn=dsn)
def main() -> int:
owner = "DWH"
output_path = Path("docs/Oracle_Authorized_Objects.md")
if len(sys.argv) > 1:
owner = sys.argv[1].strip().upper()
load_env()
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT USER FROM DUAL")
user = cur.fetchone()[0]
# Roles
cur.execute("SELECT GRANTED_ROLE FROM USER_ROLE_PRIVS")
roles = [r[0] for r in cur.fetchall()]
# Accessible objects under owner
cur.execute(
"""
SELECT OBJECT_NAME, OBJECT_TYPE
FROM ALL_OBJECTS
WHERE OWNER = :p_owner
AND OBJECT_TYPE IN ('TABLE', 'VIEW')
ORDER BY OBJECT_NAME
""",
p_owner=owner,
)
objects = cur.fetchall()
# Direct + PUBLIC grants
cur.execute(
"""
SELECT o.object_name, o.object_type, p.privilege,
CASE WHEN p.grantee = 'PUBLIC' THEN 'PUBLIC' ELSE 'DIRECT' END AS source
FROM all_tab_privs p
JOIN all_objects o
ON o.owner = p.table_schema
AND o.object_name = p.table_name
WHERE p.grantee IN (:p_user, 'PUBLIC')
AND o.owner = :p_owner
AND o.object_type IN ('TABLE', 'VIEW')
""",
p_user=user,
p_owner=owner,
)
direct_rows = cur.fetchall()
# Role grants
role_rows = []
for role in roles:
cur.execute(
"""
SELECT o.object_name, o.object_type, p.privilege, p.role AS source
FROM role_tab_privs p
JOIN all_objects o
ON o.owner = p.owner
AND o.object_name = p.table_name
WHERE p.role = :p_role
AND o.owner = :p_owner
AND o.object_type IN ('TABLE', 'VIEW')
""",
p_role=role,
p_owner=owner,
)
role_rows.extend(cur.fetchall())
# Aggregate privileges by object
info = {}
for name, otype in objects:
info[(name, otype)] = {"privs": set(), "sources": set()}
for name, otype, priv, source in direct_rows + role_rows:
key = (name, otype)
if key not in info:
info[key] = {"privs": set(), "sources": set()}
info[key]["privs"].add(priv)
info[key]["sources"].add(source)
# Fill in missing privilege/source if object is visible but not in grants
for key, data in info.items():
if not data["privs"]:
data["privs"].add("UNKNOWN")
if not data["sources"]:
data["sources"].add("SYSTEM")
type_counts = Counter(k[1] for k in info.keys())
source_counts = Counter()
for data in info.values():
for s in data["sources"]:
if s in ("DIRECT", "PUBLIC"):
source_counts[s] += 1
elif s == "SYSTEM":
source_counts["SYSTEM"] += 1
else:
source_counts["ROLE"] += 1
# Render markdown
lines = []
lines.append("# Oracle 可使用 TABLE/VIEW 清單DWH")
lines.append("")
lines.append(f"**產生時間**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
lines.append(f"**使用者**: {user}")
lines.append(f"**Schema**: {owner}")
lines.append("")
lines.append("## 摘要")
lines.append("")
lines.append(f"- 可使用物件總數: {len(info):,}")
lines.append(f"- TABLE: {type_counts.get('TABLE', 0):,}")
lines.append(f"- VIEW: {type_counts.get('VIEW', 0):,}")
lines.append(
"- 來源 (去重後物件數): "
f"DIRECT {source_counts.get('DIRECT', 0):,}, "
f"PUBLIC {source_counts.get('PUBLIC', 0):,}, "
f"ROLE {source_counts.get('ROLE', 0):,}, "
f"SYSTEM {source_counts.get('SYSTEM', 0):,}"
)
lines.append("")
lines.append("## 物件清單")
lines.append("")
lines.append("| 物件 | 類型 | 權限 | 授權來源 |")
lines.append("|------|------|------|----------|")
for name, otype in sorted(info.keys()):
data = info[(name, otype)]
obj = f"{owner}.{name}"
privs = ", ".join(sorted(data["privs"]))
sources = ", ".join(
sorted(
"ROLE" if s not in ("DIRECT", "PUBLIC", "SYSTEM") else s
for s in data["sources"]
)
)
lines.append(f"| `{obj}` | {otype} | {privs} | {sources} |")
output_path.write_text("\n".join(lines), encoding="utf-8")
cur.close()
conn.close()
print(f"Wrote {output_path} ({len(info)} objects)")
return 0
if __name__ == "__main__":
raise SystemExit(main())