597 lines
28 KiB
Python
597 lines
28 KiB
Python
# -*- 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", "旧协议 DTO,Type/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,
|
||
[
|
||
"存储过程使用 ExecuteStoredProcedure,CommandType=StoredProcedure,CommandTimeout=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)
|