You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
haoliang-net/docs/04-后端开发规范.md

439 lines
14 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# CNC机床数据采集系统 - 后端开发规范
> 最后更新2026-04-28
> 适用范围ASP.NET Web API 2 后端工程
---
## 一、技术栈
| 项 | 选型 | 版本 |
|----|------|------|
| IDE | Visual Studio 2017 | 15.9+ |
| 框架 | ASP.NET Web API 2 | .NET Framework 4.7.2 |
| ORM | Dapper | 最新稳定版 |
| 数据库 | MariaDB | 11.8 |
| 数据库驱动 | MySqlConnector | 最新稳定版非Oracle的MySql.Data |
| 测试框架 | xUnit | 最新稳定版 |
| Mock框架 | Moq | 最新稳定版 |
| JSON序列化 | Newtonsoft.Json | 12.0.3VS2017兼容 |
| 认证 | JWT Bearer Token | 自行实现不依赖Identity |
| 日志 | log4net | 最新稳定版 |
---
## 二、解决方案结构
```
E:\opencode\haoliang\
├── CncDataSystem.sln ← VS2017 解决方案文件
├── src/ ← 源码目录
│ ├── CncModels/ ← 数据模型层
│ │ ├── CncModels.csproj
│ │ ├── Entity/ ← 数据库表对应的实体类
│ │ ├── Dto/ ← API请求/响应的DTO类
│ │ ├── Enum/ ← 枚举定义
│ │ └── Constants/ ← 常量定义
│ │
│ ├── CncRepository/ ← 数据访问层
│ │ ├── CncRepository.csproj
│ │ ├── Base/ ← 基础仓储泛型CRUD
│ │ └── Impl/ ← 各表的具体仓储实现
│ │
│ ├── CncService/ ← 业务逻辑层
│ │ ├── CncService.csproj
│ │ ├── Interface/ ← 服务接口定义
│ │ └── Impl/ ← 服务实现
│ │
│ └── CncWebApi/ ← Web API 主项目
│ ├── CncWebApi.csproj
│ ├── App_Start/ ← WebApiConfig、FilterConfig
│ ├── Controllers/ ← API控制器
│ ├── Filters/ ← 自定义过滤器(认证、异常)
│ ├── Infrastructure/ ← 中间件、扩展方法
│ └── Web.config ← 数据库连接串、JWT密钥
├── tests/ ← 测试目录
│ ├── CncModels.Tests/ ← 模型层测试
│ ├── CncRepository.Tests/ ← 仓储层测试
│ ├── CncService.Tests/ ← 服务层测试
│ └── CncWebApi.Tests/ ← 控制器层测试
├── frontend/ ← 前端工程(解决方案文件夹引用,不编译)
├── docs/ ← 设计文档
└── database/ ← 数据库脚本
```
### 项目引用关系
```
CncWebApi → CncService → CncRepository → CncModels
↘ ↘ ↘
CncModels CncModels (无依赖)
```
每个项目只引用自己直接依赖的项目,不跨层引用。
---
## 三、命名规范
### 3.1 通用规则
| 项 | 规范 | 示例 |
|----|------|------|
| 类名 | PascalCase | `MachineService` |
| 方法名 | PascalCase | `GetById()` |
| 参数名 | camelCase | `machineId` |
| 局部变量 | camelCase | `totalCount` |
| 私有字段 | _camelCase | `_connectionString` |
| 常量 | PascalCase 或 UPPER_SNAKE | `MaxRetryCount` |
| 枚举值 | PascalCase | `AlertType.CollectFail` |
### 3.2 各层命名
| 层 | 类名后缀 | 示例 | 文件位置 |
|----|---------|------|---------|
| Entity | 无后缀 | `Machine`(对应表 `cnc_machine` | `CncModels/Entity/` |
| 请求DTO | `Request` 后缀 | `CreateMachineRequest` | `CncModels/Dto/` |
| 响应DTO | `Response` 后缀 | `MachineListResponse` | `CncModels/Dto/` |
| 仓储接口 | `IRepository` 后缀 | `IMachineRepository` | `CncRepository/` |
| 仓储实现 | `Repository` 后缀 | `MachineRepository` | `CncRepository/Impl/` |
| 服务接口 | `IService` 后缀 | `IMachineService` | `CncService/Interface/` |
| 服务实现 | `Service` 后缀 | `MachineService` | `CncService/Impl/` |
| 控制器 | `Controller` 后缀 | `MachineController` | `CncWebApi/Controllers/` |
| 测试类 | `Tests` 后缀 | `MachineServiceTests` | `tests/CncService.Tests/` |
### 3.3 Entity 与数据库表映射
```csharp
// 表名 cnc_machine → 类名 Machine
// 表名 cnc_daily_production → 类名 DailyProduction
// 表名 log_collect_raw → 类名 CollectRaw跨日志库仓储中指定库名
// 表名前缀 cnc_ / log_ 在Entity类名中去掉
```
### 3.4 控制器与路由
```csharp
// 控制器名 → 路由前缀
// MachineController → /api/admin/machine
// DashboardController → /api/admin/dashboard
// ScreenController → /api/screen
// SysConfigController → /api/admin/sys-config
// 路由模板统一使用属性路由
[RoutePrefix("api/admin/machine")]
public class MachineController : ApiController
{
[HttpGet, Route("")]
public IHttpActionResult GetList(...) { }
[HttpGet, Route("{id:int}")]
public IHttpActionResult GetById(int id) { }
[HttpPost, Route("")]
public IHttpActionResult Create([FromBody] CreateMachineRequest request) { }
[HttpPut, Route("{id:int}")]
public IHttpActionResult Update(int id, [FromBody] UpdateMachineRequest request) { }
[HttpDelete, Route("{id:int}")]
public IHttpActionResult Delete(int id) { }
}
```
---
## 四、注释规范
### 4.1 XML文档注释必须
所有 **public 类、方法、属性、接口** 必须有 XML 文档注释(`///`)。
```csharp
/// <summary>
/// 机床管理服务处理机床的CRUD操作和状态查询
/// </summary>
public class MachineService : IMachineService
{
/// <summary>
/// 获取机床分页列表
/// </summary>
/// <param name="query">查询条件关键字、车间ID、在线状态等</param>
/// <returns>分页结果,包含机床列表和总数</returns>
public PagedResult<MachineListItem> GetList(MachineQuery query)
{
...
}
}
```
### 4.2 行内注释(必须)
复杂逻辑、业务规则、SQL语句必须有行内注释。
```csharp
// 日均单机产量 = 日期范围内总产量 / 天数 / 机床数
// 多天范围时Y轴单位为"件/台/天",单天为"件/台"
var days = Math.Max(1, (endDate - startDate).Days + 1);
var avgQuantity = Math.Round((decimal)totalQuantity / days / machineCount, 1);
```
```csharp
// A-B-C-A-B场景程序A第二次出现时创建新段记录
// 日汇总按(machine_id, production_date, program_name)合并
if (currentSegment.ProgramName != newProgramName)
{
CloseSegment(currentSegment, "program_change");
CreateSegment(machineId, newProgramName);
}
```
### 4.3 文件头注释(必须)
每个 .cs 文件顶部必须有文件头注释:
```csharp
/// <summary>
/// 机床管理控制器
/// 对应页面MachineListPage、MachineDetailPage
/// API文档docs/03-API接口设计.md → 3.3 设备管理模块
/// 数据库表cnc_machine, cnc_worker_machine, cnc_collect_record
/// </summary>
```
---
## 五、编码规范
### 5.1 分层职责
| 层 | 职责 | 不做 |
|----|------|------|
| **Controller** | 参数校验、调用Service、包装响应格式 | 不写业务逻辑、不直接操作数据库 |
| **Service** | 业务逻辑编排、数据转换、事务管理 | 不直接写SQL、不返回IHttpActionResult |
| **Repository** | SQL编写、数据库读写、对象映射 | 不写业务判断、不知道API概念 |
| **Models** | 纯数据定义、枚举、常量 | 不包含任何逻辑 |
### 5.2 统一响应格式
所有 API 返回统一的 `ApiResponse<T>` 包装:
```csharp
/// <summary>
/// 统一API响应格式
/// </summary>
public class ApiResponse<T>
{
/// <summary>错误码0=成功</summary>
public int Code { get; set; }
/// <summary>提示信息</summary>
public string Message { get; set; }
/// <summary>业务数据</summary>
public T Data { get; set; }
public static ApiResponse<T> Success(T data, string message = "success")
=> new ApiResponse<T> { Code = 0, Message = message, Data = data };
public static ApiResponse<T> Fail(int code, string message)
=> new ApiResponse<T> { Code = code, Message = message, Data = default(T) };
}
```
### 5.3 异常处理
```csharp
// Controller 层不 try-catch由全局异常过滤器统一处理
// Service 层抛出业务异常
public class BusinessException : Exception
{
public int Code { get; }
public BusinessException(int code, string message) : base(message) { Code = code; }
}
// 使用示例
if (existingMachine != null)
throw new BusinessException(40003, "设备编码已存在");
// 全局异常过滤器
public class GlobalExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is BusinessException bex)
{
context.Response = ... // 返回 { code: bex.Code, message: bex.Message }
}
else
{
// 记录日志,返回 50001
}
}
}
```
### 5.4 数据库连接管理
```csharp
// Repository 基类提供连接,每个方法 using 自动释放
public abstract class BaseRepository
{
private readonly string _connectionString;
protected BaseRepository(string connectionString)
{
_connectionString = connectionString;
}
/// <summary>
/// 创建新的数据库连接,调用方需 using 释放
/// </summary>
protected IDbConnection CreateConnection()
{
return new MySqlConnection(_connectionString);
}
}
// 使用示例
public Machine GetById(int id)
{
using (var conn = CreateConnection())
{
return conn.QueryFirstOrDefault<Machine>(
"SELECT * FROM cnc_machine WHERE id = @Id", new { Id = id });
}
}
```
### 5.5 双库切换
```csharp
// 业务库和日志库连接串不同,通过两个 BaseRepository 子类区分
public class BusinessRepository : BaseRepository { ... } // → cnc_business
public class LogRepository : BaseRepository { ... } // → cnc_log
// 需要访问日志库的仓储继承 LogRepository
// 其他继承 BusinessRepository
```
---
## 六、测试规范
### 6.1 覆盖率要求
- **每个 public 方法必须有至少一个测试用例**
- **分支覆盖**if/else 每个分支都要测到
- **异常路径**:参数校验失败、数据不存在等异常场景必须覆盖
- 目标:**100% 方法覆盖≥90% 分支覆盖**
### 6.2 测试命名
```
[Method]_[Scenario]_[ExpectedResult]
示例:
GetList_WithKeywordFilter_ReturnsFilteredMachines
GetById_WhenNotExists_ThrowsBusinessException
Create_WithDuplicateDeviceCode_Throws40003
```
### 6.3 测试结构AAA模式
```csharp
[Fact]
public void GetList_WithKeywordFilter_ReturnsFilteredMachines()
{
// Arrange - 准备数据
var mockRepo = new Mock<IMachineRepository>();
mockRepo.Setup(r => r.GetList(It.IsAny<MachineQuery>()))
.Returns(new List<Machine> { ... });
var service = new MachineService(mockRepo.Object);
// Act - 执行操作
var result = service.GetList(new MachineQuery { Keyword = "西" });
// Assert - 验证结果
Assert.Single(result.Items);
Assert.Contains("西", result.Items[0].Name);
}
```
### 6.4 各层测试策略
| 层 | 测试方式 | Mock对象 |
|----|---------|---------|
| **Controller** | 测试路由匹配、参数校验、响应格式 | Mock Service |
| **Service** | 测试业务逻辑、数据转换、异常抛出 | Mock Repository |
| **Repository** | 测试SQL正确性、数据映射 | 使用内存数据库或测试库 |
| **Models** | 测试属性默认值、枚举值、验证逻辑 | 无依赖 |
### 6.5 测试项目配置
```csharp
// 每个测试项目引用对应的生产项目
// CncService.Tests → 引用 CncService、CncModels
// 使用 Moq 框架 mock 接口
// 测试数据通过 [InlineData] 或测试初始化方法提供
```
---
## 七、依赖注入
使用 Unity 或手动实现简易 DI 容器VS2017 默认不支持 .NET Core 风格的内置DI
```csharp
// App_Start/UnityConfig.cs 注册依赖
container.RegisterType<IMachineService, MachineService>();
container.RegisterType<IMachineRepository, MachineRepository>();
```
每个 Service 通过构造函数注入依赖的 Repository
```csharp
public class MachineService : IMachineService
{
private readonly IMachineRepository _machineRepo;
private readonly IWorkerRepository _workerRepo;
public MachineService(IMachineRepository machineRepo, IWorkerRepository workerRepo)
{
_machineRepo = machineRepo;
_workerRepo = workerRepo;
}
}
```
---
## 八、Git提交规范
- 每完成一个模块(含测试)提交一次
- 提交信息中文,格式:`feat(module): 描述`
- 编译+测试通过后才能提交
- 与前端共用同一仓库 `main` 分支
---
## 九、开发顺序
按依赖关系从底层往上开发:
| 步骤 | 内容 | 依赖 |
|------|------|------|
| 1 | 搭建解决方案 + 项目结构 + NuGet包 | 无 |
| 2 | CncModelsEntity + Dto + Enum | 无 |
| 3 | CncModels.Tests | 步骤2 |
| 4 | CncRepository基础仓储 + 连接管理) | 步骤2 |
| 5 | CncRepository.Tests登录/系统配置/车间) | 步骤4 |
| 6 | CncService业务逻辑 | 步骤4 |
| 7 | CncService.Tests | 步骤6 |
| 8 | CncWebApi控制器 + 路由 + 过滤器) | 步骤6 |
| 9 | CncWebApi.Tests | 步骤8 |
先跑通 **登录→系统设置→车间管理** 这条最小链路,再逐步扩展其他模块。