object_to_html.py
python
import cx_Oracle
import jinja2
import os
import datetime
from datetime import datetime
cx_Oracle.init_oracle_client(lib_dir=r'D:\oracleclient\instantclient_23_8')
def get_oracle_connection(config):
"""建立Oracle数据库连接"""
try:
dsn = cx_Oracle.makedsn(
config['host'],
config['port'],
service_name=config['service_name']
)
connection = cx_Oracle.connect(
user=config['username'],
password=config['password'],
dsn=dsn,
encoding="UTF-8",
nencoding="UTF-8"
)
return connection
except cx_Oracle.Error as error:
print(f"连接数据库时出错: {error}")
return None
def fetch_user_details(connection):
"""获取所有用户及其默认表空间信息"""
users = []
try:
cursor = connection.cursor()
cursor.execute("""
SELECT username, default_tablespace
FROM dba_users
WHERE username IN ('EDA_PROD','PMS_CORE','PMS_PORTAL','PMS_SEATA','PMS_SYSTEM','PMS_USER','RPT_DW','RPT_DW_READ','RPT_ODS','SPC_PROD')
ORDER BY username
""")
for row in cursor:
users.append({
'username': row[0],
'tablespace_name': row[1],
'tables': [],
'views': [],
'indexes': [],
'procedures': [],
'triggers': [],
'synonyms': [],
'sequences': [],
'functions': [],
'packages': []
})
cursor.close()
except cx_Oracle.Error as error:
print(f"获取用户信息时出错: {error}")
return users
def fetch_object_details(connection, users):
"""获取每个用户的对象详细信息及创建语句(从all_objects获取创建时间)"""
try:
cursor = connection.cursor()
# 定义对象类型及其查询配置(从all_objects获取创建时间)
object_types = {
'tables': {
'base_query': """
SELECT t.table_name, o.created, t.comments,o.owner as zso
FROM all_tab_comments t
JOIN all_objects o ON t.owner = o.owner AND t.table_name = o.object_name AND o.object_type = 'TABLE'
WHERE t.owner = :owner
""",
'object_type': 'TABLE',
'name_field': 'table_name',
'comment_field': 'comments',
'date_field': 'created'
},
'views': {
'base_query': """
SELECT v.table_name as view_name, o.created, v.comments,o.owner as zso
FROM all_tab_comments v
JOIN all_objects o ON v.owner = o.owner AND v.table_name = o.object_name AND o.object_type = 'VIEW'
WHERE v.owner = :owner
""",
'object_type': 'VIEW',
'name_field': 'view_name',
'comment_field': 'comments',
'date_field': 'created'
},
'indexes': {
'base_query': """
SELECT i.index_name, o.created, i.uniqueness,o.owner as zso
FROM dba_indexes i
JOIN all_objects o ON i.owner = o.owner AND i.index_name = o.object_name AND o.object_type = 'INDEX'
WHERE i.owner = :owner
""",
'object_type': 'INDEX',
'name_field': 'index_name',
'comment_field': 'uniqueness',
'date_field': 'created'
},
'procedures': {
'base_query': """
SELECT o.object_name, o.created, p.authid as comments,o.owner as zso
FROM all_objects o
JOIN dba_procedures p ON o.owner = p.owner AND o.object_name = p.object_name and o.object_type = 'PROCEDURE'
WHERE o.owner = :owner
""",
'object_type': 'PROCEDURE',
'name_field': 'object_name',
'comment_field': 'comments',
'date_field': 'created'
},
'triggers': {
'base_query': """
SELECT t.trigger_name, o.created, t.trigger_type,o.owner as zso
FROM dba_triggers t
JOIN all_objects o ON t.owner = o.owner AND t.trigger_name = o.object_name AND o.object_type = 'TRIGGER'
WHERE t.owner = :owner
""",
'object_type': 'TRIGGER',
'name_field': 'trigger_name',
'comment_field': 'trigger_type',
'date_field': 'created'
},
'synonyms': {
'base_query': """
SELECT s.synonym_name, o.created, s.table_owner || '.' || s.table_name||'-'||s.owner as comments,o.owner as zso
FROM dba_synonyms s
JOIN all_objects o ON s.synonym_name = o.object_name AND o.object_type = 'SYNONYM'
WHERE s.table_owner = :owner
""",
'object_type': 'SYNONYM',
'name_field': 'synonym_name',
'comment_field': 'comments',
'date_field': 'created'
},
'sequences': {
'base_query': """
SELECT s.sequence_name, o.created, s.increment_by,o.owner as zso
FROM dba_sequences s
JOIN all_objects o ON s.sequence_owner = o.owner AND s.sequence_name = o.object_name AND o.object_type = 'SEQUENCE'
WHERE o.owner = :owner
""",
'object_type': 'SEQUENCE',
'name_field': 'sequence_name',
'comment_field': 'increment_by',
'date_field': 'created'
},
'functions': {
'base_query': """
SELECT o.object_name, o.created, f.authid as comments ,o.owner as zso
FROM all_objects o
JOIN dba_procedures f ON o.owner = f.owner AND o.object_name = f.object_name and o.object_type = 'FUNCTION'
WHERE o.owner = :owner
""",
'object_type': 'FUNCTION',
'name_field': 'object_name',
'comment_field': 'comments',
'date_field': 'created'
},
'packages': {
'base_query': """
SELECT o.object_name, o.created, p.authid as comments ,o.owner as zso
FROM all_objects o
JOIN dba_procedures p ON o.owner = p.owner AND o.object_name = p.object_name and o.object_type = 'PACKAGE'
WHERE o.owner = :owner
""",
'object_type': 'PACKAGE',
'name_field': 'object_name',
'comment_field': 'comments',
'date_field': 'created'
}
}
# 为每个用户获取每种对象的详细信息及DDL
for user in users:
owner = user['username']
for obj_type, obj_config in object_types.items():
try:
# 先获取对象基本信息(从all_objects关联获取创建时间)
cursor.execute(obj_config['base_query'], {'owner': owner})
obj_list = []
for row in cursor:
obj = {
'name': row[0],
'created_at': row[1].strftime('%Y-%m-%d %H:%M:%S') if row[1] else '未知',
'comment': row[2] if row[2] else '无' ,
'zso': row[3]
}
obj_list.append(obj)
# 批量获取DDL以提高性能
if obj_list:
ddl_cursor = connection.cursor()
for obj in obj_list:
try:
# 使用DBMS_METADATA获取DDL
ddl_cursor.execute(
f"SELECT DBMS_METADATA.GET_DDL(:obj_type, :obj_name, :owner) FROM dual",
{
'obj_type': obj_config['object_type'],
'obj_name': obj['name'],
'owner': obj['zso']
}
)
ddl_row = ddl_cursor.fetchone()
obj['create_statement'] = ddl_row[0] if ddl_row and ddl_row[0] else '无法获取DDL'
except cx_Oracle.Error as ddl_error:
obj['create_statement'] = f"获取DDL失败: {str(ddl_error)}"
ddl_cursor.close()
user[obj_type] = obj_list
except cx_Oracle.Error as error:
print(f"获取{obj_type}信息时出错 (用户: {owner}): {error}")
cursor.close()
except cx_Oracle.Error as error:
print(f"获取对象详细信息时出错: {error}")
return users
def fetch_object_counts(users):
"""根据对象详细信息计算数量"""
for user in users:
user['table_count'] = len(user['tables'])
user['view_count'] = len(user['views'])
user['index_count'] = len(user['indexes'])
user['procedure_count'] = len(user['procedures'])
user['trigger_count'] = len(user['triggers'])
user['synonym_count'] = len(user['synonyms'])
user['sequence_count'] = len(user['sequences'])
user['function_count'] = len(user['functions'])
user['package_count'] = len(user['packages'])
return users
def generate_report(users, template_path, output_path,dbname):
"""使用Jinja2模板生成HTML报告(包含DDL转义处理)"""
try:
# 准备模板渲染数据
report_data = {
'dbname':dbname,
'users': users,
'generate_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
# 设置has_*标志,控制列显示
'has_tables': any(len(user['tables']) > 0 for user in users),
'has_views': any(len(user['views']) > 0 for user in users),
'has_indexes': any(len(user['indexes']) > 0 for user in users),
'has_procedures': any(len(user['procedures']) > 0 for user in users),
'has_triggers': any(len(user['triggers']) > 0 for user in users),
'has_synonyms': any(len(user['synonyms']) > 0 for user in users),
'has_sequences': any(len(user['sequences']) > 0 for user in users),
'has_functions': any(len(user['functions']) > 0 for user in users),
'has_packages': any(len(user['packages']) > 0 for user in users)
}
# 加载模板
env = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(template_path)),
# autoescape=True # 启用自动转义,防止HTML注入
)
# 自定义过滤器:转义DDL中的HTML特殊字符
env.filters['escape_ddl'] = lambda ddl: ddl.replace('<', '<').replace('>', '>').replace('&', '&')
template = env.get_template(os.path.basename(template_path))
# 渲染模板并保存
with open(output_path, 'w', encoding='utf-8') as f:
f.write(template.render(report_data))
print(f"报告已生成: {output_path}")
except Exception as e:
print(f"生成报告时出错: {e}")
def main():
"""主函数"""
# 数据库连接配置(请修改为实际配置)
db_config = {
'host': '10.12.8.61',
'port': 1521,
'service_name': 'edadb',
'username': 'system',
'password': 'Zeta@xxxx#$'
}
# 模板和输出文件路径
template_path = 'object_template.html'
output_path = 'edadb_object.html'
# 连接数据库
connection = get_oracle_connection(db_config)
if not connection:
return
try:
# 获取用户信息
users = fetch_user_details(connection)
if not users:
print("未找到用户信息")
return
# 获取对象详细信息及DDL
users = fetch_object_details(connection, users)
# 计算对象数量
users = fetch_object_counts(users)
# 生成报告
generate_report(users, template_path, output_path,dbname='EDADB')
finally:
# 关闭数据库连接
if connection:
connection.close()
print("数据库连接已关闭")
if __name__ == "__main__":
main()
手动筛选期望导出的用户
object_template.html
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.8, maximum-scale=1.5">
<title>{{ dbname }}对象统计报告</title>
<style>
/* 响应式核心样式 */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Inter", sans-serif;
background-color: #f5f7fa;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-width: 320px;
}
.page-title {
text-align: center;
margin-bottom: 30px;
}
.main-table {
width: 100%;
overflow-x: auto; /* 小屏幕显示横向滚动条 */
margin-bottom: 30px;
box-shadow: 0 2px 12px rgba(0,0,0,0.05);
border-radius: 10px;
background-color: white;
}
.user-table {
min-width: 800px;
border-collapse: collapse;
}
.user-table th,
.user-table td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
.user-table th {
background-color: #f8f9fa;
font-weight: 600;
white-space: nowrap;
}
.user-table tr:hover {
background-color: #fafafa;
}
/* 响应式断点 */
@media (max-width: 768px) {
.user-table th:nth-child(5),
.user-table td:nth-child(5),
.user-table th:nth-child(6),
.user-table td:nth-child(6) {
display: none; /* 手机端隐藏存储过程和触发器列 */
}
}
@media (max-width: 576px) {
.user-table th:nth-child(7),
.user-table td:nth-child(7),
.user-table th:nth-child(8),
.user-table td:nth-child(8) {
display: none; /* 超小屏隐藏同义词和序列列 */
}
}
/* 详情模块 */
.user-details {
margin-bottom: 30px;
background-color: white;
border-radius: 10px;
box-shadow: 0 2px 12px rgba(0,0,0,0.05);
padding: 20px;
}
.tab-nav {
display: flex;
overflow-x: auto; /* 选项卡横向滚动 */
padding-bottom: 15px;
border-bottom: 1px solid #e0e0e0;
}
.tab {
padding: 10px 18px;
cursor: pointer;
white-space: nowrap;
margin-right: 15px;
color: #666;
}
.tab.active {
color: #165dff;
border-bottom: 2px solid #165dff;
font-weight: 500;
}
.tab-content {
padding: 20px 0;
display: none;
}
.tab-content.active {
display: block;
}
.object-table {
width: 100%;
margin-bottom: 20px;
border-collapse: collapse;
}
.object-table th,
.object-table td {
padding: 10px 15px;
border-bottom: 1px solid #f0f0f0;
}
.object-table th {
background-color: #f8f9fa;
font-weight: 500;
white-space: nowrap;
}
.code-block {
background-color: #f5f7fa;
padding: 15px;
margin-top: 10px;
border-radius: 6px;
font-family: "Consolas", monospace;
font-size: 0.9em;
line-height: 1.4;
overflow-x: auto;
}
/* 操作按钮 */
.action-btn {
background-color: #165dff;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
transition: opacity 0.3s;
}
.action-btn:hover {
opacity: 0.9;
}
/* 隐藏空模块 */
.hidden {
display: none !important;
}
</style>
</head>
<body>
<div class="container">
<div class="page-title">
<h1 class="text-2xl font-bold">{{ dbname }}对象统计报告</h1>
<p class="text-gray-500 mt-2">生成时间: {{ generate_time }}</p>
</div>
<div class="main-table">
<h2 class="text-lg font-bold mb-4">用户对象统计信息</h2>
<table class="user-table">
<thead>
<tr>
<th>用户名</th>
<th>默认表空间</th>
{% if has_tables %}<th>表数量</th>{% endif %}
{% if has_views %}<th>视图数量</th>{% endif %}
{% if has_indexes %}<th>索引数量</th>{% endif %}
{% if has_procedures %}<th>存储过程</th>{% endif %}
{% if has_triggers %}<th>触发器数量</th>{% endif %}
{% if has_synonyms %}<th>同义词数量</th>{% endif %}
{% if has_sequences %}<th>序列数量</th>{% endif %}
{% if has_functions %}<th>函数数量</th>{% endif %}
{% if has_packages %}<th>包数量</th>{% endif %}
<th>操作</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.tablespace_name }}</td>
{% if has_tables %}<td>{{ user.table_count }}</td>{% endif %}
{% if has_views %}<td>{{ user.view_count }}</td>{% endif %}
{% if has_indexes %}<td>{{ user.index_count }}</td>{% endif %}
{% if has_procedures %}<td>{{ user.procedure_count }}</td>{% endif %}
{% if has_triggers %}<td>{{ user.trigger_count }}</td>{% endif %}
{% if has_synonyms %}<td>{{ user.synonym_count }}</td>{% endif %}
{% if has_sequences %}<td>{{ user.sequence_count }}</td>{% endif %}
{% if has_functions %}<td>{{ user.function_count }}</td>{% endif %}
{% if has_packages %}<td>{{ user.package_count }}</td>{% endif %}
<td>
<button class="action-btn" onclick="toggleDetails('{{ user.username }}')">
查看详情
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% for user in users %}
<div id="{{ user.username }}_details" class="user-details hidden">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold">用户: {{ user.username }}</h3>
<button class="action-btn" onclick="hideDetails('{{ user.username }}')">
收起
</button>
</div>
<div class="tab-nav">
{% set object_types = ['tables', 'views', 'indexes', 'procedures', 'triggers', 'synonyms', 'sequences', 'functions','packages'] %}
{% for type in object_types %}
{% set items = user[type] %}
{% if items|length > 0 %}
<div class="tab" data-target="{{ user.username }}_{{ type }}">
{{ type|capitalize|replace('_', ' ') }}
</div>
{% endif %}
{% endfor %}
</div>
{% for type in object_types %}
{% set items = user[type] %}
{% if items|length > 0 %}
<div id="{{ user.username }}_{{ type }}" class="tab-content">
<h4 class="text-md font-semibold mb-3">
{{ type|capitalize|replace('_', ' ') }} 列表 (共 {{ items|length }} 个)
</h4>
<table class="object-table">
<thead>
<tr>
<th>对象名</th>
<th>创建时间</th>
<th>对象注释</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td>{{ item.name }}</td>
<td>{{ item.created_at }}</td>
<td>{{ item.comment|default('无') }}</td>
<td>
<button class="action-btn" onclick="toggleCode('{{ user.username }}', '{{ type }}', '{{ item.name }}')">
查看语句
</button>
</td>
</tr>
<tr id="{{ user.username }}_{{ type }}_{{ item.name }}_code" style="display: none;">
<td colspan="4">
<div class="code-block">
{{ item.create_statement|replace('\n', '<br>')}}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endfor %}
</div>
{% endfor %}
<script>
function toggleDetails(username) {
const details = document.getElementById(`${username}_details`);
details.classList.toggle('hidden');
if (!details.classList.contains('hidden')) {
// 激活第一个存在的选项卡
const firstTab = details.querySelector('.tab');
if (firstTab) {
activateTab(firstTab);
}
}
}
function hideDetails(username) {
const details = document.getElementById(`${username}_details`);
details.classList.add('hidden');
}
function toggleCode(username, type, name) {
const codeBlock = document.getElementById(`${username}_${type}_${name}_code`);
if (codeBlock.style.display === 'none') {
codeBlock.style.display = 'table-row';
} else {
codeBlock.style.display = 'none';
}
}
function activateTab(tab) {
const target = tab.getAttribute('data-target');
// 移除其他选项卡的激活状态
const tabs = tab.closest('.user-details').querySelectorAll('.tab');
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
// 隐藏所有内容
const contents = tab.closest('.user-details').querySelectorAll('.tab-content');
contents.forEach(c => c.classList.remove('active'));
// 显示目标内容
document.getElementById(target).classList.add('active');
}
// 选项卡切换
document.addEventListener('click', (e) => {
if (e.target.classList.contains('tab')) {
activateTab(e.target);
}
});
</script>
</div>
</body>
</html>