Julin
1 year ago
3 changed files with 244 additions and 0 deletions
@ -0,0 +1,6 @@ |
|||||
|
[postgres] |
||||
|
host = 10.8.30.19 |
||||
|
port = 30432 |
||||
|
username = postgres |
||||
|
password = example |
||||
|
database = citybridge |
@ -0,0 +1,235 @@ |
|||||
|
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("数据库连接已关闭") |
@ -0,0 +1,3 @@ |
|||||
|
lxml==4.9.3 |
||||
|
psycopg2==2.9.7 |
||||
|
python-docx==0.8.11 |
Loading…
Reference in new issue