2025-09-03 10:16:04 +08:00
|
|
|
|
# .NET 6.0 开发规范 (基于ASP.NET Boilerplate)
|
|
|
|
|
|
|
|
|
|
## 📁 项目结构
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
YunDa.SOMS/
|
|
|
|
|
├── YunDa.Application/
|
|
|
|
|
│ ├── YunDa.SOMS.Application/ # API服务层
|
|
|
|
|
│ └── YunDa.SOMS.DataTransferObject/ # DTO中间层
|
|
|
|
|
├── YunDa.Domain/ # 实体与业务逻辑
|
|
|
|
|
├── YunDa.Web/
|
|
|
|
|
│ └── YunDa.SOMS.Web.MVC/ # MVC Web项目
|
|
|
|
|
└── YunDa.Infrastructure/ # 基础设施层
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 🏗️ 架构规范
|
|
|
|
|
|
|
|
|
|
### 分层职责
|
|
|
|
|
|
|
|
|
|
| 层级 | 职责 | 依赖规则 |
|
|
|
|
|
|------|------|----------|
|
|
|
|
|
| **Application** | 继承`SOMSAppServiceBase`,处理DTO转换、业务协调、API接口 | 可依赖Domain |
|
|
|
|
|
| **Domain** | 纯业务逻辑、实体定义、领域服务 | 不依赖任何外部设施 |
|
|
|
|
|
| **Infrastructure** | 数据访问、外部服务调用、技术实现 | 实现Domain接口 |
|
|
|
|
|
| **Web.MVC** | 前端页面、控制器、视图 | 依赖Application |
|
|
|
|
|
|
|
|
|
|
### 数据库配置
|
|
|
|
|
|
|
|
|
|
- **MySQL**: 配置数据存储
|
|
|
|
|
- **MongoDB**: 记录数据存储
|
|
|
|
|
- **Redis**: 数据共享与缓存
|
|
|
|
|
|
|
|
|
|
### 服务注册
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
// AppService/DomainService: Scoped生命周期
|
|
|
|
|
// 使用ABP约定自动注册
|
|
|
|
|
[Dependency(ReplaceServices = true)]
|
|
|
|
|
public class UserManagementAppService : SOMSAppServiceBase, IUserManagementAppService
|
|
|
|
|
{
|
|
|
|
|
// 自动注册为Scoped
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 📝 代码规范
|
|
|
|
|
|
|
|
|
|
### 命名约定
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
// ✅ 类名: PascalCase
|
|
|
|
|
public class UserManagementAppService : SOMSAppServiceBase
|
|
|
|
|
|
|
|
|
|
// ✅ 接口: I + PascalCase
|
|
|
|
|
public interface IUserManagementAppService : IApplicationService
|
|
|
|
|
|
|
|
|
|
// ✅ 方法: 动词开头 + Async (异步方法)
|
|
|
|
|
public async Task<RequestResult<UserOutput>> CreateUserAsync(CreateUserInput input)
|
|
|
|
|
|
|
|
|
|
// ✅ 变量/字段: camelCase,私有字段下划线前缀
|
|
|
|
|
private readonly IUserRepository _userRepository;
|
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
|
public string userName { get; set; }
|
|
|
|
|
|
|
|
|
|
// ✅ 常量: UPPER_SNAKE_CASE
|
|
|
|
|
private const string DEFAULT_USER_ROLE = "User";
|
|
|
|
|
|
|
|
|
|
// ✅ 枚举: PascalCase
|
|
|
|
|
public enum UserStatus
|
|
|
|
|
{
|
|
|
|
|
Active = 0,
|
|
|
|
|
Inactive = 1,
|
|
|
|
|
Suspended = 2
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 前后端命名差异
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
// 后端 (PascalCase)
|
|
|
|
|
public class UserCreateInput
|
|
|
|
|
{
|
|
|
|
|
public string UserName { get; set; }
|
|
|
|
|
public string EmailAddress { get; set; }
|
|
|
|
|
public DateTime CreatedTime { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 前端请求 (camelCase)
|
|
|
|
|
{
|
|
|
|
|
"userName": "john_doe",
|
|
|
|
|
"emailAddress": "john@example.com",
|
|
|
|
|
"createdTime": "2024-01-01T00:00:00Z"
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 异步编程规范
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
// ✅ 正确的异步方法
|
|
|
|
|
public async Task<RequestResult<UserOutput>> GetUserAsync(
|
|
|
|
|
Guid userId,
|
|
|
|
|
CancellationToken cancellationToken = default)
|
|
|
|
|
{
|
|
|
|
|
var user = await _userRepository
|
|
|
|
|
.GetAsync(userId, cancellationToken)
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
return new RequestResult<UserOutput>
|
|
|
|
|
{
|
|
|
|
|
Flag = true,
|
|
|
|
|
ResultData = ObjectMapper.Map<UserOutput>(user)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ❌ 避免的写法
|
|
|
|
|
public RequestResult<UserOutput> GetUser(Guid userId) // 缺少async
|
|
|
|
|
{
|
|
|
|
|
var user = _userRepository.Get(userId); // 同步调用
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### C# 8.0+ 特性使用
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
// ✅ Nullable引用类型
|
|
|
|
|
public async Task<RequestResult<string?>> GetOptionalDataAsync(Guid? id)
|
|
|
|
|
{
|
|
|
|
|
if (id is null) return RequestResult<string?>.Failed("ID不能为空");
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ✅ Switch表达式
|
|
|
|
|
public string GetStatusDescription(UserStatus status) => status switch
|
|
|
|
|
{
|
|
|
|
|
UserStatus.Active => "正常",
|
|
|
|
|
UserStatus.Inactive => "未激活",
|
|
|
|
|
UserStatus.Suspended => "已暂停",
|
|
|
|
|
_ => "未知状态"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ✅ Pattern matching
|
|
|
|
|
public bool IsValidUser(object user) => user switch
|
|
|
|
|
{
|
|
|
|
|
User { IsActive: true, EmailConfirmed: true } => true,
|
|
|
|
|
_ => false
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 🛡️ AppService标准模板
|
|
|
|
|
|
|
|
|
|
### 基础模板
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
[AbpAuthorize("Permission.User.Management")]
|
|
|
|
|
public class UserManagementAppService : SOMSAppServiceBase, IUserManagementAppService
|
|
|
|
|
{
|
|
|
|
|
private readonly IUserRepository _userRepository;
|
|
|
|
|
private readonly IUserDomainService _userDomainService;
|
|
|
|
|
|
|
|
|
|
public UserManagementAppService(
|
|
|
|
|
IUserRepository userRepository,
|
|
|
|
|
IUserDomainService userDomainService)
|
|
|
|
|
{
|
|
|
|
|
_userRepository = userRepository;
|
|
|
|
|
_userDomainService = userDomainService;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<RequestResult<UserOutput>> CreateUserAsync(
|
|
|
|
|
CreateUserInput input,
|
|
|
|
|
CancellationToken cancellationToken = default)
|
|
|
|
|
{
|
|
|
|
|
var result = new RequestResult<UserOutput>();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// 1. 输入验证
|
|
|
|
|
await ValidateInputAsync(input);
|
|
|
|
|
|
|
|
|
|
// 2. 业务逻辑处理
|
|
|
|
|
var user = await _userDomainService
|
|
|
|
|
.CreateUserAsync(input.UserName, input.EmailAddress, cancellationToken)
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
// 3. 持久化
|
|
|
|
|
await _userRepository.InsertAsync(user, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
await CurrentUnitOfWork.SaveChangesAsync(cancellationToken);
|
|
|
|
|
|
|
|
|
|
// 4. 结果转换
|
|
|
|
|
result.Flag = true;
|
|
|
|
|
result.ResultData = ObjectMapper.Map<UserOutput>(user);
|
|
|
|
|
result.Message = "用户创建成功";
|
|
|
|
|
|
|
|
|
|
Log4Helper.Info(GetType(), $"用户创建成功: {user.UserName}");
|
|
|
|
|
}
|
|
|
|
|
catch (ValidationException ex)
|
|
|
|
|
{
|
|
|
|
|
result.Flag = false;
|
|
|
|
|
result.Message = $"数据验证失败:{ex.Message}";
|
|
|
|
|
Log4Helper.Info(GetType(), "用户创建-验证异常", ex);
|
|
|
|
|
}
|
|
|
|
|
catch (BusinessException ex)
|
|
|
|
|
{
|
|
|
|
|
result.Flag = false;
|
|
|
|
|
result.Message = ex.Message;
|
|
|
|
|
Log4Helper.Warning(GetType(), "用户创建-业务异常", ex);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
result.Flag = false;
|
|
|
|
|
result.Message = "系统异常,请联系管理员";
|
|
|
|
|
Log4Helper.Error(GetType(), "用户创建-系统异常", ex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task ValidateInputAsync(CreateUserInput input)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(input.UserName))
|
|
|
|
|
throw new ValidationException("用户名不能为空");
|
|
|
|
|
|
|
|
|
|
if (await _userRepository.AnyAsync(u => u.UserName == input.UserName))
|
|
|
|
|
throw new ValidationException("用户名已存在");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### CRUD标准模板
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
public class StandardCrudAppService<TEntity, TDto, TKey, TCreateInput, TUpdateInput>
|
|
|
|
|
: SOMSAppServiceBase
|
|
|
|
|
where TEntity : class, IEntity<TKey>
|
|
|
|
|
where TDto : IEntityDto<TKey>
|
|
|
|
|
{
|
|
|
|
|
protected readonly IRepository<TEntity, TKey> Repository;
|
|
|
|
|
|
|
|
|
|
protected StandardCrudAppService(IRepository<TEntity, TKey> repository)
|
|
|
|
|
{
|
|
|
|
|
Repository = repository;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public virtual async Task<RequestResult<TDto>> GetAsync(
|
|
|
|
|
TKey id,
|
|
|
|
|
CancellationToken cancellationToken = default)
|
|
|
|
|
{
|
|
|
|
|
var result = new RequestResult<TDto>();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var entity = await Repository
|
|
|
|
|
.GetAsync(id, cancellationToken)
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
result.Flag = true;
|
|
|
|
|
result.ResultData = ObjectMapper.Map<TDto>(entity);
|
|
|
|
|
}
|
|
|
|
|
catch (EntityNotFoundException ex)
|
|
|
|
|
{
|
|
|
|
|
result.Flag = false;
|
|
|
|
|
result.Message = $"未找到ID为{id}的记录";
|
|
|
|
|
Log4Helper.Warning(GetType(), "记录不存在", ex);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
result.Flag = false;
|
|
|
|
|
result.Message = "获取数据失败";
|
|
|
|
|
Log4Helper.Error(GetType(), "获取数据异常", ex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public virtual async Task<RequestResult<PagedResultDto<TDto>>> GetPagedAsync(
|
|
|
|
|
PagedAndSortedRequestDto input,
|
|
|
|
|
CancellationToken cancellationToken = default)
|
|
|
|
|
{
|
|
|
|
|
var result = new RequestResult<PagedResultDto<TDto>>();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var query = Repository.GetAll();
|
|
|
|
|
var totalCount = await query.CountAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
var entities = await query
|
|
|
|
|
.OrderBy(input.Sorting ?? "Id")
|
|
|
|
|
.Skip(input.SkipCount)
|
|
|
|
|
.Take(input.MaxResultCount)
|
|
|
|
|
.ToListAsync(cancellationToken)
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
result.Flag = true;
|
|
|
|
|
result.ResultData = new PagedResultDto<TDto>
|
|
|
|
|
{
|
|
|
|
|
TotalCount = totalCount,
|
|
|
|
|
Items = ObjectMapper.Map<List<TDto>>(entities)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
result.Flag = false;
|
|
|
|
|
result.Message = "获取分页数据失败";
|
|
|
|
|
Log4Helper.Error(GetType(), "分页查询异常", ex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 🔧 最佳实践
|
|
|
|
|
|
|
|
|
|
### 1. 异常处理层次
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
// 业务异常 - 用户可理解的错误
|
|
|
|
|
throw new BusinessException("用户名已存在,请选择其他用户名");
|
|
|
|
|
|
|
|
|
|
// 验证异常 - 输入格式错误
|
|
|
|
|
throw new ValidationException("邮箱格式不正确");
|
|
|
|
|
|
|
|
|
|
// 系统异常 - 记录详细日志,返回通用错误消息
|
|
|
|
|
Log4Helper.Error(GetType(), "数据库连接失败", ex);
|
|
|
|
|
return RequestResult.Failed("系统异常,请联系管理员");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2. 日志记录规范
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
// Info: 正常业务流程
|
|
|
|
|
Log4Helper.Info(GetType(), $"用户 {userName} 登录成功");
|
|
|
|
|
|
|
|
|
|
// Warning: 业务异常,需要关注但不影响系统运行
|
|
|
|
|
Log4Helper.Warning(GetType(), "用户尝试访问无权限资源", ex);
|
|
|
|
|
|
|
|
|
|
// Error: 系统异常,需要立即处理
|
|
|
|
|
Log4Helper.Error(GetType(), "数据库操作失败", ex);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3. 性能优化
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
// ✅ 使用异步操作
|
|
|
|
|
await repository.GetListAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
// ✅ 合理使用缓存
|
|
|
|
|
var cacheKey = $"user_{userId}";
|
|
|
|
|
var user = await _cache.GetOrAddAsync(cacheKey,
|
|
|
|
|
() => _userRepository.GetAsync(userId),
|
|
|
|
|
TimeSpan.FromMinutes(30));
|
|
|
|
|
|
|
|
|
|
// ✅ 批量操作
|
|
|
|
|
await repository.InsertRangeAsync(users, cancellationToken);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 4. 依赖注入
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
// ✅ 构造函数注入
|
|
|
|
|
public class UserAppService : SOMSAppServiceBase
|
|
|
|
|
{
|
|
|
|
|
private readonly IUserRepository _userRepository;
|
|
|
|
|
private readonly IUserDomainService _userDomainService;
|
|
|
|
|
|
|
|
|
|
public UserAppService(
|
|
|
|
|
IUserRepository userRepository,
|
|
|
|
|
IUserDomainService userDomainService)
|
|
|
|
|
{
|
|
|
|
|
_userRepository = userRepository;
|
|
|
|
|
_userDomainService = userDomainService;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## ✅ 编译检查
|
|
|
|
|
|
|
|
|
|
- 每次编写代码后立即编译
|
|
|
|
|
- 解决所有编译错误和警告
|
|
|
|
|
- 使用 `dotnet build` 验证整个解决方案
|
|
|
|
|
- 启用 Nullable 引用类型检查
|
|
|
|
|
- 配置 EditorConfig 统一代码格式
|
|
|
|
|
|
|
|
|
|
## 🚫 开发约束
|
|
|
|
|
|
|
|
|
|
- ❌ 不需要创建测试项目
|
|
|
|
|
- ❌ 不需要生成 Summary.md 文件
|
|
|
|
|
- ❌ 避免在Domain层引用基础设施
|
|
|
|
|
- ❌ 不要在AppService中直接操作数据库连接
|
|
|
|
|
- ❌ 避免在业务逻辑中硬编码字符串
|
|
|
|
|
|
|
|
|
|
## 📚 参考文档
|
|
|
|
|
|
|
|
|
|
- [ASP.NET Boilerplate官方文档](https://aspnetboilerplate.com/Pages/Documents)
|
|
|
|
|
- .NET 6.0 官方文档
|
|
|
|
|
- C# 编程指南
|