Files
MesUniversalApi-Migration/working/render_mescommonbase_flowchart.py

262 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
from pathlib import Path
import math
from PIL import Image, ImageDraw, ImageFont
BASE_DIR = Path(__file__).resolve().parent
OUT = BASE_DIR / "MESCommonBase流程图.png"
W, H = 5200, 3800
img = Image.new("RGB", (W, H), "#F7F9FC")
draw = ImageDraw.Draw(img)
FONT_CANDIDATES = [
r"C:\Windows\Fonts\NotoSansSC-VF.ttf",
r"C:\Windows\Fonts\simhei.ttf",
r"C:\Windows\Fonts\Deng.ttf",
r"C:\Windows\Fonts\simsun.ttc",
]
def load_font(size, bold=False):
candidates = []
if bold:
candidates.extend([r"C:\Windows\Fonts\Dengb.ttf", r"C:\Windows\Fonts\simhei.ttf"])
candidates.extend(FONT_CANDIDATES)
for path in candidates:
try:
return ImageFont.truetype(path, size=size)
except Exception:
pass
return ImageFont.load_default()
font_title = load_font(72, True)
font_subtitle = load_font(34)
font_box = load_font(31)
font_box_small = load_font(27)
font_panel_title = load_font(42, True)
font_note = load_font(28)
font_edge = load_font(26)
def text_size(text, font):
bbox = draw.textbbox((0, 0), text, font=font)
return bbox[2] - bbox[0], bbox[3] - bbox[1]
def wrap_line(line, font, max_width):
if not line:
return [""]
lines = []
current = ""
for ch in line:
test = current + ch
if text_size(test, font)[0] <= max_width or not current:
current = test
else:
lines.append(current)
current = ch
if current:
lines.append(current)
return lines
def wrap_text(text, font, max_width):
lines = []
for raw in text.split("\n"):
lines.extend(wrap_line(raw, font, max_width))
return lines
def draw_multiline_center(text, cx, cy, max_width, font, fill="#172033", line_gap=12):
lines = wrap_text(text, font, max_width)
heights = [text_size(line, font)[1] for line in lines]
total_h = sum(heights) + line_gap * (len(lines) - 1)
y = cy - total_h / 2
for line, h in zip(lines, heights):
width, _ = text_size(line, font)
draw.text((cx - width / 2, y), line, font=font, fill=fill)
y += h + line_gap
def draw_multiline_left(text, x, y, max_width, font, fill="#172033", line_gap=10):
lines = wrap_text(text, font, max_width)
yy = y
for line in lines:
draw.text((x, yy), line, font=font, fill=fill)
yy += text_size(line, font)[1] + line_gap
return yy
def box_bounds(cx, cy, w, h):
return (cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2)
def draw_box(cx, cy, w, h, text, fill="#FFFFFF", outline="#2E5A8A", width=5, font=None, radius=34):
font = font or font_box
bounds = box_bounds(cx, cy, w, h)
draw.rounded_rectangle(bounds, radius=radius, fill=fill, outline=outline, width=width)
draw_multiline_center(text, cx, cy, w - 70, font)
return bounds
def draw_diamond(cx, cy, w, h, text, fill="#FFF4CC", outline="#A66F00", width=5, font=None):
font = font or font_box
points = [(cx, cy - h / 2), (cx + w / 2, cy), (cx, cy + h / 2), (cx - w / 2, cy)]
draw.polygon(points, fill=fill, outline=outline)
for i in range(width):
offset = i * 0.9
border_points = [
(cx, cy - h / 2 + offset),
(cx + w / 2 - offset, cy),
(cx, cy + h / 2 - offset),
(cx - w / 2 + offset, cy),
]
draw.line(border_points + [border_points[0]], fill=outline, width=1)
draw_multiline_center(text, cx, cy, w - 80, font)
return points
def draw_panel(x, y, w, h, title, fill="#FFFFFF", outline="#B9C4D6"):
draw.rounded_rectangle((x, y, x + w, y + h), radius=34, fill=fill, outline=outline, width=5)
draw.text((x + 45, y + 28), title, font=font_panel_title, fill="#172033")
def arrow(points, color="#344054", width=6, label=None, label_pos=None):
for p1, p2 in zip(points, points[1:]):
draw.line((p1[0], p1[1], p2[0], p2[1]), fill=color, width=width)
x1, y1 = points[-2]
x2, y2 = points[-1]
angle = math.atan2(y2 - y1, x2 - x1)
size = 24
left = (x2 - size * math.cos(angle - math.pi / 7), y2 - size * math.sin(angle - math.pi / 7))
right = (x2 - size * math.cos(angle + math.pi / 7), y2 - size * math.sin(angle + math.pi / 7))
draw.polygon([(x2, y2), left, right], fill=color)
if label:
lx, ly = label_pos if label_pos else ((x1 + x2) / 2, (y1 + y2) / 2)
tw, th = text_size(label, font_edge)
draw.rounded_rectangle(
(lx - tw / 2 - 14, ly - th / 2 - 8, lx + tw / 2 + 14, ly + th / 2 + 8),
radius=12,
fill="#F7F9FC",
)
draw.text((lx - tw / 2, ly - th / 2), label, font=font_edge, fill=color)
def draw_title():
main_title = "MESCommonBase.ashx 主流程图"
subtitle = "入口解析 -> Type 分发 -> DataLink/ExcelWebCall 执行 -> 响应输出"
tw, _ = text_size(main_title, font_title)
draw.text(((W - tw) / 2, 45), main_title, font=font_title, fill="#111827")
tw, _ = text_size(subtitle, font_subtitle)
draw.text(((W - tw) / 2, 132), subtitle, font=font_subtitle, fill="#475467")
def draw_top_flow():
cx = W / 2
draw_box(cx, 285, 900, 125, "HTTP 请求\nMES_Manage/submit/MESCommonBase.ashx", "#E8F1FF", "#2864B4", font=font_box)
draw_box(cx, 475, 1320, 145, "ProcessRequest\n设置 application/json读取 Request.InputStream", "#E8F1FF", "#2864B4", font=font_box)
draw_box(cx, 670, 1320, 145, "JsonMapper.ToObject(stream)\n尝试读取 Type / type", "#EAF7EF", "#227A45", font=font_box)
draw_box(cx, 865, 1520, 145, "JavaScriptSerializer.Deserialize<jsonobj>(stream)\n成功后用 dataobj.Type 覆盖 type", "#EAF7EF", "#227A45", font=font_box)
draw_diamond(cx, 1090, 520, 190, "dataobj\n== null?", "#FFF4CC", "#B27700", font=font_box)
draw_box(3890, 1090, 840, 130, '读取 Request["param"]\n再次解析 Type / type', "#FFF7E6", "#B27700", font=font_box_small)
draw_diamond(cx, 1315, 500, 180, "switch\n(type)", "#FFF4CC", "#B27700", font=font_box)
arrow([(cx, 348), (cx, 402)])
arrow([(cx, 548), (cx, 598)])
arrow([(cx, 742), (cx, 792)])
arrow([(cx, 938), (cx, 995)])
arrow([(cx + 260, 1090), (3470, 1090)], label="", label_pos=(3140, 1050))
arrow([(3890, 1155), (3890, 1240), (2850, 1240), (2850, 1315)])
arrow([(cx, 1185), (cx, 1225)], label="", label_pos=(2555, 1210))
def draw_branch_panel():
panel_x, panel_y, panel_w, panel_h = 160, 1460, 4880, 1120
draw_panel(panel_x, panel_y, panel_w, panel_h, "Type 分发分支", fill="#FFFFFF", outline="#C7D2E5")
arrow([(W / 2, 1405), (W / 2, panel_y + 10)])
card_w, card_h = 1080, 330
xs = [790, 1960, 3130, 4300]
y1, y2 = 1740, 2205
cards = [
(xs[0], y1, "2001 Excel 导出\nExcelWebCall.ExcelFile(jsonData)\n生成 bytes / fileName / extension\n响应:二进制下载", "#EAF7EF", "#227A45"),
(xs[1], y1, "2002 PDF 导出\nExcelWebCall.ExcelFilePdf(jsonData)\nExcel 转 PDF\n响应:二进制下载", "#EAF7EF", "#227A45"),
(xs[2], y1, "2003 合成 Excel 图片/扩展名导出\n读取 Form 第一项 dataimg[0]\nExcelWebCall.ExcelFile(jsonData)\n响应:二进制下载", "#EAF7EF", "#227A45"),
(xs[3], y1, "2004 数据库文件下载\nDataLink.ExePROCEDURE_Type2004\n返回 bytes / fileName / extension\n响应:二进制下载", "#EAF7EF", "#227A45"),
(xs[0], y2, "15 文件上传\n读取 Request.Files[0]\n拆分 name / suffix / bytes\nDataLink.ExePROCEDURE_Type15\n响应JSON", "#EEF2FF", "#4F46E5"),
(xs[1], y2, "16 数据库文件下载\nDataLink.ExePROCEDURE_Type16\n返回 fileName / suffix / bytes\nbytes 为 null 时直接 return", "#EAF7EF", "#227A45"),
(xs[2], y2, "4000 IP 查询\n读取 ServerVariables / UserHostAddress\n响应:文本 IP", "#FFF7E6", "#B27700"),
(xs[3], y2, "default 通用业务调用\nDataLink.SqlWebCall(type,jsonData,dataobj)\n进入数据库/存储过程二次分发", "#F3E8FF", "#7E22CE"),
]
for cx, cy, text, fill, outline in cards:
draw_box(cx, cy, card_w, card_h, text, fill, outline, font=font_box_small, radius=28)
return panel_x, panel_y, panel_w, panel_h, xs, y2, card_h
def draw_detail_panels(panel_info):
panel_x, panel_y, panel_w, panel_h, xs, y2, card_h = panel_info
resp_x, resp_y, resp_w, resp_h = 260, 2740, 2050, 740
detail_x, detail_y, detail_w, detail_h = 2520, 2740, 2420, 740
draw_panel(resp_x, resp_y, resp_w, resp_h, "响应输出", fill="#FFFFFF", outline="#C7D2E5")
resp_text = (
"文件分支 2001/2002/2003/2004/16\n"
" application/octet-stream + Content-Disposition\n"
" BinaryWrite(bytes) + Flush/End\n\n"
"Type 15 与 default\n"
" Response.Write(JSON 文本)\n\n"
"Type 4000\n"
" Response.Write(userIP)\n\n"
"外层 catch异常时写当前 responseText初始值为 NULL"
)
draw_multiline_left(resp_text, resp_x + 60, resp_y + 115, resp_w - 120, font_note, fill="#344054", line_gap=12)
draw_panel(detail_x, detail_y, detail_w, detail_h, "DataLink.SqlWebCall 默认分支", fill="#FFFFFF", outline="#C7D2E5")
detail_text = (
"初始化InitSystemReg 读取 Web.config 的 ConnectionString\n\n"
"8888 / 5001 / 5002加密、注册/改密、登录并生成 token\n\n"
"1 / 2 / 5旧协议存储过程Param 为拼接字符串\n\n"
"11 / 111 / 12 / 13 / 21新协议存储过程Param 为 JSON 数组字符串\n\n"
"1001 / 1002 / 3 / 4 / 7 / 22 / 3001直接 SQL、建表导入或 SQL 命令执行\n\n"
"输出:数组 JSON 或 { code, message, data }"
)
draw_multiline_left(detail_text, detail_x + 60, detail_y + 115, detail_w - 120, font_note, fill="#344054", line_gap=12)
arrow([(xs[3], y2 + card_h / 2), (xs[3], 2630), (detail_x + detail_w / 2, 2630), (detail_x + detail_w / 2, detail_y)], color="#7E22CE")
arrow([(panel_x + 1150, panel_y + panel_h), (panel_x + 1150, resp_y)], color="#227A45")
arrow([(detail_x, detail_y + detail_h / 2), (resp_x + resp_w, detail_y + detail_h / 2)], color="#7E22CE")
return resp_x, resp_y, resp_w, resp_h
def draw_note_and_end(resp_panel):
resp_x, resp_y, resp_w, resp_h = resp_panel
note_x, note_y, note_w, note_h = 520, 3545, 4160, 150
draw.rounded_rectangle((note_x, note_y, note_x + note_w, note_y + note_h), radius=30, fill="#FFF1F3", outline="#C01048", width=5)
note = "关键注意:入口层缺少统一鉴权和白名单;客户端可通过 Type + Name/Param 触发 SQL 或存储过程能力,异常多数不记录日志。"
draw_multiline_center(note, note_x + note_w / 2, note_y + note_h / 2, note_w - 120, font_note, fill="#7A271A", line_gap=8)
end_x, end_y = W - 560, H - 125
draw_box(end_x, end_y, 420, 95, "请求结束", "#E8F1FF", "#2864B4", font=font_box_small, radius=45)
arrow([(resp_x + resp_w / 2, resp_y + resp_h), (resp_x + resp_w / 2, H - 125), (end_x - 210, H - 125)], color="#344054")
footer = "源文件working/MESCommonBase流程图.mmd PNGworking/MESCommonBase流程图.png"
fw, _ = text_size(footer, font_edge)
draw.text(((W - fw) / 2, H - 45), footer, font=font_edge, fill="#667085")
draw_title()
draw_top_flow()
panel_info = draw_branch_panel()
resp_panel = draw_detail_panels(panel_info)
draw_note_and_end(resp_panel)
img.save(OUT, format="PNG", dpi=(220, 220), optimize=True)
print(OUT)
print(f"{W}x{H}")