462 lines
17 KiB
Markdown
462 lines
17 KiB
Markdown
# 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.JsonData`、`JsonMapper`:本项目内置 JSON 类型与解析器,不是 Newtonsoft 的 `JObject`。
|
||
- `DataLinkMesWork.DataLink`:SQL、存储过程、文件上传下载的实际执行层。
|
||
- `MESDownloadExcel.ExcelWebCall`:Excel/PDF 导出。
|
||
- `Web.config` 的 `appSettings["ConnectionString"]`:数据库连接字符串来源。
|
||
|
||
## 2. 总体执行流程
|
||
|
||
```text
|
||
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
|
||
|-- default:DataLink.SqlWebCall(type, jsonData, dataobj)
|
||
```
|
||
|
||
## 3. 请求解析规则
|
||
|
||
### 3.1 支持的入参来源
|
||
|
||
程序支持三类请求载荷:
|
||
|
||
1. JSON 请求体:
|
||
|
||
```json
|
||
{
|
||
"type": "11",
|
||
"name": "存储过程名",
|
||
"param": "[{\"name\":\"@id\",\"value\":\"1\",\"type\":\"int\"}]"
|
||
}
|
||
```
|
||
|
||
2. 旧对象协议请求体:
|
||
|
||
```json
|
||
{
|
||
"Type": "1",
|
||
"Name": "存储过程名",
|
||
"Param": "@id=1=int",
|
||
"Pagination": "1&20",
|
||
"token": "..."
|
||
}
|
||
```
|
||
|
||
3. 表单或查询参数 `param`:
|
||
|
||
```text
|
||
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` 等大量方法使用这种格式:
|
||
|
||
```json
|
||
{
|
||
"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, ...)` 解析,实际分隔符是:
|
||
|
||
```text
|
||
参数名=参数值=类型
|
||
参数名=参数值=类型=output
|
||
多个参数用 & 分隔
|
||
```
|
||
|
||
示例:
|
||
|
||
```text
|
||
@工位号=OP10=string&@数量=10=int&@ItemCount=0=int=output
|
||
```
|
||
|
||
`Type=5` 解析方式不同:多个参数用 `|` 分隔,单个参数内部用 `&` 分隔:
|
||
|
||
```text
|
||
参数名&参数值&类型|参数名&参数值&类型
|
||
```
|
||
|
||
## 8. 文件上传下载约定
|
||
|
||
### 8.1 Type=15 上传文件
|
||
|
||
入口行为:
|
||
|
||
- 读取 `context.Request.Files` 的第一个文件。
|
||
- 整个文件一次性读入 `byte[]`。
|
||
- 从文件名最后一个 `.` 拆出 `name` 和 `suffix`。
|
||
- 调用 `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 格式不统一,常见失败为:
|
||
|
||
```json
|
||
[{"result":"0"}]
|
||
```
|
||
|
||
登录失败会带 `msg`,Type 21/22 使用:
|
||
|
||
```json
|
||
{"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 命令的能力。如果接口暴露到非可信网络,风险很高。
|
||
|
||
2. token 校验是可选的。
|
||
|
||
多个下游方法只在 `token` 非空时调用 `CheckToken(token)`;如果不传 token,通常不会拒绝请求。`Type=15/16/2004` 等文件分支也没有在入口层做统一鉴权。
|
||
|
||
3. 外层吞异常,返回值会误导调用方。
|
||
|
||
解析、分发和下载过程中大量 `catch { }` 或 `catch(Exception err)` 不记录日志。下载分支一旦异常,可能返回 `"NULL"` 或半截二进制响应,前端不易判断真实原因。
|
||
|
||
4. 文件上传一次性读入内存。
|
||
|
||
`Type=15` 使用 `new byte[files[0].InputStream.Length]` 一次性读取,没有文件大小、扩展名、MIME、病毒扫描或上传字段校验。
|
||
|
||
5. 上传文件名拆分不稳健。
|
||
|
||
文件名没有 `.` 时,`LastIndexOf(".")` 为 `-1`,`Substring` 会抛异常,最终只返回 `"NULL"`。
|
||
|
||
6. IP 获取逻辑疑似写反。
|
||
|
||
代码是:
|
||
|
||
```csharp
|
||
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` 有值时优先取它;当前逻辑相反。
|
||
|
||
7. `Response.End()` 位于外层 try 内。
|
||
|
||
`Response.End()` 在 ASP.NET 中可能触发 `ThreadAbortException`。当前外层 catch 会尝试继续写 `responseText`,存在响应污染或异常行为风险。
|
||
|
||
8. 业务返回格式不统一。
|
||
|
||
同一个入口可能返回二进制、数组 JSON、对象 JSON、纯文本 IP、`NULL`、异常消息字符串。前端调用需要按 Type 单独处理。
|
||
|
||
9. 明文数据库连接配置。
|
||
|
||
`Web.config` 中存在明文连接字符串和高权限数据库账号,建议至少迁移到安全配置或部署环境变量,并降低数据库账号权限。
|
||
|
||
10. Type=7 可按请求内容 DROP/CREATE 表。
|
||
|
||
`ExePROCEDURE_Type7` 使用请求中的 `Name/name` 作为表名,拼接 `DROP TABLE` 和 `CREATE TABLE`,虽然值插入部分对单引号做了替换,但表名和列名仍来自请求,风险较高。
|
||
|
||
## 12. 建议改造方向
|
||
|
||
短期建议:
|
||
|
||
- 在 `MESCommonBase.ashx` 入口层统一鉴权,不允许无 token 进入数据库执行类 Type。
|
||
- 建立 `Type + Name` 白名单,禁止客户端传任意 SQL 或任意存储过程名。
|
||
- 给上传文件增加大小、扩展名、字段数量和文件名格式校验。
|
||
- 为所有 catch 写日志,至少记录 Type、Name、请求来源 IP、异常消息和堆栈。
|
||
- 修正 `Type=4000` 的 IP 获取逻辑。
|
||
- 下载分支用 `CompleteRequest()` 替代 `Response.End()`。
|
||
|
||
中期建议:
|
||
|
||
- 统一请求 DTO,避免同时使用 `JsonMapper`、`JavaScriptSerializer` 和大小写混合字段。
|
||
- 统一响应格式。二进制下载保留文件响应,JSON 分支统一 `{ code, message, data }`。
|
||
- 将旧协议 `Param` 拼接字符串逐步迁移到 JSON 数组参数。
|
||
- 对 SQL 执行能力做分层:普通业务接口不应暴露直接 SQL 文本执行。
|
||
|
||
长期建议:
|
||
|
||
- 把通用网关拆成明确的业务接口,按模块授权、审计和限流。
|
||
- 数据库账号按读、写、文件、管理等能力拆分最小权限。
|
||
- 移除配置中的明文密码,使用部署环境的密钥管理能力。
|
||
|
||
## 13. 快速调用示例
|
||
|
||
### 13.1 新协议执行存储过程并返回表 JSON
|
||
|
||
```json
|
||
{
|
||
"type": "11",
|
||
"name": "存储过程名",
|
||
"param": "[{\"name\":\"@工位号\",\"value\":\"OP10\",\"type\":\"string\"}]",
|
||
"token": "token值"
|
||
}
|
||
```
|
||
|
||
### 13.2 新协议分页存储过程
|
||
|
||
```json
|
||
{
|
||
"type": "111",
|
||
"name": "分页存储过程名",
|
||
"param": "[{\"name\":\"@关键字\",\"value\":\"abc\",\"type\":\"string\"}]",
|
||
"pageSize": 20,
|
||
"pageList": 1,
|
||
"token": "token值"
|
||
}
|
||
```
|
||
|
||
下游会自动追加:
|
||
|
||
- `PageCurrent`
|
||
- `PageSize`
|
||
- `PageCount` 输出参数
|
||
- `ItemCount` 输出参数
|
||
|
||
返回结构类似:
|
||
|
||
```json
|
||
{
|
||
"rows": [],
|
||
"total": "0"
|
||
}
|
||
```
|
||
|
||
### 13.3 上传文件
|
||
|
||
请求方式:`multipart/form-data`
|
||
|
||
表单字段:
|
||
|
||
- `param`:JSON 字符串,至少包含 `type=15`、`name`、`param`。
|
||
- 文件字段:第一个上传文件会被读取。
|
||
|
||
示例 `param`:
|
||
|
||
```json
|
||
{
|
||
"type": "15",
|
||
"name": "上传文件存储过程名",
|
||
"param": "[{\"name\":\"@业务ID\",\"value\":\"123\",\"type\":\"int\"},{\"name\":\"@文件名\",\"value\":\"\",\"type\":\"string\"},{\"name\":\"@后缀\",\"value\":\"\",\"type\":\"string\"},{\"name\":\"@内容\",\"value\":\"\",\"type\":\"binary\"}]"
|
||
}
|
||
```
|
||
|
||
注意:最后三个参数会被代码覆盖为文件名、后缀和二进制内容。
|
||
|
||
### 13.4 下载数据库文件
|
||
|
||
```json
|
||
{
|
||
"type": "16",
|
||
"name": "下载文件存储过程名",
|
||
"param": "[{\"name\":\"@文件ID\",\"value\":\"123\",\"type\":\"int\"}]"
|
||
}
|
||
```
|
||
|
||
存储过程需返回:文件名、后缀、二进制内容。
|
||
|
||
## 14. 结论
|
||
|
||
`MESCommonBase.ashx` 是一个高度通用的 MES 通讯入口,核心价值是用一个地址承载 SQL 查询、存储过程调用、Excel/PDF 导出、文件上传和文件下载。但它当前把大量执行权交给客户端请求参数,且鉴权、白名单、异常日志和文件安全控制都比较弱。若该接口面向非可信调用方,应优先补齐入口鉴权和可执行对象白名单,再逐步拆分成明确的业务接口。
|