整体架构4个项目 标准分层 接口解耦 模块化适配EF Core / Dapper / JWT / 过滤器 / 中间件 / RabbitMQ / RESTful / 路由易维护、可扩展。一、整体架构回顾最终结构项目组成一共 4 个项目全部 .NET 8DeviceRepair.ApiWebAPI 启动项目 / 表现层 控制器、路由、过滤器、中间件、Swagger、管道配置DeviceRepair.Core核心类库底层不依赖任何项目 实体、枚举、DTO、通用返回模型、仓储 / 服务接口DeviceRepair.Infrastructure基础设施层 EF 上下文、仓储实现、Dapper、JWT 工具、RabbitMQ、服务注册扩展DeviceRepair.Services业务逻辑层 业务服务实现按模块拆分依赖规则单向依赖解耦核心DeviceRepair.Api → 引用 DeviceRepair.Core → 引用 DeviceRepair.Services → 引用 DeviceRepair.Infrastructure DeviceRepair.Services → 只引用 DeviceRepair.Core DeviceRepair.Infrastructure → 只引用 DeviceRepair.Core DeviceRepair.Core → 无任何项目引用最底层第一阶段创建 4 个项目 配置引用1. 打开 Visual Studio 2022新建空白解决方案命名DeviceRepair依次创建 4 个项目项目 1DeviceRepair.ApiASP.NET Core Web API模板ASP.NET Core Web API框架.NET 8取消使用控制器可选本文用控制器模式、取消 HTTPS名称DeviceRepair.Api启动项目项目 2DeviceRepair.Core类库模板类库框架.NET 8名称DeviceRepair.Core项目 3DeviceRepair.Infrastructure类库模板类库框架.NET 8名称DeviceRepair.Infrastructure项目 4DeviceRepair.Services类库模板类库框架.NET 8名称DeviceRepair.Services配置项目引用必须严格按依赖来右键项目 →添加 → 项目引用DeviceRepair.Api勾选DeviceRepair.Core、DeviceRepair.Services、DeviceRepair.InfrastructureDeviceRepair.Services勾选DeviceRepair.CoreDeviceRepair.Infrastructure勾选DeviceRepair.CoreDeviceRepair.Core不添加任何引用统一安装 NuGet 包3.1 DeviceRepair.Core无需第三方包纯模型 / 接口不装任何 NuGet。3.2 DeviceRepair.Infrastructure基础设施核心第三方包右键 → 管理 NuGet 程序包安装Microsoft.EntityFrameworkCore Microsoft.EntityFrameworkCore.SqlServer Microsoft.EntityFrameworkCore.Tools Dapper Microsoft.AspNetCore.Authentication.JwtBearer Microsoft.Extensions.ConfigurationProject SdkMicrosoft.NET.Sdk PropertyGroup TargetFrameworknet8.0/TargetFramework ImplicitUsingsenable/ImplicitUsings Nullableenable/Nullable /PropertyGroup ItemGroup PackageReference IncludeDapper Version2.1.35 / PackageReference IncludeMicrosoft.AspNetCore.Authentication.JwtBearer Version8.0.17 / PackageReference IncludeMicrosoft.EntityFrameworkCore Version8.0.17 / PackageReference IncludeMicrosoft.EntityFrameworkCore.Relational Version8.0.17 / PackageReference IncludeMicrosoft.EntityFrameworkCore.Tools Version8.0.17 PrivateAssetsall / PackageReference IncludeMicrosoft.EntityFrameworkCore.SqlServer Version8.0.17 / PackageReference IncludeMicrosoft.Extensions.Configuration Version8.0.0 / /ItemGroup ItemGroup ProjectReference Include..\DeviceRepair.Core\DeviceRepair.Core.csproj / /ItemGroup /Project3.3 DeviceRepair.Services无需额外包3.4 DeviceRepair.ApiWebAPI 基础包默认自带无需额外安装。开发 DeviceRepair.Core核心层该层存放实体、枚举、DTO、通用返回体、接口先写完这一层。1. 新建文件夹结构CoreDeviceRepair.Core ├─ Entities # 数据库实体 ├─ Enums # 枚举 ├─ Dtos # 前后端交互入参/出参 ├─ Interfaces # 仓储接口、服务接口 └─ ResultModel.cs # 全局统一返回格式2. 编写全局统一返回模型ResultModel.csnamespace DeviceRepair.Core { /// summary /// RESTful 统一接口返回格式 /// /summary public class ResultModelT { public int Code { get; set; } public string Msg { get; set; } string.Empty; public T? Data { get; set; } /// summary /// 成功返回 /// /summary public static ResultModelT Success(T? data, string msg 请求成功) { return new ResultModelT { Code 200, Msg msg, Data data }; } /// summary /// 失败返回 /// /summary public static ResultModelT Fail(string msg 请求失败, int code 400) { return new ResultModelT { Code code, Msg msg }; } } }3. Enums 枚举新建Enums/GlobalEnum.csnamespace DeviceRepair.Core.Enums { /// summary /// 用户角色 /// /summary public enum UserRole { Staff 0, // 普通员工 RepairMan 1, // 维修员 Admin 2 // 管理员 } /// summary /// 工单状态 /// /summary public enum OrderStatus { WaitAccept 0, // 待接单 Processing 1, // 处理中 Finished 2, // 已完成 Cancel 3 // 已作废 } }4. Entities 数据库实体4.1 User.cs 用户实体using DeviceRepair.Core.Enums; using System.ComponentModel.DataAnnotations; namespace DeviceRepair.Core.Entities { public class User { public int Id { get; set; } [MaxLength(50)] public string UserName { get; set; } string.Empty; [MaxLength(50)] public string Password { get; set; } string.Empty; [MaxLength(50)] public string? RealName { get; set; } public UserRole Role { get; set; } public DateTime CreateTime { get; set; } public bool IsDeleted { get; set; } } }4.2 Device.cs 设备实体using System.ComponentModel.DataAnnotations; namespace DeviceRepair.Core.Entities { public class Device { public int Id { get; set; } [MaxLength(100)] public string DeviceName { get; set; } string.Empty; [MaxLength(50)] public string? DeviceType { get; set; } [MaxLength(100)] public string? Location { get; set; } public bool IsDeleted { get; set; } } }4.3 RepairOrder.cs 报修工单实体using DeviceRepair.Core.Enums; namespace DeviceRepair.Core.Entities { public class RepairOrder { public int Id { get; set; } public int UserId { get; set; } public int DeviceId { get; set; } public string ProblemDesc { get; set; } string.Empty; public OrderStatus OrderStatus { get; set; } public int? RepairUserId { get; set; } public string? RepairRemark { get; set; } public DateTime CreateTime { get; set; } public DateTime? FinishTime { get; set; } public bool IsDeleted { get; set; } // 导航属性EF Core联表使用 public User User { get; set; } null!; public Device Device { get; set; } null!; public User? RepairUser { get; set; } } }4.4 SystemMessage.cs 消息实体RabbitMQ 用namespace DeviceRepair.Core.Entities { public class SystemMessage { public int Id { get; set; } public int ReceiveUserId { get; set; } public string Content { get; set; } string.Empty; public bool IsRead { get; set; } public DateTime CreateTime { get; set; } } }5. Dtos 入参模型新建Dtos/LoginDto.cs登录入参namespace DeviceRepair.Core.Dtos { /// summary /// 登录请求参数 /// /summary public class LoginDto { public string UserName { get; set; } string.Empty; public string Password { get; set; } string.Empty; } }Interfaces 接口层仓储 服务接口6.1 通用基础仓储接口IBaseRepository.cs所有仓储统一规范using DeviceRepair.Core.Entities; namespace DeviceRepair.Core.Interfaces { /// summary /// 通用仓储接口 /// /summary /// typeparam nameT实体/typeparam public interface IBaseRepositoryT where T : class { TaskListT GetListAsync(); TaskT? GetByIdAsync(int id); Task AddAsync(T entity); Task UpdateAsync(T entity); Task DeleteAsync(int id); } }6.2 用户仓储接口IUserRepository.cs继承通用仓储扩展自有方法using DeviceRepair.Core.Entities; namespace DeviceRepair.Core.Interfaces { public interface IUserRepository : IBaseRepositoryUser { /// summary /// 校验账号密码 /// /summary TaskUser? CheckLoginAsync(string userName, string password); } }6.3 账号服务接口IAccountService.cs业务服务接口using DeviceRepair.Core.Dtos; using DeviceRepair.Core; namespace DeviceRepair.Core.Interfaces { public interface IAccountService { /// summary /// 登录 /// /summary TaskResultModelstring LoginAsync(LoginDto dto); } }✅Core 层全部完成模型、枚举、DTO、接口全部定义完毕。开发 DeviceRepair.Infrastructure基础设施层职责实现接口、EF 上下文、Dapper、JWT、服务注册扩展1. 新建文件夹结构InfrastructureDeviceRepair.Infrastructure ├─ Data # EF Core 数据库上下文 ├─ Repositories # 仓储实现EF ├─ Utils # 工具类Jwt、Dapper、配置 └─ Extensions # 服务注册扩展解耦 Program.cs2. Data 目录数据库上下文AppDbContext.csusing DeviceRepair.Core.Entities; using Microsoft.EntityFrameworkCore; namespace DeviceRepair.Infrastructure.Data { public class AppDbContext : DbContext { public AppDbContext(DbContextOptionsAppDbContext options) : base(options) { } public DbSetUser Users SetUser(); public DbSetDevice Devices SetDevice(); public DbSetRepairOrder RepairOrders SetRepairOrder(); public DbSetSystemMessage SystemMessages SetSystemMessage(); // 全局软删除过滤 protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.EntityUser().HasQueryFilter(u !u.IsDeleted); modelBuilder.EntityDevice().HasQueryFilter(d !d.IsDeleted); modelBuilder.EntityRepairOrder().HasQueryFilter(o !o.IsDeleted); modelBuilder.EntitySystemMessage().HasQueryFilter(m true); } } }3. Utils 工具类3.1 ConfigHelper.cs 配置读取using Microsoft.Extensions.Configuration; namespace DeviceRepair.Infrastructure.Utils { //.NET 官方推荐用依赖注入传 IConfiguration而非静态全局变量。现在这种静态帮助类,适合个人练习、小型项目、快速开发 public static class ConfigHelper { //这是一个静态配置帮助类专门用来读取项目配置文件里的数据库连接字符串后续代码不用反复写读取逻辑直接调用方法就能拿到连接串。 public static IConfiguration Configuration { get; set; } null!; //IConfiguration.NET 配置根对象代表整个 appsettings.json 文件。 //固定语法专门读取 appsettings.json 里 ConnectionStrings 节点下名为 SqlServer 的配置。 public static string GetConnectionString() { //任意地方调用拿连接串,string conn ConfigHelper.GetConnectionString(); return Configuration.GetConnectionString(SqlServer) ?? string.Empty; } } }3.2 JwtHelper.cs JWT 生成工具using DeviceRepair.Core.Entities; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace DeviceRepair.Infrastructure.Utils { //JWT 令牌生成工具类登录成功后用它生成登录凭证Token前端拿着这个令牌访问需要登录的接口服务端就能识别用户身份。 public static class JwtHelper { public static string GenerateToken(User user, string secretKey, int expireMinutes) { //Claim身份信息单元就是把用户数据塞进令牌里接口后续可以从令牌取出这些信息。 var claims new ListClaim { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.UserName), new Claim(Role, user.Role.ToString()) }; //前端拿到 Token 后后端不用再查数据库直接解析就能拿到用户 ID、账号、角色。 // SymmetricSecurityKey把传入的密钥字符串转成加密可用的字节格式 //SigningCredentials指定加密算法这里用 HmacSha256 对称加密 //作用给令牌加签名别人篡改令牌内容后服务端能检测出来。 var key new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)); var cred new SigningCredentials(key, SecurityAlgorithms.HmacSha256); // 构建 JWT 令牌本体 // expires令牌过期时间当前时间 传入的分钟数 //claims上面装好的用户身份信息 //signingCredentials加密签名规则 var token new JwtSecurityToken( expires: DateTime.Now.AddMinutes(expireMinutes), claims: claims, signingCredentials: cred); return new JwtSecurityTokenHandler().WriteToken(token); } } }3.3 DapperHelper.cs Dapper 工具类后续复杂查询用using Dapper; using Microsoft.Data.SqlClient; using System.Data; namespace DeviceRepair.Infrastructure.Utils { public static class DapperHelper { private static IDbConnection GetConn(string connStr) { return new SqlConnection(connStr); } public static async TaskListT QueryAsyncT(string connStr, string sql, object? param null) { using var conn GetConn(connStr); return (await conn.QueryAsyncT(sql, param)).ToList(); } } }4. Repositories 仓储实现UserRepository.cs 实现 IUserRepositoryusing DeviceRepair.Core.Entities; using DeviceRepair.Core.Interfaces; using DeviceRepair.Infrastructure.Data; using Microsoft.EntityFrameworkCore; namespace DeviceRepair.Infrastructure.Repositories { public class UserRepository : IUserRepository { private readonly AppDbContext _db; public UserRepository(AppDbContext db) { _db db; } public async TaskListUser GetListAsync() { return await _db.Users.ToListAsync(); } public async TaskUser? GetByIdAsync(int id) { return await _db.Users.FindAsync(id); } public async Task AddAsync(User entity) { await _db.Users.AddAsync(entity); await _db.SaveChangesAsync(); } public async Task UpdateAsync(User entity) { _db.Users.Update(entity); await _db.SaveChangesAsync(); } public async Task DeleteAsync(int id) { var user await GetByIdAsync(id); if (user ! null) { user.IsDeleted true; await UpdateAsync(user); } } // 登录校验 public async TaskUser? CheckLoginAsync(string userName, string password) { return await _db.Users .FirstOrDefaultAsync(u u.UserName userName u.Password password); } } }5. Extensions 服务注册扩展核心解耦新建ServiceCollectionExtensions.cs把所有依赖注入、JWT、数据库注册全部抽离Program.cs保持极简。using DeviceRepair.Core.Interfaces; using DeviceRepair.Infrastructure.Data; using DeviceRepair.Infrastructure.Repositories; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using System.Text; namespace DeviceRepair.Infrastructure.Extensions { public static class ServiceCollectionExtensions { /// summary /// 注册EF 仓储 /// /summary public static void AddDbAndRepositories(this IServiceCollection services, IConfiguration config) { string connStr config.GetConnectionString(SqlServer)!; services.AddDbContextAppDbContext(opt { opt.UseSqlServer(connStr); }); // 仓储注入 services.AddScopedIUserRepository, UserRepository(); } /// summary /// 注册JWT认证 /// /summary public static void AddJwtAuthentication(this IServiceCollection services, IConfiguration config) { // 1. 从配置文件读取JWT加密密钥 string secretKey config[Jwt:SecretKey]!; // 2. 启用认证默认使用JWT方案 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(opt { // 配置Token校验规则 opt.TokenValidationParameters new TokenValidationParameters { ValidateIssuer false, // 不校验令牌签发方 ValidateAudience false, // 不校验令牌接收方 ValidateLifetime true, // 必开校验令牌是否过期 // 验签密钥必须和生成Token的密钥完全一致防止篡改 IssuerSigningKey new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)) }; }); // 3. 启用授权功能配合 [Authorize] 特性使用 services.AddAuthorization(); } } }✅Infrastructure 层完成EF、仓储、工具、服务扩展全部写完。开发 DeviceRepair.Services业务逻辑层按业务模块拆分只依赖接口不碰数据库、不写 SQL。1. 文件夹结构ServicesDeviceRepair.Services └─ Modules └─ Account # 账号/登录模块 └─ AccountService.cs2. 编写 AccountService.cs实现 IAccountServiceusing DeviceRepair.Core; using DeviceRepair.Core.Dtos; using DeviceRepair.Core.Entities; using DeviceRepair.Core.Interfaces; using DeviceRepair.Infrastructure.Utils; using Microsoft.Extensions.Configuration; namespace DeviceRepair.Services.Modules.Account { public class AccountService : IAccountService { private readonly IUserRepository _userRepo; private readonly IConfiguration _config; public AccountService(IUserRepository userRepo, IConfiguration config) { _userRepo userRepo; _config config; } public async TaskResultModelstring LoginAsync(LoginDto dto) { // 1. 调用仓储查询用户 User? user await _userRepo.CheckLoginAsync(dto.UserName, dto.Password); if (user null) { return ResultModelstring.Fail(账号或密码错误, 401); } // 2. 读取JWT配置、生成Token string secret _config[Jwt:SecretKey]!; int expire int.Parse(_config[Jwt:ExpireMinutes]!); string token JwtHelper.GenerateToken(user, secret, expire); return ResultModelstring.Success(token, 登录成功); } } }3. 在扩展类追加「业务服务注册」回到DeviceRepair.Infrastructure/Extensions/ServiceCollectionExtensions.cs新增方法/// summary /// 注册所有业务服务 /// /summary public static void AddBusinessServices(this IServiceCollection services) { services.AddScopedIAccountService, AccountService(); }✅Services 层完成业务逻辑与数据访问彻底解耦。第五阶段开发 DeviceRepair.Api启动项目 接口层职责路由、控制器、过滤器、中间件、管道配置。1. 文件夹结构ApiDeviceRepair.Api ├─ Controllers # 控制器 ├─ Filters # 自定义过滤器后续添加 ├─ Middlewares # 自定义中间件后续添加 ├─ appsettings.json └─ Program.cs2. 配置 appsettings.json{ ConnectionStrings: { SqlServer: Server.;DatabaseDeviceRepairDB;Trusted_ConnectionTrue;TrustServerCertificateTrue }, Jwt: { SecretKey: ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdef, ExpireMinutes: 60 }, Logging: { LogLevel: { Default: Information, Microsoft.AspNetCore: Warning } }, AllowedHosts: * }说明本地 SQL Express 把Server.改为Server.\SQLEXPRESS3. 编写控制器 Controllers/AccountController.csRESTful 版本路由 特性路由using DeviceRepair.Core; using DeviceRepair.Core.Dtos; using DeviceRepair.Core.Interfaces; using Microsoft.AspNetCore.Mvc; namespace DeviceRepair.Api.Controllers { [Route(api/v1/[controller])] [ApiController] public class AccountController : ControllerBase { private readonly IAccountService _accountService; public AccountController(IAccountService accountService) { _accountService accountService; } /// summary /// 登录接口 POST /// /summary [HttpPost(login)] public async TaskActionResultResultModelstring Login([FromBody] LoginDto dto) { var result await _accountService.LoginAsync(dto); return Ok(result); } } }4. 极简 Program.cs使用扩展方法一行注册using DeviceRepair.Infrastructure.Extensions; using DeviceRepair.Infrastructure.Utils; var builder WebApplication.CreateBuilder(args); // 初始化配置工具 ConfigHelper.Configuration builder.Configuration; // 1. 基础控制器 builder.Services.AddControllers(); // 2. 统一注册数据库、仓储、业务服务、JWT builder.Services.AddDbAndRepositories(builder.Configuration); builder.Services.AddBusinessServices(); builder.Services.AddJwtAuthentication(builder.Configuration); // Swagger builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app builder.Build(); // 开发环境开启Swagger if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } // 认证、授权顺序不能乱 app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();执行 EF Core 迁移命令1. 打开【包管理器控制台】在 Visual Studio 顶部菜单视图→其他窗口→包管理器控制台或者直接在下方搜索栏输入PMC打开。2. 设置默认项目在包管理器控制台的顶部将默认项目设置为DeviceRepair.Infrastructure。3. 执行创建迁移命令输入以下命令并回车Add-Migration InitialCreate这会在DeviceRepair.Infrastructure项目中创建一个Migrations文件夹里面包含迁移代码。4. 执行更新数据库命令输入以下命令并回车Update-DatabaseEF Core 会自动根据你的实体和迁移文件在SqlServer中创建DeviceRepairDB数据库和所有表。5. SQL Server 执行建库建表脚本打开 SSMS执行下面 SQL创建库、表、测试数据CREATE DATABASE DeviceRepairDB; GO USE DeviceRepairDB; GO -- 用户表 CREATE TABLE Users( Id INT PRIMARY KEY IDENTITY(1,1), UserName NVARCHAR(50) NOT NULL, Password NVARCHAR(50) NOT NULL, RealName NVARCHAR(50), Role INT NOT NULL DEFAULT 0, CreateTime DATETIME NOT NULL DEFAULT GETDATE(), IsDeleted BIT NOT NULL DEFAULT 0 ); -- 设备表 CREATE TABLE Devices( Id INT PRIMARY KEY IDENTITY(1,1), DeviceName NVARCHAR(100) NOT NULL, DeviceType NVARCHAR(50), Location NVARCHAR(100), IsDeleted BIT NOT NULL DEFAULT 0 ); -- 工单表 CREATE TABLE RepairOrders( Id INT PRIMARY KEY IDENTITY(1,1), UserId INT NOT NULL, DeviceId INT NOT NULL, ProblemDesc NVARCHAR(500) NOT NULL, OrderStatus INT NOT NULL DEFAULT 0, RepairUserId INT NULL, RepairRemark NVARCHAR(500), CreateTime DATETIME NOT NULL DEFAULT GETDATE(), FinishTime DATETIME NULL, IsDeleted BIT NOT NULL DEFAULT 0 ); -- 消息表 CREATE TABLE SystemMessages( Id INT PRIMARY KEY IDENTITY(1,1), ReceiveUserId INT NOT NULL, Content NVARCHAR(200) NOT NULL, IsRead BIT NOT NULL DEFAULT 0, CreateTime DATETIME NOT NULL DEFAULT GETDATE() ); -- 测试账号 INSERT INTO Users(UserName,Password,RealName,Role) VALUES (staff,123456,普通员工,0), (repair,123456,维修员,1), (admin,123456,管理员,2); -- 测试设备 INSERT INTO Devices(DeviceName,DeviceType,Location) VALUES (办公电脑A,电脑,1楼办公室), (打印机01,打印机,前台);第六阶段运行项目 验证整体流程将DeviceRepair.Api设为启动项目点击运行打开 Swagger 页面调用接口api/v1/Account/login入参json{ UserName:admin, Password:123456 }正常返回200Token→整套架构跑通下一阶段学习路线按顺序继续当前已完成 ✅ 四层模块化架构搭建 ✅ 分层解耦 接口抽象 ✅ EF Core 基础使用 ✅ JWT 登录认证 ✅ RESTful 版本路由接下来按顺序逐个攻克剩余技术自定义过滤器授权过滤、参数校验、操作日志自定义中间件全局异常、跨域、请求日志新增设备 / 工单控制器融合Dapper做复杂查询 / 分页集成RabbitMQ模块化实现消息推送对接 Vue3 前端你现在确认项目能正常运行后我们开始写过滤器。
3-从零搭建 模块化分层架构 .NET 8 报修系统
整体架构4个项目 标准分层 接口解耦 模块化适配EF Core / Dapper / JWT / 过滤器 / 中间件 / RabbitMQ / RESTful / 路由易维护、可扩展。一、整体架构回顾最终结构项目组成一共 4 个项目全部 .NET 8DeviceRepair.ApiWebAPI 启动项目 / 表现层 控制器、路由、过滤器、中间件、Swagger、管道配置DeviceRepair.Core核心类库底层不依赖任何项目 实体、枚举、DTO、通用返回模型、仓储 / 服务接口DeviceRepair.Infrastructure基础设施层 EF 上下文、仓储实现、Dapper、JWT 工具、RabbitMQ、服务注册扩展DeviceRepair.Services业务逻辑层 业务服务实现按模块拆分依赖规则单向依赖解耦核心DeviceRepair.Api → 引用 DeviceRepair.Core → 引用 DeviceRepair.Services → 引用 DeviceRepair.Infrastructure DeviceRepair.Services → 只引用 DeviceRepair.Core DeviceRepair.Infrastructure → 只引用 DeviceRepair.Core DeviceRepair.Core → 无任何项目引用最底层第一阶段创建 4 个项目 配置引用1. 打开 Visual Studio 2022新建空白解决方案命名DeviceRepair依次创建 4 个项目项目 1DeviceRepair.ApiASP.NET Core Web API模板ASP.NET Core Web API框架.NET 8取消使用控制器可选本文用控制器模式、取消 HTTPS名称DeviceRepair.Api启动项目项目 2DeviceRepair.Core类库模板类库框架.NET 8名称DeviceRepair.Core项目 3DeviceRepair.Infrastructure类库模板类库框架.NET 8名称DeviceRepair.Infrastructure项目 4DeviceRepair.Services类库模板类库框架.NET 8名称DeviceRepair.Services配置项目引用必须严格按依赖来右键项目 →添加 → 项目引用DeviceRepair.Api勾选DeviceRepair.Core、DeviceRepair.Services、DeviceRepair.InfrastructureDeviceRepair.Services勾选DeviceRepair.CoreDeviceRepair.Infrastructure勾选DeviceRepair.CoreDeviceRepair.Core不添加任何引用统一安装 NuGet 包3.1 DeviceRepair.Core无需第三方包纯模型 / 接口不装任何 NuGet。3.2 DeviceRepair.Infrastructure基础设施核心第三方包右键 → 管理 NuGet 程序包安装Microsoft.EntityFrameworkCore Microsoft.EntityFrameworkCore.SqlServer Microsoft.EntityFrameworkCore.Tools Dapper Microsoft.AspNetCore.Authentication.JwtBearer Microsoft.Extensions.ConfigurationProject SdkMicrosoft.NET.Sdk PropertyGroup TargetFrameworknet8.0/TargetFramework ImplicitUsingsenable/ImplicitUsings Nullableenable/Nullable /PropertyGroup ItemGroup PackageReference IncludeDapper Version2.1.35 / PackageReference IncludeMicrosoft.AspNetCore.Authentication.JwtBearer Version8.0.17 / PackageReference IncludeMicrosoft.EntityFrameworkCore Version8.0.17 / PackageReference IncludeMicrosoft.EntityFrameworkCore.Relational Version8.0.17 / PackageReference IncludeMicrosoft.EntityFrameworkCore.Tools Version8.0.17 PrivateAssetsall / PackageReference IncludeMicrosoft.EntityFrameworkCore.SqlServer Version8.0.17 / PackageReference IncludeMicrosoft.Extensions.Configuration Version8.0.0 / /ItemGroup ItemGroup ProjectReference Include..\DeviceRepair.Core\DeviceRepair.Core.csproj / /ItemGroup /Project3.3 DeviceRepair.Services无需额外包3.4 DeviceRepair.ApiWebAPI 基础包默认自带无需额外安装。开发 DeviceRepair.Core核心层该层存放实体、枚举、DTO、通用返回体、接口先写完这一层。1. 新建文件夹结构CoreDeviceRepair.Core ├─ Entities # 数据库实体 ├─ Enums # 枚举 ├─ Dtos # 前后端交互入参/出参 ├─ Interfaces # 仓储接口、服务接口 └─ ResultModel.cs # 全局统一返回格式2. 编写全局统一返回模型ResultModel.csnamespace DeviceRepair.Core { /// summary /// RESTful 统一接口返回格式 /// /summary public class ResultModelT { public int Code { get; set; } public string Msg { get; set; } string.Empty; public T? Data { get; set; } /// summary /// 成功返回 /// /summary public static ResultModelT Success(T? data, string msg 请求成功) { return new ResultModelT { Code 200, Msg msg, Data data }; } /// summary /// 失败返回 /// /summary public static ResultModelT Fail(string msg 请求失败, int code 400) { return new ResultModelT { Code code, Msg msg }; } } }3. Enums 枚举新建Enums/GlobalEnum.csnamespace DeviceRepair.Core.Enums { /// summary /// 用户角色 /// /summary public enum UserRole { Staff 0, // 普通员工 RepairMan 1, // 维修员 Admin 2 // 管理员 } /// summary /// 工单状态 /// /summary public enum OrderStatus { WaitAccept 0, // 待接单 Processing 1, // 处理中 Finished 2, // 已完成 Cancel 3 // 已作废 } }4. Entities 数据库实体4.1 User.cs 用户实体using DeviceRepair.Core.Enums; using System.ComponentModel.DataAnnotations; namespace DeviceRepair.Core.Entities { public class User { public int Id { get; set; } [MaxLength(50)] public string UserName { get; set; } string.Empty; [MaxLength(50)] public string Password { get; set; } string.Empty; [MaxLength(50)] public string? RealName { get; set; } public UserRole Role { get; set; } public DateTime CreateTime { get; set; } public bool IsDeleted { get; set; } } }4.2 Device.cs 设备实体using System.ComponentModel.DataAnnotations; namespace DeviceRepair.Core.Entities { public class Device { public int Id { get; set; } [MaxLength(100)] public string DeviceName { get; set; } string.Empty; [MaxLength(50)] public string? DeviceType { get; set; } [MaxLength(100)] public string? Location { get; set; } public bool IsDeleted { get; set; } } }4.3 RepairOrder.cs 报修工单实体using DeviceRepair.Core.Enums; namespace DeviceRepair.Core.Entities { public class RepairOrder { public int Id { get; set; } public int UserId { get; set; } public int DeviceId { get; set; } public string ProblemDesc { get; set; } string.Empty; public OrderStatus OrderStatus { get; set; } public int? RepairUserId { get; set; } public string? RepairRemark { get; set; } public DateTime CreateTime { get; set; } public DateTime? FinishTime { get; set; } public bool IsDeleted { get; set; } // 导航属性EF Core联表使用 public User User { get; set; } null!; public Device Device { get; set; } null!; public User? RepairUser { get; set; } } }4.4 SystemMessage.cs 消息实体RabbitMQ 用namespace DeviceRepair.Core.Entities { public class SystemMessage { public int Id { get; set; } public int ReceiveUserId { get; set; } public string Content { get; set; } string.Empty; public bool IsRead { get; set; } public DateTime CreateTime { get; set; } } }5. Dtos 入参模型新建Dtos/LoginDto.cs登录入参namespace DeviceRepair.Core.Dtos { /// summary /// 登录请求参数 /// /summary public class LoginDto { public string UserName { get; set; } string.Empty; public string Password { get; set; } string.Empty; } }Interfaces 接口层仓储 服务接口6.1 通用基础仓储接口IBaseRepository.cs所有仓储统一规范using DeviceRepair.Core.Entities; namespace DeviceRepair.Core.Interfaces { /// summary /// 通用仓储接口 /// /summary /// typeparam nameT实体/typeparam public interface IBaseRepositoryT where T : class { TaskListT GetListAsync(); TaskT? GetByIdAsync(int id); Task AddAsync(T entity); Task UpdateAsync(T entity); Task DeleteAsync(int id); } }6.2 用户仓储接口IUserRepository.cs继承通用仓储扩展自有方法using DeviceRepair.Core.Entities; namespace DeviceRepair.Core.Interfaces { public interface IUserRepository : IBaseRepositoryUser { /// summary /// 校验账号密码 /// /summary TaskUser? CheckLoginAsync(string userName, string password); } }6.3 账号服务接口IAccountService.cs业务服务接口using DeviceRepair.Core.Dtos; using DeviceRepair.Core; namespace DeviceRepair.Core.Interfaces { public interface IAccountService { /// summary /// 登录 /// /summary TaskResultModelstring LoginAsync(LoginDto dto); } }✅Core 层全部完成模型、枚举、DTO、接口全部定义完毕。开发 DeviceRepair.Infrastructure基础设施层职责实现接口、EF 上下文、Dapper、JWT、服务注册扩展1. 新建文件夹结构InfrastructureDeviceRepair.Infrastructure ├─ Data # EF Core 数据库上下文 ├─ Repositories # 仓储实现EF ├─ Utils # 工具类Jwt、Dapper、配置 └─ Extensions # 服务注册扩展解耦 Program.cs2. Data 目录数据库上下文AppDbContext.csusing DeviceRepair.Core.Entities; using Microsoft.EntityFrameworkCore; namespace DeviceRepair.Infrastructure.Data { public class AppDbContext : DbContext { public AppDbContext(DbContextOptionsAppDbContext options) : base(options) { } public DbSetUser Users SetUser(); public DbSetDevice Devices SetDevice(); public DbSetRepairOrder RepairOrders SetRepairOrder(); public DbSetSystemMessage SystemMessages SetSystemMessage(); // 全局软删除过滤 protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.EntityUser().HasQueryFilter(u !u.IsDeleted); modelBuilder.EntityDevice().HasQueryFilter(d !d.IsDeleted); modelBuilder.EntityRepairOrder().HasQueryFilter(o !o.IsDeleted); modelBuilder.EntitySystemMessage().HasQueryFilter(m true); } } }3. Utils 工具类3.1 ConfigHelper.cs 配置读取using Microsoft.Extensions.Configuration; namespace DeviceRepair.Infrastructure.Utils { //.NET 官方推荐用依赖注入传 IConfiguration而非静态全局变量。现在这种静态帮助类,适合个人练习、小型项目、快速开发 public static class ConfigHelper { //这是一个静态配置帮助类专门用来读取项目配置文件里的数据库连接字符串后续代码不用反复写读取逻辑直接调用方法就能拿到连接串。 public static IConfiguration Configuration { get; set; } null!; //IConfiguration.NET 配置根对象代表整个 appsettings.json 文件。 //固定语法专门读取 appsettings.json 里 ConnectionStrings 节点下名为 SqlServer 的配置。 public static string GetConnectionString() { //任意地方调用拿连接串,string conn ConfigHelper.GetConnectionString(); return Configuration.GetConnectionString(SqlServer) ?? string.Empty; } } }3.2 JwtHelper.cs JWT 生成工具using DeviceRepair.Core.Entities; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace DeviceRepair.Infrastructure.Utils { //JWT 令牌生成工具类登录成功后用它生成登录凭证Token前端拿着这个令牌访问需要登录的接口服务端就能识别用户身份。 public static class JwtHelper { public static string GenerateToken(User user, string secretKey, int expireMinutes) { //Claim身份信息单元就是把用户数据塞进令牌里接口后续可以从令牌取出这些信息。 var claims new ListClaim { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.UserName), new Claim(Role, user.Role.ToString()) }; //前端拿到 Token 后后端不用再查数据库直接解析就能拿到用户 ID、账号、角色。 // SymmetricSecurityKey把传入的密钥字符串转成加密可用的字节格式 //SigningCredentials指定加密算法这里用 HmacSha256 对称加密 //作用给令牌加签名别人篡改令牌内容后服务端能检测出来。 var key new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)); var cred new SigningCredentials(key, SecurityAlgorithms.HmacSha256); // 构建 JWT 令牌本体 // expires令牌过期时间当前时间 传入的分钟数 //claims上面装好的用户身份信息 //signingCredentials加密签名规则 var token new JwtSecurityToken( expires: DateTime.Now.AddMinutes(expireMinutes), claims: claims, signingCredentials: cred); return new JwtSecurityTokenHandler().WriteToken(token); } } }3.3 DapperHelper.cs Dapper 工具类后续复杂查询用using Dapper; using Microsoft.Data.SqlClient; using System.Data; namespace DeviceRepair.Infrastructure.Utils { public static class DapperHelper { private static IDbConnection GetConn(string connStr) { return new SqlConnection(connStr); } public static async TaskListT QueryAsyncT(string connStr, string sql, object? param null) { using var conn GetConn(connStr); return (await conn.QueryAsyncT(sql, param)).ToList(); } } }4. Repositories 仓储实现UserRepository.cs 实现 IUserRepositoryusing DeviceRepair.Core.Entities; using DeviceRepair.Core.Interfaces; using DeviceRepair.Infrastructure.Data; using Microsoft.EntityFrameworkCore; namespace DeviceRepair.Infrastructure.Repositories { public class UserRepository : IUserRepository { private readonly AppDbContext _db; public UserRepository(AppDbContext db) { _db db; } public async TaskListUser GetListAsync() { return await _db.Users.ToListAsync(); } public async TaskUser? GetByIdAsync(int id) { return await _db.Users.FindAsync(id); } public async Task AddAsync(User entity) { await _db.Users.AddAsync(entity); await _db.SaveChangesAsync(); } public async Task UpdateAsync(User entity) { _db.Users.Update(entity); await _db.SaveChangesAsync(); } public async Task DeleteAsync(int id) { var user await GetByIdAsync(id); if (user ! null) { user.IsDeleted true; await UpdateAsync(user); } } // 登录校验 public async TaskUser? CheckLoginAsync(string userName, string password) { return await _db.Users .FirstOrDefaultAsync(u u.UserName userName u.Password password); } } }5. Extensions 服务注册扩展核心解耦新建ServiceCollectionExtensions.cs把所有依赖注入、JWT、数据库注册全部抽离Program.cs保持极简。using DeviceRepair.Core.Interfaces; using DeviceRepair.Infrastructure.Data; using DeviceRepair.Infrastructure.Repositories; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using System.Text; namespace DeviceRepair.Infrastructure.Extensions { public static class ServiceCollectionExtensions { /// summary /// 注册EF 仓储 /// /summary public static void AddDbAndRepositories(this IServiceCollection services, IConfiguration config) { string connStr config.GetConnectionString(SqlServer)!; services.AddDbContextAppDbContext(opt { opt.UseSqlServer(connStr); }); // 仓储注入 services.AddScopedIUserRepository, UserRepository(); } /// summary /// 注册JWT认证 /// /summary public static void AddJwtAuthentication(this IServiceCollection services, IConfiguration config) { // 1. 从配置文件读取JWT加密密钥 string secretKey config[Jwt:SecretKey]!; // 2. 启用认证默认使用JWT方案 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(opt { // 配置Token校验规则 opt.TokenValidationParameters new TokenValidationParameters { ValidateIssuer false, // 不校验令牌签发方 ValidateAudience false, // 不校验令牌接收方 ValidateLifetime true, // 必开校验令牌是否过期 // 验签密钥必须和生成Token的密钥完全一致防止篡改 IssuerSigningKey new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)) }; }); // 3. 启用授权功能配合 [Authorize] 特性使用 services.AddAuthorization(); } } }✅Infrastructure 层完成EF、仓储、工具、服务扩展全部写完。开发 DeviceRepair.Services业务逻辑层按业务模块拆分只依赖接口不碰数据库、不写 SQL。1. 文件夹结构ServicesDeviceRepair.Services └─ Modules └─ Account # 账号/登录模块 └─ AccountService.cs2. 编写 AccountService.cs实现 IAccountServiceusing DeviceRepair.Core; using DeviceRepair.Core.Dtos; using DeviceRepair.Core.Entities; using DeviceRepair.Core.Interfaces; using DeviceRepair.Infrastructure.Utils; using Microsoft.Extensions.Configuration; namespace DeviceRepair.Services.Modules.Account { public class AccountService : IAccountService { private readonly IUserRepository _userRepo; private readonly IConfiguration _config; public AccountService(IUserRepository userRepo, IConfiguration config) { _userRepo userRepo; _config config; } public async TaskResultModelstring LoginAsync(LoginDto dto) { // 1. 调用仓储查询用户 User? user await _userRepo.CheckLoginAsync(dto.UserName, dto.Password); if (user null) { return ResultModelstring.Fail(账号或密码错误, 401); } // 2. 读取JWT配置、生成Token string secret _config[Jwt:SecretKey]!; int expire int.Parse(_config[Jwt:ExpireMinutes]!); string token JwtHelper.GenerateToken(user, secret, expire); return ResultModelstring.Success(token, 登录成功); } } }3. 在扩展类追加「业务服务注册」回到DeviceRepair.Infrastructure/Extensions/ServiceCollectionExtensions.cs新增方法/// summary /// 注册所有业务服务 /// /summary public static void AddBusinessServices(this IServiceCollection services) { services.AddScopedIAccountService, AccountService(); }✅Services 层完成业务逻辑与数据访问彻底解耦。第五阶段开发 DeviceRepair.Api启动项目 接口层职责路由、控制器、过滤器、中间件、管道配置。1. 文件夹结构ApiDeviceRepair.Api ├─ Controllers # 控制器 ├─ Filters # 自定义过滤器后续添加 ├─ Middlewares # 自定义中间件后续添加 ├─ appsettings.json └─ Program.cs2. 配置 appsettings.json{ ConnectionStrings: { SqlServer: Server.;DatabaseDeviceRepairDB;Trusted_ConnectionTrue;TrustServerCertificateTrue }, Jwt: { SecretKey: ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdef, ExpireMinutes: 60 }, Logging: { LogLevel: { Default: Information, Microsoft.AspNetCore: Warning } }, AllowedHosts: * }说明本地 SQL Express 把Server.改为Server.\SQLEXPRESS3. 编写控制器 Controllers/AccountController.csRESTful 版本路由 特性路由using DeviceRepair.Core; using DeviceRepair.Core.Dtos; using DeviceRepair.Core.Interfaces; using Microsoft.AspNetCore.Mvc; namespace DeviceRepair.Api.Controllers { [Route(api/v1/[controller])] [ApiController] public class AccountController : ControllerBase { private readonly IAccountService _accountService; public AccountController(IAccountService accountService) { _accountService accountService; } /// summary /// 登录接口 POST /// /summary [HttpPost(login)] public async TaskActionResultResultModelstring Login([FromBody] LoginDto dto) { var result await _accountService.LoginAsync(dto); return Ok(result); } } }4. 极简 Program.cs使用扩展方法一行注册using DeviceRepair.Infrastructure.Extensions; using DeviceRepair.Infrastructure.Utils; var builder WebApplication.CreateBuilder(args); // 初始化配置工具 ConfigHelper.Configuration builder.Configuration; // 1. 基础控制器 builder.Services.AddControllers(); // 2. 统一注册数据库、仓储、业务服务、JWT builder.Services.AddDbAndRepositories(builder.Configuration); builder.Services.AddBusinessServices(); builder.Services.AddJwtAuthentication(builder.Configuration); // Swagger builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app builder.Build(); // 开发环境开启Swagger if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } // 认证、授权顺序不能乱 app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();执行 EF Core 迁移命令1. 打开【包管理器控制台】在 Visual Studio 顶部菜单视图→其他窗口→包管理器控制台或者直接在下方搜索栏输入PMC打开。2. 设置默认项目在包管理器控制台的顶部将默认项目设置为DeviceRepair.Infrastructure。3. 执行创建迁移命令输入以下命令并回车Add-Migration InitialCreate这会在DeviceRepair.Infrastructure项目中创建一个Migrations文件夹里面包含迁移代码。4. 执行更新数据库命令输入以下命令并回车Update-DatabaseEF Core 会自动根据你的实体和迁移文件在SqlServer中创建DeviceRepairDB数据库和所有表。5. SQL Server 执行建库建表脚本打开 SSMS执行下面 SQL创建库、表、测试数据CREATE DATABASE DeviceRepairDB; GO USE DeviceRepairDB; GO -- 用户表 CREATE TABLE Users( Id INT PRIMARY KEY IDENTITY(1,1), UserName NVARCHAR(50) NOT NULL, Password NVARCHAR(50) NOT NULL, RealName NVARCHAR(50), Role INT NOT NULL DEFAULT 0, CreateTime DATETIME NOT NULL DEFAULT GETDATE(), IsDeleted BIT NOT NULL DEFAULT 0 ); -- 设备表 CREATE TABLE Devices( Id INT PRIMARY KEY IDENTITY(1,1), DeviceName NVARCHAR(100) NOT NULL, DeviceType NVARCHAR(50), Location NVARCHAR(100), IsDeleted BIT NOT NULL DEFAULT 0 ); -- 工单表 CREATE TABLE RepairOrders( Id INT PRIMARY KEY IDENTITY(1,1), UserId INT NOT NULL, DeviceId INT NOT NULL, ProblemDesc NVARCHAR(500) NOT NULL, OrderStatus INT NOT NULL DEFAULT 0, RepairUserId INT NULL, RepairRemark NVARCHAR(500), CreateTime DATETIME NOT NULL DEFAULT GETDATE(), FinishTime DATETIME NULL, IsDeleted BIT NOT NULL DEFAULT 0 ); -- 消息表 CREATE TABLE SystemMessages( Id INT PRIMARY KEY IDENTITY(1,1), ReceiveUserId INT NOT NULL, Content NVARCHAR(200) NOT NULL, IsRead BIT NOT NULL DEFAULT 0, CreateTime DATETIME NOT NULL DEFAULT GETDATE() ); -- 测试账号 INSERT INTO Users(UserName,Password,RealName,Role) VALUES (staff,123456,普通员工,0), (repair,123456,维修员,1), (admin,123456,管理员,2); -- 测试设备 INSERT INTO Devices(DeviceName,DeviceType,Location) VALUES (办公电脑A,电脑,1楼办公室), (打印机01,打印机,前台);第六阶段运行项目 验证整体流程将DeviceRepair.Api设为启动项目点击运行打开 Swagger 页面调用接口api/v1/Account/login入参json{ UserName:admin, Password:123456 }正常返回200Token→整套架构跑通下一阶段学习路线按顺序继续当前已完成 ✅ 四层模块化架构搭建 ✅ 分层解耦 接口抽象 ✅ EF Core 基础使用 ✅ JWT 登录认证 ✅ RESTful 版本路由接下来按顺序逐个攻克剩余技术自定义过滤器授权过滤、参数校验、操作日志自定义中间件全局异常、跨域、请求日志新增设备 / 工单控制器融合Dapper做复杂查询 / 分页集成RabbitMQ模块化实现消息推送对接 Vue3 前端你现在确认项目能正常运行后我们开始写过滤器。