第二十章:最佳实践与常见问题
一、项目结构最佳实践
1.1 推荐的分层架构
Furion 推荐使用分层架构来组织项目,以下是标准的四层架构:
MyApp.sln
├── MyApp.Web.Entry/ # 入口层(Web 启动项目)
│ ├── Program.cs
│ ├── appsettings.json
│ └── wwwroot/
├── MyApp.Application/ # 应用层(业务逻辑、服务、DTO)
│ ├── Services/
│ ├── Dtos/
│ └── Mappers/
├── MyApp.Core/ # 核心层(实体、枚举、常量、接口定义)
│ ├── Entities/
│ ├── Enums/
│ ├── Constants/
│ └── Interfaces/
├── MyApp.EntityFramework.Core/ # 数据层(数据库上下文、仓储、种子数据)
│ ├── DbContexts/
│ ├── Repositories/
│ ├── Migrations/
│ └── SeedData/
└── MyApp.Tests/ # 测试层
├── UnitTests/
└── IntegrationTests/
1.2 各层职责说明
| 层 | 项目 | 职责 | 依赖 |
|---|---|---|---|
| 入口层 | Web.Entry | 程序入口、中间件配置、启动配置 | 引用 Application |
| 应用层 | Application | 业务逻辑、服务实现、动态 API | 引用 Core |
| 核心层 | Core | 实体定义、接口、常量、枚举 | 仅引用 Furion |
| 数据层 | EntityFramework.Core | 数据库上下文、仓储实现 | 引用 Core |
| 测试层 | Tests | 单元测试、集成测试 | 引用 Application |
1.3 领域驱动设计(DDD)思想
MyApp.sln
├── MyApp.Web.Entry/ # 用户接口层
├── MyApp.Application/ # 应用服务层
│ ├── Commands/ # 命令处理(写操作)
│ │ ├── CreateOrderCommand.cs
│ │ └── CreateOrderCommandHandler.cs
│ ├── Queries/ # 查询处理(读操作)
│ │ ├── GetOrderQuery.cs
│ │ └── GetOrderQueryHandler.cs
│ └── EventHandlers/ # 事件处理
├── MyApp.Domain/ # 领域层
│ ├── Aggregates/ # 聚合根
│ │ ├── Order/
│ │ │ ├── Order.cs # 聚合根实体
│ │ │ ├── OrderItem.cs # 值对象
│ │ │ └── IOrderRepository.cs # 仓储接口
│ │ └── User/
│ ├── Events/ # 领域事件
│ │ └── OrderCreatedEvent.cs
│ ├── Services/ # 领域服务
│ │ └── PricingService.cs
│ └── ValueObjects/ # 值对象
│ ├── Money.cs
│ └── Address.cs
├── MyApp.Infrastructure/ # 基础设施层
│ ├── Persistence/
│ │ ├── AppDbContext.cs
│ │ └── Repositories/
│ └── ExternalServices/
└── MyApp.Tests/
1.4 领域实体设计示例
/// <summary>
/// 订单聚合根
/// </summary>
public class Order : EntityBase
{
/// <summary>
/// 订单号
/// </summary>
public string OrderNo { get; private set; }
/// <summary>
/// 订单状态
/// </summary>
public OrderStatus Status { get; private set; }
/// <summary>
/// 买家ID
/// </summary>
public long BuyerId { get; private set; }
/// <summary>
/// 订单项列表
/// </summary>
private readonly List<OrderItem> _orderItems = new();
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly();
/// <summary>
/// 订单总金额
/// </summary>
public decimal TotalAmount => _orderItems.Sum(item => item.SubTotal);
/// <summary>
/// 创建订单
/// </summary>
public static Order Create(long buyerId, List<OrderItem> items)
{
if (items == null || !items.Any())
throw new BusinessException("订单项不能为空");
var order = new Order
{
OrderNo = GenerateOrderNo(),
BuyerId = buyerId,
Status = OrderStatus.Pending
};
foreach (var item in items)
{
order._orderItems.Add(item);
}
// 发布领域事件
order.AddDomainEvent(new OrderCreatedEvent(order));
return order;
}
/// <summary>
/// 支付订单
/// </summary>
public void Pay()
{
if (Status != OrderStatus.Pending)
throw new BusinessException("只有待支付的订单才能支付");
Status = OrderStatus.Paid;
AddDomainEvent(new OrderPaidEvent(this));
}
/// <summary>
/// 取消订单
/// </summary>
public void Cancel(string reason)
{
if (Status == OrderStatus.Shipped)
throw new BusinessException("已发货的订单不能取消");
Status = OrderStatus.Cancelled;
AddDomainEvent(new OrderCancelledEvent(this, reason));
}
private static string GenerateOrderNo()
{
return $"ORD{DateTime.Now:yyyyMMddHHmmss}{Random.Shared.Next(1000, 9999)}";
}
}
/// <summary>
/// 订单状态枚举
/// </summary>
public enum OrderStatus
{
Pending = 0, // 待支付
Paid = 1, // 已支付
Shipped = 2, // 已发货
Completed = 3, // 已完成
Cancelled = 4 // 已取消
}
二、编码规范
2.1 命名规范
| 元素 | 规范 | 正确示例 | 错误示例 |
|---|---|---|---|
| 类名 | PascalCase | UserService |
userService |
| 接口 | I + PascalCase | IUserService |
UserServiceInterface |
| 方法 | PascalCase | GetUserById |
getUserById |
| 属性 | PascalCase | UserName |
userName |
| 私有字段 | _ + camelCase | _userService |
userService |
| 局部变量 | camelCase | userName |
UserName |
| 常量 | PascalCase | MaxRetryCount |
MAX_RETRY_COUNT |
| 枚举值 | PascalCase | OrderStatus.Pending |
OrderStatus.PENDING |
| 异步方法 | 以 Async 结尾 | GetUserAsync |
GetUser |
| DTO 类 | 以 Dto 结尾 | UserDto |
UserModel |
| 输入模型 | 以 Input 结尾 | CreateUserInput |
CreateUserRequest |
2.2 注释规范
/// <summary>
/// 用户管理服务
/// </summary>
/// <remarks>
/// 提供用户的增删改查功能,包含数据验证和权限检查。
/// </remarks>
public class UserAppService : IDynamicApiController
{
/// <summary>
/// 根据 ID 获取用户详情
/// </summary>
/// <param name="id">用户 ID</param>
/// <returns>用户详情信息</returns>
/// <exception cref="BusinessException">当用户不存在时抛出</exception>
public async Task<UserDto> GetAsync(long id)
{
// 查询用户(包含部门信息)
var user = await _repository
.Include(u => u.Department)
.FirstOrDefaultAsync(u => u.Id == id)
?? throw Oops.Oh("用户不存在");
return user.Adapt<UserDto>();
}
}
2.3 代码组织规范
/// <summary>
/// 类成员排列顺序规范
/// </summary>
public class SampleService
{
// 1. 常量
private const int MaxRetryCount = 3;
// 2. 静态字段
private static readonly object _lock = new();
// 3. 私有字段
private readonly IUserRepository _userRepository;
private readonly ILogger<SampleService> _logger;
// 4. 构造函数
public SampleService(
IUserRepository userRepository,
ILogger<SampleService> logger)
{
_userRepository = userRepository;
_logger = logger;
}
// 5. 公有属性
public string ServiceName => "SampleService";
// 6. 公有方法
public async Task<UserDto> GetUserAsync(long id)
{
return await _userRepository.GetByIdAsync(id);
}
// 7. 受保护方法
protected virtual void OnUserCreated(User user)
{
_logger.LogInformation("用户创建: {UserId}", user.Id);
}
// 8. 私有方法
private bool ValidateInput(CreateUserInput input)
{
return !string.IsNullOrWhiteSpace(input.UserName);
}
}
三、性能优化技巧
3.1 异步编程最佳实践
/// <summary>
/// 异步编程最佳实践
/// </summary>
public class AsyncBestPractices
{
private readonly IRepository<Order> _orderRepo;
private readonly IRepository<Product> _productRepo;
private readonly ICacheService _cache;
// ✅ 正确:并行执行多个独立的异步操作
public async Task<DashboardDto> GetDashboardAsync()
{
var orderCountTask = _orderRepo.CountAsync();
var productCountTask = _productRepo.CountAsync();
var recentOrdersTask = _orderRepo.AsQueryable()
.OrderByDescending(o => o.CreatedTime)
.Take(10)
.ToListAsync();
await Task.WhenAll(orderCountTask, productCountTask, recentOrdersTask);
return new DashboardDto
{
OrderCount = orderCountTask.Result,
ProductCount = productCountTask.Result,
RecentOrders = recentOrdersTask.Result
};
}
// ❌ 错误:同步阻塞异步方法
public DashboardDto GetDashboardSync_Wrong()
{
// 可能导致死锁!
var orderCount = _orderRepo.CountAsync().Result;
return new DashboardDto { OrderCount = orderCount };
}
// ✅ 正确:使用 ConfigureAwait(false) 在库代码中
public async Task<int> GetCountInLibraryAsync()
{
return await _orderRepo.CountAsync().ConfigureAwait(false);
}
// ✅ 正确:避免不必要的 async/await
public Task<Order> GetOrderAsync(long id)
{
// 简单的透传不需要 async/await
return _orderRepo.FindAsync(id);
}
}
3.2 批量操作优化
/// <summary>
/// 批量操作优化
/// </summary>
public class BatchOperationService
{
private readonly IRepository<Product> _repository;
// ❌ 低效:逐条插入
public async Task ImportProducts_Slow(List<ProductInput> inputs)
{
foreach (var input in inputs)
{
var product = input.Adapt<Product>();
await _repository.InsertAsync(product);
}
}
// ✅ 高效:批量插入
public async Task ImportProducts_Fast(List<ProductInput> inputs)
{
var products = inputs.Select(i => i.Adapt<Product>()).ToList();
await _repository.InsertRangeAsync(products);
}
// ✅ 更高效:使用 EFCore.BulkExtensions
public async Task ImportProducts_Fastest(List<ProductInput> inputs)
{
var products = inputs.Select(i => i.Adapt<Product>()).ToList();
await _repository.Context.BulkInsertAsync(products);
}
}
3.3 缓存策略优化
/// <summary>
/// 多级缓存策略
/// </summary>
public class CacheOptimizationService
{
private readonly IMemoryCache _memoryCache;
private readonly IDistributedCache _distributedCache;
private readonly IRepository<Product> _repository;
public CacheOptimizationService(
IMemoryCache memoryCache,
IDistributedCache distributedCache,
IRepository<Product> repository)
{
_memoryCache = memoryCache;
_distributedCache = distributedCache;
_repository = repository;
}
/// <summary>
/// 两级缓存查询
/// </summary>
public async Task<ProductDto> GetProductAsync(long id)
{
var cacheKey = $"product:{id}";
// 第一级:内存缓存(最快)
if (_memoryCache.TryGetValue(cacheKey, out ProductDto cached))
{
return cached;
}
// 第二级:分布式缓存(Redis)
var json = await _distributedCache.GetStringAsync(cacheKey);
if (!string.IsNullOrEmpty(json))
{
var dto = JsonSerializer.Deserialize<ProductDto>(json);
// 回填内存缓存
_memoryCache.Set(cacheKey, dto, TimeSpan.FromMinutes(5));
return dto;
}
// 第三级:数据库查询
var product = await _repository.FindAsync(id);
if (product == null) return null;
var result = product.Adapt<ProductDto>();
// 回填两级缓存
_memoryCache.Set(cacheKey, result, TimeSpan.FromMinutes(5));
await _distributedCache.SetStringAsync(cacheKey,
JsonSerializer.Serialize(result),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
});
return result;
}
}
3.4 数据库查询优化
/// <summary>
/// 数据库查询优化示例
/// </summary>
public class QueryOptimizationService
{
private readonly IRepository<Order> _repository;
// ✅ 使用投影查询,只查需要的字段
public async Task<List<OrderListDto>> GetOrderListAsync()
{
return await _repository.AsQueryable()
.Select(o => new OrderListDto
{
Id = o.Id,
OrderNo = o.OrderNo,
TotalAmount = o.TotalAmount,
Status = o.Status,
CreatedTime = o.CreatedTime
})
.ToListAsync();
}
// ✅ 使用 AsNoTracking 提高查询性能
public async Task<List<Order>> GetOrdersReadOnlyAsync()
{
return await _repository.AsQueryable()
.AsNoTracking()
.ToListAsync();
}
// ✅ 避免 N+1 查询问题
public async Task<List<Order>> GetOrdersWithItemsAsync()
{
// 使用 Include 预加载关联数据
return await _repository.AsQueryable()
.Include(o => o.OrderItems)
.Include(o => o.Buyer)
.ToListAsync();
}
// ✅ 使用分页查询避免大量数据加载
public async Task<PagedResult<OrderDto>> GetPagedOrdersAsync(
int page, int pageSize)
{
var query = _repository.AsQueryable()
.OrderByDescending(o => o.CreatedTime);
var totalCount = await query.CountAsync();
var items = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(o => o.Adapt<OrderDto>())
.ToListAsync();
return new PagedResult<OrderDto>
{
Items = items,
TotalCount = totalCount,
Page = page,
PageSize = pageSize
};
}
}
四、安全最佳实践
4.1 输入验证与防护
/// <summary>
/// 安全输入验证
/// </summary>
public class SecurityBestPractices
{
/// <summary>
/// ✅ 使用参数化查询防止 SQL 注入
/// </summary>
public async Task<List<User>> SearchUsersAsync(string keyword)
{
// ✅ 正确:使用参数化查询
return await _context.Users
.Where(u => u.UserName.Contains(keyword))
.ToListAsync();
// ❌ 危险:拼接 SQL(SQL 注入风险)
// var sql = $"SELECT * FROM Users WHERE UserName LIKE '%{keyword}%'";
}
/// <summary>
/// ✅ XSS 防护 - 输出编码
/// </summary>
public string SanitizeOutput(string input)
{
return System.Net.WebUtility.HtmlEncode(input);
}
/// <summary>
/// ✅ 敏感数据加密存储
/// </summary>
public class UserEntity
{
public long Id { get; set; }
public string UserName { get; set; }
// 手机号脱敏存储
[SensitiveData]
public string Phone { get; set; }
// 身份证号加密存储
[Encrypted]
public string IdCard { get; set; }
}
}
4.2 CSRF 防护
// Program.cs - CSRF 防护配置
builder.Services.AddAntiforgery(options =>
{
options.HeaderName = "X-CSRF-TOKEN";
options.Cookie.Name = "CSRF-TOKEN";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
// 在需要 CSRF 防护的接口上使用
[ValidateAntiForgeryToken]
public async Task<UserDto> CreateUser(CreateUserInput input)
{
// ...
}
4.3 敏感数据处理
/// <summary>
/// 数据脱敏工具类
/// </summary>
public static class DataMasking
{
/// <summary>
/// 手机号脱敏:138****1234
/// </summary>
public static string MaskPhone(string phone)
{
if (string.IsNullOrEmpty(phone) || phone.Length < 7)
return phone;
return phone[..3] + "****" + phone[^4..];
}
/// <summary>
/// 身份证号脱敏:110***********1234
/// </summary>
public static string MaskIdCard(string idCard)
{
if (string.IsNullOrEmpty(idCard) || idCard.Length < 8)
return idCard;
return idCard[..3] + new string('*', idCard.Length - 7) + idCard[^4..];
}
/// <summary>
/// 邮箱脱敏:z***n@example.com
/// </summary>
public static string MaskEmail(string email)
{
if (string.IsNullOrEmpty(email)) return email;
var atIndex = email.IndexOf('@');
if (atIndex <= 1) return email;
return email[0] + new string('*', Math.Min(atIndex - 1, 3))
+ email[(atIndex - 1)..];
}
}
五、单元测试与集成测试
5.1 单元测试(xUnit + Moq)
using Xunit;
using Moq;
/// <summary>
/// 用户服务单元测试
/// </summary>
public class UserServiceTests
{
private readonly Mock<IRepository<User>> _mockRepo;
private readonly Mock<ILogger<UserAppService>> _mockLogger;
private readonly UserAppService _service;
public UserServiceTests()
{
_mockRepo = new Mock<IRepository<User>>();
_mockLogger = new Mock<ILogger<UserAppService>>();
_service = new UserAppService(_mockRepo.Object, _mockLogger.Object);
}
[Fact]
public async Task GetUser_ExistingId_ReturnsUser()
{
// Arrange
var expectedUser = new User { Id = 1, UserName = "张三" };
_mockRepo.Setup(r => r.FindAsync(1))
.ReturnsAsync(expectedUser);
// Act
var result = await _service.GetAsync(1);
// Assert
Assert.NotNull(result);
Assert.Equal("张三", result.UserName);
}
[Fact]
public async Task GetUser_NonExistingId_ThrowsException()
{
// Arrange
_mockRepo.Setup(r => r.FindAsync(999))
.ReturnsAsync((User)null);
// Act & Assert
await Assert.ThrowsAsync<Exception>(
() => _service.GetAsync(999));
}
[Theory]
[InlineData("")]
[InlineData(null)]
[InlineData(" ")]
public async Task CreateUser_InvalidName_ThrowsValidation(string name)
{
// Arrange
var input = new CreateUserInput { UserName = name };
// Act & Assert
await Assert.ThrowsAsync<ValidationException>(
() => _service.CreateAsync(input));
}
[Fact]
public async Task CreateUser_ValidInput_ReturnsNewUser()
{
// Arrange
var input = new CreateUserInput
{
UserName = "李四",
Email = "lisi@example.com"
};
_mockRepo.Setup(r => r.InsertAsync(It.IsAny<User>()))
.ReturnsAsync((User u) => new EntityEntry<User>(u));
// Act
var result = await _service.CreateAsync(input);
// Assert
Assert.NotNull(result);
Assert.Equal("李四", result.UserName);
_mockRepo.Verify(r => r.InsertAsync(It.IsAny<User>()), Times.Once);
}
}
5.2 集成测试
using Microsoft.AspNetCore.Mvc.Testing;
using System.Net.Http.Json;
/// <summary>
/// API 集成测试
/// </summary>
public class UserApiIntegrationTests
: IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public UserApiIntegrationTests(
WebApplicationFactory<Program> factory)
{
_client = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// 替换为测试数据库
services.RemoveAll(typeof(DbContextOptions<DefaultDbContext>));
services.AddDbContext<DefaultDbContext>(options =>
{
options.UseInMemoryDatabase("TestDb");
});
});
}).CreateClient();
}
[Fact]
public async Task GetUserList_ReturnsSuccessStatusCode()
{
// Act
var response = await _client.GetAsync("/api/user/list");
// Assert
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadFromJsonAsync<RESTfulResult<List<UserDto>>>();
Assert.True(result.Success);
}
[Fact]
public async Task CreateUser_ValidInput_Returns200()
{
// Arrange
var input = new CreateUserInput
{
UserName = "测试用户",
Email = "test@example.com",
Phone = "13800138000"
};
// Act
var response = await _client.PostAsJsonAsync("/api/user", input);
// Assert
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadFromJsonAsync<RESTfulResult<UserDto>>();
Assert.True(result.Success);
Assert.Equal("测试用户", result.Data.UserName);
}
[Fact]
public async Task CreateUser_InvalidInput_Returns400()
{
// Arrange
var input = new CreateUserInput
{
UserName = "", // 空用户名
Email = "invalid-email"
};
// Act
var response = await _client.PostAsJsonAsync("/api/user", input);
// Assert
Assert.Equal(System.Net.HttpStatusCode.BadRequest,
response.StatusCode);
}
}
六、API 文档维护
6.1 Swagger 增强配置
// Program.cs - Swagger 增强配置
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "企业管理系统 API",
Version = "v1",
Description = "基于 Furion 框架的企业管理系统",
Contact = new OpenApiContact
{
Name = "技术团队",
Email = "dev@example.com",
Url = new Uri("https://example.com")
},
License = new OpenApiLicense
{
Name = "MIT",
Url = new Uri("https://opensource.org/licenses/MIT")
}
});
// Bearer Token 认证配置
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT 认证,在下方输入 Bearer {token}",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
// 加载所有 XML 注释文件
var xmlFiles = Directory.GetFiles(
AppContext.BaseDirectory, "*.xml");
foreach (var xmlFile in xmlFiles)
{
options.IncludeXmlComments(xmlFile, true);
}
});
6.2 接口分组与版本管理
/// <summary>
/// 按模块分组的 API 接口
/// </summary>
[ApiDescriptionSettings("系统管理", Name = "User", Order = 100,
Description = "用户管理相关接口")]
public class UserAppService : IDynamicApiController
{
/// <summary>
/// 获取用户列表
/// </summary>
/// <remarks>
/// 示例请求:
///
/// GET /api/user/list?page=1&pageSize=20
///
/// 注意事项:
/// - 需要登录后才能访问
/// - 默认每页返回 20 条数据
/// - 支持关键字模糊搜索
/// </remarks>
[DisplayName("获取用户列表")]
public async Task<PagedResult<UserDto>> GetListAsync(
[FromQuery] UserQueryInput input)
{
// ...
}
}
七、版本管理与发布策略
7.1 语义化版本规范
| 版本号 | 格式 | 说明 | 示例 |
|---|---|---|---|
| 主版本 | X.0.0 | 不兼容的 API 变更 | 2.0.0 |
| 次版本 | 0.X.0 | 向后兼容的新功能 | 1.1.0 |
| 修订版 | 0.0.X | 向后兼容的问题修复 | 1.0.1 |
| 预发布 | X.Y.Z-tag | 预发布版本标记 | 1.0.0-beta.1 |
7.2 Git 分支策略
main(生产分支)
├── develop(开发分支)
│ ├── feature/user-management(功能分支)
│ ├── feature/order-system(功能分支)
│ └── feature/payment(功能分支)
├── release/v1.2.0(发布分支)
└── hotfix/fix-login-bug(热修复分支)
# 功能开发流程
git checkout develop
git checkout -b feature/new-feature
# ... 开发完成 ...
git checkout develop
git merge feature/new-feature
git branch -d feature/new-feature
# 发布流程
git checkout develop
git checkout -b release/v1.2.0
# ... 修复发布前的 bug ...
git checkout main
git merge release/v1.2.0
git tag -a v1.2.0 -m "Release v1.2.0"
git checkout develop
git merge release/v1.2.0
git branch -d release/v1.2.0
# 热修复流程
git checkout main
git checkout -b hotfix/fix-critical-bug
# ... 修复 ...
git checkout main
git merge hotfix/fix-critical-bug
git tag -a v1.2.1 -m "Hotfix v1.2.1"
git checkout develop
git merge hotfix/fix-critical-bug
git branch -d hotfix/fix-critical-bug
八、常见问题汇总
8.1 启动与配置相关
Q1:应用启动失败,提示 “Unable to configure HTTPS endpoint”
// 解决方案:信任开发证书
// 在命令行执行:
// dotnet dev-certs https --trust
// 或在 Program.cs 中禁用 HTTPS(仅开发环境)
if (builder.Environment.IsDevelopment())
{
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(5000); // 仅 HTTP
});
}
Q2:Furion 服务注册失败,提示找不到程序集
// 解决方案:确保所有项目正确引用 Furion
// 在 Web.Entry 的 Program.cs 中
Serve.Run(RunOptions.Default
.AddAssemblies(typeof(SomeServiceInOtherProject).Assembly));
Q3:配置文件修改后不生效
// 解决方案:确保启用了配置热重载
builder.Configuration.AddJsonFile(
"appsettings.json",
optional: false,
reloadOnChange: true // 关键:启用热重载
);
// 使用 IOptionsMonitor 监听配置变更
public class MyService
{
public MyService(IOptionsMonitor<MyOptions> options)
{
options.OnChange(newOptions =>
{
Console.WriteLine("配置已更新");
});
}
}
8.2 数据库相关
Q4:数据库连接失败
// 检查连接字符串格式
// MySQL:
"Server=localhost;Port=3306;Database=mydb;Uid=root;Pwd=123456;CharSet=utf8mb4;"
// PostgreSQL:
"Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=123456;"
// SQL Server:
"Server=localhost;Database=mydb;User Id=sa;Password=123456;TrustServerCertificate=True;"
Q5:EF Core 迁移失败
# 确保安装了 EF Core 工具
dotnet tool install --global dotnet-ef
# 指定启动项目和迁移项目
dotnet ef migrations add InitialCreate \
--project MyApp.EntityFramework.Core \
--startup-project MyApp.Web.Entry
dotnet ef database update \
--project MyApp.EntityFramework.Core \
--startup-project MyApp.Web.Entry
Q6:EF Core 查询性能差
// 1. 使用 AsNoTracking(只读查询)
var users = await _context.Users.AsNoTracking().ToListAsync();
// 2. 使用投影查询
var dtos = await _context.Users.Select(u => new UserDto
{
Id = u.Id,
Name = u.UserName
}).ToListAsync();
// 3. 避免 N+1 查询
var orders = await _context.Orders
.Include(o => o.Items) // 预加载
.ToListAsync();
8.3 跨域相关
Q7:前端请求跨域被拒绝
// Program.cs - CORS 配置
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowFrontend", policy =>
{
policy.WithOrigins(
"http://localhost:3000", // React 开发服务器
"http://localhost:8080", // Vue 开发服务器
"https://www.example.com" // 生产域名
)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
// 注意:UseRouting 之后、UseAuthentication 之前
app.UseRouting();
app.UseCors("AllowFrontend");
app.UseAuthentication();
app.UseAuthorization();
Q8:跨域 Cookie 无法携带
// 确保同时满足以下条件:
// 1. 服务端允许 Credentials
policy.AllowCredentials();
// 2. 前端请求设置 withCredentials
// axios.defaults.withCredentials = true;
// 3. Cookie 设置 SameSite=None 和 Secure
builder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
8.4 认证与授权相关
Q9:JWT Token 验证失败
// 检查 JWT 配置
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "myapp", // 必须与生成 Token 时一致
ValidateAudience = true,
ValidAudience = "myapp", // 必须与生成 Token 时一致
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes("your-secret-key-at-least-32-chars")),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
});
Q10:接口返回 401 但已传递 Token
// 确保中间件注册顺序正确
app.UseRouting();
app.UseAuthentication(); // 先认证
app.UseAuthorization(); // 后授权
app.MapControllers();
// 检查 Token 格式:Authorization: Bearer eyJhbGciOiJIUzI1NiI...
8.5 性能相关
Q11:接口响应慢
// 排查步骤:
// 1. 启用请求日志,找出慢接口
app.UseSerilogRequestLogging();
// 2. 使用 MiniProfiler 分析性能
builder.Services.AddMiniProfiler(options =>
{
options.RouteBasePath = "/profiler";
});
// 3. 检查数据库查询
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(connectionString);
if (builder.Environment.IsDevelopment())
{
options.EnableSensitiveDataLogging();
options.LogTo(Console.WriteLine, LogLevel.Information);
}
});
Q12:内存持续增长(内存泄漏)
// 常见原因和解决方案:
// 1. 事件处理器未取消订阅
public class MyService : IDisposable
{
private readonly EventHandler _handler;
public MyService()
{
_handler = OnEvent;
SomeEvent += _handler;
}
public void Dispose()
{
SomeEvent -= _handler; // ✅ 取消订阅
}
}
// 2. IDisposable 资源未释放
// 使用 using 语句
using var stream = new FileStream("file.txt", FileMode.Open);
// 3. 静态集合持续增长
// 使用 ConcurrentDictionary + 定期清理
8.6 部署相关
Q13:Docker 容器启动后无法访问
# 检查端口映射
docker run -p 5000:80 myapp # 主机 5000 → 容器 80
# 确保应用监听 0.0.0.0 而非 localhost
# 设置环境变量:ASPNETCORE_URLS=http://+:80
Q14:Linux 部署后中文乱码
# 安装中文语言包
sudo apt-get install -y locales
sudo locale-gen zh_CN.UTF-8
export LANG=zh_CN.UTF-8
# 安装中文字体(PDF 生成需要)
sudo apt-get install -y fonts-wqy-zenhei fonts-wqy-microhei
Q15:Nginx 反向代理 WebSocket 失败
# WebSocket 代理配置
location /ws {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400;
}
8.7 其他常见问题
Q16:文件上传大小限制
// Kestrel 配置
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.MaxRequestBodySize = 100 * 1024 * 1024; // 100MB
});
// Form 配置
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = 100 * 1024 * 1024;
});
Q17:时区问题(UTC vs 本地时间)
// 统一使用 UTC 存储,展示时转换
// 配置 JSON 序列化
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(
new DateTimeJsonConverter("yyyy-MM-dd HH:mm:ss"));
});
// 数据库存储 UTC
public class BaseEntity
{
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
}
Q18:Swagger 页面空白或报错
// 确保 XML 注释文件正确生成
// .csproj 中添加:
// <GenerateDocumentationFile>true</GenerateDocumentationFile>
// <NoWarn>$(NoWarn);1591</NoWarn>
// 检查 Swagger 路由是否冲突
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1");
options.RoutePrefix = "swagger";
});
Q19:动态 API 路由不生效
// 确保服务类实现了 IDynamicApiController
public class MyService : IDynamicApiController // ✅
{
public string GetName() => "Hello";
}
// 确保 AddDynamicApiControllers 已注册
builder.Services.AddControllers()
.AddDynamicApiControllers(); // ✅
Q20:依赖注入生命周期问题
// 常见错误:在 Singleton 中注入 Scoped 服务
// ❌ 错误
builder.Services.AddSingleton<MySingletonService>();
builder.Services.AddScoped<MyScopedService>();
public class MySingletonService
{
// 这会导致 Scoped 服务被提升为 Singleton
public MySingletonService(MyScopedService scopedService) { }
}
// ✅ 正确:使用 IServiceScopeFactory
public class MySingletonService
{
private readonly IServiceScopeFactory _scopeFactory;
public MySingletonService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task DoWorkAsync()
{
using var scope = _scopeFactory.CreateScope();
var scopedService = scope.ServiceProvider
.GetRequiredService<MyScopedService>();
await scopedService.ExecuteAsync();
}
}
九、从其他框架迁移到 Furion
9.1 ABP 框架迁移指南
| ABP 概念 | Furion 对应 | 迁移要点 |
|---|---|---|
| ApplicationService | IDynamicApiController | 继承接口即可 |
| IRepository<T> | IRepository<T> | 基本一致 |
| AbpModule | Startup 配置 | 改为标准 ASP.NET Core 启动方式 |
| [UnitOfWork] | [UnitOfWork] | Furion 也支持 |
| EventBus | IEventPublisher | 接口略有不同 |
| 权限系统 | 自定义或集成 | 需要重新实现 |
9.2 传统 WebAPI 迁移指南
// 传统 WebAPI 控制器
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
var user = await _service.GetByIdAsync(id);
return Ok(user);
}
}
// 迁移到 Furion 动态 API
[ApiDescriptionSettings(Name = "User")]
public class UserAppService : IDynamicApiController
{
public async Task<UserDto> Get(int id)
{
return await _service.GetByIdAsync(id);
}
}
// 效果相同,代码更简洁,自动路由映射
十、Furion 生态项目推荐
10.1 推荐项目
| 项目名称 | 说明 | GitHub |
|---|---|---|
| Admin.NET | 基于 Furion 的通用后台管理框架 | zuohuaijun/Admin.NET |
| SimpleAdmin | 简洁的后台管理系统 | 基于 Furion |
| Furion.Pure | Furion 纯净版(无额外依赖) | MonkSoul/Furion |
| Furion.Template | 项目模板脚手架 | dotnet new furion |
10.2 快速创建项目
# 安装 Furion 项目模板
dotnet new install Furion.Template.Api
# 创建新项目
dotnet new furion -n MyApp
# 或使用纯净版模板
dotnet new install Furion.Pure.Template.Api
dotnet new furionpure -n MyApp
十一、学习资源汇总
11.1 官方资源
| 资源 | 地址 | 说明 |
|---|---|---|
| 官方文档 | https://furion.net | 最全面的文档 |
| GitHub 仓库 | https://github.com/MonkSoul/Furion | 源码和 Issue |
| NuGet 包 | https://www.nuget.org/packages/Furion | 包下载 |
| Gitee 镜像 | https://gitee.com/dotnetchina/Furion | 国内镜像 |
11.2 社区资源
| 资源 | 说明 |
|---|---|
| QQ 群 | Furion 官方交流群 |
| 微信群 | 关注公众号获取入群二维码 |
| B 站教程 | 搜索 “Furion 教程” 获取视频教程 |
| 博客园 | 搜索 Furion 相关技术博客 |
| CSDN | Furion 实战系列文章 |
11.3 推荐学习路径
第一阶段:基础入门(1-2周)
├── C# 语言基础
├── ASP.NET Core 基础
├── Furion 快速上手
└── 完成第一个 CRUD 应用
第二阶段:核心能力(2-4周)
├── 依赖注入深入理解
├── EF Core 数据操作
├── 数据验证与异常处理
├── 身份认证与授权
└── 完成一个完整的管理系统
第三阶段:进阶提升(4-8周)
├── 缓存与性能优化
├── 事件总线与消息队列
├── 定时任务与后台服务
├── 多租户架构
└── 微服务架构设计
第四阶段:生产实践(持续学习)
├── Docker 容器化部署
├── CI/CD 流水线搭建
├── 监控与日志体系
├── 安全加固
└── 性能调优
十二、未来展望
12.1 Furion 框架发展趋势
Furion 框架作为国产 .NET 开源框架的佼佼者,未来将持续在以下方向发力:
- 云原生支持:深度集成 Dapr、Service Mesh 等云原生技术
- .NET 新版本跟进:第一时间适配 .NET 最新版本特性
- AI 集成:集成大语言模型(LLM)和 AI 辅助开发能力
- 低代码平台:提供可视化开发工具,降低开发门槛
- 微服务生态:完善微服务相关组件(服务发现、配置中心、链路追踪)
- 国产化适配:持续优化对国产操作系统和数据库的支持
- 性能优化:利用 .NET 最新特性持续提升框架性能
- 开发者体验:更完善的文档、示例和开发工具
12.2 持续学习建议
/// <summary>
/// 技术成长路线
/// </summary>
public class GrowthPath
{
// 1. 深入理解 .NET 运行时机制
// 2. 学习设计模式和架构模式
// 3. 关注性能优化和安全实践
// 4. 参与开源社区贡献
// 5. 关注行业趋势和新技术
}
恭喜你完成了 Furion 框架系列教程的学习!从基础入门到高级特性,从开发实践到部署运维,你已经具备了使用 Furion 框架开发企业级应用的完整能力。希望本教程能够帮助你在实际项目中快速落地,构建高质量的 .NET 应用程序。
百小僧(MonkSoul) 创建的 Furion 框架:让 .NET 开发更简单、更通用、更流行!