运维小工具
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

236 lines
8.4 KiB

import os
import logging
import configparser
import psycopg2
from docx import Document
from docx.shared import Inches, Pt, RGBColor
from docx.enum.text import WD_LINE_SPACING
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_CELL_VERTICAL_ALIGNMENT
from docx.oxml import parse_xml
from docx.oxml.ns import qn, nsdecls
LOG_DIR = 'log'
DOC_DIR = 'output'
def set_global_normal_style(doc):
style = doc.styles['Normal']
style.font.name = 'Times New Roman' # 英文、数字字体
style._element.rPr.rFonts.set(qn('w:eastAsia'), u'宋体') # 中文字体
style.font.size = Pt(10.5) # 五号
style.paragraph_format.space_before = 0 # 段前
style.paragraph_format.space_after = 0 # 段后
style.paragraph_format.line_spacing_rule = WD_LINE_SPACING.SINGLE # 单倍行距
def set_paragraph_space(p):
# 设置间距
p.paragraph_format.space_before = 0 # 段前
p.paragraph_format.space_after = 0 # 段后
p.paragraph_format.line_spacing = Pt(20) # 设置段落行距为20磅
def set_paragraph_format(p):
set_paragraph_space(p)
# 设置首行缩进2个字符
p.paragraph_format.first_line_indent = Inches(0.3)
def set_heading_format(h, r):
set_paragraph_space(h)
r.font.name = 'Times New Roman'
r._element.rPr.rFonts.set(qn('w:eastAsia'), u'宋体')
r.font.size = Pt(10.5) # 五号
r.font.color.rgb = RGBColor(0, 0, 0)
r.bold = True
def set_table_header_background_color(table, cols_count, bg_color):
shading_list = locals()
for i in range(cols_count):
shading_list['shading_elm_' + str(i)] = parse_xml(
r'<w:shd {} w:fill="{bgColor}"/>'.format(nsdecls('w'), bgColor=bg_color))
table.rows[0].cells[i]._tc.get_or_add_tcPr().append(shading_list['shading_elm_' + str(i)])
def insert_table_to_doc(doc, t_fields):
# 在文档中添加一个空表格
table = doc.add_table(rows=0, cols=6, style='Table Grid')
# 添加表格标题
heading_cells = table.add_row().cells
heading_cells[0].text = '序号'
heading_cells[1].text = '字段名称'
heading_cells[2].text = '类型'
heading_cells[3].text = '长度'
heading_cells[4].text = '必填'
heading_cells[5].text = '描述'
# 设置表头背景色(浅灰色)
set_table_header_background_color(table, 6, '#D9D9D9')
# 循环添加数据
for field in t_fields:
row_cells = table.add_row().cells
row_cells[0].text = f'{field["field_sn"]}'
row_cells[1].text = field["field_name"]
row_cells[2].text = field["field_type"]
row_cells[3].text = f'{field["field_length"]}' if field["field_length"] is not None else ''
row_cells[4].text = field["field_required"]
row_cells[5].text = f'{field["field_comment"]}' if field["field_comment"] is not None else ''
# 设置表格垂直对齐方式
table.alignment = WD_TABLE_ALIGNMENT.CENTER
# 设置单元格对齐方式
for row in table.rows:
for cell in row.cells:
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER # 垂直居中
cell.paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.CENTER # 水平居中
def write_tables_to_word():
# 创建一个新的Word文档
doc = Document()
set_global_normal_style(doc)
h = doc.add_heading(level=1)
r = h.add_run('一、数据表结构设计')
set_heading_format(h, r)
for t_data in tables_data:
t_fields = t_data['table_fields']
p = doc.add_paragraph('表名:{} 字段数目:{}'.format(t_data['table_name'], len(t_fields)))
set_paragraph_format(p)
p = doc.add_paragraph('描述:{}'.format(t_data['table_comment'] if t_data['table_comment'] is not None else ''))
set_paragraph_format(p)
insert_table_to_doc(doc, t_fields) # 将当前表的结构说明添加到文档
doc.add_paragraph() # 每个表后面加一个空行
# 保存文档
report_file_name = '数据库表结构设计.docx'
report_file = f'{DOC_DIR}/{report_file_name}'
doc.save(report_file)
logging.info('数据库表说明文档创建成功:{}'.format(report_file))
print('数据库表说明文档创建成功:{}'.format(report_file))
def create_postgres_connection():
# 获取 postgres 配置参数
ch_cfg = config['postgres']
host = os.environ.get('PG_HOST', ch_cfg['host'])
port = int(os.environ.get('PG_PORT', ch_cfg.getint('port')))
username = os.environ.get('PG_USERNAME', ch_cfg['username'])
password = os.environ.get('PG_PASSWORD', ch_cfg['password'])
database = os.environ.get('PG_DATABASE', ch_cfg['database'])
# 建立连接
try:
conn = psycopg2.connect(
host=host,
port=port,
user=username,
password=password,
dbname=database
)
logging.info("成功连接到数据库")
return conn
except psycopg2.Error as e:
logging.error("数据库连接错误:{}".format(e))
raise RuntimeError('数据库连接错误:', error)
def querystring_all_tables():
return '''
select relname as table_name,(select description from pg_description where objoid=oid and objsubid=0)
as table_comment from pg_class where relkind ='r' and relname NOT LIKE 'pg%' AND relname NOT LIKE 'sql_%'
order by table_name;
'''
def querystring_fields_in_table(t_name):
return '''
SELECT
ROW_NUMBER() OVER () AS 序号,
a.attname as 字段名称,
CASE
WHEN format_type(a.atttypid, a.atttypmod) ~ '^character varying\(' THEN 'varchar'
WHEN format_type(a.atttypid, a.atttypmod) ~ '^numeric\(' THEN 'numeric'
WHEN format_type(a.atttypid, a.atttypmod) ~ '^timestamp' THEN 'timestamp'
ELSE format_type(a.atttypid, a.atttypmod)
END as 类型,
CASE
WHEN a.atttypmod - 4 > 0 THEN
CASE
WHEN format_type(a.atttypid, a.atttypmod) ~ '^numeric\(' THEN
substring(format_type(a.atttypid, a.atttypmod) from '\((\d+,\d+)\)')
WHEN format_type(a.atttypid, a.atttypmod) ~ '^timestamp' THEN NULL
ELSE '' || a.atttypmod - 4
END
ELSE NULL
END as 长度,
(CASE WHEN a.attnotnull = true THEN '' ELSE '' END) as 必填,
col_description(a.attrelid, a.attnum) as 描述
FROM pg_attribute a
WHERE attstattarget = -1 AND attrelid = (SELECT oid FROM pg_class WHERE relname = '{}');
'''.format(t_name)
def try_create_dir(dir_name):
# 判断目录是否存在,如果不存在,则创建目录
if not os.path.exists(dir_name):
print(f"目录'{dir_name}'不存在,即将创建...")
os.makedirs(dir_name)
print(f"目录'{dir_name}'创建成功!")
if __name__ == '__main__':
try:
try_create_dir(DOC_DIR)
try_create_dir(LOG_DIR)
logging.basicConfig(filename='{}/runtime.log'.format(LOG_DIR), level=logging.INFO,
format='%(asctime)s.%(msecs)03d - %(levelname)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
config = configparser.ConfigParser()
config.read('config.ini')
connection = create_postgres_connection()
cursor = connection.cursor()
cursor.execute(querystring_all_tables())
table_rows = cursor.fetchall()
tables_data = []
for (table_name, table_comment) in table_rows:
cursor.execute(querystring_fields_in_table(table_name))
field_rows = cursor.fetchall()
fields_data = []
for field in field_rows:
fields_data.append({
'field_sn': field[0], # 序号
'field_name': field[1], # 字段名称
'field_type': field[2], # 类型
'field_length': field[3], # 长度
'field_required': field[4], # 必填
'field_comment': field[5] # 描述
})
tables_data.append({
'table_name': table_name,
'table_comment': table_comment,
'table_fields': fields_data
})
cursor.close()
write_tables_to_word() # 生成 word 文档
except Exception as error:
print('程序运行出错:', error)
logging.error('程序运行出错:{}'.format(error))
# 关闭数据库连接
finally:
if connection:
connection.close()
logging.info("数据库连接已关闭")