11 KiB
11 KiB
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 项目骨架创建 之前,必须满足以下条件:
- 开发机已安装
.NET 10 SDK。 dotnet --list-sdks输出中包含10.0.x。- 在
MesUniversalApi/根目录创建global.json,锁定实际安装的10.0.x。 - 不以
net9.0或更低版本临时创建正式项目骨架。 - 旧系统目录只用于盘点和对照,新增代码只进入
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 实现顺序:
SqlServerProviderPostgreSqlProviderMySqlProvider
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
优先兼容:
11111121315162004
高风险 Type 先禁用:
34722100110023001
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
开发步骤:
- 定义文件 action。
- 实现上传大小限制。
- 实现扩展名白名单。
- 实现 MIME 校验。
- 实现数据库二进制写入。
- 实现下载响应头。
- 写文件 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. 首轮开发建议
第一轮只做最小闭环:
- 安装并验证
.NET 10 SDK。 - 新建 WebAPI 项目。
- 加统一响应。
- 加 Swagger。
- 加 SQL Server Provider。
- 加一个 actionId 查询。
- 加一个 legacy Type=11 查询。
- Linux 本地或 Docker 启动。
- 记录验收证据。