Files
MesUniversalApi-Migration/working/MESCommonBase响应输出与SqlWebCall默认分支详细文档.md

23 KiB
Raw Permalink Blame History

MESCommonBase 响应输出与 SqlWebCall 默认分支详细文档

文档范围:流程图中下半部分的两块内容。

  • 响应输出
  • DataLink.SqlWebCall 默认分支

关联入口:MES_Manage/submit/MESCommonBase.ashx

生成时间2026-07-02

1. 相关程序清单

程序文件 关键位置 作用
MES_Manage/submit/MESCommonBase.ashx ProcessRequestswitch(type) 和外层 catch 负责把不同 Type 的结果写入 HTTP 响应
02DataLinkMesWork/DataLinkMesWork.SqlWebCall.cs DataLink.SqlWebCall(int type, JsonData jsonData, jsonobj dataobj) 默认分支二次分发入口
02DataLinkMesWork/DataLinkMesWork.SqlWebCall.cs InitSystemReg(...) 初始化数据库连接字符串
02DataLinkMesWork/DataLinkMesWork.SqlWebCall.cs GetString_JsonData(...) 从新协议 JSON 读取 Type/Name/Param/token 等字段
02DataLinkMesWork/DataLinkMesWork.cs ExePROCEDURE_Type*ExecuteInsertMesWorkExecuteSelectMesWork 具体 SQL、存储过程、登录、分页、返回格式处理
02DataLinkMesWork/P0.MES.Common/bizDataAccess/SQLCommon.cs ExecuteStoredProcedureExecuteDataTableExecuteDatasetExecuteInsertMesWorkExecuteSelectMesWork 底层 ADO.NET 数据库执行封装
01BasicData/BasicData/BasicData.cs jsonobj 旧协议请求对象
MES_Manage/Web.config appSettings["ConnectionString"] 默认数据库连接配置来源

2. 这部分在总流程中的位置

MESCommonBase.ashx 先解析请求体或 param 参数,拿到 type 后进入 switch(type)

流程图下半部分对应的是:

switch(type)
  |
  |-- 文件下载分支 2001/2002/2003/2004/16 -> 响应输出:二进制下载
  |-- 文件上传分支 15 -> 响应输出JSON 文本
  |-- IP 查询分支 4000 -> 响应输出:文本 IP
  |-- default -> DataLink.SqlWebCall(...) -> 响应输出JSON 文本

也就是说,响应输出 是所有分支最终面向 HTTP 客户端的出口;DataLink.SqlWebCall 默认分支 是普通业务请求的二次路由器。

3. 响应输出:程序流程

3.1 默认响应头

ProcessRequest 进入后先执行:

context.Response.ContentType = "application/json";

因此除文件下载分支外,默认都会以 application/json 作为响应类型。需要注意:Type=4000 实际写出的是纯文本 IP但 Content-Type 没有改成 text/plain

3.2 二进制下载响应

适用 Type

  • 2001
  • 2002
  • 2003
  • 2004
  • 16

典型代码模式:

HttpContext.Current.Response.ContentType = "application/octet-stream";
HttpContext.Current.Response.AddHeader(
    "Content-Disposition",
    "attachment; filename=" + HttpUtility.UrlEncode(fileName, Encoding.UTF8)
);
HttpContext.Current.Response.AddHeader(
    "Access-Control-Expose-Headers",
    "Content-Disposition"
);
HttpContext.Current.Response.BinaryWrite(bytes);
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.End();

字段含义:

字段 含义
ContentType = application/octet-stream 告诉浏览器按二进制文件处理
Content-Disposition = attachment 触发浏览器下载,而不是直接打开
filename=... 下载文件名,使用 UTF-8 URL 编码处理中文文件名
Access-Control-Expose-Headers 允许跨域前端读取 Content-Disposition 响应头
BinaryWrite(bytes) 写出文件二进制
Flush() 刷新响应缓冲区
End() / Close() 结束响应

不同文件分支的差异:

Type 生成文件的程序 文件名来源 结束方式
2001 ExcelWebCall.ExcelFile(...) fileName + "." + fileExtension Response.End()
2002 ExcelWebCall.ExcelFilePdf(...) fileName + "." + fileExtension,通常为 PDF Response.End()
2003 ExcelWebCall.ExcelFile(...) fileName + "." + dataimg[0] Response.Close()
2004 DataLink.ExePROCEDURE_Type2004(...) 数据库返回文件名和扩展名 Response.End()
16 DataLink.ExePROCEDURE_Type16(...) 数据库返回文件名和后缀 Response.End()

3.3 Type=16 的空文件处理

Type=16 下载前有一层空判断:

DataLink.ExePROCEDURE_Type16(jsonData, out bytes, out fileName, out suffix);
if (bytes == null) return;

如果下游没有返回文件二进制,则直接 return,不会写 JSON 错误,也不会写文件响应。调用方看到的可能是空响应。

3.4 Type=15 上传文件后的 JSON 响应

Type=15 是上传文件分支,不返回下载流。

流程:

读取 context.Request.Files
  |
  |-- 有文件:
  |     读取第一个文件 InputStream 为 byte[]
  |     从 FileName 拆 name/suffix
  |     调 DataLink.ExePROCEDURE_Type15(jsonData, name, suffix, bytes)
  |     Response.Write(responseText)
  |
  |-- 无文件:
        name=""
        suffix=""
        bytes=new byte[1]
        调同一个 ExePROCEDURE_Type15
        Response.Write(responseText)

responseText 来自下游存储过程调用,常见返回:

[{"result":"1"}]

或:

[{"result":"0"}]

3.5 Type=4000 的文本响应

Type=4000 返回请求 IP

context.Response.Write(userIP);

当前 IP 判断逻辑:

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

注意:这里逻辑疑似写反。通常 HTTP_X_FORWARDED_FOR 有值时才优先取它;当前代码在有转发头时反而取 REMOTE_ADDR

3.6 default 分支的 JSON 响应

普通业务请求进入 default

responseText = DataLinkMesWork.DataLink.SqlWebCall(type, jsonData, dataobj);
context.Response.Headers.Remove("Server");
context.Response.Write(responseText);

特点:

  • 响应体完全由 SqlWebCall 返回值决定。
  • 尝试移除 Server 响应头。
  • 仍使用入口处默认的 application/json
  • SqlWebCall 对未支持 Type 返回空字符串。

3.7 外层异常响应

ProcessRequest 外层包了一个总 try/catch

string responseText = "NULL";

try
{
    ...
}
catch(Exception err)
{
    context.Response.Write(responseText);
}

异常后的响应行为:

  • 不记录异常。
  • 不设置 HTTP 状态码。
  • 写出当前 responseText
  • 如果异常发生在下游调用前,返回 "NULL"
  • 如果异常发生在 responseText 已赋值之后,可能返回旧结果。

4. DataLink.SqlWebCall 默认分支:入口流程

入口签名:

public static string SqlWebCall(int type, JsonData jsonData, jsonobj dataobj)

调用来源:

DataLinkMesWork.DataLink.SqlWebCall(type, jsonData, dataobj)

调用时机:MESCommonBase.ashxswitch(type) 没有匹配到 2001/2002/2003/2004/15/16/4000 时进入。

4.1 初始化数据库连接

SqlWebCall 开始时检查静态字段 initSystemIsOk

if (!initSystemIsOk)
{
    if (!InitSystemReg(out resultReg))
    {
        return resultReg;
    }
}

InitSystemReg 当前实际逻辑:

connectionString = ConfigurationManager.AppSettings["ConnectionString"];
initSystemIsOk = true;
return true;

说明:

  • 第一次进入 SqlWebCall 时,从 Web.config 读取 ConnectionString
  • 成功后设置静态标记,后续调用复用。
  • 原先注册号、序列号、加密连接串等校验逻辑已经被注释。

4.2 二次分发

初始化后进入 switch(type)

SqlWebCall(type,jsonData,dataobj)
  |
  |-- 8888 / 5001 / 5002加密、注册/改密、登录
  |-- 1 / 2 / 5旧协议存储过程
  |-- 11 / 111 / 12 / 13 / 21新协议存储过程
  |-- 1001 / 1002 / 3 / 4 / 7 / 22 / 3001SQL、建表导入、SQL 命令

SqlWebCall 自身不直接访问数据库;它只选择具体方法。真正数据库执行在:

  • DataLinkMesWork.cs
  • SQLCommon.cs

5. SqlWebCall 支持的 Type 明细

5.1 登录、注册、加密类

Type 方法 输入对象 主要流程 返回
8888 GetEncryptStr(jsonData) 新协议 JsonData 读取 Name/name,用 AesKeyIvGenerator.Encrypt(name1, SN, SN) 加密 加密字符串,失败默认 []
5001 ExePROCEDURE_Type11_AddUser(jsonData) 新协议 JsonData 读取 Param 数组,找到 Password 后 MD5再执行 Name 指定存储过程 存储过程首表 JSON 或 []
5002 ExePROCEDURE_Type11_Login(jsonData) 新协议 JsonData 解密前端密码,执行 Name 指定存储过程查账号,再比对数据库密码,成功后写入 token 成功返回用户表 JSON 并追加 token,失败返回 result=0,msg=...

登录成功后会调用固定存储过程:

权限管理_Token_增加数据

校验 token 时调用:

权限管理_Token_查询数据

5.2 旧协议存储过程类

旧协议使用 BasicData.jsonobj dataobj,字段为:

public string Type;
public string ModularID;
public string Name;
public string Param;
public string UserID;
public string Pagination;
public string token;
public bool HasReturn;
Type 方法 执行对象 参数格式 返回
1 ExePROCEDURE_Type1(dataobj) Name 的第一个 & 前作为存储过程名 @p=value=type&@p2=value=type 首个 DataTable JSON分页时返回带总数结构失败 result=0
2 ExePROCEDURE_Type2(dataobj) Name 指定存储过程 同 Type 1 成功 result=1,失败 result=0
5 ExePROCEDURE_Type5(dataobj) Name 指定存储过程 `@p&value&type @p2&value&type`

旧协议参数解析在 SQLCommon.GetCmdParam(...) 中完成:

多个参数:用 & 分隔
单个参数:参数名=值=类型
输出参数:参数名=值=类型=output

支持类型:

  • int
  • string
  • boolean
  • datetime
  • 默认按字符串处理

token 行为:

  • Type=1/2/3/4/5 都是“如果 token 非空则校验”。
  • 不传 token 时通常不会拒绝执行。

5.3 新协议存储过程类

新协议使用 JsonData jsonData,字段从请求 JSON 中读取:

{
  "type": "11",
  "name": "存储过程名",
  "param": "[{\"name\":\"@p\",\"value\":\"v\",\"type\":\"string\"}]",
  "token": "..."
}

通用字段读取由 GetString_JsonData(...) 完成,兼容大小写:

大写字段 小写字段 含义
Type type Type
Name name 存储过程名或 SQL 文本
Param param 参数数组字符串
UserID userID 用户
Pagination pagination 旧分页参数
HasReturn hasReturn 是否返回
ModularID modularID 模块
token token token

Type=11查询型存储过程

方法:

ExePROCEDURE_Type11(jsonData)

流程:

读取 Name/Param/token
  |
  |-- token 非空CheckToken(token),失败返回 [{"result":"3"}]
  |
  |-- Param 为空:无参数执行存储过程
  |-- Param 非空:解析 JSON 数组为 SqlParameter[]
  |
  |-- SQLCommon.ExecuteStoredProcedure(... out DataSet ...)
  |
  |-- 有结果表:
        |-- 有 pageSize/pageList内存分页返回分页 JSON
        |-- 无分页字段:返回首个 DataTable JSON
  |
  |-- 有 output 参数:包装为 {"result":..., "output":...}

Type=111服务端分页存储过程

方法:

ExePROCEDURE_Type111(jsonData)

它解决“通讯服务器分页”的问题。

如果请求中包含:

{
  "pageSize": 20,
  "pageList": 1
}

程序会自动向参数数组追加 4 个参数:

参数名 说明
PageCurrent pageList 当前页
PageSize pageSize 每页条数
PageCount 1111type=intoutput=1 输出参数
ItemCount 1111type=intoutput=1 输出参数,总记录数

执行后返回:

{
  "rows": [],
  "total": "总记录数"
}

其中 total 来自输出参数 ItemCount

Type=12执行型存储过程

方法:

ExePROCEDURE_Type12(jsonData)

特点:

  • 执行 Name/name 指定存储过程。
  • 不关注 DataSet。
  • 成功返回:
[{"result":"1"}]
  • 失败返回:
[{"result":"0"}]
  • 如果有输出参数,返回:
{
  "result": [{"result":"1"}],
  "output": [{"参数名":"参数值"}]
}

Type=13返回 DataSet JSON

方法:

ExePROCEDURE_Type13(jsonData)

特点:

  • 执行 Name/name 指定存储过程。
  • 返回整个 DataSet 的 JSON而不是只返回第一张表。
  • 有输出参数时包装为:
{
  "result": { "DataSet序列化结果": "..." },
  "output": [{"参数名":"参数值"}]
}

Type=21新响应结构的存储过程

方法:

ExePROCEDURE_Type21(jsonData)

特点:

  • 仍然执行 Name/name 指定存储过程。
  • 返回结构改为:
{
  "code": "200",
  "message": "",
  "data": {}
}

或:

{
  "code": "500",
  "message": "错误信息",
  "data": {}
}

注意:这里是否成功主要看底层返回的 result 字符串是否为空,不完全等同于数据库是否返回数据。

5.4 SQL 文本和建表导入类

Type 方法 执行对象 返回
1001 ExecuteInsertMesWork(jsonData) Name/name 作为 SQL 文本,参数来自 Param JSON 数组 成功 result=1;有输出时返回 output
1002 ExecuteSelectMesWork(jsonData) Name/name 作为 SQL 查询文本,参数来自 Param JSON 数组 DataTable JSON支持 pageSize/pageList 内存分页
3 ExePROCEDURE_Type3(dataobj) dataobj.Name 作为 SQL 查询文本 DataTable JSON
4 ExePROCEDURE_Type4(dataobj) dataobj.Name 作为非查询 SQL 文本 成功 result=1
7 ExePROCEDURE_Type7(jsonData) Name/name 作为表名,Param 作为行数据 删除并重建表,批量插入,成功 result=1
22 ExePROCEDURE_Type22(dataobj) dataobj.Name 作为 SQL 文本 { code, message, data }
3001 DbCallType1003_SqlCmd.SqlExec(jsonData) SQL 命令执行入口 由该方法决定

这类 Type 的共性:

  • 执行对象来自客户端 Name/name
  • 参数虽然使用 SqlParameter 绑定,但 SQL 文本或表名本身不是固定白名单。
  • 需要调用方和网络边界可信,否则风险很高。

6. SqlWebCall 参数处理流程

6.1 新协议 Param 数组字符串

常见格式:

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

处理逻辑:

param 字符串
  |
  |-- JsonMapper.ToObject(param)
  |
  |-- 遍历数组
        |-- nameSqlParameter 名
        |-- value参数值如果是数组转为逗号字符串并做 Unicode 解码
        |-- type存在时做类型转换
        |-- output == "1":改为输出参数
  |
  |-- SQLCommon.ExecuteStoredProcedure / ExecuteSelectMesWork / ExecuteInsertMesWork

支持类型主要为:

  • int
  • string
  • boolean
  • bool
  • datetime
  • 默认字符串

6.2 旧协议 Param 拼接字符串

Type 1/2 的旧协议格式:

@p1=value1=string&@p2=10=int&@out=0=int=output

SQLCommon.GetCmdParam(...) 处理逻辑:

Param
  |
  |-- 按 & 分割参数
  |-- 过滤包含 == / null / undefined 的项
  |-- 每项按 = 分割
  |-- 长度 2直接字符串参数
  |-- 长度 3按类型转换
  |-- 长度 4 且第 4 项为 output设为输出参数

Type 5 的旧协议格式不同:

@p1&value1&String|@p2&10&Int

7. 底层数据库执行链路

7.1 存储过程查询

典型调用链:

MESCommonBase.ashx default
  -> DataLink.SqlWebCall(...)
  -> DataLink.ExePROCEDURE_Type11/13/21(...)
  -> SQLCommon.ExecuteStoredProcedure(...)
  -> SqlConnection / SqlDataAdapter
  -> DataSet / DataTable
  -> JsonHelper / JsonConvert 序列化
  -> Response.Write(responseText)

SQLCommon.ExecuteStoredProcedure(... out DataSet ...) 的关键行为:

  • 创建 SqlConnection
  • 创建 SqlCommand(procedureName, conn)
  • 设置 CommandType = StoredProcedure
  • 设置 CommandTimeout = 0,即不限制命令超时。
  • 添加 SqlParameter[]
  • 使用 SqlDataAdapter.Fill(ds) 填充结果集。
  • 异常时写 ApplicationLog,并通过 errorMessage 返回异常字符串。

7.2 SQL 查询

典型调用链:

Type 1002 / 3 / 22
  -> SQLCommon.ExecuteSelectMesWork 或 ExecuteDataTable / ExecuteDataset
  -> SqlDataAdapter(sql, conn)
  -> Fill(DataTable/DataSet)
  -> JSON

7.3 SQL 增删改

典型调用链:

Type 1001 / 4
  -> SQLCommon.ExecuteInsertMesWork 或 ExecuteNonQuery
  -> SqlCommand.CommandText = SQL 文本
  -> ExecuteNonQuery / ExecuteScalar
  -> result=1 或 result=0

8. 返回格式汇总

8.1 成功返回首表 JSON

常见于:

  • Type=1
  • Type=3
  • Type=5
  • Type=11
  • Type=1002

示例:

[
  {
    "字段1": "值1",
    "字段2": "值2"
  }
]

8.2 成功/失败标志

常见于:

  • Type=2
  • Type=4
  • Type=12
  • Type=1001

成功:

[{"result":"1"}]

失败:

[{"result":"0"}]

8.3 token 失败

部分方法在 token 非空且校验失败时返回:

[{"result":"3"}]

8.4 输出参数包装

普通输出参数:

{
  "result": [{"result":"1"}],
  "output": [{"@out":"123"}]
}

分页输出参数:

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

8.5 新结构响应

常见于:

  • Type=21
  • Type=22
{
  "code": "200",
  "message": "",
  "data": {}
}

失败:

{
  "code": "500",
  "message": "错误信息",
  "data": {}
}

8.6 空或未支持 Type

SqlWebCall 对未匹配 Type 没有 default 处理,result 初始为空字符串。

因此 MESCommonBase.ashx default 分支可能返回空响应体:

""

如果进入外层异常,则可能返回:

NULL

9. 鉴权与 token 逻辑

CheckToken(token) 逻辑:

token == "" -> true
token 非空:
  调 权限管理_Token_查询数据
    |
    |-- 存储过程报错 -> false
    |-- 返回空表 -> false
    |-- 返回有数据 -> true

关键影响:

  • 多数业务方法只有在 token 非空时才调用 CheckToken
  • 如果请求不传 token常见路径不会拒绝。
  • SqlWebCall 入口层没有统一鉴权。
  • 因此权限控制依赖调用方是否传 token以及下游存储过程是否自行校验。

10. 风险点与维护注意事项

10.1 响应层风险

  1. Response.End() 位于大 try 内,可能触发 ThreadAbortException,随后外层 catch 写出 responseText
  2. 下载失败时没有统一 JSON 错误,Type=16bytes == null 会直接空返回。
  3. Type=4000 返回纯文本,但 Content-Type 仍可能是 application/json
  4. 外层异常不记录日志,定位问题困难。
  5. 多种响应格式混用,前端必须按 Type 分别处理。

10.2 SqlWebCall 风险

  1. 客户端可通过 Name/name 指定存储过程名、SQL 文本或表名。
  2. Type=3/4/22/1001/1002/3001 存在直接 SQL 执行能力。
  3. Type=7 可根据请求表名执行 DROP TABLECREATE TABLE
  4. token 校验不是统一强制的。
  5. CommandTimeout = 0 可能导致长时间阻塞。
  6. 失败返回经常只有 result=0,缺少错误原因。

10.3 参数层风险

  1. 新协议 Param 是 JSON 字符串,不是 JSON 对象;调用方需要二次转义。
  2. 输出参数类型转换依赖 typeoutput 字段,格式错误会进入 catch 并返回 result=0
  3. 数组参数被拼成逗号字符串,数组元素里如果本身包含逗号,语义会丢失。
  4. 旧协议分隔符为 &=|,参数值包含这些字符时容易解析错误。

11. 建议改进方案

11.1 低成本修复

  • MESCommonBase.ashx default 分支前统一校验 token。
  • 未支持 Type 返回明确错误:
{"code":"400","message":"Unsupported Type","data":null}
  • 下载分支改为 HttpContext.Current.ApplicationInstance.CompleteRequest(),避免 Response.End() 的线程中止异常。
  • Type=4000 设置 ContentType = "text/plain",并修正代理 IP 判断逻辑。
  • 外层 catch 记录异常Type、Name、IP、异常堆栈。

11.2 中期改造

  • 建立 Type + Name 白名单,禁止任意存储过程名和 SQL 文本。
  • Param 从“JSON 字符串”改为真正的 JSON 数组字段。
  • 统一 JSON 返回结构:
{
  "code": "200",
  "message": "",
  "data": {}
}
  • 将 SQL 执行类 Type 与普通业务接口分离。

11.3 长期改造

  • 按业务模块拆分接口,减少万能网关。
  • 数据库账号按能力拆分权限。
  • 移除明文连接字符串,使用部署环境密钥。
  • 对文件下载、上传、SQL 执行加入审计日志和限流。

12. 典型流程示例

12.1 Type=11 查询型存储过程

请求:

{
  "type": "11",
  "name": "MES_工位_查询",
  "param": "[{\"name\":\"@工位号\",\"value\":\"OP10\",\"type\":\"string\"}]",
  "token": "token值"
}

流程:

MESCommonBase.ashx default
  -> SqlWebCall(11,jsonData,dataobj)
  -> InitSystemReg 读取连接字符串
  -> ExePROCEDURE_Type11
  -> GetString_JsonData 读取 name/param/token
  -> token 非空则 CheckToken
  -> Param 转 SqlParameter[]
  -> SQLCommon.ExecuteStoredProcedure
  -> JsonHelper.DataTableToJson
  -> Response.Write(JSON)

12.2 Type=111 分页型存储过程

请求:

{
  "type": "111",
  "name": "MES_工位_分页查询",
  "param": "[{\"name\":\"@关键字\",\"value\":\"OP\",\"type\":\"string\"}]",
  "pageSize": 20,
  "pageList": 1
}

内部自动追加分页参数:

PageCurrent = 1
PageSize = 20
PageCount = 输出参数
ItemCount = 输出参数

响应:

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

12.3 Type=1002 SQL 查询

请求:

{
  "type": "1002",
  "name": "select * from Test where f1=@f1",
  "param": "[{\"name\":\"@f1\",\"value\":\"T002\",\"type\":\"string\"}]"
}

流程:

SqlWebCall(1002)
  -> ExecuteSelectMesWork(jsonData)
  -> SQLCommon.ExecuteSelectMesWork(sql, params, connectionString, out dt)
  -> DataTableToJson

说明:参数值通过 SqlParameter 绑定,但 SQL 文本本身来自请求。

13. 结论

流程图中的 响应输出 是整个 MESCommonBase.ashx 的 HTTP 出口负责二进制下载、JSON 文本、IP 文本和异常兜底输出。DataLink.SqlWebCall 默认分支 是普通业务请求的二次分发中心,它按 Type 调用不同数据库执行方法覆盖登录、存储过程、SQL 查询、SQL 增删改、分页和新结构返回。

这部分程序的核心问题不是流程复杂,而是执行权过于通用:客户端可通过 type + name + param 控制大量数据库行为。维护时应优先关注统一鉴权、Type/Name 白名单、异常日志和响应格式统一。