






课程背景介绍
随着人工智能的发展,特别是ChatGPT等大语言模型的出现,为开发者提供了一个全新的学习编程的方式。
通过AI辅助,我们可以:
快速获取示例代码。
理解编程概念。
解决开发问题。
加速学习过程。
开发需求,实现功能,甚至做项目。
本课程阿笨老师借助 AI 的强大编程能力,精心打造实际项目里三层架构的真实案例。从项目搭建到功能实现,每一个环节都深度融合前沿技术与实战经验。这些内容,将彻底打破你对传统编程的认知,重塑你的开发思维,带你领略 AI 时代编程的全新魅力。
在AI时代,谁掌握了AI技巧,谁就能在竞争中领先一步,抢占技术发展的制高点 !
第1章 项目基础搭建
1.1 项目分层架构图
1.2 创建解决方案和项目结构
# 创建解决方案
dotnet new sln -n ABenNet.AI.UserManagePlus
# 创建各个项目
dotnet new webapi -n UserManagePlus.API
dotnet new classlib -n UserManagePlus.Domain
dotnet new classlib -n UserManagePlus.Service
dotnet new classlib -n UserManagePlus.Repository
dotnet new classlib -n UserManagePlus.Common
# 将项目添加到解决方案
dotnet sln add UserManagePlus.API
dotnet sln add UserManagePlus.Domain
dotnet sln add UserManagePlus.Service
dotnet sln add UserManagePlus.Repository
dotnet sln add UserManagePlus.Common
1.2 配置项目引用关系和依赖关系
UserManagePlus.API 依赖 UserManagePlus.Service
UserManagePlus.Service 依赖 UserManagePlus.Repository 和 UserManagePlus.Domain
UserManagePlus.Repository 依赖 UserManagePlus.Domain
所有项目都依赖 UserManagePlus.Common
cd UserManagePlus.API
dotnet add reference ../UserManagePlus.Service
dotnet add reference ../UserManagePlus.Common
cd ../UserManagePlus.Service
dotnet add reference ../UserManagePlus.Repository
dotnet add reference ../UserManagePlus.Domain
dotnet add reference ../UserManagePlus.Common
cd ../UserManagePlus.Repository
dotnet add reference ../UserManagePlus.Domain
dotnet add reference ../UserManagePlus.Common
cd ../UserManagePlus.Domain
dotnet add reference ../UserManagePlus.Common
UserManagePlus.API:
/Controllers - 控制器
/Filters - 过滤器
/Middlewares - 中间件
UserManagePlus.Domain:
/Entities - 实体类
UserManagePlus.Service:
/Interfaces - 服务接口
/Services - 服务实现
/DTOs - 数据传输对象
UserManagePlus.Repository:
/Interfaces - 仓储接口
/Repositories - 仓储实现
/DbContexts - EF Core上下文
UserManagePlus.Common:
/Helpers - 帮助类
/Extensions - 扩展方法
/Constants - 常量定义
这是基础项目结构的搭建。接下来我们需要:
安装必要的NuGet包
配置Swagger
配置JWT认证
# 创建API项目文件夹
New-Item -Path ".\UserManagePlus.API\Controllers" -ItemType Directory -Force
New-Item -Path ".\UserManagePlus.API\Filters" -ItemType Directory -Force
New-Item -Path ".\UserManagePlus.API\Middlewares" -ItemType Directory -Force
# 创建Domain项目文件夹
New-Item -Path ".\UserManagePlus.Domain\Entities" -ItemType Directory -Force
# 创建Service项目文件夹
New-Item -Path ".\UserManagePlus.Service\Interfaces" -ItemType Directory -Force
New-Item -Path ".\UserManagePlus.Service\Services" -ItemType Directory -Force
New-Item -Path ".\UserManagePlus.Service\DTOs" -ItemType Directory -Force
# 创建Repository项目文件夹
New-Item -Path ".\UserManagePlus.Repository\Interfaces" -ItemType Directory -Force
New-Item -Path ".\UserManagePlus.Repository\Repositories" -ItemType Directory -Force
New-Item -Path ".\UserManagePlus.Repository\DbContexts" -ItemType Directory -Force
# 创建Common项目文件夹
New-Item -Path ".\UserManagePlus.Common\Helpers" -ItemType Directory -Force
New-Item -Path ".\UserManagePlus.Common\Extensions" -ItemType Directory -Force
New-Item -Path ".\UserManagePlus.Common\Constants" -ItemType Directory -Force
# 在每个文件夹中创建一个空的 .gitkeep 文件以保持文件夹结构
Get-ChildItem -Directory -Recurse | ForEach-Object {
New-Item -Path "$($_.FullName)\.gitkeep" -ItemType File -Force
}
# 更新项目文件以包含文件夹
$projects = @(
"UserManagePlus.API",
"UserManagePlus.Domain",
"UserManagePlus.Service",
"UserManagePlus.Repository",
"UserManagePlus.Common"
)
foreach ($project in $projects) {
$csproj = Get-Item ".\$project\$project.csproj"
$content = Get-Content $csproj
if (!($content -match "<ItemGroup>\s*<Folder Include=""*"" >\s*</ItemGroup>")) {
$folderGroup = "`n <ItemGroup>`n"
Get-ChildItem ".\$project" -Directory | ForEach-Object {
$folderPath = $_.Name
$folderGroup += " <Folder Include=""$folderPath\"" >`n"
}
$folderGroup += " </ItemGroup>"
$content = $content -replace "</Project>", "$folderGroup`n</Project>"
$content | Set-Content $csproj -Force
}
}
Write-Host "Folders created and added to projects successfully!" -ForegroundColor Green
在解决方案根目录下打开PowerShell
PS D:\Project\GitHubProject\AIProject\ABenNet.AI.UserManagePlus>
运行脚本:
.\setup-folders.ps1
为每个项目创建所需的文件夹结构
在每个文件夹中创建.gitkeep文件(确保空文件夹也能被git追踪)
更新每个项目的.csproj文件,确保文件夹被正确包含在项目中
显示成功消息
如果遇到执行策略问题,可以通过以下命令临时允许脚本执行:
powershellCopySet-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
然后再运行脚本。完成后,所有文件夹都会被创建并正确添加到项目中。
您可以在Visual Studio中查看项目结构,确认所有文件夹都已正确创建和包含。
升级 EF Core 工具到匹配版本:
dotnet tool update --global dotnet-ef --version 9.0.1
UserManagePlus.API 项目需要添加的包:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Swashbuckle.AspNetCore
dotnet add package Microsoft.AspNetCore.OpenApi
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
UserManagePlus.Repository 项目需要添加的包:
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
UserManagePlus.Common 项目需要添加的包:
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json
# 存储每个项目需要安装的包
$packages = @{
"UserManagePlus.API" = @(
"Microsoft.AspNetCore.Authentication.JwtBearer",
"Swashbuckle.AspNetCore",
"Microsoft.AspNetCore.OpenApi",
"Microsoft.EntityFrameworkCore.Design",
"Microsoft.EntityFrameworkCore.Tools"
)
"UserManagePlus.Repository" = @(
"Microsoft.EntityFrameworkCore",
"Microsoft.EntityFrameworkCore.SqlServer",
"Microsoft.EntityFrameworkCore.Tools"
)
"UserManagePlus.Common" = @(
"Microsoft.Extensions.Configuration",
"Microsoft.Extensions.Configuration.Json"
)
}
# 遍历每个项目安装包
foreach ($project in $packages.Keys) {
Write-Host "Installing packages for $project..." -ForegroundColor Yellow
foreach ($package in $packages[$project]) {
try {
Write-Host " Installing $package..." -ForegroundColor Cyan
dotnet add ".\$project\$project.csproj" package $package
Write-Host " Successfully installed $package" -ForegroundColor Green
}
catch {
Write-Host " Failed to install $package" -ForegroundColor Red
Write-Host $_.Exception.Message -ForegroundColor Red
}
}
Write-Host "Completed package installation for $project" -ForegroundColor Green
Write-Host ""
}
Write-Host "All packages installation completed!" -ForegroundColor Green
1.3 配置Swagger
// 添加Swagger服务
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "UserManagePlus.API",
Version = "v1",
Description = "UserManagePlus API",
Contact = new OpenApiContact
{
Name = "API Support",
Email = "support@example.com"
}
});
添加JWT认证配置
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: 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);
}
});
//启用Swagger
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
c.RoutePrefix = string.Empty; 这样可以直接访问根目录
});
1.4 配置JWT认证中间件
// UserManagePlusCommon/Constants/JwtSettings.cs
public class JwtSettings
{
public string SecretKey { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int ExpirationInMinutes { get; set; }
}
// 添加JWT配置
var jwtSettings = builder.Configuration.GetSection("JwtSettings").Get<JwtSettings>();
builder.Services.Configure<JwtSettings>(builder.Configuration.GetSection("JwtSettings"));
builder.Services.AddAuthorization();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings.Issuer,
ValidAudience = jwtSettings.Audience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(jwtSettings.SecretKey))
};
});
{
"JwtSettings": {
"SecretKey": "eZDnyH3vzxsK8;-AC%9TdX+~Bj$(VE{L",
"Issuer": "UserManagePlus",
"Audience": "UserManagePlusAPI",
"ExpirationInMinutes": 60
}
}
对于 JWT 的 SecretKey,我建议使用以下方式生成一个安全的密钥:
https://passwordsgenerator.net/
选择长度为 32 或 64 字符
//app.UseHttpsRedirection();
// 启用路由
app.UseRouting();
app.UseAuthentication(); 添加认证中间件
app.UseAuthorization(); // 添加授权中间件
// 映射控制器路由
app.MapControllers();
//app.UseHttpsRedirection();
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5211",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
第2章 数据库设计与EF Core配置
2.1 实体类的设计
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UserManagePlus.Common.Constants
{
public enum VerificationCodeType
{
Registration = 1,
PasswordReset = 2,
EmailChange = 3
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UserManagePlus.Domain.Entities
{
public abstract class BaseEntity
{
public int Id { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime? UpdatedTime { get; set; }
public bool IsDeleted { get; set; }
}
}
namespace UserManagePlus.Domain.Entities
{
public class User : BaseEntity
{
public string Email { get; set; }
public string PasswordHash { get; set; }
public string PasswordSalt { get; set; }
public bool IsActive { get; set; }
public bool IsEmailConfirmed { get; set; }
public DateTime? LastLoginTime { get; set; }
}
}
using UserManagePlus.Common.Constants;
namespace UserManagePlus.Domain.Entities
{
public class VerificationCode : BaseEntity
{
public string Email { get; set; } // 存储发送目标邮箱
public string Code { get; set; }
public VerificationCodeType CodeType { get; set; }
public DateTime ExpirationTime { get; set; }
public bool IsUsed { get; set; }
public DateTime SendTime { get; set; }
}
}
2.2 EF Core配置与数据库迁移
// UserManagePlus.Repository/DbContexts/UserManageDbContext.cs
public class UserManageDbContext : DbContext
{
public UserManageDbContext(DbContextOptions<UserManageDbContext> options)
: base(options)
{
}
public DbSet<User> Users { get; set; }
public DbSet<VerificationCode> VerificationCodes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
User实体配置
modelBuilder.Entity<User>(entity =>
{
配置主键为自增
entity.Property(e => e.Id)
.UseIdentityColumn();
entity.Property(e => e.Email)
.IsRequired()
.HasMaxLength(100);
entity.Property(e => e.PasswordHash)
.IsRequired()
.HasMaxLength(256);
entity.Property(e => e.PasswordSalt)
.IsRequired()
.HasMaxLength(128);
软删除过滤器
entity.HasQueryFilter(e => !e.IsDeleted);
创建唯一索引
entity.HasIndex(e => e.Email)
.IsUnique()
.HasFilter("[IsDeleted] = 0");
});
VerificationCode实体配置
modelBuilder.Entity<VerificationCode>(entity =>
{
配置主键为自增
entity.Property(e => e.Id)
.UseIdentityColumn();
entity.Property(e => e.Code)
.IsRequired()
.HasMaxLength(6);
entity.Property(e => e.Email)
.IsRequired()
.HasMaxLength(100);
entity.Property(e => e.CodeType)
.IsRequired();
软删除过滤器
entity.HasQueryFilter(e => !e.IsDeleted);
创建索引以优化验证码查询
entity.HasIndex(e => new { e.Email, e.SendTime });
});
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
var entries = ChangeTracker.Entries()
.Where(e => e.Entity is BaseEntity && e.State is EntityState.Added or EntityState.Modified);
foreach (var entry in entries)
{
var entity = (BaseEntity)entry.Entity;
if (entry.State == EntityState.Added)
{
entity.CreatedTime = DateTime.UtcNow;
}
else if (entry.State == EntityState.Modified)
{
entity.UpdatedTime = DateTime.UtcNow;
}
}
return base.SaveChangesAsync(cancellationToken);
}
}
{
"ConnectionStrings": {
"DefaultConnection": "Server=127.0.0.1;Database=UserManagePlusDb;User Id=sa;Password=123456;TrustServerCertificate=True"
}
}
builder.Services.AddDbContext<UserManageDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
# 在Repository项目目录下执行
dotnet ef migrations add InitialCreate --startup-project ../UserManagePlus.API
dotnet ef database update --startup-project ../UserManagePlus.API
2.3 仓储层接口设计和实现
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using UserManagePlus.Domain.Entities;
namespace UserManagePlus.Repository.Interfaces
{
<summary>
通用仓储接口
定义了实体的基本CRUD操作和查询方法
</summary>
<typeparam name="TEntity">实体类型,必须继承自BaseEntity</typeparam>
public interface IRepository<TEntity> where TEntity : BaseEntity
{
<summary>
根据ID异步获取实体
</summary>
<param name="id">实体ID</param>
<returns>返回查找到的实体,如果未找到则返回null</returns>
Task<TEntity> GetByIdAsync(int id);
<summary>
异步获取所有实体
</summary>
<returns>返回实体集合</returns>
Task<IEnumerable<TEntity>> GetAllAsync();
<summary>
异步添加新实体
</summary>
<param name="entity">要添加的实体</param>
<returns>返回添加后的实体(包含生成的ID)</returns>
<exception cref="ArgumentNullException">当实体为null时抛出</exception>
Task<TEntity> AddAsync(TEntity entity);
<summary>
异步更新实体
</summary>
<param name="entity">要更新的实体</param>
<returns>更新操作的任务</returns>
<exception cref="ArgumentNullException">当实体为null时抛出</exception>
Task UpdateAsync(TEntity entity);
<summary>
异步删除实体
</summary>
<param name="id">要删除的实体ID</param>
<returns>删除操作的任务</returns>
<exception cref="KeyNotFoundException">当实体不存在时抛出</exception>
Task DeleteAsync(int id);
<summary>
异步检查实体是否存在
</summary>
<param name="id">要检查的实体ID</param>
<returns>如果实体存在返回true,否则返回false</returns>
Task<bool> ExistsAsync(int id);
<summary>
获取实体的查询接口
</summary>
<returns>返回可查询的IQueryable接口</returns>
<remarks>
此方法用于构建复杂查询,返回IQueryable以支持延迟执行
</remarks>
IQueryable<TEntity> GetQueryable();
<summary>
异步批量添加实体
</summary>
<param name="entities">要添加的实体集合</param>
<returns>添加操作的任务</returns>
<exception cref="ArgumentNullException">当实体集合为null时抛出</exception>
Task AddRangeAsync(IEnumerable<TEntity> entities);
<summary>
异步批量更新实体
</summary>
<param name="entities">要更新的实体集合</param>
<returns>更新操作的任务</returns>
<exception cref="ArgumentNullException">当实体集合为null时抛出</exception>
Task UpdateRangeAsync(IEnumerable<TEntity> entities);
<summary>
异步根据条件删除实体
</summary>
<param name="predicate">删除条件表达式</param>
<returns>删除操作的任务</returns>
<remarks>
此方法将删除所有满足条件的实体
使用软删除机制,不会真正从数据库中删除数据
</remarks>
<example>
<code>
删除所有未激活的用户
await DeleteWhereAsync(u => !u.IsActive);
</code>
</example>
Task DeleteWhereAsync(Expression<Func<TEntity, bool>> predicate);
}
}
using UserManagePlus.Domain.Entities;
namespace UserManagePlus.Repository.Interfaces
{
<summary>
用户仓储接口
</summary>
public interface IUserRepository : IRepository<User>
{
<summary>
根据邮箱地址获取用户
</summary>
Task<User> GetByEmailAsync(string email);
<summary>
检查邮箱是否已存在
</summary>
Task<bool> IsEmailExistsAsync(string email);
<summary>
更新用户的最后登录时间
</summary>
Task<bool> UpdateLastLoginTimeAsync(int userId);
<summary>
更新用户密码
</summary>
Task<bool> UpdatePasswordAsync(int userId, string passwordHash, string passwordSalt);
<summary>
更新邮箱确认状态
</summary>
Task<bool> UpdateEmailConfirmationStatusAsync(int userId, bool isConfirmed);
<summary>
更新用户状态
</summary>
Task<bool> UpdateUserStatusAsync(int userId, bool isActive);
<summary>
检查用户密码是否正确
</summary>
Task<bool> CheckPasswordAsync(int userId, string passwordHash);
<summary>
获取活跃用户
</summary>
Task<IEnumerable<User>> GetActiveUsersAsync();
<summary>
获取指定时间段内注册的用户
</summary>
Task<IEnumerable<User>> GetUsersByRegistrationDateAsync(DateTime startTime, DateTime endTime);
}
}
using UserManagePlus.Common.Constants;
using UserManagePlus.Domain.Entities;
namespace UserManagePlus.Repository.Interfaces
{
<summary>
验证码仓储接口
</summary>
public interface IVerificationCodeRepository : IRepository<VerificationCode>
{
<summary>
检查是否可以发送验证码
</summary>
Task<bool> CanSendVerificationCode(string email);
<summary>
将验证码标记为已使用
</summary>
Task<bool> MarkCodeAsUsedAsync(string email, string code, VerificationCodeType codeType);
<summary>
清理过期的验证码
</summary>
Task<int> CleanExpiredCodesAsync();
<summary>
验证码是否有效
</summary>
Task<bool> IsCodeValidAsync(string email, string code, VerificationCodeType codeType);
}
}
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using UserManagePlus.Domain.Entities;
using UserManagePlus.Repository.DbContexts;
using UserManagePlus.Repository.Interfaces;
namespace UserManagePlus.Repository.Repositories
{
<summary>
通用仓储的基类实现
提供实体的基本CRUD操作和查询方法
</summary>
<typeparam name="TEntity">实体类型,必须继承自BaseEntity</typeparam>
<remarks>
此类提供了基本的数据访问功能,包括:
- 基本的CRUD操作
- 软删除支持
- 异步操作支持
- 批量操作支持
</remarks>
public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : BaseEntity
{
<summary>
数据库上下文
</summary>
protected readonly UserManageDbContext _context;
<summary>
实体集
</summary>
protected readonly DbSet<TEntity> _dbSet;
<summary>
初始化仓储基类的新实例
</summary>
<param name="context">数据库上下文</param>
<exception cref="ArgumentNullException">当context为null时抛出</exception>
public BaseRepository(UserManageDbContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_dbSet = context.Set<TEntity>();
}
<summary>
根据ID异步获取实体
</summary>
<param name="id">实体ID</param>
<returns>返回查找到的实体,如果未找到则返回null</returns>
public virtual async Task<TEntity> GetByIdAsync(int id)
{
return await _dbSet.FindAsync(id);
}
<summary>
异步获取所有实体
</summary>
<returns>返回实体集合</returns>
<remarks>
此方法会返回未被软删除的所有实体
</remarks>
public virtual async Task<IEnumerable<TEntity>> GetAllAsync()
{
return await _dbSet.ToListAsync();
}
<summary>
异步添加新实体
</summary>
<param name="entity">要添加的实体</param>
<returns>返回添加后的实体(包含生成的ID)</returns>
<exception cref="ArgumentNullException">当entity为null时抛出</exception>
<exception cref="Exception">当添加操作失败时抛出</exception>
public virtual async Task<TEntity> AddAsync(TEntity entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
try
{
entity.CreatedTime = DateTime.Now;
entity.IsDeleted = false;
await _dbSet.AddAsync(entity);
await _context.SaveChangesAsync();
return entity;
}
catch (Exception ex)
{
throw new Exception($"Could not add entity: {ex.Message}", ex);
}
}
<summary>
异步更新实体
</summary>
<param name="entity">要更新的实体</param>
<returns>更新操作的任务</returns>
<exception cref="ArgumentNullException">当entity为null时抛出</exception>
<exception cref="Exception">当更新操作失败时抛出</exception>
<remarks>
此方法会自动设置UpdatedTime,并确保CreatedTime和IsDeleted不被修改
</remarks>
public virtual async Task UpdateAsync(TEntity entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
try
{
entity.UpdatedTime = DateTime.Now;
_context.Entry(entity).State = EntityState.Modified;
确保这些字段不被修改
_context.Entry(entity).Property(x => x.CreatedTime).IsModified = false;
_context.Entry(entity).Property(x => x.IsDeleted).IsModified = false;
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
throw new Exception($"Could not update entity: {ex.Message}", ex);
}
}
<summary>
异步删除实体(软删除)
</summary>
<param name="id">要删除的实体ID</param>
<returns>删除操作的任务</returns>
<exception cref="KeyNotFoundException">当实体不存在时抛出</exception>
<exception cref="Exception">当删除操作失败时抛出</exception>
public virtual async Task DeleteAsync(int id)
{
try
{
var entity = await GetByIdAsync(id);
if (entity == null)
throw new KeyNotFoundException($"Entity with id {id} not found");
软删除
entity.IsDeleted = true;
entity.UpdatedTime = DateTime.Now;
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
throw new Exception($"Could not delete entity: {ex.Message}", ex);
}
}
<summary>
异步检查实体是否存在
</summary>
<param name="id">要检查的实体ID</param>
<returns>如果实体存在返回true,否则返回false</returns>
public virtual async Task<bool> ExistsAsync(int id)
{
return await _dbSet.AnyAsync(e => e.Id == id && !e.IsDeleted);
}
<summary>
获取实体的查询接口
</summary>
<returns>返回可查询的IQueryable接口</returns>
<remarks>
此方法返回的IQueryable可用于构建复杂查询
</remarks>
public virtual IQueryable<TEntity> GetQueryable()
{
return _dbSet.AsQueryable();
}
<summary>
异步批量添加实体
</summary>
<param name="entities">要添加的实体集合</param>
<returns>添加操作的任务</returns>
<exception cref="ArgumentNullException">当entities为null时抛出</exception>
<exception cref="Exception">当批量添加操作失败时抛出</exception>
public virtual async Task AddRangeAsync(IEnumerable<TEntity> entities)
{
if (entities == null)
throw new ArgumentNullException(nameof(entities));
try
{
foreach (var entity in entities)
{
entity.CreatedTime = DateTime.Now;
entity.IsDeleted = false;
}
await _dbSet.AddRangeAsync(entities);
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
throw new Exception($"Could not add entities: {ex.Message}", ex);
}
}
<summary>
异步批量更新实体
</summary>
<param name="entities">要更新的实体集合</param>
<returns>更新操作的任务</returns>
<exception cref="ArgumentNullException">当entities为null时抛出</exception>
<exception cref="Exception">当批量更新操作失败时抛出</exception>
public virtual async Task UpdateRangeAsync(IEnumerable<TEntity> entities)
{
if (entities == null)
throw new ArgumentNullException(nameof(entities));
try
{
foreach (var entity in entities)
{
entity.UpdatedTime = DateTime.Now;
_context.Entry(entity).State = EntityState.Modified;
_context.Entry(entity).Property(x => x.CreatedTime).IsModified = false;
_context.Entry(entity).Property(x => x.IsDeleted).IsModified = false;
}
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
throw new Exception($"Could not update entities: {ex.Message}", ex);
}
}
<summary>
异步根据条件删除实体(软删除)
</summary>
<param name="predicate">删除条件表达式</param>
<returns>删除操作的任务</returns>
<exception cref="Exception">当批量删除操作失败时抛出</exception>
<remarks>
此方法使用软删除机制,将所有符合条件的实体标记为已删除
</remarks>
public virtual async Task DeleteWhereAsync(Expression<Func<TEntity, bool>> predicate)
{
try
{
var entities = await _dbSet.Where(predicate).ToListAsync();
foreach (var entity in entities)
{
entity.IsDeleted = true;
entity.UpdatedTime = DateTime.Now;
}
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
throw new Exception($"Could not delete entities: {ex.Message}", ex);
}
}
}
}
using Microsoft.EntityFrameworkCore;
using UserManagePlus.Domain.Entities;
using UserManagePlus.Repository.DbContexts;
using UserManagePlus.Repository.Interfaces;
namespace UserManagePlus.Repository.Repositories
{
<summary>
用户仓储的具体实现
</summary>
public class UserRepository : BaseRepository<User>, IUserRepository
{
<summary>
构造函数
</summary>
<param name="context">数据库上下文</param>
public UserRepository(UserManageDbContext context) : base(context)
{
}
<summary>
根据邮箱地址获取用户
</summary>
<param name="email">邮箱地址</param>
<returns>用户实体,如果不存在则返回null</returns>
public async Task<User> GetByEmailAsync(string email)
{
if (string.IsNullOrEmpty(email))
throw new ArgumentNullException(nameof(email));
return await _dbSet
.FirstOrDefaultAsync(u => u.Email == email && !u.IsDeleted);
}
<summary>
检查邮箱是否已存在
</summary>
<param name="email">要检查的邮箱地址</param>
<returns>true表示邮箱已存在,false表示邮箱不存在</returns>
public async Task<bool> IsEmailExistsAsync(string email)
{
if (string.IsNullOrEmpty(email))
throw new ArgumentNullException(nameof(email));
return await _dbSet.AnyAsync(u => u.Email == email && !u.IsDeleted);
}
<summary>
更新用户的最后登录时间
</summary>
<param name="userId">用户ID</param>
<returns>更新操作是否成功</returns>
public async Task<bool> UpdateLastLoginTimeAsync(int userId)
{
try
{
var user = await GetByIdAsync(userId);
if (user == null)
return false;
user.LastLoginTime = DateTime.UtcNow;
await UpdateAsync(user);
return true;
}
catch
{
return false;
}
}
<summary>
更新用户密码
</summary>
<param name="userId">用户ID</param>
<param name="passwordHash">新密码的哈希值</param>
<param name="passwordSalt">新密码的盐值</param>
<returns>更新操作是否成功</returns>
public async Task<bool> UpdatePasswordAsync(int userId, string passwordHash, string passwordSalt)
{
if (string.IsNullOrEmpty(passwordHash))
throw new ArgumentNullException(nameof(passwordHash));
if (string.IsNullOrEmpty(passwordSalt))
throw new ArgumentNullException(nameof(passwordSalt));
try
{
var user = await GetByIdAsync(userId);
if (user == null)
return false;
user.PasswordHash = passwordHash;
user.PasswordSalt = passwordSalt;
await UpdateAsync(user);
return true;
}
catch
{
return false;
}
}
<summary>
更新邮箱确认状态
</summary>
<param name="userId">用户ID</param>
<param name="isConfirmed">确认状态</param>
<returns>更新操作是否成功</returns>
public async Task<bool> UpdateEmailConfirmationStatusAsync(int userId, bool isConfirmed)
{
try
{
var user = await GetByIdAsync(userId);
if (user == null)
return false;
user.IsEmailConfirmed = isConfirmed;
await UpdateAsync(user);
return true;
}
catch
{
return false;
}
}
<summary>
更新用户状态
</summary>
<param name="userId">用户ID</param>
<param name="isActive">活动状态</param>
<returns>更新操作是否成功</returns>
public async Task<bool> UpdateUserStatusAsync(int userId, bool isActive)
{
try
{
var user = await GetByIdAsync(userId);
if (user == null)
return false;
user.IsActive = isActive;
await UpdateAsync(user);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 检查用户密码是否正确
/// </summary>
/// <param name="userId">用户ID</param>
/// <param name="passwordHash">待验证的密码哈希</param>
/// <returns>true表示密码正确,false表示密码错误</returns>
public async Task<bool> CheckPasswordAsync(int userId, string passwordHash)
{
if (string.IsNullOrEmpty(passwordHash))
throw new ArgumentNullException(nameof(passwordHash));
var user = await GetByIdAsync(userId);
return user != null && user.PasswordHash == passwordHash;
}
/// <summary>
/// 获取活跃用户
/// </summary>
/// <returns>活跃用户列表</returns>
public async Task<IEnumerable<User>> GetActiveUsersAsync()
{
return await _dbSet
.Where(u => u.IsActive && !u.IsDeleted)
.ToListAsync();
}
/// <summary>
/// 获取指定时间段内注册的用户
/// </summary>
/// <param name="startTime">开始时间</param>
/// <param name="endTime">结束时间</param>
/// <returns>符合条件的用户列表</returns>
public async Task<IEnumerable<User>> GetUsersByRegistrationDateAsync(DateTime startTime, DateTime endTime)
{
return await _dbSet
.Where(u => u.CreatedTime >= startTime &&
u.CreatedTime <= endTime &&
!u.IsDeleted)
.ToListAsync();
}
}
}
using Microsoft.EntityFrameworkCore;
using UserManagePlus.Common.Constants;
using UserManagePlus.Domain.Entities;
using UserManagePlus.Repository.DbContexts;
using UserManagePlus.Repository.Interfaces;
namespace UserManagePlus.Repository.Repositories
{
/// <summary>
/// 验证码仓储的具体实现
/// </summary>
public class VerificationCodeRepository : BaseRepository<VerificationCode>, IVerificationCodeRepository
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="context">数据库上下文</param>
public VerificationCodeRepository(UserManageDbContext context) : base(context)
{
}
/// <summary>
/// 检查是否可以发送验证码
/// 规则:60分钟内最多发送3次
/// </summary>
/// <param name="email">目标邮箱地址</param>
/// <returns>true表示可以发送,false表示已达到发送限制</returns>
public async Task<bool> CanSendVerificationCode(string email)
{
if (string.IsNullOrEmpty(email))
throw new ArgumentNullException(nameof(email));
var hourAgo = DateTime.Now.AddHours(-1);
var count = await _dbSet.CountAsync(v =>
v.Email == email &&
v.SendTime >= hourAgo &&
!v.IsDeleted);
return count < 3;
}
/// <summary>
/// 将验证码标记为已使用
/// </summary>
public async Task<bool> MarkCodeAsUsedAsync(string email, string code, VerificationCodeType codeType)
{
var verificationCode = await _dbSet
.FirstOrDefaultAsync(v => v.Email == email &&
v.Code == code &&
v.CodeType == codeType &&
!v.IsDeleted &&
!v.IsUsed);
verificationCode.IsUsed = true;
verificationCode.UpdatedTime = DateTime.Now;
await _context.SaveChangesAsync();
return true;
}
/// <summary>
/// 清理过期的验证码
/// </summary>
/// <returns>清理的记录数</returns>
public async Task<int> CleanExpiredCodesAsync()
{
var expiredCodes = await _dbSet
.Where(v => v.ExpirationTime < DateTime.Now && !v.IsDeleted)
.ToListAsync();
foreach (var code in expiredCodes)
{
await DeleteAsync(code.Id);
}
return expiredCodes.Count;
}
/// <summary>
/// 验证码是否有效
/// </summary>
/// <param name="email">邮箱地址</param>
/// <param name="code">验证码</param>
/// <param name="codeType">验证码类型</param>
/// <returns>true表示验证码有效,false表示验证码无效</returns>
public async Task<bool> IsCodeValidAsync(string email, string code, VerificationCodeType codeType)
{
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(code))
return false;
var verificationCode = await _dbSet
.FirstOrDefaultAsync(v =>
v.Email == email &&
v.Code == code &&
v.CodeType == codeType &&
v.ExpirationTime > DateTime.Now &&
!v.IsUsed &&
!v.IsDeleted);
return verificationCode != null;
}
}
}
第3章 业务逻辑层实现
3.1 用户注册功能
创建DTO类
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UserManagePlus.Service.DTOs
{
public class UserRegistrationDto
{
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email format")]
[MaxLength(100)]
public string Email { get; set; }
[Required(ErrorMessage = "Password is required")]
[MinLength(6, ErrorMessage = "Password must be at least 6 characters")]
[MaxLength(100)]
public string Password { get; set; }
[Required(ErrorMessage = "Verification code is required")]
[StringLength(6, MinimumLength = 6, ErrorMessage = "Verification code must be 6 characters")]
public string VerificationCode { get; set; }
}
}
实现密码加密
实现邮箱验证
using UserManagePlus.Common.Constants;
namespace UserManagePlus.Service.Interfaces
{
public interface IEmailService
{
/// <summary>
/// 发送验证码邮件
/// </summary>
Task SendVerificationCodeEmailAsync(string to, string code, VerificationCodeType codeType);
}
}
using Microsoft.Extensions.Configuration;
using System.Net.Mail;
using System.Net;
using UserManagePlus.Common.Constants;
using UserManagePlus.Service.Interfaces;
namespace UserManagePlus.Service.Services
{
public class EmailService : IEmailService
{
private readonly IConfiguration _configuration;
public EmailService(IConfiguration configuration)
{
_configuration = configuration;
}
public async Task SendVerificationCodeEmailAsync(string to, string code, VerificationCodeType codeType)
{
var subject = codeType switch
{
VerificationCodeType.Registration => "Registration Verification Code",
VerificationCodeType.PasswordReset => "Password Reset Verification Code",
_ => "Verification Code"
};
var body = $"Your verification code is: {code}\nThis code will expire in 5 minutes.";
await SendEmailAsync(to, subject, body);
}
private async Task SendEmailAsync(string to, string subject, string body)
{
using var message = new MailMessage();
message.To.Add(to);
message.Subject = subject;
message.Body = body;
message.From = new MailAddress(_configuration["Email:From"]);
using var client = new SmtpClient(_configuration["Email:SmtpServer"]);
client.Port = int.Parse(_configuration["Email:Port"]);
client.Credentials = new NetworkCredential(
_configuration["Email:Username"],
_configuration["Email:Password"]);
client.EnableSsl = true;
await client.SendMailAsync(message);
}
}
}
完成注册逻辑
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UserManagePlus.Common.Constants;
using UserManagePlus.Service.DTOs;
namespace UserManagePlus.Service.Interfaces
{
public interface IUserService
{
/// <summary>
/// 用户注册
/// </summary>
Task<UserDto> RegisterAsync(UserRegistrationDto registrationDto);
/// <summary>
/// 用户登录
/// </summary>
Task<(UserDto User, string Token)> LoginAsync(UserLoginDto loginDto);
/// <summary>
/// 发送验证码
/// </summary>
Task<bool> SendVerificationCodeAsync(string email, VerificationCodeType codeType);
/// <summary>
/// 重置密码
/// </summary>
Task<bool> ResetPasswordAsync(PasswordResetDto resetDto);
/// <summary>
/// 验证验证码
/// </summary>
Task<bool> VerifyCodeAsync(string email, string code, VerificationCodeType codeType);
/// <summary>
/// 获取用户信息
/// </summary>
Task<UserDto> GetUserAsync(int userId);
}
}
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using UserManagePlus.Common.Constants;
using UserManagePlus.Common.Helpers;
using UserManagePlus.Domain.Entities;
using UserManagePlus.Repository.Interfaces;
using UserManagePlus.Service.DTOs;
using UserManagePlus.Service.Interfaces;
namespace UserManagePlus.Service.Services
{
public class UserService : IUserService
{
private readonly IUserRepository _userRepository;
private readonly IVerificationCodeRepository _verificationCodeRepository;
private readonly IEmailService _emailService;
private readonly JwtSettings _jwtSettings;
public UserService(
IUserRepository userRepository,
IVerificationCodeRepository verificationCodeRepository,
IEmailService emailService,
IOptions<JwtSettings> jwtSettings)
{
_userRepository = userRepository;
_verificationCodeRepository = verificationCodeRepository;
_emailService = emailService;
_jwtSettings = jwtSettings.Value;
}
public async Task<UserDto> RegisterAsync(UserRegistrationDto registrationDto)
{
// 验证邮箱是否已存在
if (await _userRepository.IsEmailExistsAsync(registrationDto.Email))
throw new Exception("Email already exists");
// 验证验证码
if (!await _verificationCodeRepository.IsCodeValidAsync(
registrationDto.Email,
registrationDto.VerificationCode,
VerificationCodeType.Registration))
throw new Exception("Invalid verification code");
// 创建密码哈希
string passwordHash, passwordSalt;
PasswordHelper.CreatePasswordHash(registrationDto.Password, out passwordHash, out passwordSalt);
// 创建用户实体
var user = new User
{
Email = registrationDto.Email,
PasswordHash = passwordHash,
PasswordSalt = passwordSalt,
IsActive = true,
IsEmailConfirmed = true
};
// 保存用户
await _userRepository.AddAsync(user);
// 标记验证码已使用
await _verificationCodeRepository.MarkCodeAsUsedAsync(registrationDto.Email, registrationDto.VerificationCode, VerificationCodeType.Registration);
return MapToDto(user);
}
public async Task<(UserDto User, string Token)> LoginAsync(UserLoginDto loginDto)
{
var user = await _userRepository.GetByEmailAsync(loginDto.Email);
if (user == null)
throw new Exception("Invalid email or password");
if (!user.IsActive)
throw new Exception("Account is not active");
// 验证密码
if (!PasswordHelper.VerifyPasswordHash(loginDto.Password, user.PasswordHash, user.PasswordSalt))
throw new Exception("Invalid email or password");
// 更新最后登录时间
await _userRepository.UpdateLastLoginTimeAsync(user.Id);
// 生成JWT token
var token = GenerateJwtToken(user);
return (MapToDto(user), token);
}
public async Task<bool> SendVerificationCodeAsync(string email, VerificationCodeType codeType)
{
// 检查发送频率限制
if (!await _verificationCodeRepository.CanSendVerificationCode(email))
throw new Exception("Too many verification code requests");
// 生成验证码
string code = CodeGenerator.GenerateVerificationCode();
// 创建验证码记录
var verificationCode = new VerificationCode
{
Email = email,
Code = code,
CodeType = codeType,
ExpirationTime = DateTime.Now.AddMinutes(5),
SendTime = DateTime.Now,
IsUsed = false
};
// 保存验证码
await _verificationCodeRepository.AddAsync(verificationCode);
// 发送验证码邮件
await _emailService.SendVerificationCodeEmailAsync(email, code, codeType);
return true;
}
public async Task<bool> ResetPasswordAsync(PasswordResetDto resetDto)
{
// 验证验证码
if (!await _verificationCodeRepository.IsCodeValidAsync(
resetDto.Email,
resetDto.VerificationCode,
VerificationCodeType.PasswordReset))
throw new Exception("Invalid verification code");
var user = await _userRepository.GetByEmailAsync(resetDto.Email);
if (user == null)
throw new Exception("User not found");
// 创建新的密码哈希
string passwordHash, passwordSalt;
PasswordHelper.CreatePasswordHash(resetDto.NewPassword, out passwordHash, out passwordSalt);
// 更新密码
await _userRepository.UpdatePasswordAsync(user.Id, passwordHash, passwordSalt);
// 标记验证码已使用
await _verificationCodeRepository.MarkCodeAsUsedAsync(resetDto.Email, resetDto.VerificationCode, VerificationCodeType.PasswordReset);
return true;
}
public async Task<bool> VerifyCodeAsync(string email, string code, VerificationCodeType codeType)
{
return await _verificationCodeRepository.IsCodeValidAsync(email, code, codeType);
}
public async Task<UserDto> GetUserAsync(int userId)
{
var user = await _userRepository.GetByIdAsync(userId);
if (user == null)
throw new Exception("User not found");
return MapToDto(user);
}
private string GenerateJwtToken(User user)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_jwtSettings.SecretKey);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Email, user.Email)
}),
Expires = DateTime.UtcNow.AddMinutes(_jwtSettings.ExpirationInMinutes),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature),
Issuer = _jwtSettings.Issuer,
Audience = _jwtSettings.Audience
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
private UserDto MapToDto(User user)
{
return new UserDto
{
Id = user.Id,
Email = user.Email,
IsActive = user.IsActive,
IsEmailConfirmed = user.IsEmailConfirmed,
LastLoginTime = user.LastLoginTime,
CreatedTime = user.CreatedTime
};
}
}
}
3.2 用户登录功能
创建DTO类
using System.ComponentModel.DataAnnotations;
namespace UserManagePlus.Service.DTOs
{
public class UserLoginDto
{
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email format")]
public string Email { get; set; }
[Required(ErrorMessage = "Password is required")]
public string Password { get; set; }
}
}
namespace UserManagePlus.Service.DTOs
{
public class UserDto
{
public int Id { get; set; }
public string Email { get; set; }
public bool IsActive { get; set; }
public bool IsEmailConfirmed { get; set; }
public DateTime? LastLoginTime { get; set; }
public DateTime CreatedTime { get; set; }
}
}
实现JWT token生成
实现登录验证
记录登录时间
3.1 密码重置功能
创建DTO类
using System.ComponentModel.DataAnnotations;
namespace UserManagePlus.Service.DTOs
{
public class PasswordResetDto
{
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email format")]
public string Email { get; set; }
[Required(ErrorMessage = "New password is required")]
[MinLength(6, ErrorMessage = "Password must be at least 6 characters")]
public string NewPassword { get; set; }
[Required(ErrorMessage = "Verification code is required")]
[StringLength(6, MinimumLength = 6, ErrorMessage = "Verification code must be 6 characters")]
public string VerificationCode { get; set; }
}
}
发送重置验证码
验证重置请求
实现邮箱验证
更新用户密码
第4章 API接口层实现
4.1、统一返回结果
创建统一返回模型
实现返回结果包装
using Microsoft.AspNetCore.Mvc;
using UserManagePlus.Common.Constants;
namespace UserManagePlus.API.Controllers
{
public abstract class ApiControllerBase : Controller
{
#region Success Response
protected IActionResult Success<T>(T data, string message = "Success")
{
return Json(ApiResult<T>.Ok(data, message));
}
protected IActionResult Success(string message = "Success")
{
return Json(ApiResult<object>.Ok(null, message));
}
#endregion
#region Error Response
protected IActionResult Error(string message = "Fail")
{
return Json(ApiResult<object>.Fail(ErrorCodes.ServerError, message, null));
}
#endregion
}
}
4.2、全局异常处理
创建异常处理中间件
using System.Text.Json;
namespace UserManagePlus.API.Middlewares
{
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "An unhandled exception occurred.");
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
return context.Response.WriteAsync(JsonSerializer.Serialize(new
{
success = false,
message = "An error occurred while processing your request.",
error = exception.Message
}));
}
}
public static class ExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ExceptionMiddleware>();
}
}
}
配置异常处理
app.UseExceptionMiddleware();
4.3、用户控制器开发
实现注册接口
实现登录接口
实现密码重置接口
实现获取用户信息接口
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using UserManagePlus.Common.Constants;
using UserManagePlus.Domain.Entities;
using UserManagePlus.Service.DTOs;
using UserManagePlus.Service.Interfaces;
namespace UserManagePlus.API.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class UserController : ApiControllerBase
{
private readonly IUserService _userService;
public UserController(IUserService userService)
{
_userService = userService;
}
/// <summary>
/// 用户注册
/// </summary>
[HttpPost("register")]
[ProducesResponseType(typeof(ApiResult<UserDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> Register(UserRegistrationDto model)
{
var result = await _userService.RegisterAsync(model);
return Success(result, "Registration successful");
}
/// <summary>
/// 用户登录
/// </summary>
[HttpPost("login")]
[ProducesResponseType(typeof(ApiResult<(UserDto user, string token)>), StatusCodes.Status200OK)]
public async Task<IActionResult> Login(UserLoginDto model)
{
var (user, token) = await _userService.LoginAsync(model);
return Success(new { User = user, Token = token }, "Login successful");
}
/// <summary>
/// 发送验证码
/// </summary>
[HttpPost("send-code")]
[ProducesResponseType(typeof(ApiResult<bool>), StatusCodes.Status200OK)]
public async Task<IActionResult> SendVerificationCode(string email, VerificationCodeType codeType)
{
var result = await _userService.SendVerificationCodeAsync(email, codeType);
return Success(result, "Verification code sent");
}
/// <summary>
/// 重置密码
/// </summary>
[HttpPost("reset-password")]
[ProducesResponseType(typeof(ApiResult<bool>), StatusCodes.Status200OK)]
public async Task<IActionResult> ResetPassword(PasswordResetDto model)
{
var result = await _userService.ResetPasswordAsync(model);
return Success(result, "Password reset successful");
}
/// <summary>
/// 获取用户信息
/// </summary>
[Authorize]
[HttpGet("profile")]
[ProducesResponseType(typeof(ApiResult<UserDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetProfile()
{
var userId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier)?.Value);
var user = await _userService.GetUserAsync(userId);
return Success(user);
}
/// <summary>
/// 验证验证码
/// </summary>
[HttpPost("verify-code")]
[ProducesResponseType(typeof(ApiResult<bool>), StatusCodes.Status200OK)]
public async Task<IActionResult> VerifyCode(string email, string code, VerificationCodeType codeType)
{
var result = await _userService.VerifyCodeAsync(email, code, codeType);
return Success(result, result ? "Code verified" : "Invalid code");
}
}
}
"Email": {
"From": "422159763@qq.com",
"SmtpServer": "smtp.qq.com",
"Port": 587,
"Username": "422159763@qq.com",
"Password": "ejeagyqhpplicadf" //替换自己邮箱服务商的SMTP
}
4.4、WebApi视图ModelState验证过滤器
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using UserManagePlus.Common.Constants;
namespace UserManagePlus.API.Filters
{
public class ApiValidationFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errors = context.ModelState
.SelectMany(x => x.Value.Errors)
.Select(x => x.ErrorMessage)
.ToList();
context.Result = new BadRequestObjectResult(
ApiResult<object>.Fail(
ErrorCodes.ValidationFailed,
string.Join("; ", errors)
)
);
}
}
}
}
var builder = WebApplication.CreateBuilder(args);
/* 参看网站:https://blog.csdn.net/sammy520/article/details/117173361
//1.Net Core 禁用模型验证过滤器
SuppressModelStateInvalidFilter = true时,会关闭默认模型验证过滤器。
[ApiController] 默认自带有400模型验证,且优先级比较高,如果需要自定义模型验证,则需要先关闭默认的模型验证
*/
// 关闭默认的ModelState验证过滤器。
builder.Services.Configure<ApiBehaviorOptions>(options => options.SuppressModelStateInvalidFilter = true);
// 添加控制器并配置过滤器
builder.Services.AddControllers(options =>
{
options.Filters.Add<ApiValidationFilter>(); // 全局注册验证过滤器
});
第5章 项目完善和部署
5.1、项目部署
下载 .NET(Linux、macOS 和 Windows)
下载 .NET 9.0 (Linux、macOS 和 Windows)
5.2、发布项目
5.3、配置IIS
5.4、Docker部署SQL Server 2022数据库
打开cmd命令行窗口
cd C:\Users\hp\docker
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=千万不能设置为简单的密码123456否则启动后立即停止的情况,示例:123+QWE@COM" ^
-p 1433:1433 --name sqlserver2022 --hostname sqlserver2022 ^
-e "MSSQL_COLLATION=Chinese_PRC_CI_AS" ^
-e "TZ=Asia/Shanghai" ^
-v %cd%/mssql/data:/var/opt/mssql/data ^
-d ^
--restart=always ^
mcr.microsoft.com/mssql/server:2022-latest
5.5、Docker部署项目
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"JwtSettings": {
"SecretKey": "eZDnyH3vzxsK8;-AC%9TdX+~Bj$(VE{L",
"Issuer": "UserManagePlus",
"Audience": "UserManagePlusAPI",
"ExpirationInMinutes": 60
},
"ConnectionStrings": {
"DefaultConnection": "Server=host.docker.internal;Database=UserManagePlusDb;User Id=sa;Password=123456;TrustServerCertificate=True"
},
"Email": {
"From": "422159763@qq.com",
"SmtpServer": "smtp.qq.com",
"Port": 587,
"Username": "422159763@qq.com",
"Password": "ejeagyqhpplicadf"
}
}
ConnectionStrings连接字符串中的Server调整为host.docker.internal,比如,您在主机上运行 ASP.NET CORE WebApi服务器,Docker 容器可以通过网络访问连接到主机的SQLSERVER 具体名为host.docker.internal 。当您在 Windows 或 Mac 计算机上工作时,这是最简单的技术。
第6章 AI时代来临,编程新机遇!
AI时代来临,编程新机遇!
在AI的浪潮下,编程不再仅仅是技术,更是让AI为你服务的能力。想要驾驭AI?从现在开始,向AI编程迈出第一步,让AI成为你事业的得力助手!
这门课程将充分展现阿笨NET老师的实战AI编程技能——你现在看到的仅仅是冰山一角!在课程中,你将接触到远超你想象的AI编程开发实战技巧,这些技能将彻底改变你的开发思维,带你领跑AI时代的技术前沿。
阿笨老师凭借多年的开发与教学经验,带来的是最贴近实际项目的内容,不仅仅是理论知识,更是实战经验的沉淀。如果你想在AI编程领域领先一步,阿笨NET老师的VIP课程是你不可错过的机会!
感兴趣的朋友,可以私信联系阿笨老师,成为终生VIP学员,提前掌握未来的开发技能,赢在起跑线上!
价格可能稍高,但请相信,真正值得的知识,是可以让你快速回报的投资。无论你是入行新人,还是寻求技术突破的开发者,阿笨老师的课程,都能带你走向成功!
需要了解更多或报名的朋友,欢迎私信阿笨NET老师,一起开启AI编程的无限可能!