Files
MesUniversalApi-Migration/working/build_mescommonbase_migration_ppt.py

597 lines
28 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
from datetime import date
from PIL import Image
from pptx import Presentation
from pptx.dml.color import RGBColor
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
from pptx.enum.text import PP_ALIGN
from pptx.util import Inches, Pt
BASE = Path(__file__).resolve().parent
ASSET_DIR = BASE / "ppt_assets"
OUT = BASE / "MESCommonBase迁移分析资料包.pptx"
COLORS = {
"bg": RGBColor(247, 249, 252),
"navy": RGBColor(17, 24, 39),
"text": RGBColor(52, 64, 84),
"muted": RGBColor(102, 112, 133),
"line": RGBColor(199, 210, 229),
"blue": RGBColor(40, 100, 180),
"green": RGBColor(34, 122, 69),
"purple": RGBColor(126, 34, 206),
"amber": RGBColor(178, 119, 0),
"red": RGBColor(192, 16, 72),
"blue_light": RGBColor(232, 241, 255),
"green_light": RGBColor(234, 247, 239),
"purple_light": RGBColor(243, 232, 255),
"amber_light": RGBColor(255, 247, 230),
"red_light": RGBColor(255, 241, 243),
"white": RGBColor(255, 255, 255),
}
FONT = "Microsoft YaHei"
FONT_ALT = "SimHei"
def crop_images():
ASSET_DIR.mkdir(exist_ok=True)
specs = [
(
"MESCommonBase流程图.png",
[
("总流程_完整.png", None),
("总流程_入口解析.png", (1450, 0, 3800, 1450)),
("总流程_Type分支.png", (110, 1450, 5100, 2580)),
("总流程_响应默认.png", (190, 2600, 5000, 3520)),
],
),
(
"MESCommonBase响应输出与SqlWebCall主流程图.png",
[
("响应主_完整.png", None),
("响应主_分支出口.png", (120, 760, 5280, 2050)),
("响应主_规则异常.png", (300, 2100, 5150, 2880)),
],
),
(
"MESCommonBase响应输出与SqlWebCall次流程图.png",
[
("SqlWebCall_完整.png", None),
("SqlWebCall_初始化.png", (1500, 120, 4750, 820)),
("SqlWebCall_Type二次分发.png", (110, 900, 5700, 2600)),
("SqlWebCall_数据库返回.png", (260, 2680, 5550, 3540)),
("SqlWebCall_风险.png", (520, 3650, 5300, 3950)),
],
),
]
for src_name, crops in specs:
src = BASE / src_name
if not src.exists():
continue
with Image.open(src) as im:
for out_name, box in crops:
out = ASSET_DIR / out_name
if box is None:
im.copy().save(out)
else:
im.crop(box).save(out)
def set_font(paragraph, size=None, bold=None, color=None):
for run in paragraph.runs:
run.font.name = FONT
try:
run._r.rPr.rFonts.set("{http://schemas.openxmlformats.org/wordprocessingml/2006/main}eastAsia", FONT)
except Exception:
pass
if size is not None:
run.font.size = Pt(size)
if bold is not None:
run.font.bold = bold
if color is not None:
run.font.color.rgb = color
def apply_run_font(run, size, color=COLORS["text"], bold=False):
run.font.name = FONT
try:
run._r.rPr.rFonts.set("{http://schemas.openxmlformats.org/wordprocessingml/2006/main}eastAsia", FONT)
except Exception:
pass
run.font.size = Pt(size)
run.font.color.rgb = color
run.font.bold = bold
class Deck:
def __init__(self):
self.prs = Presentation()
self.prs.slide_width = Inches(13.333)
self.prs.slide_height = Inches(7.5)
self.slide_no = 0
def save(self):
self.prs.save(OUT)
def blank(self):
slide = self.prs.slides.add_slide(self.prs.slide_layouts[6])
self.slide_no += 1
bg = slide.background.fill
bg.solid()
bg.fore_color.rgb = COLORS["bg"]
return slide
def add_footer(self, slide, section="MESCommonBase 迁移分析资料包"):
tx = slide.shapes.add_textbox(Inches(0.45), Inches(7.12), Inches(9.5), Inches(0.25))
p = tx.text_frame.paragraphs[0]
run = p.add_run()
run.text = section
apply_run_font(run, 8, COLORS["muted"])
tx2 = slide.shapes.add_textbox(Inches(12.0), Inches(7.12), Inches(0.85), Inches(0.25))
p2 = tx2.text_frame.paragraphs[0]
p2.alignment = PP_ALIGN.RIGHT
run2 = p2.add_run()
run2.text = str(self.slide_no)
apply_run_font(run2, 8, COLORS["muted"])
def title(self, slide, title, subtitle=None):
tx = slide.shapes.add_textbox(Inches(0.55), Inches(0.32), Inches(12.2), Inches(0.65))
p = tx.text_frame.paragraphs[0]
run = p.add_run()
run.text = title
apply_run_font(run, 26, COLORS["navy"], True)
if subtitle:
st = slide.shapes.add_textbox(Inches(0.58), Inches(0.92), Inches(12.0), Inches(0.32))
p2 = st.text_frame.paragraphs[0]
run2 = p2.add_run()
run2.text = subtitle
apply_run_font(run2, 10.5, COLORS["muted"])
def section_slide(self, title, subtitle):
slide = self.blank()
bar = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, 0, 0, Inches(13.333), Inches(7.5))
bar.fill.solid()
bar.fill.fore_color.rgb = RGBColor(238, 244, 255)
bar.line.color.rgb = RGBColor(238, 244, 255)
tx = slide.shapes.add_textbox(Inches(1.1), Inches(2.45), Inches(11.0), Inches(0.75))
p = tx.text_frame.paragraphs[0]
run = p.add_run()
run.text = title
apply_run_font(run, 34, COLORS["navy"], True)
st = slide.shapes.add_textbox(Inches(1.12), Inches(3.28), Inches(10.5), Inches(0.45))
p2 = st.text_frame.paragraphs[0]
run2 = p2.add_run()
run2.text = subtitle
apply_run_font(run2, 15, COLORS["text"])
self.add_footer(slide)
return slide
def card(self, slide, x, y, w, h, title, body=None, fill="white", line="line", title_color="navy"):
shape = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.ROUNDED_RECTANGLE, Inches(x), Inches(y), Inches(w), Inches(h))
shape.fill.solid()
shape.fill.fore_color.rgb = COLORS[fill]
shape.line.color.rgb = COLORS[line]
shape.line.width = Pt(1.2)
tx = slide.shapes.add_textbox(Inches(x + 0.18), Inches(y + 0.13), Inches(w - 0.36), Inches(h - 0.22))
tf = tx.text_frame
tf.word_wrap = True
p = tf.paragraphs[0]
run = p.add_run()
run.text = title
apply_run_font(run, 13, COLORS[title_color], True)
if body:
p2 = tf.add_paragraph()
p2.space_before = Pt(4)
run2 = p2.add_run()
run2.text = body
apply_run_font(run2, 9.5, COLORS["text"])
return shape
def bullets(self, slide, x, y, w, h, items, font_size=13, color=COLORS["text"], title=None):
tx = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
tf = tx.text_frame
tf.word_wrap = True
tf.clear()
if title:
p = tf.paragraphs[0]
run = p.add_run()
run.text = title
apply_run_font(run, font_size + 2, COLORS["navy"], True)
for idx, item in enumerate(items):
p = tf.add_paragraph() if title or idx else tf.paragraphs[0]
p.text = ""
p.level = 0
p.space_after = Pt(3)
run = p.add_run()
run.text = f"{item}"
apply_run_font(run, font_size, color)
return tx
def add_image_fit(self, slide, image_name, x, y, w, h, border=True):
path = ASSET_DIR / image_name
if not path.exists():
return None
with Image.open(path) as im:
iw, ih = im.size
max_w, max_h = Inches(w), Inches(h)
scale = min(max_w / iw, max_h / ih)
pic_w, pic_h = int(iw * scale), int(ih * scale)
left = Inches(x) + int((max_w - pic_w) / 2)
top = Inches(y) + int((max_h - pic_h) / 2)
if border:
rect = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.ROUNDED_RECTANGLE, Inches(x), Inches(y), Inches(w), Inches(h))
rect.fill.solid()
rect.fill.fore_color.rgb = COLORS["white"]
rect.line.color.rgb = COLORS["line"]
rect.line.width = Pt(1.1)
return slide.shapes.add_picture(str(path), left, top, width=pic_w, height=pic_h)
def table(self, slide, x, y, w, h, data, col_widths=None, font_size=8.5):
rows, cols = len(data), len(data[0])
shape = slide.shapes.add_table(rows, cols, Inches(x), Inches(y), Inches(w), Inches(h))
table = shape.table
if col_widths:
for idx, cw in enumerate(col_widths):
table.columns[idx].width = Inches(cw)
for r, row in enumerate(data):
for c, value in enumerate(row):
cell = table.cell(r, c)
cell.text = str(value)
cell.margin_left = Inches(0.05)
cell.margin_right = Inches(0.05)
fill = cell.fill
fill.solid()
fill.fore_color.rgb = RGBColor(238, 244, 255) if r == 0 else COLORS["white"]
for p in cell.text_frame.paragraphs:
p.alignment = PP_ALIGN.CENTER if r == 0 else PP_ALIGN.LEFT
for run in p.runs:
apply_run_font(run, font_size, COLORS["navy"] if r == 0 else COLORS["text"], r == 0)
return shape
def build_deck():
crop_images()
deck = Deck()
# 1 cover
slide = deck.blank()
bg = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, 0, 0, Inches(13.333), Inches(7.5))
bg.fill.solid()
bg.fill.fore_color.rgb = RGBColor(238, 244, 255)
bg.line.color.rgb = RGBColor(238, 244, 255)
tx = slide.shapes.add_textbox(Inches(0.9), Inches(1.45), Inches(11.6), Inches(1.0))
p = tx.text_frame.paragraphs[0]
run = p.add_run()
run.text = "MESCommonBase 迁移分析资料包"
apply_run_font(run, 36, COLORS["navy"], True)
st = slide.shapes.add_textbox(Inches(0.95), Inches(2.45), Inches(10.8), Inches(0.72))
p2 = st.text_frame.paragraphs[0]
run2 = p2.add_run()
run2.text = "响应输出、SqlWebCall 默认分支、参数协议、数据库链路与移植检查清单"
apply_run_font(run2, 17, COLORS["text"])
deck.card(slide, 0.95, 4.6, 3.2, 1.05, "分析对象", "MES_Manage/submit/MESCommonBase.ashx", "white", "blue", "blue")
deck.card(slide, 4.45, 4.6, 3.2, 1.05, "资料来源", "working 目录中的梳理文档、流程图和源码定位", "white", "green", "green")
deck.card(slide, 7.95, 4.6, 3.2, 1.05, "用途", "为项目移植、风险评估和接口改造提供资料", "white", "purple", "purple")
deck.add_footer(slide)
# 2 deck map
slide = deck.blank()
deck.title(slide, "资料包结构", "从可视化流程、程序机制、风险到移植步骤,按实际迁移顺序组织。")
cards = [
("1. 范围与入口", "程序定位、资料文件、核心依赖", "blue_light", "blue"),
("2. 主流程", "请求解析、Type 首层分发、响应出口", "green_light", "green"),
("3. SqlWebCall", "初始化、二次分发、参数处理、数据库执行", "purple_light", "purple"),
("4. 迁移资料", "配置、依赖、测试矩阵、风险和建议", "amber_light", "amber"),
]
for i, (title, body, fill, line) in enumerate(cards):
deck.card(slide, 0.8 + i * 3.1, 1.65, 2.75, 1.3, title, body, fill, line, line.replace("_light", "") if "_light" in line else line)
deck.bullets(
slide,
0.9,
3.45,
11.8,
2.25,
[
"PPT 中的流程图采用完整图 + 局部放大方式,避免大图缩放后看不清。",
"Markdown 文档保留在 working 中PPT 只提取迁移分析需要的结论和路径。",
"源 Mermaid 文件、PNG、渲染脚本一并保留后续可继续改图或补充页面。",
],
font_size=14,
)
deck.add_footer(slide)
# 3 file index
slide = deck.blank()
deck.title(slide, "working 目录资料索引", "PPT 已整合这些文件,原文件仍保留作为可追溯资料。")
data = [
["文件", "用途"],
["MESCommonBase程序梳理.md", "完整梳理入口、Type 分支、文件上传下载和风险点"],
["MESCommonBase流程图.mmd / .png", "主程序总览流程图"],
["MESCommonBase响应输出与SqlWebCall默认分支详细文档.md", "响应输出和 SqlWebCall 默认分支专项说明"],
["响应输出与SqlWebCall主流程图.mmd / .png", "主图:首层分支与 HTTP 响应出口"],
["响应输出与SqlWebCall次流程图.mmd / .png", "次图SqlWebCall 内部二次分发与数据库链路"],
["render_*.py / build_*.py", "流程图和 PPT 自动生成脚本"],
]
deck.table(slide, 0.7, 1.35, 11.95, 4.9, data, col_widths=[4.4, 7.55], font_size=9.2)
deck.add_footer(slide)
# 4 program positioning
slide = deck.blank()
deck.title(slide, "程序定位与迁移目标", "MESCommonBase 是通用提交网关,不是单一业务接口。")
deck.card(slide, 0.75, 1.35, 3.7, 4.7, "程序定位", "ASP.NET WebHandler\n实现 IHttpHandler\n入口ProcessRequest\n根据 Type 分流", "blue_light", "blue", "blue")
deck.card(slide, 4.75, 1.35, 3.7, 4.7, "关键职责", "文件导出/下载\n文件上传\nIP 查询\n通用数据库调用\n响应统一写出", "green_light", "green", "green")
deck.card(slide, 8.75, 1.35, 3.7, 4.7, "移植目标", "保留协议兼容\n迁移依赖 DLL 和配置\n还原数据库执行能力\n补齐风险控制和验证用例", "purple_light", "purple", "purple")
deck.add_footer(slide)
# 5 source dependencies
slide = deck.blank()
deck.title(slide, "程序依赖关系", "移植时需要同时关注 Web 入口、业务库、基础库、Excel 生成库和 Web.config。")
data = [
["依赖", "迁移关注点"],
["MES_Manage/submit/MESCommonBase.ashx", "WebHandler 入口,需部署到 submit 路径"],
["DataLinkMesWork.dll / 源码", "SqlWebCall、存储过程、SQL 执行、文件上传下载"],
["BasicData.dll / jsonobj", "旧协议 DTOType/Name/Param/token 等字段"],
["WriteExcelNPOI.dll / ExcelWebCall", "2001/2002/2003 Excel/PDF 导出"],
["Newtonsoft.Json、NPOI、Spire、Lazy.Captcha 等", "bin 目录运行依赖,版本需一致或验证兼容"],
["Web.config appSettings ConnectionString", "默认连接串来源,当前使用明文配置"],
]
deck.table(slide, 0.65, 1.35, 12.05, 4.95, data, col_widths=[4.0, 8.05], font_size=9.1)
deck.add_footer(slide)
# 6 section
deck.section_slide("第一部分:主流程与响应输出", "说明 MESCommonBase 如何从 type 分支走向二进制下载、JSON 文本、IP 文本和异常兜底。")
# 7 full overview
slide = deck.blank()
deck.title(slide, "MESCommonBase 总体流程图", "完整图用于把入口解析、Type 分支、响应出口和风险位置放在一张图中查看。")
deck.add_image_fit(slide, "总流程_完整.png", 0.55, 1.15, 12.25, 5.85)
deck.add_footer(slide)
# 8 entry crop
slide = deck.blank()
deck.title(slide, "入口解析与 Type 识别", "迁移时要保持三种入参来源的兼容JSON body、jsonobj、Request[\"param\"]。")
deck.add_image_fit(slide, "总流程_入口解析.png", 0.65, 1.2, 7.0, 4.9)
deck.bullets(
slide,
8.0,
1.45,
4.55,
4.6,
[
"默认 ContentType 设置为 application/json。",
"先用 JsonMapper 解析,再尝试反序列化 jsonobj。",
"multipart/form-data 场景通常依赖 Request[\"param\"]。",
"解析失败被吞掉type 可能保持默认 0 并进入 default。",
],
font_size=13.2,
title="入口兼容点",
)
deck.add_footer(slide)
# 9 type branch crop
slide = deck.blank()
deck.title(slide, "Type 首层分支总览", "首层分支决定响应出口:文件流、上传 JSON、IP 文本或进入 SqlWebCall。")
deck.add_image_fit(slide, "总流程_Type分支.png", 0.55, 1.18, 12.2, 4.05)
data = [
["Type", "方向", "输出"],
["2001/2002/2003/2004/16", "文件生成或数据库文件读取", "二进制下载"],
["15", "文件上传并调用存储过程", "JSON 文本"],
["4000", "获取请求 IP", "文本 IP"],
["default", "DataLink.SqlWebCall 二次分发", "JSON 或空字符串"],
]
deck.table(slide, 1.0, 5.45, 11.3, 1.25, data, col_widths=[2.7, 5.2, 3.4], font_size=8.5)
deck.add_footer(slide)
# 10 response main
slide = deck.blank()
deck.title(slide, "响应输出主流程图", "主图将四类响应出口分开:文件下载、上传 JSON、IP 文本、default JSON。")
deck.add_image_fit(slide, "响应主_分支出口.png", 0.55, 1.15, 12.25, 5.75)
deck.add_footer(slide)
# 11 response rules
slide = deck.blank()
deck.title(slide, "响应规则与异常兜底", "迁移时要特别关注 Response.End、空响应和异常不记录的问题。")
deck.add_image_fit(slide, "响应主_规则异常.png", 0.7, 1.22, 11.9, 3.2)
deck.bullets(
slide,
1.0,
4.75,
11.2,
1.55,
[
"文件下载统一设置 application/octet-stream 和 Content-Disposition前端通过响应头取文件名。",
"外层 catch 只写 responseText默认值为 NULL不设置 HTTP 状态码。",
"Type=16 若 bytes 为 null 直接 return调用方可能收到空响应。",
],
font_size=13.1,
)
deck.add_footer(slide)
# 12 section
deck.section_slide("第二部分SqlWebCall 默认分支", "说明普通业务请求进入 DataLink.SqlWebCall 后如何初始化、二次分发、解析参数、执行数据库并返回。")
# 13 secondary full
slide = deck.blank()
deck.title(slide, "SqlWebCall 默认分支完整图", "完整图用于快速定位初始化、四类 Type、数据库执行和返回出口。")
deck.add_image_fit(slide, "SqlWebCall_完整.png", 0.55, 1.1, 12.25, 5.9)
deck.add_footer(slide)
# 14 init
slide = deck.blank()
deck.title(slide, "SqlWebCall 初始化流程", "第一次进入时读取 Web.config 的 ConnectionString并通过静态标记复用。")
deck.add_image_fit(slide, "SqlWebCall_初始化.png", 0.75, 1.25, 7.0, 3.05)
deck.bullets(
slide,
8.1,
1.35,
4.45,
3.7,
[
"InitSystemReg 当前直接读取 appSettings[\"ConnectionString\"]。",
"原注册号、序列号、加密连接串校验逻辑已注释。",
"移植后需确保 Web.config 中连接串名称和权限可用。",
],
font_size=13,
title="迁移关注点",
)
deck.card(slide, 0.95, 5.35, 11.35, 0.75, "结论", "数据库连接配置是 SqlWebCall 能否工作的第一检查点;同时建议在移植阶段评估明文连接串和账号权限。", "red_light", "red", "red")
deck.add_footer(slide)
# 15 type secondary
slide = deck.blank()
deck.title(slide, "SqlWebCall Type 二次分发", "default 分支内部按 Type 再拆成四类:登录/加密、旧协议、新协议、SQL/命令。")
deck.add_image_fit(slide, "SqlWebCall_Type二次分发.png", 0.55, 1.12, 12.25, 5.9)
deck.add_footer(slide)
# 16 type table
slide = deck.blank()
deck.title(slide, "SqlWebCall Type 能力表", "迁移测试时可按这些 Type 分组准备用例。")
data = [
["分类", "Type", "方法/能力", "迁移重点"],
["登录/加密", "8888/5001/5002", "加密、注册/改密、登录生成 token", "确认加密逻辑和 token 存储过程"],
["旧协议存储过程", "1/2/5", "jsonobj + 拼接字符串 Param", "兼容旧前端参数格式"],
["新协议存储过程", "11/111/12/13/21", "JsonData + JSON 数组字符串 Param", "确认存储过程名、参数、分页输出"],
["SQL/命令", "1001/1002/3/4/7/22/3001", "直接 SQL、建表导入、命令执行", "强烈建议白名单和权限隔离"],
]
deck.table(slide, 0.55, 1.3, 12.25, 4.3, data, col_widths=[2.0, 2.3, 4.2, 3.75], font_size=8.2)
deck.card(slide, 0.75, 6.0, 11.85, 0.65, "关键判断", "这部分是移植风险最大的区域协议兼容、数据库权限、SQL 执行能力、token 逻辑都集中在 SqlWebCall 下游。", "amber_light", "amber", "amber")
deck.add_footer(slide)
# 17 param protocol
slide = deck.blank()
deck.title(slide, "参数协议:新协议与旧协议", "迁移时要避免把 Param 当作普通对象,它在新协议中通常是 JSON 数组字符串。")
deck.card(slide, 0.7, 1.25, 5.9, 4.7, "新协议 Param", "适用11/111/12/13/15/16/21/2004 等\n\n格式:\n[{\"name\":\"@p\",\"value\":\"v\",\"type\":\"string\"}]\n\n特点:\n- Param 字段本身是字符串\n- 支持 type 和 output\n- 数组 value 会转逗号字符串", "green_light", "green", "green")
deck.card(slide, 6.95, 1.25, 5.65, 4.7, "旧协议 Param", "适用1/2/5 等\n\nType 1/2\n@p=value=type&@out=0=int=output\n\nType 5\n@p&value&type|@p2&value&type\n\n特点:\n- 分隔符敏感\n- 值包含 &、=、| 时容易出错", "purple_light", "purple", "purple")
deck.add_footer(slide)
# 18 db and return
slide = deck.blank()
deck.title(slide, "数据库执行链路与返回格式", "SqlWebCall 只路由,真正执行在 DataLinkMesWork.cs 和 SQLCommon.cs。")
deck.add_image_fit(slide, "SqlWebCall_数据库返回.png", 0.62, 1.15, 12.05, 4.0)
deck.bullets(
slide,
0.85,
5.45,
11.8,
1.1,
[
"存储过程使用 ExecuteStoredProcedureCommandType=StoredProcedureCommandTimeout=0。",
"SQL 查询使用 ExecuteSelectMesWork / ExecuteDataTable / ExecuteDataset。",
"返回格式混合DataTable JSON、result=1/0、rows-total、code-message-data、空字符串。",
],
font_size=12.6,
)
deck.add_footer(slide)
# 19 risk
slide = deck.blank()
deck.title(slide, "风险与迁移控制点", "这些问题不一定阻塞移植,但会影响上线后的安全性和可维护性。")
left = [
"token 多数是非空才校验;不传 token 的路径常见不拒绝。",
"客户端可控制 Name/name可能是存储过程名、SQL 文本或表名。",
"Type=7 可按请求内容 DROP/CREATE 表。",
"CommandTimeout=0慢 SQL 或阻塞存储过程可能长期占用请求。",
]
right = [
"下载异常和 bytes=null 缺少统一错误响应。",
"外层 catch 不记录日志,排障成本高。",
"明文连接串和数据库账号权限需要重新评估。",
"响应格式不统一,前端和联调工具需按 Type 分别判断。",
]
deck.card(slide, 0.75, 1.28, 5.85, 4.9, "SqlWebCall 风险", "\n".join(f"{x}" for x in left), "red_light", "red", "red")
deck.card(slide, 6.95, 1.28, 5.65, 4.9, "响应与部署风险", "\n".join(f"{x}" for x in right), "amber_light", "amber", "amber")
deck.add_footer(slide)
# 20 section
deck.section_slide("第三部分:项目移植资料", "给迁移实施人员使用:环境、配置、文件、数据库、接口验证和上线前检查。")
# 21 migration checklist
slide = deck.blank()
deck.title(slide, "移植检查清单", "按部署前必须确认的对象拆分。")
data = [
["类别", "检查项", "说明"],
["运行环境", ".NET Framework 4.8 / IIS / ASP.NET", "WebHandler 运行环境需一致或验证兼容"],
["Web 文件", "MES_Manage/submit/MESCommonBase.ashx", "路径和 Class 名需保持"],
["DLL 依赖", "Bin 下 DataLinkMesWork、BasicData、WriteExcelNPOI 等", "版本差异会影响运行和导出"],
["配置", "Web.config / ConnectionString / CORS", "连接串、跨域、默认文档、handlers"],
["数据库", "存储过程、表、权限、token 过程", "SqlWebCall 能力依赖数据库对象"],
["文件导出", "NPOI、Spire、模板逻辑", "2001/2002/2003 验证"],
["安全", "token、白名单、日志、账号权限", "建议迁移阶段同步补强"],
]
deck.table(slide, 0.55, 1.28, 12.25, 5.25, data, col_widths=[1.5, 4.4, 6.35], font_size=8.5)
deck.add_footer(slide)
# 22 migration steps
slide = deck.blank()
deck.title(slide, "建议移植实施顺序", "先让原协议跑通,再逐步补齐安全和可维护性。")
steps = [
("1", "环境准备", ".NET/IIS/站点路径/应用程序池"),
("2", "文件迁移", "ashx、Bin、Web.config、模板和静态资源"),
("3", "数据库迁移", "库表、存储过程、权限、token 相关过程"),
("4", "接口冒烟", "15/16/2001/2002/4000/default 常用 Type"),
("5", "风险补强", "鉴权、白名单、日志、连接串和权限"),
("6", "回归验收", "按 Type 分组准备测试矩阵和下载校验"),
]
for i, (num, title, body) in enumerate(steps):
x = 0.75 + (i % 3) * 4.05
y = 1.35 + (i // 3) * 2.35
deck.card(slide, x, y, 3.55, 1.65, f"{num}. {title}", body, "white", "blue" if i < 3 else "green", "navy")
deck.card(slide, 1.1, 6.3, 11.1, 0.55, "执行原则", "先保持协议兼容,再处理风险收敛;不要在未完成回归测试前重构 Param 协议或响应格式。", "amber_light", "amber", "amber")
deck.add_footer(slide)
# 23 test matrix
slide = deck.blank()
deck.title(slide, "迁移验证用例矩阵", "每组至少准备一个成功、一个失败、一个边界场景。")
data = [
["场景", "覆盖 Type", "验证点"],
["Excel/PDF 导出", "2001/2002/2003", "文件名、扩展名、下载流、跨域响应头"],
["数据库文件下载", "16/2004", "bytes 为空、文件名、后缀、二进制一致性"],
["文件上传", "15", "无文件、有文件、文件名无扩展名、大文件"],
["IP 查询", "4000", "代理头、REMOTE_ADDR、UserHostAddress"],
["旧协议", "1/2/5", "Param 分隔符、输出参数、分页、token"],
["新协议", "11/111/12/13/21", "JSON 数组 Param、分页、DataSet、新结构返回"],
["SQL 执行", "1001/1002/3/4/7/22/3001", "SQL 参数、权限、白名单、错误处理"],
]
deck.table(slide, 0.55, 1.25, 12.25, 5.45, data, col_widths=[2.35, 2.5, 7.4], font_size=8.3)
deck.add_footer(slide)
# 24 recommended improvements
slide = deck.blank()
deck.title(slide, "移植后的改造建议", "不建议在第一天全部重构,但建议列入上线前后计划。")
deck.card(slide, 0.75, 1.25, 3.75, 4.95, "短期", "• 统一 token 鉴权\n• 未支持 Type 返回明确错误\n• 下载空文件返回可识别错误\n• 外层 catch 写日志\n• 修正 Type=4000 IP 逻辑", "green_light", "green", "green")
deck.card(slide, 4.8, 1.25, 3.75, 4.95, "中期", "• 建立 Type + Name 白名单\n• Param 从字符串迁移为 JSON 数组\n• 统一 JSON 返回结构\n• SQL 执行能力与普通接口分离", "blue_light", "blue", "blue")
deck.card(slide, 8.85, 1.25, 3.75, 4.95, "长期", "• 按业务模块拆分接口\n• 数据库账号最小权限\n• 移除明文连接串\n• 加入审计日志和限流", "purple_light", "purple", "purple")
deck.add_footer(slide)
# 25 appendix
slide = deck.blank()
deck.title(slide, "附录:资料追溯与后续维护", "PPT 是汇报和迁移视图Markdown 与 Mermaid 是详细资料和可维护源。")
deck.bullets(
slide,
0.9,
1.35,
11.9,
4.6,
[
"详细文字资料MESCommonBase程序梳理.md、MESCommonBase响应输出与SqlWebCall默认分支详细文档.md。",
"流程图源文件MESCommonBase流程图.mmd、MESCommonBase响应输出与SqlWebCall主流程图.mmd、MESCommonBase响应输出与SqlWebCall次流程图.mmd。",
"高清流程图:三张 PNG 已在 working 中保存PPT 中按完整图和局部截图引用。",
"自动生成脚本render_*.py 与 build_mescommonbase_migration_ppt.py可在文档更新后重新生成资料。",
],
font_size=14,
)
deck.card(slide, 1.1, 6.1, 11.1, 0.65, "输出文件", "working/MESCommonBase迁移分析资料包.pptx", "white", "blue", "blue")
deck.add_footer(slide)
deck.save()
if __name__ == "__main__":
build_deck()
print(OUT)