暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

ASP.NET Core + WebAPI + EF Core 三层架构快速入门指南【AI编程源码仅售¥9.99】


课程背景介绍

随着人工智能的发展,特别是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编程的无限可能!

文章转载自跟着阿笨一起玩NET,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论