业务递进顺序推进,路线:Dapper 接入 → 通用分页 + DTO 封装 → RabbitMQ 消息队列 → 部署 & 配置优化,循序渐进,贴合项目实战流程。

目标:EF 负责常规 CRUD,Dapper 负责联表、分页、复杂 SQL,二者混用,不冲突。

1. 确认 NuGet 包

DeviceRepair.Infrastructure 已提前安装 Dapper + Microsoft.EntityFrameworkCore.SqlServer,无需重复安装。

2. 改造通用 Dapper 工具类

路径:DeviceRepair.Infrastructure/Utils/DapperHelper.cs 整合连接串读取、常用查询方法,兼容现有配置:

using Dapper;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using System.Data;

namespace DeviceRepair.Infrastructure.Utils
{
    public static class DapperHelper
    {
        private static string _connStr = string.Empty;

        /// <summary>
        /// 初始化连接字符串
        /// </summary>
        public static void Init(IConfiguration config)
        {
            _connStr = config.GetConnectionString("SqlServer") ?? string.Empty;
        }

        /// <summary>
        /// 获取数据库连接
        /// </summary>
        private static IDbConnection GetConnection()
        {
            return new SqlConnection(_connStr);
        }

        /// <summary>
        /// 单表/多表查询
        /// </summary>
        public static async Task<List<T>> QueryAsync<T>(string sql, object? param = null)
        {
            using var conn = GetConnection();
            return (await conn.QueryAsync<T>(sql, param)).ToList();
        }

        /// <summary>
        /// 执行增删改SQL
        /// </summary>
        public static async Task<int> ExecuteAsync(string sql, object? param = null)
        {
            using var conn = GetConnection();
            return await conn.ExecuteAsync(sql, param);
        }
    }
}

3. 初始化 Dapper(统一入口)

打开 ServiceCollectionExtensions.cs,在 AddDbAndRepositories 方法末尾添加初始化:

public static void AddDbAndRepositories(this IServiceCollection services, IConfiguration config)
{
    string connStr = config.GetConnectionString("SqlServer")!;
    services.AddDbContext<AppDbContext>(opt =>
    {
        opt.UseSqlServer(connStr);
    });

    // 注册全部仓储
    services.AddScoped<IUserRepository, UserRepository>();
    services.AddScoped<IBaseRepository<Device>, DeviceRepository>();
    services.AddScoped<IBaseRepository<RepairOrder>, RepairOrderRepository>();
    services.AddScoped<IBaseRepository<SystemMessage>, SystemMessageRepository>();

    // 初始化 Dapper 连接串
    DapperHelper.Init(config);
}

4. 扩展仓储:新增 Dapper 专属接口

4.1 新建分页接口,针对工单做联表查询(工单 + 提交人 + 设备),作为复杂查询示例:

using DeviceRepair.Core.Dtos;

namespace DeviceRepair.Core.Interfaces
{
    public interface IOrderDapperRepository
    {
        /// <summary>
        /// 工单联表分页查询
        /// </summary>
        Task<PageResult<RepairOrderDto>> GetOrderPageListAsync(PageQuery dto);
    }
}

4.2 新建 Dapper 实体 / 查询 DTO

先提前创建通用分页 DTO

namespace DeviceRepair.Core.Dtos
{
    /// <summary>
    /// 分页入参
    /// </summary>
    //用途:前端做分页列表时,传给后端的参数。
    public class PageQuery
    {
        public int PageIndex { get; set; } = 1;  // 当前页码,默认第1页
        public int PageSize { get; set; } = 10;  // 每页几条数据,默认每页10条
    }

    /// <summary>
    /// 分页出参
    /// </summary>
    /// <typeparam name="T">数据实体</typeparam>
  //作用:后端查完数据,把「总条数、当前页、每页大小、数据列表」打包一起返回给前端,前端就能渲染分页控件 + 列表。
    public class PageResult<T>
    {
        public int Total { get; set; }                // 数据总条数
        public int PageIndex { get; set; }            // 当前页码
        public int PageSize { get; set; }             // 每页条数
        public List<T> Data { get; set; } = new List<T>(); // 当前页的数据列表
    }

    /// <summary>
    /// 工单联表展示DTO,数据库里工单表、用户表、设备表是分开的,联表查询后,不能直接返回数据库实体(字段冗余、不安全),所以单独建这个类。
    /// </summary>
    public class RepairOrderDto
    {//专门存放「多张表联查后」要展示给前端的字段,只保留页面需要的内容。
        public int Id { get; set; }
        public string UserName { get; set; } = string.Empty;    // 提交工单的用户名
        public string DeviceName { get; set; } = string.Empty; // 故障设备名
        public string ProblemDesc { get; set; } = string.Empty;// 故障描述
        public int OrderStatus { get; set; }                   // 工单状态码
        public DateTime CreateTime { get; set; }               // 工单创建时间
    }
}

4.3 Dapper 仓储实现

路径:DeviceRepair.Infrastructure/Repositories/OrderDapperRepository.cs

using DeviceRepair.Core.Dtos;
using DeviceRepair.Core.Interfaces;
using DeviceRepair.Infrastructure.Utils;

namespace DeviceRepair.Infrastructure.Repositories
{
    public class OrderDapperRepository : IOrderDapperRepository
    {
        public async Task<PageResult<RepairOrderDto>> GetOrderPageListAsync(PageQuery dto)
        {
            var pageResult = new PageResult<RepairOrderDto>
            {
                PageIndex = dto.PageIndex,
                PageSize = dto.PageSize
            };

            // 1. 查询总条数
            string countSql = @"
                SELECT COUNT(1) 
                FROM RepairOrders o
                LEFT JOIN Users u ON o.UserId = u.Id
                LEFT JOIN Devices d ON o.DeviceId = d.Id
                WHERE o.IsDeleted = 0";
            pageResult.Total = await DapperHelper.ExecuteAsync(countSql);

            // 2. 分页联表查询
            int skip = (dto.PageIndex - 1) * dto.PageSize;
            string dataSql = $@"
                SELECT o.Id, u.UserName, d.DeviceName, o.ProblemDesc, o.OrderStatus, o.CreateTime
                FROM RepairOrders o
                LEFT JOIN Users u ON o.UserId = u.Id
                LEFT JOIN Devices d ON o.DeviceId = d.Id
                WHERE o.IsDeleted = 0
                ORDER BY o.CreateTime DESC
                OFFSET {skip} ROWS FETCH NEXT {dto.PageSize} ROWS ONLY";

            pageResult.Data = await DapperHelper.QueryAsync<RepairOrderDto>(dataSql);
            return pageResult;
        }
    }
}
 int skip = (dto.PageIndex - 1) * dto.PageSize;
  • 第 1 页:(1-1)*10 = 0 → 跳过 0 条,取前 10 条
  • 第 2 页:(2-1)*10 = 10 → 跳过前 10 条,取接下来 10 条
  • 第 3 页:(3-1)*10 = 20 → 跳过前 20 条

skip = 要跳过多少条数据。

4.4 注册 Dapper 仓储

ServiceCollectionExtensions.cs 追加注册:

// Dapper 专属仓储
services.AddScoped<IOrderDapperRepository, OrderDapperRepository>();

5. 新增分页接口(控制器测试)

打开 RepairOrderController.cs,添加分页接口:

private readonly IBaseRepository<RepairOrder> _orderRepo;
// 注入Dapper仓储
private readonly IOrderDapperRepository _orderDapperRepo;

public RepairOrderController(IBaseRepository<RepairOrder> orderRepo, IOrderDapperRepository orderDapperRepo)
{
    _orderRepo = orderRepo;
    _orderDapperRepo = orderDapperRepo;
}

/// <summary>
/// 工单联表分页查询(Dapper实现)
/// </summary>
[HttpGet("page")]
public async Task<ActionResult<ResultModel<PageResult<RepairOrderDto>>>> GetPageList([FromQuery] PageQuery dto)
{
    var data = await _orderDapperRepo.GetOrderPageListAsync(dto);
    return Ok(ResultModel<PageResult<RepairOrderDto>>.Success(data));
}

6. 测试

重启项目,Swagger 调用 /api/v1/RepairOrder/page 接口,传入分页参数,验证联表 + 分页数据正常返回。


阶段二:通用分页 & DTO 规范化封装

基于上一阶段的分页基础,统一入参 / 出参 DTO 规范、全局分页逻辑、实体与 DTO 映射规则,解决实体直接返回前端的安全问题。

  1. 统一区分:InputDto(入参)、OutputDto(出参),禁止实体直接对外暴露
  2. 封装通用分页基类,全模块复用
  3. 简单映射(手动映射,后续可扩展 AutoMapper)
  4. 改造现有接口,全部使用 DTO 交互

核心目标:隔离数据库实体、统一入参 / 出参规范、通用分页复用、规范字段映射,禁止直接把实体返回前端,兼顾安全性与可维护性。

一、目录结构调整(统一 DTO 分层)

DeviceRepair.Core/Dtos 下拆分目录,区分入参、出参、通用模型:

DeviceRepair.Core
├─ Entities
├─ Enums
├─ Interfaces
├─ Dtos
│  ├─ Common        # 通用模型(分页、全局请求/响应)
│  ├─ Input         # 接口入参 DTO
│  └─ Output        # 接口出参 DTO
└─ Utils

二、封装通用基础模型(Common)

1. 通用分页模型(全局复用)

namespace DeviceRepair.Core.Dtos.Common
{
    /// <summary>
    /// 分页查询入参(所有分页接口统一使用)
    /// </summary>
    public class PageQuery
    {
        /// <summary>
        /// 页码,默认第1页
        /// </summary>
        public int PageIndex { get; set; } = 1;

        /// <summary>
        /// 页大小,默认10条
        /// </summary>
        public int PageSize { get; set; } = 10;
    }

    /// <summary>
    /// 分页结果出参(所有分页接口统一返回)
    /// </summary>
    /// <typeparam name="T">列表项实体/DTO</typeparam>
    public class PageResult<T>
    {
        /// <summary>
        /// 总数据条数
        /// </summary>
        public int Total { get; set; }

        /// <summary>
        /// 当前页码
        /// </summary>
        public int PageIndex { get; set; }

        /// <summary>
        /// 每页条数
        /// </summary>
        public int PageSize { get; set; }

        /// <summary>
        /// 数据列表
        /// </summary>
        public List<T> Data { get; set; } = new List<T>();
    }
}

2. 全局基础 DTO(可选,后续扩展查询条件可用)

新建 Dtos/Common/BaseQuery.cs

namespace DeviceRepair.Core.Dtos.Common
{
    /// <summary>
    /// 通用查询基类(可扩展关键字搜索、时间范围等)
    /// </summary>
    public class BaseQuery : PageQuery
    {
        /// <summary>
        /// 关键字模糊查询
        /// </summary>
        public string? Keyword { get; set; }
    }
}

三、按模块拆分 入参 DTO(Input)

把原有接口实体传参、零散 DTO 全部迁移至 Input禁止控制器直接接收 Entity

1. 账号模块 Input

新建 Dtos/Input/Account/LoginInput.cs

namespace DeviceRepair.Core.Dtos.Input.Account
{
    /// <summary>
    /// 登录入参
    /// </summary>
    public class LoginInput
    {
        public string UserName { get; set; } = string.Empty;
        public string Password { get; set; } = string.Empty;
    }
}

2. 设备模块 Input

新建 Dtos/Input/Device/DeviceInput.cs

using System.ComponentModel.DataAnnotations;

namespace DeviceRepair.Core.Dtos.Input.Device
{
    /// <summary>
    /// 新增/编辑设备入参
    /// </summary>
    public class DeviceInput
    {
        public int Id { get; set; }

        [Required(ErrorMessage = "设备名称不能为空")]
        [MaxLength(100, ErrorMessage = "设备名称最长100字符")]
        public string DeviceName { get; set; } = string.Empty;

        [MaxLength(50)]
        public string? DeviceType { get; set; }

        [MaxLength(100)]
        public string? Location { get; set; }
    }
}

3. 工单模块 Input

新建 Dtos/Input/RepairOrder/OrderInput.cs

using System.ComponentModel.DataAnnotations;

namespace DeviceRepair.Core.Dtos.Input.RepairOrder
{
    /// <summary>
    /// 新增/编辑工单入参
    /// </summary>
    public class OrderInput
    {
        public int Id { get; set; }

        [Required(ErrorMessage = "请选择用户")]
        public int UserId { get; set; }

        [Required(ErrorMessage = "请选择设备")]
        public int DeviceId { get; set; }

        [Required(ErrorMessage = "问题描述不能为空")]
        [MaxLength(500)]
        public string ProblemDesc { get; set; } = string.Empty;

        public int? RepairUserId { get; set; }
        public string? RepairRemark { get; set; }
    }
}

4. 消息模块 Input

新建 Dtos/Input/Message/MessageInput.cs

using System.ComponentModel.DataAnnotations;

namespace DeviceRepair.Core.Dtos.Input.Message
{
    /// <summary>
    /// 新增/编辑消息入参
    /// </summary>
    public class MessageInput
    {
        public int Id { get; set; }

        [Required]
        public int ReceiveUserId { get; set; }

        [Required]
        [MaxLength(200)]
        public string Content { get; set; } = string.Empty;
    }
}

四、按模块拆分 出参 DTO(Output)

创建对外输出模型,过滤敏感字段(如密码)、精简冗余字段,前端只拿需要的数据。

1. 账号模块 Output

新建 Dtos/Output/Account/UserOutput.cs

using DeviceRepair.Core.Enums;

namespace DeviceRepair.Core.Dtos.Output.Account
{
    /// <summary>
    /// 用户信息出参(屏蔽密码)
    /// </summary>
    public class UserOutput
    {
        public int Id { get; set; }
        public string UserName { get; set; } = string.Empty;
        public string? RealName { get; set; }
        public UserRole Role { get; set; }
        public DateTime CreateTime { get; set; }
    }
}

2. 设备模块 Output

新建 Dtos/Output/Device/DeviceOutput.cs

 

namespace DeviceRepair.Core.Dtos.Output.Device
{
    /// <summary>
    /// 设备信息出参
    /// </summary>
    public class DeviceOutput
    {
        public int Id { get; set; }
        public string DeviceName { get; set; } = string.Empty;
        public string? DeviceType { get; set; }
        public string? Location { get; set; }
    }
}

3. 工单模块 Output(含联表分页 DTO)

新建 Dtos/Output/RepairOrder/OrderOutput.cs

namespace DeviceRepair.Core.Dtos.Output.RepairOrder
{
    /// <summary>
    /// 工单基础信息出参
    /// </summary>
    public class OrderOutput
    {
        public int Id { get; set; }
        public int UserId { get; set; }
        public int DeviceId { get; set; }
        public string ProblemDesc { get; set; } = string.Empty;
        public int OrderStatus { get; set; }
        public int? RepairUserId { get; set; }
        public string? RepairRemark { get; set; }
        public DateTime CreateTime { get; set; }
        public DateTime? FinishTime { get; set; }
    }

    /// <summary>
    /// 工单联表分页出参(Dapper 专用)
    /// </summary>
    public class OrderPageOutput
    {
        public int Id { get; set; }
        public string UserName { get; set; } = string.Empty;
        public string DeviceName { get; set; } = string.Empty;
        public string ProblemDesc { get; set; } = string.Empty;
        public int OrderStatus { get; set; }
        public DateTime CreateTime { get; set; }
    }
}

4. 消息模块 Output

新建 Dtos/Output/Message/MessageOutput.cs

namespace DeviceRepair.Core.Dtos.Output.Message
{
    /// <summary>
    /// 系统消息出参
    /// </summary>
    public class MessageOutput
    {
        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; }
    }
}

五、统一实体 ↔ DTO 映射规则

现阶段使用手动映射(轻量化,无第三方组件),约定映射位置:

  1. Controller 层:简单字段映射(单实体)
  2. Repository/Service 层:复杂联表、分页映射
  3. 禁止在视图 / 前端直接使用数据库实体

扩展:可复用映射静态类(可选)

新建 DeviceRepair.Core/Utils/ModelMapper.cs,集中写映射方法,全局调用

using DeviceRepair.Core.Entities;
using DeviceRepair.Core.Dtos.Input.Device;
using DeviceRepair.Core.Dtos.Input.RepairOrder;
using DeviceRepair.Core.Dtos.Input.Message;
using DeviceRepair.Core.Dtos.Output.Account;
using DeviceRepair.Core.Dtos.Output.Device;
using DeviceRepair.Core.Dtos.Output.RepairOrder;
using DeviceRepair.Core.Dtos.Output.Message;

namespace DeviceRepair.Core.Utils
{
    /// <summary>
    /// 实体与DTO手动映射工具
    /// </summary>
    public static class ModelMapper
    {
        #region 用户映射
        public static UserOutput ToOutput(this User entity)
        {
            return new UserOutput
            {
                Id = entity.Id,
                UserName = entity.UserName,
                RealName = entity.RealName,
                Role = entity.Role,
                CreateTime = entity.CreateTime
            };
        }
        #endregion

        #region 设备映射
        public static Device ToEntity(this DeviceInput dto)
        {
            return new Device
            {
                Id = dto.Id,
                DeviceName = dto.DeviceName,
                DeviceType = dto.DeviceType,
                Location = dto.Location
            };
        }

        public static DeviceOutput ToOutput(this Device entity)
        {
            return new DeviceOutput
            {
                Id = entity.Id,
                DeviceName = entity.DeviceName,
                DeviceType = entity.DeviceType,
                Location = entity.Location
            };
        }
        #endregion

        #region 工单映射
        public static RepairOrder ToEntity(this OrderInput dto)
        {
            return new RepairOrder
            {
                Id = dto.Id,
                UserId = dto.UserId,
                DeviceId = dto.DeviceId,
                ProblemDesc = dto.ProblemDesc,
                RepairUserId = dto.RepairUserId,
                RepairRemark = dto.RepairRemark
            };
        }

        public static OrderOutput ToOutput(this RepairOrder entity)
        {
            return new OrderOutput
            {
                Id = entity.Id,
                UserId = entity.UserId,
                DeviceId = entity.DeviceId,
                ProblemDesc = entity.ProblemDesc,
                OrderStatus = entity.OrderStatus,
                RepairUserId = entity.RepairUserId,
                RepairRemark = entity.RepairRemark,
                CreateTime = entity.CreateTime,
                FinishTime = entity.FinishTime
            };
        }
        #endregion

        #region 消息映射
        public static SystemMessage ToEntity(this MessageInput dto)
        {
            return new SystemMessage
            {
                Id = dto.Id,
                ReceiveUserId = dto.ReceiveUserId,
                Content = dto.Content
            };
        }

        public static MessageOutput ToOutput(this SystemMessage entity)
        {
            return new MessageOutput
            {
                Id = entity.Id,
                ReceiveUserId = entity.ReceiveUserId,
                Content = entity.Content,
                IsRead = entity.IsRead,
                CreateTime = entity.CreateTime
            };
        }
        #endregion
    }
}
  1. 你现在这种:手动映射(当前代码)

    • 优点:无第三方依赖、灵活、可控、排查问题简单,小 / 中型项目常用。
    • 缺点:字段多了会写很多重复赋值代码。
  2. 行业常用进阶:AutoMapper 自动映射 第三方库,配置一行代码就能自动拷贝同名字段,不用手写 A.XX=B.XX。 你现在是手动版映射,是学习基础。

六、改造原有接口:全部切换为 DTO 交互

逐个修改控制器,替换入参、出参,使用映射方法,删除直接返回实体的写法。

1. AccountController 改造

using DeviceRepair.Core;
using DeviceRepair.Core.Dtos.Input.Account;
using DeviceRepair.Core.Interfaces;
using Microsoft.AspNetCore.Authorization;
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>
        /// 登录
        /// </summary>
        [HttpPost("login")]
        [AllowAnonymous]
        public async Task<ActionResult<ResultModel<string>>> Login([FromBody] LoginInput dto)
        {
            var result = await _accountService.LoginAsync(dto);
            return Ok(result);
        }
    }
}

同步修改 IAccountServiceAccountService 入参:

IAccountService.cs
using DeviceRepair.Core;
using DeviceRepair.Core.Dtos.Input.Account;

namespace DeviceRepair.Core.Interfaces
{
    public interface IAccountService
    {
        Task<ResultModel<string>> LoginAsync(LoginInput dto);
    }
}
AccountService.cs
using DeviceRepair.Core;
using DeviceRepair.Core.Dtos.Input.Account;
using DeviceRepair.Core.Entities;
using DeviceRepair.Core.Interfaces;
using DeviceRepair.Core.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 Task<ResultModel<string>> LoginAsync(LoginInput dto)
        {
            var user = await _userRepo.CheckLoginAsync(dto.UserName, dto.Password);
            if (user == null)
            {
                return ResultModel<string>.Fail("账号或密码错误", 401);
            }

            var secret = _config["Jwt:SecretKey"]!;
            var expire = int.Parse(_config["Jwt:ExpireMinutes"]!);
            var token = JwtHelper.GenerateToken(user, secret, expire);
            return ResultModel<string>.Success(token, "登录成功");
        }
    }
}

2. DeviceController 改造

using DeviceRepair.Core;
using DeviceRepair.Core.Dtos.Common;
using DeviceRepair.Core.Dtos.Input.Device;
using DeviceRepair.Core.Dtos.Output.Device;
using DeviceRepair.Core.Entities;
using DeviceRepair.Core.Enums;
using DeviceRepair.Core.Interfaces;
using DeviceRepair.Core.Utils;
using DeviceRepair.Api.Filters;
using Microsoft.AspNetCore.Mvc;

namespace DeviceRepair.Api.Controllers
{
    [Route("api/v1/[controller]")]
    [ApiController]
    public class DeviceController : ControllerBase
    {
        private readonly IBaseRepository<Device> _deviceRepo;

        public DeviceController(IBaseRepository<Device> deviceRepo)
        {
            _deviceRepo = deviceRepo;
        }

        [HttpGet]
        public async Task<ActionResult<ResultModel<List<DeviceOutput>>>> GetList()
        {
            var list = await _deviceRepo.GetListAsync();
            var outputList = list.Select(x => x.ToOutput()).ToList();
            return Ok(ResultModel<List<DeviceOutput>>.Success(outputList));
        }

        [HttpGet("{id}")]
        public async Task<ActionResult<ResultModel<DeviceOutput?>>> GetById(int id)
        {
            var entity = await _deviceRepo.GetByIdAsync(id);
            if (entity == null)
                return Ok(ResultModel<DeviceOutput?>.Fail("数据不存在"));

            return Ok(ResultModel<DeviceOutput?>.Success(entity.ToOutput()));
        }

        [HttpPost]
        [AllowRole(UserRole.Admin)]
        public async Task<ActionResult<ResultModel<bool>>> Add([FromBody] DeviceInput dto)
        {
            var entity = dto.ToEntity();
            await _deviceRepo.AddAsync(entity);
            return Ok(ResultModel<bool>.Success(true, "新增成功"));
        }

        [HttpPut]
        [AllowRole(UserRole.Admin)]
        public async Task<ActionResult<ResultModel<bool>>> Update([FromBody] DeviceInput dto)
        {
            var entity = dto.ToEntity();
            await _deviceRepo.UpdateAsync(entity);
            return Ok(ResultModel<bool>.Success(true, "修改成功"));
        }

        [HttpDelete("{id}")]
        [AllowRole(UserRole.Admin)]
        public async Task<ActionResult<ResultModel<bool>>> Delete(int id)
        {
            await _deviceRepo.DeleteAsync(id);
            return Ok(ResultModel<bool>.Success(true, "删除成功"));
        }
    }
}

3. RepairOrderController 改造(含 Dapper 分页)

using DeviceRepair.Core;
using DeviceRepair.Core.Dtos.Common;
using DeviceRepair.Core.Dtos.Input.RepairOrder;
using DeviceRepair.Core.Dtos.Output.RepairOrder;
using DeviceRepair.Core.Entities;
using DeviceRepair.Core.Enums;
using DeviceRepair.Core.Interfaces;
using DeviceRepair.Core.Utils;
using DeviceRepair.Api.Filters;
using Microsoft.AspNetCore.Mvc;

namespace DeviceRepair.Api.Controllers
{
    [Route("api/v1/[controller]")]
    [ApiController]
    public class RepairOrderController : ControllerBase
    {
        private readonly IBaseRepository<RepairOrder> _orderRepo;
        private readonly IOrderDapperRepository _orderDapperRepo;

        public RepairOrderController(IBaseRepository<RepairOrder> orderRepo, IOrderDapperRepository orderDapperRepo)
        {
            _orderRepo = orderRepo;
            _orderDapperRepo = orderDapperRepo;
        }

        [HttpGet]
        public async Task<ActionResult<ResultModel<List<OrderOutput>>>> GetList()
        {
            var list = await _orderRepo.GetListAsync();
            var output = list.Select(x => x.ToOutput()).ToList();
            return Ok(ResultModel<List<OrderOutput>>.Success(output));
        }

        [HttpGet("{id}")]
        public async Task<ActionResult<ResultModel<OrderOutput?>>> GetById(int id)
        {
            var entity = await _orderRepo.GetByIdAsync(id);
            if (entity == null)
                return Ok(ResultModel<OrderOutput?>.Fail("工单不存在"));

            return Ok(ResultModel<OrderOutput?>.Success(entity.ToOutput()));
        }

        [HttpPost]
        [AllowRole(UserRole.Staff, UserRole.RepairMan)]
        public async Task<ActionResult<ResultModel<bool>>> Add([FromBody] OrderInput dto)
        {
            var entity = dto.ToEntity();
            entity.CreateTime = DateTime.Now;
            entity.OrderStatus = OrderStatus.WaitAccept;
            await _orderRepo.AddAsync(entity);
            return Ok(ResultModel<bool>.Success(true, "工单提交成功"));
        }

        [HttpPut]
        [AllowRole(UserRole.Admin)]
        public async Task<ActionResult<ResultModel<bool>>> Update([FromBody] OrderInput dto)
        {
            var entity = dto.ToEntity();
            await _orderRepo.UpdateAsync(entity);
            return Ok(ResultModel<bool>.Success(true, "工单修改成功"));
        }

        [HttpDelete("{id}")]
        [AllowRole(UserRole.Admin)]
        public async Task<ActionResult<ResultModel<bool>>> Delete(int id)
        {
            await _orderRepo.DeleteAsync(id);
            return Ok(ResultModel<bool>.Success(true, "工单删除成功"));
        }

        /// <summary>
        /// Dapper 联表分页查询
        /// </summary>
        [HttpGet("page")]
        public async Task<ActionResult<ResultModel<PageResult<OrderPageOutput>>>> GetPageList([FromQuery] PageQuery dto)
        {
            var pageData = await _orderDapperRepo.GetOrderPageListAsync(dto);
            return Ok(ResultModel<PageResult<OrderPageOutput>>.Success(pageData));
        }
    }
}

同步修改 IOrderDapperRepositoryOrderDapperRepository 中 DTO 引用:

IOrderDapperRepository.cs
using DeviceRepair.Core.Dtos.Common;
using DeviceRepair.Core.Dtos.Output.RepairOrder;

namespace DeviceRepair.Core.Interfaces
{
    public interface IOrderDapperRepository
    {
        Task<PageResult<OrderPageOutput>> GetOrderPageListAsync(PageQuery dto);
    }
}
OrderDapperRepository.cs
using DeviceRepair.Core.Dtos.Common;
using DeviceRepair.Core.Dtos.Output.RepairOrder;
using DeviceRepair.Core.Interfaces;
using DeviceRepair.Infrastructure.Utils;

namespace DeviceRepair.Infrastructure.Repositories
{
    public class OrderDapperRepository : IOrderDapperRepository
    {
        public async Task<PageResult<OrderPageOutput>> GetOrderPageListAsync(PageQuery dto)
        {
            var pageResult = new PageResult<OrderPageOutput>
            {
                PageIndex = dto.PageIndex,
                PageSize = dto.PageSize
            };

            string countSql = @"SELECT COUNT(1) FROM RepairOrders o LEFT JOIN Users u ON o.UserId=u.Id LEFT JOIN Devices d ON o.DeviceId=d.Id WHERE o.IsDeleted=0";
            pageResult.Total = await DapperHelper.ExecuteAsync(countSql);

            int skip = (dto.PageIndex - 1) * dto.PageSize;
            string dataSql = $@"
                SELECT o.Id, u.UserName, d.DeviceName, o.ProblemDesc, o.OrderStatus, o.CreateTime
                FROM RepairOrders o
                LEFT JOIN Users u ON o.UserId = u.Id
                LEFT JOIN Devices d ON o.DeviceId = d.Id
                WHERE o.IsDeleted = 0
                ORDER BY o.CreateTime DESC
                OFFSET {skip} ROWS FETCH NEXT {dto.PageSize} ROWS ONLY";

            pageResult.Data = await DapperHelper.QueryAsync<OrderPageOutput>(dataSql);
            return pageResult;
        }
    }
}

4. SystemMessageController 改造(自行参照上面规则完成)

规则一致:入参用 MessageInput、出参用 MessageOutput,调用 ModelMapper 映射。

七、测试验证

  1. 清理并重新生成解决方案,确认无编译报错
  2. 启动项目,打开 Swagger
  3. 全流程测试:
    • 登录接口:接收 LoginInput,正常返回 Token
    • 设备 / 工单 / 消息 增删改查:入参校验生效、返回精简 DTO、无敏感字段
    • 分页接口:传入 PageIndex/PageSize,正常返回分页结构体
  4. 检查数据库实体不再直接暴露给前端,安全性达标

本阶段总结

✅ DTO 分层规范(Common/Input/Output) ✅ 通用分页模型全局复用 ✅ 实体与 DTO 手动映射工具类统一管理 ✅ 所有接口完成 DTO 改造,隔离数据库实体 ✅ 参数校验、字段长度约束统一配置


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐