Files
MesUniversalApi-Migration/working/MESCommonBase程序梳理.md

17 KiB
Raw Blame History

MESCommonBase.ashx 程序梳理

梳理对象:MES_Manage/submit/MESCommonBase.ashx

梳理时间2026-07-02

1. 程序定位

MESCommonBase.ashx 是一个 ASP.NET WebHandler类名为 MESCommonBase,实现 IHttpHandler。它不是单一业务接口,而是 MES Web 端的通用提交网关:

  • 接收 HTTP 请求。
  • 从请求体 JSON 或 param 参数中解析 Type/type
  • Type 分发到文件导出、文件上传、文件下载、IP 查询或通用数据库调用。
  • 普通业务请求最终委托给 DataLinkMesWork.DataLink.SqlWebCall(...)

入口方法:

  • ProcessRequest(HttpContext context)
  • IsReusable = false

主要依赖:

  • BasicData.jsonobj:旧协议请求对象,字段包括 Type/Name/Param/UserID/Pagination/token/HasReturn/ModularID
  • DataLinkMesWork.JsonDataJsonMapper:本项目内置 JSON 类型与解析器,不是 Newtonsoft 的 JObject
  • DataLinkMesWork.DataLinkSQL、存储过程、文件上传下载的实际执行层。
  • MESDownloadExcel.ExcelWebCallExcel/PDF 导出。
  • Web.configappSettings["ConnectionString"]:数据库连接字符串来源。

2. 总体执行流程

HTTP 请求进入 MESCommonBase.ashx
  |
  |-- 默认设置 Response.ContentType = application/json
  |-- 读取 Request.InputStream 为 stream
  |
  |-- 第一次解析JsonMapper.ToObject(stream)
  |     成功则尝试读取 Type 或 type
  |
  |-- 第二次解析JavaScriptSerializer.Deserialize<jsonobj>(stream)
  |     成功则用 dataobj.Type 覆盖 type
  |
  |-- 如果 dataobj == null
  |     尝试读取 HttpContext.Current.Request["param"]
  |     再用 JsonMapper 解析并读取 Type/type
  |
  |-- switch(type)
        |-- 2001/2002/2003/2004生成并下载文件
        |-- 15上传文件到数据库或存储过程
        |-- 16从数据库或存储过程取文件并下载
        |-- 4000返回请求 IP
        |-- defaultDataLink.SqlWebCall(type, jsonData, dataobj)

3. 请求解析规则

3.1 支持的入参来源

程序支持三类请求载荷:

  1. JSON 请求体:
{
  "type": "11",
  "name": "存储过程名",
  "param": "[{\"name\":\"@id\",\"value\":\"1\",\"type\":\"int\"}]"
}
  1. 旧对象协议请求体:
{
  "Type": "1",
  "Name": "存储过程名",
  "Param": "@id=1=int",
  "Pagination": "1&20",
  "token": "..."
}
  1. 表单或查询参数 param
param={"type":"15","name":"上传文件存储过程","param":"[...]"}

第 3 种主要用于 multipart 上传场景,因为 multipart 请求体无法直接按 JSON 解析。

3.2 Type 读取优先级

实际代码中的优先级为:

  1. JsonMapper.ToObject(stream) 后读取 Type/type
  2. JavaScriptSerializer.Deserialize<jsonobj>(stream) 成功后,用 dataobj.Type 覆盖前面的 type
  3. 如果 dataobj == null,读取 Request["param"],再解析 Type/type

注意:所有解析异常都被吞掉,失败时 type 保持默认值 0,最终进入默认分支。

4. MESCommonBase.ashx 的 Type 分支

Type 分支用途 下游调用 响应
2001 生成 Excel 文件并下载 ExcelWebCall.ExcelFile(jsonData, out bytes, out fileName, ref fileExtension) application/octet-stream 二进制下载
2002 生成 PDF 文件并下载 ExcelWebCall.ExcelFilePdf(jsonData, out bytes, out fileName, out fileExtension) application/octet-stream 二进制下载
2003 合成 Excel 图片或按表单指定扩展名导出 ExcelWebCall.ExcelFile(jsonData, out bytes, out fileName, ref dataimg[0]) application/octet-stream 二进制下载
2004 从存储过程结果中取文件并下载 DataLink.ExePROCEDURE_Type2004(jsonData, out bytes, out fileName, out fileExtension) application/octet-stream 二进制下载
15 上传文件 DataLink.ExePROCEDURE_Type15(jsonData, name, suffix, bytes) JSON 文本
16 下载数据库文件 DataLink.ExePROCEDURE_Type16(jsonData, out bytes, out fileName, out suffix) application/octet-stream 二进制下载
4000 返回客户端 IP 直接读取 ServerVariables / UserHostAddress 文本 IP
其他 通用数据库调用 DataLink.SqlWebCall(type, jsonData, dataobj) JSON 文本

5. 默认分支 DataLink.SqlWebCall

默认分支是主要业务入口。MESCommonBase.ashx 只负责传入 type/jsonData/dataobj,实际能力由 DataLinkMesWork.DataLink.SqlWebCall 决定。

当前 SqlWebCall(int type, JsonData jsonData, jsonobj dataobj) 支持的主要 Type

Type 下游方法 作用概括
8888 GetEncryptStr(jsonData) 返回加密后的字符串,使用请求中的 Name/name
5001 ExePROCEDURE_Type11_AddUser(jsonData) 注册或修改密码相关逻辑
5002 ExePROCEDURE_Type11_Login(jsonData) 登录,校验加密密码,成功后生成 token
1001 ExecuteInsertMesWork(jsonData) 执行客户端传入 SQL偏插入/更新场景
1002 ExecuteSelectMesWork(jsonData) 执行客户端传入 SQL返回 DataTable JSON
1 ExePROCEDURE_Type1(dataobj) 旧协议执行存储过程并返回首表 JSON支持分页和输出参数
2 ExePROCEDURE_Type2(dataobj) 旧协议执行存储过程,成功返回 result=1
3 ExePROCEDURE_Type3(dataobj) 直接执行 Name 中的 SQL 查询并返回 JSON
4 ExePROCEDURE_Type4(dataobj) 直接执行 Name 中的非查询 SQL
5 ExePROCEDURE_Type5(dataobj) 旧协议执行存储过程并返回首表 JSON参数格式略不同
7 ExePROCEDURE_Type7(jsonData) 按 Vue/Excel 数据创建或重建数据库表并批量插入
11 ExePROCEDURE_Type11(jsonData) 新协议执行存储过程并返回首表 JSON
111 ExePROCEDURE_Type111(jsonData) 新协议分页存储过程,自动追加分页入参和输出参数
12 ExePROCEDURE_Type12(jsonData) 新协议执行存储过程,成功返回 result=1
13 ExePROCEDURE_Type13(jsonData) 新协议执行存储过程,返回整个 DataSet JSON
21 ExePROCEDURE_Type21(jsonData) 新结构返回:{ code, message, data }
22 ExePROCEDURE_Type22(dataobj) 执行 Name 中 SQL返回 { code, message, data }
3001 DbCallType1003_SqlCmd.SqlExec(jsonData) SQL 命令执行入口

SqlWebCall 初始化时调用 InitSystemReg(),该方法直接从 appSettings["ConnectionString"] 读取连接字符串,并把 initSystemIsOk 置为 true。原来的注册或授权校验逻辑已被注释。

6. 通用请求字段

新旧协议里字段大小写混用,DataLink.GetString_JsonData(...) 同时兼容大写和小写:

字段 含义
Type / type 第一层分发类型
Name / name 存储过程名、SQL 文本、表名或导出配置入口,具体含义由 Type 决定
Param / param 参数。新协议通常是 JSON 数组字符串,旧协议是拼接字符串
UserID / userID 用户标识,部分日志或存储过程使用
Pagination / pagination 旧分页参数,形如 页码&每页行数,但不同方法里的顺序存在差异
pageSize / pageList 新分页参数
HasReturn / hasReturn 是否有返回值,当前入口未直接使用
ModularID / modularID 子组件或模块标识,主要用于日志
token token。只有下游部分方法在 token 非空时校验

7. Param 参数格式

7.1 新协议 JSON 数组字符串

Type=11/12/13/15/16/21/111/2004 等大量方法使用这种格式:

{
  "type": "11",
  "name": "存储过程名",
  "param": "[{\"name\":\"@工位号\",\"value\":\"OP10\",\"type\":\"string\"},{\"name\":\"@数量\",\"value\":\"10\",\"type\":\"int\"}]"
}

参数对象常见字段:

字段 含义
name SQL 参数名
value 参数值;数组会被拼成逗号字符串
type 可选,支持 int/string/boolean/bool/datetime
output 值为 "1" 时作为输出参数

7.2 旧协议字符串参数

Type=1/2 通过 SQLCommon.GetCmdParam(jsonobj, ...) 解析,实际分隔符是:

参数名=参数值=类型
参数名=参数值=类型=output
多个参数用 & 分隔

示例:

@工位号=OP10=string&@数量=10=int&@ItemCount=0=int=output

Type=5 解析方式不同:多个参数用 | 分隔,单个参数内部用 & 分隔:

参数名&参数值&类型|参数名&参数值&类型

8. 文件上传下载约定

8.1 Type=15 上传文件

入口行为:

  • 读取 context.Request.Files 的第一个文件。
  • 整个文件一次性读入 byte[]
  • 从文件名最后一个 . 拆出 namesuffix
  • 调用 DataLink.ExePROCEDURE_Type15(jsonData, name, suffix, bytes)

下游行为:

  • jsonData.Name/name 取存储过程名。
  • jsonData.Param/param 取参数数组。
  • 在执行前强制把参数数组的最后 3 个参数值改为:
    • 倒数第 3 个:文件名,不含后缀。
    • 倒数第 2 个:文件后缀。
    • 倒数第 1 个:文件二进制。

因此上传文件对应的存储过程参数定义必须预留最后三个参数用于文件名、后缀和二进制内容。

如果没有上传文件,仍会调用同一个存储过程,只是:

  • name = ""
  • suffix = ""
  • bytes = new byte[1]

8.2 Type=16 下载文件

入口行为:

  • 调用 DataLink.ExePROCEDURE_Type16(...)
  • 如果 bytes == null,直接返回,不写响应。
  • 否则按 fileName + "." + suffix 下载。

下游约定:

存储过程返回的第一个 DataTable 第一行必须至少有 3 列:

  1. 文件名。
  2. 后缀。
  3. byte[] 文件内容。

8.3 Type=2004 下载文件

下游调用 ExePROCEDURE_Type2004,该方法执行 Name/name 指定的存储过程。返回数据约定为第一个 DataTable 第一行:

  1. byte[] 文件内容。
  2. 文件名。
  3. 扩展名。

入口再拼成 fileName.extension 下载。

8.4 Type=2001/2002/2003 导出 Excel/PDF

这些分支调用 MESDownloadExcel.ExcelWebCall

  • 2001:生成 Excel。
  • 2002:先生成 Excel再转换为 PDF。
  • 2003:从表单第一项取扩展名或额外数据,再生成文件。

ExcelWebCall 内部通过 SqlServerCmd.SqlCmd.GetDataSetByJson(...) 获取数据集,再按 tabletype/tablecode 选择 WriteExcelNPOI 的不同模板或导出方法。

9. 响应行为

普通 JSON 分支:

  • 默认 ContentType = application/json
  • 默认分支会尝试移除 Server 响应头。
  • 成功或失败 JSON 格式不统一,常见失败为:
[{"result":"0"}]

登录失败会带 msgType 21/22 使用:

{"code":"500","message":"...","data":{}}

下载分支:

  • 设置 ContentType = application/octet-stream
  • 添加 Content-Disposition: attachment; filename=...
  • 添加 Access-Control-Expose-Headers: Content-Disposition,便于前端跨域读取文件名。
  • 使用 BinaryWrite(bytes) 写出文件。

外层异常处理:

  • responseText 初始值是 "NULL"
  • 任意未处理异常都会被 catch然后写出当前 responseText
  • 异常对象 err 未记录,排查线上问题较困难。

10. 数据库与配置

MES_Manage/Web.config 中配置:

  • 目标框架:.NET Framework 4.8
  • CORS允许所有来源、常见方法和 Content-Type
  • 连接字符串:appSettings["ConnectionString"]

连接字符串在配置文件中包含明文数据库账号密码。梳理文档不展开具体值,但这是部署与安全审计时需要重点处理的问题。

11. 关键风险点

  1. 客户端可直接决定存储过程名或 SQL 文本。

Type=1/2/5/11/12/13/15/16/21/2004 等使用请求中的 Name/name 作为存储过程名;Type=3/4/22/1001/1002/3001 等存在直接执行客户端 SQL 文本或 SQL 命令的能力。如果接口暴露到非可信网络,风险很高。

  1. token 校验是可选的。

多个下游方法只在 token 非空时调用 CheckToken(token);如果不传 token通常不会拒绝请求。Type=15/16/2004 等文件分支也没有在入口层做统一鉴权。

  1. 外层吞异常,返回值会误导调用方。

解析、分发和下载过程中大量 catch { }catch(Exception err) 不记录日志。下载分支一旦异常,可能返回 "NULL" 或半截二进制响应,前端不易判断真实原因。

  1. 文件上传一次性读入内存。

Type=15 使用 new byte[files[0].InputStream.Length] 一次性读取没有文件大小、扩展名、MIME、病毒扫描或上传字段校验。

  1. 上传文件名拆分不稳健。

文件名没有 . 时,LastIndexOf(".")-1Substring 会抛异常,最终只返回 "NULL"

  1. IP 获取逻辑疑似写反。

代码是:

if (context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"] != "")
    userIP = context.Request.ServerVariables["REMOTE_ADDR"];
else
    userIP = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];

通常应该在 HTTP_X_FORWARDED_FOR 有值时优先取它;当前逻辑相反。

  1. Response.End() 位于外层 try 内。

Response.End() 在 ASP.NET 中可能触发 ThreadAbortException。当前外层 catch 会尝试继续写 responseText,存在响应污染或异常行为风险。

  1. 业务返回格式不统一。

同一个入口可能返回二进制、数组 JSON、对象 JSON、纯文本 IP、NULL、异常消息字符串。前端调用需要按 Type 单独处理。

  1. 明文数据库连接配置。

Web.config 中存在明文连接字符串和高权限数据库账号,建议至少迁移到安全配置或部署环境变量,并降低数据库账号权限。

  1. Type=7 可按请求内容 DROP/CREATE 表。

ExePROCEDURE_Type7 使用请求中的 Name/name 作为表名,拼接 DROP TABLECREATE TABLE,虽然值插入部分对单引号做了替换,但表名和列名仍来自请求,风险较高。

12. 建议改造方向

短期建议:

  • MESCommonBase.ashx 入口层统一鉴权,不允许无 token 进入数据库执行类 Type。
  • 建立 Type + Name 白名单,禁止客户端传任意 SQL 或任意存储过程名。
  • 给上传文件增加大小、扩展名、字段数量和文件名格式校验。
  • 为所有 catch 写日志,至少记录 Type、Name、请求来源 IP、异常消息和堆栈。
  • 修正 Type=4000 的 IP 获取逻辑。
  • 下载分支用 CompleteRequest() 替代 Response.End()

中期建议:

  • 统一请求 DTO避免同时使用 JsonMapperJavaScriptSerializer 和大小写混合字段。
  • 统一响应格式。二进制下载保留文件响应JSON 分支统一 { code, message, data }
  • 将旧协议 Param 拼接字符串逐步迁移到 JSON 数组参数。
  • 对 SQL 执行能力做分层:普通业务接口不应暴露直接 SQL 文本执行。

长期建议:

  • 把通用网关拆成明确的业务接口,按模块授权、审计和限流。
  • 数据库账号按读、写、文件、管理等能力拆分最小权限。
  • 移除配置中的明文密码,使用部署环境的密钥管理能力。

13. 快速调用示例

13.1 新协议执行存储过程并返回表 JSON

{
  "type": "11",
  "name": "存储过程名",
  "param": "[{\"name\":\"@工位号\",\"value\":\"OP10\",\"type\":\"string\"}]",
  "token": "token值"
}

13.2 新协议分页存储过程

{
  "type": "111",
  "name": "分页存储过程名",
  "param": "[{\"name\":\"@关键字\",\"value\":\"abc\",\"type\":\"string\"}]",
  "pageSize": 20,
  "pageList": 1,
  "token": "token值"
}

下游会自动追加:

  • PageCurrent
  • PageSize
  • PageCount 输出参数
  • ItemCount 输出参数

返回结构类似:

{
  "rows": [],
  "total": "0"
}

13.3 上传文件

请求方式:multipart/form-data

表单字段:

  • paramJSON 字符串,至少包含 type=15nameparam
  • 文件字段:第一个上传文件会被读取。

示例 param

{
  "type": "15",
  "name": "上传文件存储过程名",
  "param": "[{\"name\":\"@业务ID\",\"value\":\"123\",\"type\":\"int\"},{\"name\":\"@文件名\",\"value\":\"\",\"type\":\"string\"},{\"name\":\"@后缀\",\"value\":\"\",\"type\":\"string\"},{\"name\":\"@内容\",\"value\":\"\",\"type\":\"binary\"}]"
}

注意:最后三个参数会被代码覆盖为文件名、后缀和二进制内容。

13.4 下载数据库文件

{
  "type": "16",
  "name": "下载文件存储过程名",
  "param": "[{\"name\":\"@文件ID\",\"value\":\"123\",\"type\":\"int\"}]"
}

存储过程需返回:文件名、后缀、二进制内容。

14. 结论

MESCommonBase.ashx 是一个高度通用的 MES 通讯入口,核心价值是用一个地址承载 SQL 查询、存储过程调用、Excel/PDF 导出、文件上传和文件下载。但它当前把大量执行权交给客户端请求参数,且鉴权、白名单、异常日志和文件安全控制都比较弱。若该接口面向非可信调用方,应优先补齐入口鉴权和可执行对象白名单,再逐步拆分成明确的业务接口。