Files
MesUniversalApi-Migration/working1/02-项目程序开发详细步骤.md

11 KiB
Raw Blame History

02-项目程序开发详细步骤

1. 开发总路线

项目推荐按以下顺序推进:

准备阶段
  -> 建立 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. 新项目结构

建议结构:

MesUniversalApi/
  src/
    MesUniversalApi.Api/
    MesUniversalApi.Application/
    MesUniversalApi.Contracts/
    MesUniversalApi.Domain/
    MesUniversalApi.Infrastructure/
  tests/
    MesUniversalApi.Tests/
    MesUniversalApi.IntegrationTests/

当前移植根目录已先行创建为:

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 骨架

建议命令:

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 创建正式骨架。

依赖关系:

Api -> Application -> Domain
Api -> Contracts
Application -> Contracts
Application -> Infrastructure abstractions
Infrastructure -> Domain / Contracts

5. 第二步:统一响应和异常处理

定义响应:

public sealed class ApiResponse<T>
{
    public string Code { get; init; } = "200";
    public string Message { get; init; } = "";
    public T? Data { get; init; }
    public string TraceId { get; init; } = "";
}

分页响应:

public sealed class PageResult
{
    public IReadOnlyList<IDictionary<string, object?>> Rows { get; init; } = [];
    public long Total { get; init; }
    public int PageIndex { get; init; }
    public int PageSize { get; init; }
}

错误中间件:

ExceptionHandlingMiddleware
  -> 捕获异常
  -> 记录 traceId/user/action
  -> 返回 ApiResponse<object>

6. 第三步:配置数据源

配置模型:

public sealed class DataSourceDefinition
{
    public string Name { get; init; } = "";
    public DatabaseKind Kind { get; init; }
    public string ConnectionStringName { get; init; } = "";
}

配置示例:

{
  "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"
      }
    }
  }
}

连接字符串用环境变量:

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 抽象

核心接口:

public interface IDatabaseProvider
{
    DatabaseKind Kind { get; }
    Task<QueryResult> QueryAsync(DbExecutionContext context, CancellationToken ct);
    Task<NonQueryResult> ExecuteAsync(DbExecutionContext context, CancellationToken ct);
    Task<ScalarResult> ScalarAsync(DbExecutionContext context, CancellationToken ct);
}

执行上下文:

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<DbParameterValue> 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

POST /api/v1/legacy/execute

流程:

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 白名单

新增接口:

POST /api/v1/actions/{actionId}/execute

动作定义:

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<ParameterDefinition> Parameters { get; init; } = [];
    public IReadOnlyDictionary<DatabaseKind, ProviderCommandDefinition> Commands { get; init; }
        = new Dictionary<DatabaseKind, ProviderCommandDefinition>();
}

执行流程:

ActionController
  -> ActionService
  -> 读取 ActionDefinition
  -> 校验权限
  -> 校验参数
  -> 根据 dataSource 选择 DatabaseKind
  -> 根据 DatabaseKind 选择 CommandDefinition
  -> Provider 执行
  -> 统一响应

11. 第八步:文件上传下载

上传接口:

POST /api/v1/files/{actionId}/upload

下载接口:

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 过期。

不沿用旧模式:

token 非空才校验

新模式:

除登录和健康检查外,默认必须鉴权

15. 第十二步:日志与审计

每次执行记录:

  • traceId
  • userId
  • clientIp
  • actionId
  • legacyType
  • legacyName
  • dataSource
  • databaseKind
  • commandKind
  • durationMs
  • rowsAffected
  • resultCode
  • errorSummary

日志不记录明文密码和大二进制。

16. 第十三步Linux 部署

Docker

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

[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 反代:

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 成功和失败。

回归测试:

旧 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. 记录验收证据。