# 02-项目程序开发详细步骤 ## 1. 开发总路线 项目推荐按以下顺序推进: ```text 准备阶段 -> 建立 WebAPI 骨架 -> 建立通用契约和响应 -> 建立数据库 Provider 抽象 -> 实现 SQL Server Provider -> 实现 Legacy 兼容入口 -> 加入 actionId 白名单 -> 实现文件上传下载 -> 实现 PostgreSQL Provider -> 实现 MySQL Provider -> Linux 部署和回归测试 ``` ## 2. 技术栈 推荐: - 语言:C# - 框架:ASP.NET Core Web API - 运行时:.NET 10 LTS - 数据访问:ADO.NET + Provider 策略;必要时配合 Dapper - SQL Server 驱动:`Microsoft.Data.SqlClient` - PostgreSQL 驱动:`Npgsql` - MySQL 驱动:`MySqlConnector` - 部署:Linux + Docker 或 systemd - 反向代理:Nginx - 文档:OpenAPI/Swagger - 日志:Serilog 或 Microsoft.Extensions.Logging + OpenTelemetry ## 3. 新项目结构 建议结构: ```text MesUniversalApi/ src/ MesUniversalApi.Api/ MesUniversalApi.Application/ MesUniversalApi.Contracts/ MesUniversalApi.Domain/ MesUniversalApi.Infrastructure/ tests/ MesUniversalApi.Tests/ MesUniversalApi.IntegrationTests/ ``` 当前移植根目录已先行创建为: ```text MesUniversalApi/ src/ tests/ deploy/ docker/ systemd/ docs/ tools/ evidence/ ``` 职责: | 项目 | 职责 | | --- | --- | | `Api` | Controller、鉴权、Swagger、过滤器、中间件 | | `Application` | ActionService、LegacyTypeRouter、FileService、AuthService | | `Contracts` | 请求和响应 DTO | | `Domain` | ActionDefinition、DataSourceDefinition、业务模型 | | `Infrastructure` | 数据库 Provider、日志、配置、JWT | | `Tests` | 单元测试和集成测试 | ### 3.1 开工前置条件 开始 `T002 新 WebAPI 项目骨架创建` 之前,必须满足以下条件: 1. 开发机已安装 `.NET 10 SDK`。 2. `dotnet --list-sdks` 输出中包含 `10.0.x`。 3. 在 `MesUniversalApi/` 根目录创建 `global.json`,锁定实际安装的 `10.0.x`。 4. 不以 `net9.0` 或更低版本临时创建正式项目骨架。 5. 旧系统目录只用于盘点和对照,新增代码只进入 `MesUniversalApi/`。 截至 2026-07-02,当前机器仅检测到 `.NET SDK 9.0.311`,因此本轮只完成目录和文档准备,不执行 `net10.0` 项目生成。 ## 4. 第一步:创建 WebAPI 骨架 建议命令: ```bash cd MesUniversalApi dotnet new globaljson --sdk-version 10.0.xxx dotnet new sln -n MesUniversalApi dotnet new webapi -f net10.0 -n MesUniversalApi.Api -o src/MesUniversalApi.Api dotnet new classlib -f net10.0 -n MesUniversalApi.Application -o src/MesUniversalApi.Application dotnet new classlib -f net10.0 -n MesUniversalApi.Contracts -o src/MesUniversalApi.Contracts dotnet new classlib -f net10.0 -n MesUniversalApi.Domain -o src/MesUniversalApi.Domain dotnet new classlib -f net10.0 -n MesUniversalApi.Infrastructure -o src/MesUniversalApi.Infrastructure dotnet sln add src/**/*.csproj ``` 说明: - `10.0.xxx` 需替换为开发机实际安装的 `.NET 10 SDK` 版本号。 - 以上命令在 `.NET 10 SDK` 安装完成后执行。 - 当前轮次不以 `net9.0` 代替 `net10.0` 创建正式骨架。 依赖关系: ```text Api -> Application -> Domain Api -> Contracts Application -> Contracts Application -> Infrastructure abstractions Infrastructure -> Domain / Contracts ``` ## 5. 第二步:统一响应和异常处理 定义响应: ```csharp public sealed class ApiResponse { public string Code { get; init; } = "200"; public string Message { get; init; } = ""; public T? Data { get; init; } public string TraceId { get; init; } = ""; } ``` 分页响应: ```csharp public sealed class PageResult { public IReadOnlyList> Rows { get; init; } = []; public long Total { get; init; } public int PageIndex { get; init; } public int PageSize { get; init; } } ``` 错误中间件: ```text ExceptionHandlingMiddleware -> 捕获异常 -> 记录 traceId/user/action -> 返回 ApiResponse ``` ## 6. 第三步:配置数据源 配置模型: ```csharp public sealed class DataSourceDefinition { public string Name { get; init; } = ""; public DatabaseKind Kind { get; init; } public string ConnectionStringName { get; init; } = ""; } ``` 配置示例: ```json { "Database": { "DefaultDataSource": "mes-main", "DataSources": { "mes-main": { "Kind": "SqlServer", "ConnectionStringName": "MES_SQLSERVER" }, "mes-pg": { "Kind": "PostgreSql", "ConnectionStringName": "MES_POSTGRES" }, "mes-mysql": { "Kind": "MySql", "ConnectionStringName": "MES_MYSQL" } } } } ``` 连接字符串用环境变量: ```bash ConnectionStrings__MES_SQLSERVER="Server=...;Database=...;User Id=...;Password=...;TrustServerCertificate=True" ConnectionStrings__MES_POSTGRES="Host=...;Database=...;Username=...;Password=..." ConnectionStrings__MES_MYSQL="Server=...;Database=...;User ID=...;Password=..." ``` ## 7. 第四步:实现 Provider 抽象 核心接口: ```csharp public interface IDatabaseProvider { DatabaseKind Kind { get; } Task QueryAsync(DbExecutionContext context, CancellationToken ct); Task ExecuteAsync(DbExecutionContext context, CancellationToken ct); Task ScalarAsync(DbExecutionContext context, CancellationToken ct); } ``` 执行上下文: ```csharp public sealed class DbExecutionContext { public string DataSourceName { get; init; } = ""; public DatabaseKind DatabaseKind { get; init; } public CommandKind CommandKind { get; init; } public string CommandText { get; init; } = ""; public IReadOnlyList Parameters { get; init; } = []; public PageRequest? Page { get; init; } public int CommandTimeoutSeconds { get; init; } = 60; } ``` Provider 实现顺序: 1. `SqlServerProvider` 2. `PostgreSqlProvider` 3. `MySqlProvider` ## 8. 第五步:SQL Server Provider 先支持 SQL Server,原因: - 当前旧系统以 SQL Server 存储过程为主。 - 先迁移入口,降低一次性风险。 实现能力: - Text 查询。 - Text 非查询。 - StoredProcedure 查询。 - StoredProcedure 非查询。 - 输出参数。 - DataSet/DataTable 动态 JSON。 - 文件二进制。 关键点: - 使用 `Microsoft.Data.SqlClient`。 - `CommandType.StoredProcedure` 用于旧存储过程。 - 参数名前缀统一由 Provider 处理。 - 命令超时不要照搬 `0`,默认建议 60 秒,可按 action 配置。 ## 9. 第六步:Legacy 兼容入口 Controller: ```http POST /api/v1/legacy/execute ``` 流程: ```text LegacyController -> 接收 type/name/param -> LegacyTypeRouter -> 白名单检查 -> LegacyParamParser -> DbExecutionContext -> Provider 执行 -> ApiResponse ``` 优先兼容: - `11` - `111` - `12` - `13` - `15` - `16` - `2004` 高风险 Type 先禁用: - `3` - `4` - `7` - `22` - `1001` - `1002` - `3001` ## 10. 第七步:actionId 白名单 新增接口: ```http POST /api/v1/actions/{actionId}/execute ``` 动作定义: ```csharp public sealed class ActionDefinition { public string ActionId { get; init; } = ""; public string Module { get; init; } = ""; public bool Enabled { get; init; } public string[] RequiredRoles { get; init; } = []; public IReadOnlyList Parameters { get; init; } = []; public IReadOnlyDictionary Commands { get; init; } = new Dictionary(); } ``` 执行流程: ```text ActionController -> ActionService -> 读取 ActionDefinition -> 校验权限 -> 校验参数 -> 根据 dataSource 选择 DatabaseKind -> 根据 DatabaseKind 选择 CommandDefinition -> Provider 执行 -> 统一响应 ``` ## 11. 第八步:文件上传下载 上传接口: ```http POST /api/v1/files/{actionId}/upload ``` 下载接口: ```http GET /api/v1/files/{actionId}/download ``` 开发步骤: 1. 定义文件 action。 2. 实现上传大小限制。 3. 实现扩展名白名单。 4. 实现 MIME 校验。 5. 实现数据库二进制写入。 6. 实现下载响应头。 7. 写文件 hash 校验测试。 二进制类型: | 数据库 | 类型 | | --- | --- | | SQL Server | `varbinary(max)` | | PostgreSQL | `bytea` | | MySQL | `longblob` | ## 12. 第九步:PostgreSQL Provider 实现内容: - `NpgsqlConnection` - 参数转换。 - Text 查询。 - Function 查询。 - 非查询执行。 - `bytea` 文件读写。 - `LIMIT/OFFSET` 分页。 迁移建议: - SQL Server 查询型存储过程迁移为 PostgreSQL function。 - 输出参数尽量改为返回列或 JSON。 - 不做简单字符串替换,按 action 重写 SQL。 ## 13. 第十步:MySQL Provider 实现内容: - `MySqlConnection` - 参数转换。 - Text 查询。 - `CALL procedure(...)`。 - 非查询执行。 - `longblob` 文件读写。 - `LIMIT/OFFSET` 分页。 注意: - MySQL procedure 输出参数处理与 SQL Server 不同。 - 同一个 action 可为 MySQL 配置单独 SQL。 - 不要求 SQL Server 存储过程原样迁移。 ## 14. 第十一步:鉴权与授权 实现: - JWT 登录。 - `[Authorize]` 保护执行类接口。 - action 级权限。 - 用户角色映射。 - token 过期。 不沿用旧模式: ```text token 非空才校验 ``` 新模式: ```text 除登录和健康检查外,默认必须鉴权 ``` ## 15. 第十二步:日志与审计 每次执行记录: - traceId - userId - clientIp - actionId - legacyType - legacyName - dataSource - databaseKind - commandKind - durationMs - rowsAffected - resultCode - errorSummary 日志不记录明文密码和大二进制。 ## 16. 第十三步:Linux 部署 Docker: ```bash dotnet publish -c Release -o publish docker build -t mes-universal-api:1.0.0 . docker run -d -p 8080:8080 --env-file .env mes-universal-api:1.0.0 ``` systemd: ```ini [Service] WorkingDirectory=/opt/mes-api ExecStart=/usr/bin/dotnet /opt/mes-api/MesUniversalApi.Api.dll Restart=always Environment=ASPNETCORE_URLS=http://0.0.0.0:8080 ``` Nginx 反代: ```nginx location / { proxy_pass http://127.0.0.1:8080; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } ``` ## 17. 第十四步:测试 单元测试: - 参数解析。 - 类型转换。 - action 白名单。 - 权限校验。 - 响应包装。 集成测试: - SQL Server 查询。 - SQL Server 存储过程。 - PostgreSQL 查询。 - MySQL 查询。 - 文件上传下载。 - JWT 成功和失败。 回归测试: ```text 旧 ashx 响应 vs 新 legacy/execute 响应 ``` 性能测试: - 大查询。 - 分页。 - 文件上传下载。 - 长存储过程。 - 连接池。 ## 18. 首轮开发建议 第一轮只做最小闭环: 1. 安装并验证 `.NET 10 SDK`。 2. 新建 WebAPI 项目。 3. 加统一响应。 4. 加 Swagger。 5. 加 SQL Server Provider。 6. 加一个 actionId 查询。 7. 加一个 legacy Type=11 查询。 8. Linux 本地或 Docker 启动。 9. 记录验收证据。