# BaseDb.Core **Repository Path**: ttaua/BaseDb.Core ## Basic Information - **Project Name**: BaseDb.Core - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-18 - **Last Updated**: 2026-06-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # HJBaseDb.Core — 企业级 SqlSugar ORM 通用操作层 基于 **SqlSugar v5.1.4** 深度封装的企业级通用数据库操作层,专为解决实际项目中的安全、审计、可维护性问题而设计。 ### 核心优势 | 优势 | 说明 | |------|------| | \U0001f6e1\ufe0f **安全内置** | SQL注入全特征检测(13种模式)、DDL生产环境阻断、日志自动脱敏、AES-256字段加密、异常消息脱敏 — 无需额外配置,开箱即用 | | \U0001f4cb **零配置审计** | 继承 BaseEntity 自动填充雪花ID / CreateTime / UpdateTime / OperatorId / IsDeleted,AOP + 方法级双重兜底,绝无遗漏 | | \U0001f504 **三库统一** | 同一套 API 操作 **MySQL** / **SQL Server** / **SQLite**,一行代码切换数据库类型,业务层完全无感知 | | \U0001f3af **多框架兼容** | 一套源码同时编译 **net461 / net472 / net48 / netcoreapp3.1 / net6.0 / net8.0 / net9.0**,从 .NET Framework 老旧项目到 .NET 9 最新版本无缝衔接 | | \U0001f9f5 **高并发安全** | SnowflakeIdGenerator(Lazy+原子操作)、UserContext(AsyncLocal异步流隔离)、DbFactory(ConcurrentDictionary+Lazy) 全部设计为高并发线程安全 | | \U0001f517 **连接池智能管理** | 相同配置复用实例减少资源、连接耗尽自动告警(可配置阈值)、配置变更在线重建无需重启 | | \U0001f4ca **慢SQL检测** | AOP拦截自动计时,超阈值即输出告警日志,无需额外性能监控工具,快速定位慢查询 | | \U0001f3d7\ufe0f **生产就绪约束** | 禁止手写 DELETE 绕过软删除、禁止嵌套事务、禁止配置明文密码 — 从架构层面杜绝常见生产事故 | 支持 **MySQL / SQL Server / SQLite** 三种数据库,覆盖 **.NET Framework 4.6.1+ ~ .NET 9** 七个目标框架。 --- ## 目录 1. [快速开始](#1-快速开始) - [1.1 NuGet 包安装](#11-nuget-包安装) - [1.2 项目引用配置](#12-项目引用配置) - [1.3 配置文件详解](#13-配置文件详解) - [1.4 最简初始化代码](#14-最简初始化代码) - [1.5 演示项目一键运行](#15-演示项目一键运行) - [1.6 集成到项目](#16-集成到项目) 2. [实体定义规范](#2-实体定义规范) - [2.1 必须继承 BaseEntity](#21-必须继承-baseentity) - [2.2 实体字段映射规则](#22-实体字段映射规则) - [2.3 实体定义完整示例](#23-实体定义完整示例) 3. [仓储层 API 详解](#3-仓储层-api-详解) - [3.1 新增操作](#31-新增操作) - [3.2 更新操作](#32-更新操作) - [3.3 查询操作](#33-查询操作) - [3.4 分页查询(含多字段排序)](#34-分页查询含多字段排序) - [3.5 多表联查](#35-多表联查) - [3.6 软删除](#36-软删除) - [3.7 事务](#37-事务) 4. [自定义 SQL 安全层](#4-自定义-sql-安全层) - [4.1 列表查询](#41-列表查询) - [4.2 单条查询](#42-单条查询) - [4.3 标量查询](#43-标量查询) - [4.4 执行 DML](#44-执行-dml) - [4.5 SQL 分页查询](#45-sql-分页查询) - [4.6 存储过程](#46-存储过程) - [4.7 逻辑删除自动过滤](#47-逻辑删除自动过滤) 5. [安全机制详解](#5-安全机制详解) - [5.1 DDL 高危操作拦截](#51-ddl-高危操作拦截) - [5.2 SQL 注入全特征检测](#52-sql-注入全特征检测) - [5.3 日志数据脱敏](#53-日志数据脱敏) - [5.4 AES 字段加密](#54-aes-字段加密) - [5.5 异常安全输出](#55-异常安全输出) 6. [审计系统](#6-审计系统) - [6.1 自动字段填充规则](#61-自动字段填充规则) - [6.2 用户上下文配置](#62-用户上下文配置) 7. [连接池管理](#7-连接池管理) - [7.1 单例工厂模式](#71-单例工厂模式) - [7.2 连接耗尽告警](#72-连接耗尽告警) - [7.3 配置变更重建](#73-配置变更重建) 8. [雪花 ID 生成器](#8-雪花-id-生成器) - [8.1 分布式部署配置](#81-分布式部署配置) - [8.2 从 ID 解析生成时间](#82-从-id-解析生成时间) 9. [多框架兼容说明](#9-多框架兼容说明) 10. [强制规范与禁止操作](#10-强制规范与禁止操作) 11. [常见问题 FAQ](#11-常见问题-faq) --- ## 1. 快速开始 ### 1.1 NuGet 包安装 > ⚠️ **SqlSugar v5.1.4 注意事项:** `.NET Framework` 目标使用 `SqlSugar` 包,`.NET Core / .NET 5+` 目标使用 `SqlSugarCore` 包。`DataFilterType` 枚举仅支持 `InsertByObject` / `UpdateByObject`,不支持 `InsertByColumns` / `UpdateByColumns`。 ```bash # .NET Framework (net461 / net472 / net48) Install-Package SqlSugar -Version 5.1.4.170 Install-Package MySqlConnector -Version 2.3.7 Install-Package Microsoft.Data.SqlClient -Version 5.2.2 Install-Package Microsoft.Data.Sqlite -Version 8.0.10 Install-Package Microsoft.Extensions.Configuration -Version 8.0.0 Install-Package Microsoft.Extensions.Configuration.Json -Version 8.0.0 Install-Package Microsoft.Extensions.Configuration.EnvironmentVariables -Version 8.0.0 Install-Package Microsoft.Bcl.AsyncInterfaces -Version 8.0.0 # .NET Core / .NET 5+ (netcoreapp3.1 / net6.0 / net8.0 / net9.0) # 将 SqlSugar 替换为 SqlSugarCore,其余包相同 Install-Package SqlSugarCore -Version 5.1.4.170 ``` ### 1.2 项目引用配置 **方式一:项目引用(推荐,方便调试源码)** ```bash dotnet add YourApp.csproj reference HJBaseDb.Core/HJBaseDb.Core.csproj ``` **方式二:NuGet 包引用** 将 `HJBaseDb.Core` 打包为 NuGet 包后引用。 ### 1.3 配置文件详解 **appsettings.json(.NET Core / .NET 5+ 使用)** ```json { "DbSettings": { "DatabaseType": "MySql", "ConnectionString": "Server=localhost;Port=3306;Database=MyDb;Uid=root;Pwd=1qaz@2wsx;Charset=utf8mb4;AllowLoadLocalInfile=false;", "MaxPoolSize": 100, "ConnectTimeout": 30, "CommandTimeout": 30, "IdleTime": 300, "SqliteFilePath": "App_Data/EnterpriseDb.db", "EnableSqlLog": false, "SlowSqlThresholdMs": 1000, "IsProduction": true, "EnableConnectionExhaustionWarning": true, "ConnectionExhaustionThreshold": 0.8 } } ``` | 配置项 | 默认值 | 类型 | 说明 | |--------|--------|------|------| | `DatabaseType` | `"MySql"` | string | 数据库类型:`MySql` / `SqlServer` / `Sqlite` | | `ConnectionString` | 必填 | string | 数据库连接字符串。⚠️ 生产环境禁止明文存密码 | | `MaxPoolSize` | `100` | int | 连接池最大连接数,最小 1 | | `ConnectTimeout` | `30` | int | 连接超时(秒),最小 1 | | `CommandTimeout` | `30` | int | 命令执行超时(秒),最小 1 | | `IdleTime` | `300` | int | 连接空闲回收时间(秒)。⚠️ 当前 SqlSugar 版本暂未使用此参数 | | `SqliteFilePath` | `"App_Data/EnterpriseDb.db"` | string | 仅 Sqlite 类型使用。支持相对/绝对路径 | | `EnableSqlLog` | `false` | bool | 是否输出 SQL 日志到 Debug。生产环境建议关闭 | | `SlowSqlThresholdMs` | `1000` | int | 慢 SQL 阈值(毫秒),超时后告警 | | `IsProduction` | `true` | bool | 是否为生产环境。生产环境禁用 DDL 操作 | | `EnableConnectionExhaustionWarning` | `true` | bool | 是否启用连接耗尽告警 | | `ConnectionExhaustionThreshold` | `0.8` | double | 告警阈值比例,范围 0.1~1.0。如 0.8 表示连接数达到 MaxPoolSize 的 80% 时告警 | **App.config(.NET Framework 使用)** ```xml
``` **SQL Server 配置示例** ```json { "DbSettings": { "DatabaseType": "SqlServer", "ConnectionString": "Server=localhost;Database=MyDb;User Id=sa;Password=YourPassword;TrustServerCertificate=true;", "MaxPoolSize": 100, "ConnectTimeout": 30, "CommandTimeout": 30, "IsProduction": true, "EnableSqlLog": false, "SlowSqlThresholdMs": 1000 } } ``` > \U0001f4a1 SQL Server 连接字符串注意事项: > - `TrustServerCertificate=true` 用于开发环境自签名证书;生产环境建议使用正式证书并设为 `false` > - 推荐使用 `User Id` / `Password` 方式(SQL Server 身份验证)或 `Integrated Security=SSPI`(Windows 身份验证) > - 连接池默认 `Max Pool Size=100`,可在连接字符串中通过 `Max Pool Size=200` 覆盖 **SQLite 配置示例** ```json { "DbSettings": { "DatabaseType": "Sqlite", "ConnectionString": "DataSource=App_Data/EnterpriseDb.db;Cache=Shared;", "SqliteFilePath": "App_Data/EnterpriseDb.db", "MaxPoolSize": 10, "ConnectTimeout": 10, "CommandTimeout": 30, "IsProduction": false, "EnableSqlLog": true, "SlowSqlThresholdMs": 500 } } ``` > \U0001f4a1 SQLite 注意事项: > - `SqliteFilePath` 支持相对路径和绝对路径;相对路径以应用程序启动目录为基准 > - `ConnectionString` 中的 `DataSource` 可省略(框架自动从 `SqliteFilePath` 拼接完整连接串) > - 数据库文件自动创建(含目录);**生产环境确保目录权限仅程序运行账号可访问** > - SQLite 不适用高并发写入场景,MaxPoolSize 建议控制在 10~20 > - 慢 SQL 阈值建议设低(500ms),因为 SQLite 本地操作通常很快,超过阈值可能意味着索引缺失 ### 1.4 最简初始化代码 ```csharp using Core.DbConfig; using Core.Entity; using Core.Repository; // ===== 第一步:配置数据库连接 ===== var settings = new DbSettings { DatabaseType = "MySql", ConnectionString = "Server=localhost;Database=MyDb;Uid=root;Pwd=xxx;Charset=utf8mb4;", MaxPoolSize = 50, IsProduction = false, // 开发模式,允许自动建表 EnableSqlLog = true // 开发调试时查看 SQL 日志 }; // ===== 第二步:设置登录用户(审计字段 OperatorId 从此读取) ===== // ★ 在 Web 应用的授权中间件中调用,每个请求设置一次 UserContext.SetOperatorId(10001, "admin"); // ===== 第三步:创建仓储实例 ===== var repo = new BaseRepository(settings); // ===== 第四步:插入数据(审计字段自动填充) ===== var user = new DemoUser { Name = "张三", Email = "zhangsan@test.com" }; int rows = repo.Insert(user); Console.WriteLine($"插入成功!Id={user.Id}"); // 雪花ID自动生成 // 后台自动填充: // Id = 1745689234567001 (雪花ID) // CreateTime = 2026-06-18 10:00 (自动) // UpdateTime = 2026-06-18 10:00 (自动) // OperatorId = 10001 (来自 UserContext) // IsDeleted = false (默认) ``` ### 1.5 演示项目一键运行 项目自带完整演示工程,覆盖所有功能的实际运行效果: ```bash cd Demo dotnet run ``` 演示内容: - Part 1: 插入(单条 / 批量 / 智能插入 + Async) - Part 2: 查询(GetById / GetList / First / Any / Count + Async) - Part 3: 更新(全量 / 选择性 / 指定列 / 批量 + Async) - Part 4: 分页 + 排序(单字段 / 多字段 Sorts 列表 / Async) - Part 5: 软删除(单条 / 批量 / Async / 绕过全局过滤验证) - Part 6: 自定义 SQL 安全查询(List / First / Scalar / Page) - Part 7: 自定义 SQL 执行(DDL 拦截 + SQL 注入检测 + 拼接检测) - Part 8: 多表联查(Inner / Left / Right Join + Async) - Part 9: 事务(提交 / 回滚 / 嵌套阻止 / Async) - Part 10: 存储过程(输出参数 / 异步 / 参数名注入防护) - Part 11: 字段加密(AES-256-CBC 加解密 / IsEncrypted / Attribute) - Part 12: 雪花 ID 生成(NextId / NextIdString / GetIdTime / 分布式配置 / 扩展方法) - Part 13: 数据脱敏(全脱敏 / 密码 / IP / 各方法单独调用) - Part 14: SQL 注入检测(13 种模式 / 安全值不误报 / int 范围 / 批量) - Part 15: 连接管理(活跃数 / 复用 / 重建 / 清空) - Part 16: 异常安全(DbException 安全消息 / SecurityException / 未认证 / 用户切换) 如需连接自己的数据库,修改 `Demo/Program.cs` 第 17 行的 `ConnectionStr` 变量。 ### 1.6 集成到项目 HJBaseDb.Core 支持多种项目类型的集成,以下给出常见场景的完整代码示例。 --- **控制台应用(Console)** ```csharp using Core.DbConfig; using Core.Entity; using Core.Repository; var settings = new DbSettings { DatabaseType = "MySql", // MySql / SqlServer / Sqlite ConnectionString = "Server=localhost;...", IsProduction = false, // 开发环境设为 false EnableSqlLog = true // 查看 SQL 执行日志 }; UserContext.SetOperatorId(10001, "admin"); var repo = new BaseRepository(settings); var user = new DemoUser { Name = "张三" }; repo.Insert(user); Console.WriteLine($"插入成功: Id={user.Id}"); ``` --- **ASP.NET Core Web API(推荐方式)** 通过 DI 容器管理仓储生命周期,每个 HTTP 请求独立实例: ```csharp // Program.cs — 注册服务 builder.Services.AddScoped(sp => { var config = sp.GetRequiredService(); var settings = new DbSettings { DatabaseType = config["DbSettings:DatabaseType"], ConnectionString = config["DbSettings:ConnectionString"], IsProduction = builder.Environment.IsProduction(), EnableConnectionExhaustionWarning = true }; return new BaseRepository(settings); }); // 添加用户上下文中间件(自动注入当前用户) app.UseUserContext(); ``` ```csharp // UserContextMiddleware.cs — 用户上下文中间件 public class UserContextMiddleware { private readonly RequestDelegate _next; public UserContextMiddleware(RequestDelegate next) => _next = next; public async Task InvokeAsync(HttpContext context) { var userIdClaim = context.User?.FindFirst("UserId")?.Value; if (long.TryParse(userIdClaim, out long userId)) { UserContext.SetOperatorId(userId, context.User.Identity?.Name); } try { await _next(context); } finally { UserContext.Clear(); } // ★ 必须清理,防止 AsyncLocal 泄漏 } } // 扩展方法用于注册中间件 public static class UserContextMiddlewareExtensions { public static IApplicationBuilder UseUserContext(this IApplicationBuilder app) => app.UseMiddleware(); } ``` ```csharp // Controller 中使用 [ApiController] [Route("api/[controller]")] public class UserController : ControllerBase { private readonly BaseRepository _repo; public UserController(BaseRepository repo) => _repo = repo; [HttpPost] public IActionResult Create([FromBody] DemoUser user) { _repo.Insert(user); return Ok(new { user.Id }); } [HttpGet("{id}")] public IActionResult Get(long id) { var user = _repo.GetById(id); return user == null ? NotFound() : Ok(user); } } ``` --- **WinForms / WPF 桌面应用** 桌面应用长生命周期运行,通常使用单一实例: ```csharp // 应用启动时初始化一次 var settings = new DbSettings { DatabaseType = "Sqlite", // 桌面应用推荐 SQLite SqliteFilePath = "App_Data/app.db", ConnectionString = "DataSource=App_Data/app.db;", IsProduction = true }; // 全局单例仓储(桌面应用不涉及多租户,可复用同一实例) public static class Db { private static readonly DbSettings Settings = new() { DatabaseType = "Sqlite", SqliteFilePath = "App_Data/app.db", ConnectionString = "DataSource=App_Data/app.db;", IsProduction = true }; public static BaseRepository For() where T : BaseEntity, new() => new BaseRepository(Settings); } // 使用方式 var repo = Db.For(); var user = repo.GetById(123L); ``` --- **同时操作多个数据库** ```csharp // 分别创建不同数据库的配置 var mysqlSettings = new DbSettings { DatabaseType = "MySql", ConnectionString = "Server=mysql-host;Database=db1;..." }; var sqliteSettings = new DbSettings { DatabaseType = "Sqlite", SqliteFilePath = "local.db", ConnectionString = "DataSource=local.db;" }; // 各自独立实例,互不干扰 var mysqlRepo = new BaseRepository(mysqlSettings); var sqliteRepo = new BaseRepository(sqliteSettings); mysqlRepo.Insert(user); // 写入 MySQL sqliteRepo.Insert(user); // 写入 SQLite ``` --- ## 2. 实体定义规范 ### 2.1 必须继承 BaseEntity 项目中所有业务实体 **必须** 继承 `BaseEntity`,否则审计字段(CreateTime/UpdateTime/OperatorId/IsDeleted)不会自动填充,软删除全局查询过滤器也不会生效。 **BaseEntity 内置的 5 个审计字段:** | 字段 | 类型 | 数据库类型 | 自动填充规则 | 说明 | |------|------|-----------|-------------|------| | `Id` | `long` | `BIGINT` 主键 | 插入时 = 雪花ID(Id=0 时自动生成) | 全局唯一,跨库不重复,支持分布式 | | `CreateTime` | `DateTime` | `DATETIME` | 插入时 = `DateTime.Now` | 标记 `IsOnlyIgnoreUpdate`,更新时忽略 | | `UpdateTime` | `DateTime` | `DATETIME` | 插入/更新时 = `DateTime.Now` | AOP 自动刷新 | | `OperatorId` | `long` | `BIGINT` | 从 `UserContext.Current` 读取 | 操作人 ID。未登录时 = 0 | | `IsDeleted` | `bool` | `BIT/TINYINT` | 插入时 = `false` | 全局查询过滤器自动过滤 `true` 记录 | ### 2.2 实体字段映射规则 - 使用 `[SugarTable("table_name")]` 指定表名 - 使用 `[SugarColumn(...)]` 指定列属性 - 所有字段默认 **不可为 null**,可为 null 的字段需显式标记 `[SugarColumn(IsNullable = true)]` - 主键使用 `[SugarColumn(IsPrimaryKey = true)]`,但 BaseEntity.Id 已自动设置 - 不允许手动设置 `CreateTime` 和 `OperatorId`(AOP 自动填充) - 不允许物理删除(只能通过 SoftDelete 方法设置 IsDeleted=true) ### 2.3 实体定义完整示例 ```csharp using Core.Entity; using SqlSugar; /// /// 用户表实体 — 对应 demo_user 表 /// [SugarTable("demo_user")] // 映射到数据库表 demo_user public class DemoUser : BaseEntity // ★ 必须继承 BaseEntity { /// 用户名 [SugarColumn(ColumnDescription = "用户名")] public string Name { get; set; } /// 邮箱 [SugarColumn(ColumnDescription = "邮箱", IsNullable = true)] // 允许数据库 NULL public string Email { get; set; } /// 手机号 [SugarColumn(ColumnDescription = "手机号", IsNullable = true)] public string Phone { get; set; } /// 密码哈希 [SugarColumn(ColumnDescription = "密码哈希", IsNullable = true)] public string PasswordHash { get; set; } } /// /// 订单表实体 — 对应 demo_order 表 /// 用于演示多表联查、多字段排序 /// [SugarTable("demo_order")] public class DemoOrder : BaseEntity { /// 用户ID(关联 demo_user.Id) [SugarColumn(ColumnDescription = "用户ID")] public long UserId { get; set; } /// 商品名称 [SugarColumn(ColumnDescription = "商品名称")] public string ProductName { get; set; } /// 金额(保留2位小数) [SugarColumn(ColumnDescription = "金额", DecimalDigits = 2)] public decimal Amount { get; set; } /// 状态 0=待支付 1=已支付 2=已发货 3=已完成 [SugarColumn(ColumnDescription = "状态")] public int Status { get; set; } } ``` --- ## 3. 仓储层 API 详解 ### 3.1 新增操作 **Insert — 插入单条(全字段写入)** ```csharp // 同步 var user = new DemoUser { Name = "张三", Email = "zhangsan@test.com" }; int rows = repo.Insert(user); // user.Id 自动赋值为雪花ID // user.CreateTime 自动赋值为当前时间 // user.UpdateTime 自动赋值为当前时间 // user.OperatorId 自动赋值为 UserContext.Current.OperatorId // user.IsDeleted 自动赋值为 false Console.WriteLine($"已插入,主键Id={user.Id}"); // 异步 int rowsAsync = await repo.InsertAsync(user); ``` **InsertBulk — 批量插入(每行独立审计字段)** ```csharp var users = new List { new() { Name = "李四", Email = "lisi@test.com" }, new() { Name = "王五", Email = "wangwu@test.com" }, new() { Name = "赵六", Email = "zhaoliu@test.com" }, }; int totalRows = repo.InsertBulk(users); // 返回所有行的总影响行数 // 每个实体的 Id / CreateTime / UpdateTime / OperatorId / IsDeleted 均已自动填充 // 异步 int totalRowsAsync = await repo.InsertBulkAsync(users); ``` **InsertSelective — 智能插入(仅非空字段)** ```csharp // 只赋值 Name,其他字段为 null var partial = new DemoUser { Name = "周八" }; // ★ Email / Phone / PasswordHash 为 null,不参与 INSERT 语句 // 数据库这些列的默认值(如果有)不会被覆盖为 null int rows = repo.InsertSelective(partial); ``` > `Insert` vs `InsertSelective` 区别:Insert 生成 `INSERT INTO (col1, col2, ..., colN) VALUES (val1, val2, ..., valN)`,null 字段也会写入。InsertSelective 跳过 null 字段,生成 `INSERT INTO (Name, ...) VALUES (@Name, ...)`。 ### 3.2 更新操作 **Update — 全字段更新** ```csharp // 先查出来,再修改 var user = repo.GetById(123456L); user.Name = "张三(已更新)"; user.Email = "newemail@test.com"; // 所有字段写入数据库(包括未修改的字段) int rows = repo.Update(user); // 自动刷新 UpdateTime = DateTime.Now // 自动刷新 OperatorId = UserContext.Current.OperatorId // 异步 int rowsAsync = await repo.UpdateAsync(user); ``` **UpdateSelective — 选择性更新(仅非 null 字段)** ```csharp // 只创建包含 Id 和要修改字段的新对象 var partialUpdate = new DemoUser { Id = 123456L, // ★ 必须设置 Id Email = "newemail@test.com" // 只修改邮箱 }; // Name / Phone / PasswordHash 为 null,不参与 UPDATE SET int rows = repo.UpdateSelective(partialUpdate); // 生成的 SQL: UPDATE demo_user SET Email=@Email, UpdateTime=@UpdateTime, OperatorId=@OperatorId WHERE Id=@Id ``` **UpdateColumns — 指定列更新** ```csharp // 通过表达式指定要更新的列 int rows = repo.UpdateColumns(user, e => new { e.Name, e.Phone }); // 生成的 SQL: UPDATE demo_user SET Name=@Name, Phone=@Phone, UpdateTime=@UpdateTime, OperatorId=@OperatorId WHERE Id=@Id // ★ UpdateTime 和 OperatorId 自动追加,不可省略 ``` **UpdateBatch — 批量更新** ```csharp var users = new List { user1, user2, user3 }; int totalRows = repo.UpdateBatch(users); // 每个实体自动刷新审计字段 // 异步 int totalRowsAsync = await repo.UpdateBatchAsync(users); ``` ### 3.3 查询操作 所有查询方法自动追加 `IsDeleted = false` 过滤条件,已软删除的数据不会被查到。 **GetById — 按主键查单条** ```csharp var user = repo.GetById(123456L); // SELECT * FROM demo_user WHERE Id = 123456 AND IsDeleted = 0 // 异步 var userAsync = await repo.GetByIdAsync(123456L); ``` **GetList — 查列表** ```csharp // 查全部 List all = repo.GetList(); // 条件查询 List liUsers = repo.GetList(u => u.Name.Contains("李")); // 条件 + 排序(按指定字段) List sorted = repo.GetList( u => u.Name.Contains("张"), orderByField: "CreateTime", isAsc: false); // 异步 List allAsync = await repo.GetListAsync(); List liAsync = await repo.GetListAsync(u => u.Name.Contains("李")); ``` **First — 查首条匹配** ```csharp var user = repo.First(u => u.Name == "张三"); // 如果不存在返回 null // 异步 var userAsync = await repo.FirstAsync(u => u.Name == "张三"); ``` **Count — 统计数量** ```csharp int total = repo.Count(); // 全部(不含已删除) int matched = repo.Count(u => u.Name.Contains("张")); // 条件统计 // 异步 int totalAsync = await repo.CountAsync(); ``` **Any — 判断是否存在** ```csharp bool exists = repo.Any(u => u.Name == "张三"); // 异步 bool existsAsync = await repo.AnyAsync(u => u.Name == "张三"); ``` ### 3.4 分页查询(含多字段排序) **PageInput 分页入参模型** | 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `PageIndex` | `int` | `1` | 当前页码,从 1 开始,小于 1 自动修正为 1 | | `PageSize` | `int` | `20` | 每页条数,最大 1000 | | `SortField` | `string` | `null` | 排序字段名(单字段排序时使用) | | `SortOrder` | `string` | `"DESC"` | 排序方向:`ASC`=升序,`DESC`=降序 | | `Sorts` | `List` | `null` | 多字段排序列表(优先级高于 SortField) | **PageResult\ 分页返回模型** | 属性 | 类型 | 说明 | |------|------|------| | `PageIndex` | `int` | 当前页码 | | `PageSize` | `int` | 每页条数 | | `TotalCount` | `int` | 总记录数 | | `TotalPages` | `int` | 总页数(自动计算) | | `Items` | `List` | 当前页数据列表 | **单字段排序分页** ```csharp var page = new PageInput { PageIndex = 1, PageSize = 20, SortField = "CreateTime", // 按创建时间排序 SortOrder = "DESC" // 降序 }; // 查全部(无条件) var result = repo.GetPageList(page); Console.WriteLine($"总记录={result.TotalCount}, 总页数={result.TotalPages}"); foreach (var user in result.Items) Console.WriteLine($"[{user.Id}] {user.Name}"); // 异步 var resultAsync = await repo.GetPageListAsync(page); // 带条件查询 var filtered = repo.GetPageList(u => u.Name.Contains("张"), page); ``` **多字段排序分页** ```csharp var page = new PageInput { PageIndex = 1, PageSize = 10, Sorts = new List { new("Status", isAsc: false), // 先按状态降序 new("Amount", isAsc: false), // 再按金额降序 } }; var result = orderRepo.GetPageList(page); // ORDER BY Status DESC, Amount DESC // 异步 var resultAsync = await orderRepo.GetPageListAsync(page); ``` **排序优先级:** 1. `Sorts` 列表(多字段)— 最高优先级 2. `SortField` + `SortOrder`(单字段)— 中间优先级 3. 默认 `Id DESC`(仅对继承 BaseEntity 的类型生效)— 兜底 **无条件分页(快捷方式)** ```csharp var result = repo.GetPageList(page); // 等同于 repo.GetPageList(null, page) ``` ### 3.5 多表联查 支持 `Left Join` / `Inner Join` / `Right Join` 三种类型。 ```csharp // 查询已支付订单(Status>=1),附带用户名 var joinResult = repo.QueryJoin( JoinType.Inner, // 联查类型 (u, o) => u.Id == o.UserId, // 关联条件 (u, o) => new // 选择返回的列 { UserName = u.Name, ProductName = o.ProductName, Amount = o.Amount, Status = o.Status }, whereExpr: (u, o) => o.Status >= 1); // 可选过滤条件 foreach (var item in joinResult) Console.WriteLine($"用户={item.UserName}, 商品={item.ProductName}"); // 异步 var resultAsync = await repo.QueryJoinAsync( JoinType.Left, (u, o) => u.Id == o.UserId, (u, o) => new { UserName = u.Name, ProductName = o.ProductName }); ``` > ⚠️ 联查类型选择: > - `JoinType.Left` — 左连接,保留主表全部数据(T1 为基准) > - `JoinType.Inner` — 内连接,仅返回匹配成功的数据 > - `JoinType.Right` — 右连接,保留关联表全部数据(T2 为基准) ### 3.6 软删除 所有删除操作统一使用 **UPDATE SET IsDeleted=true**,不提供物理 DELETE 的对外接口。 ```csharp // 单条软删除 int rows = repo.SoftDelete(123456L); // 生成的 SQL: UPDATE demo_user SET IsDeleted=true, UpdateTime=now, OperatorId=10001 WHERE Id=123456 // 批量软删除 int batchRows = repo.SoftDeleteBatch(new List { 1L, 2L, 3L, 4L, 5L }); // 生成的 SQL: UPDATE demo_user SET IsDeleted=true, UpdateTime=now WHERE Id IN (1,2,3,4,5) // 异步 await repo.SoftDeleteAsync(123456L); await repo.SoftDeleteBatchAsync(new List { 1L, 2L, 3L }); ``` **软删除后的查询行为:** ```csharp // ★ 全局自动过滤:GetById 返回 null var deletedUser = repo.GetById(123456L); // → null(因为全局查询自动加了 AND IsDeleted = 0) // ★ 自定义 SQL 也自动过滤(autoFilterDeleted 默认 true) var list = repo.SqlQueryList("SELECT * FROM demo_user WHERE Id=@id", new { id = 123456L }); // → [](自动追加了 AND IsDeleted = 0) // ★ 需要查询已删除记录时,关闭自动过滤 var includeDeleted = repo.SqlQueryList( "SELECT * FROM demo_user WHERE Id=@id", new { id = 123456L }, autoFilterDeleted: false); // 关闭自动附加 AND IsDeleted = 0 // → 返回一条记录,其 IsDeleted = true ``` ### 3.7 事务 **基本事务用法** ```csharp // 同步事务 repo.UseTransaction(() => { var user = new DemoUser { Name = "事务用户A" }; repo.Insert(user); var order = new DemoOrder { UserId = user.Id, ProductName = "商品", Amount = 100.00m, Status = 0 }; repo.Insert(order); // 事务内所有操作一起提交 }, "批量创建用户和订单"); ``` **异步事务** ```csharp await repo.UseTransactionAsync(async () => { var user = new DemoUser { Name = "异步事务用户" }; await repo.InsertAsync(user); var order = new DemoOrder { UserId = user.Id, ProductName = "商品", Amount = 100m }; await repo.InsertAsync(order); }, "异步事务"); ``` **事务回滚(异常时自动)** ```csharp try { repo.UseTransaction(() => { var user = new DemoUser { Name = "即将回滚的用户" }; repo.Insert(user); // 故意抛出异常,触发回滚 throw new InvalidOperationException("模拟业务异常"); }); } catch (InvalidOperationException) { Console.WriteLine("事务已自动回滚,Insert 的数据不会被写入数据库"); } ``` **嵌套事务检测** ```csharp try { repo.UseTransaction(() => { repo.UseTransaction(() => { // ★ 这行代码不会执行,会抛出 InvalidOperationException }, "内层事务"); }, "外层事务"); } catch (InvalidOperationException ex) { Console.WriteLine(ex.Message); // "不支持嵌套事务" } ``` > 💡 如果一个事务块内需要多个数据库操作,全部放在同一个回调中即可,不需要嵌套。库设计上不支持嵌套事务是因为:大多数业务场景不需要、避免 SavePoint 兼容问题。 --- ## 4. 自定义 SQL 安全层 > ⚠️ **安全红线:所有自定义 SQL 必须使用参数化传参,禁止字符串拼接!** 自定义 SQL 方法统一经过三关安全检查: 1. **空检测** — SQL 为空直接抛异常 2. **DDL 拦截** — 生产环境检测到 DROP/ALTER/TRUNCATE/CREATE 直接阻止 3. **注入检测** — 参数值中的 SQL 注入特征(UNION SELECT / OR 1=1 / WAITFOR DELAY 等)被拦截 ### 4.1 列表查询 ```csharp // 同步 var list = repo.SqlQueryList( "SELECT * FROM demo_user WHERE Name LIKE @name AND Status = @status", new { name = "%张%", status = 1 }); // 异步 var listAsync = await repo.SqlQueryListAsync( "SELECT * FROM demo_user WHERE Name LIKE @name", new { name = "%张%" }); ``` ### 4.2 单条查询 ```csharp // 查询结果为空时返回 null var user = repo.SqlQueryFirst( "SELECT * FROM demo_user WHERE Id = @id", new { id = 123456L }); // 异步 var userAsync = await repo.SqlQueryFirstAsync( "SELECT * FROM demo_user WHERE Id = @id", new { id = 123456L }); ``` ### 4.3 标量查询 ```csharp // 返回单个值(COUNT, SUM, MAX 等) int count = repo.SqlQueryScalar("SELECT COUNT(*) FROM demo_user"); string name = repo.SqlQueryScalar("SELECT Name FROM demo_user WHERE Id = @id", new { id = 1L }); // ★ 查询结果为空时: // 值类型(int/bool/decimal 等)返回 0 // 字符串类型返回 null // 避免 null 转换异常 // 异步 int countAsync = await repo.SqlQueryScalarAsync("SELECT COUNT(*) FROM demo_user"); ``` ### 4.4 执行 DML ```csharp // 仅允许 INSERT / UPDATE / DELETE / SELECT / SET / SHOW / CALL / BEGIN / COMMIT / ROLLBACK / DECLARE / EXEC / EXECUTE // 不允许 DDL(CREATE TABLE / DROP TABLE 等),生产环境由 DdlGuard 拦截 int affected = repo.SqlExecute( "UPDATE demo_user SET Name = @name, Email = @email WHERE Id = @id", new { name = "新名字", email = "new@test.com", id = 123456L }); // 异步 int affectedAsync = await repo.SqlExecuteAsync( "DELETE FROM demo_user WHERE Id = @id", new { id = 999L }); ``` ### 4.5 SQL 分页查询 ```csharp var sqlPage = repo.SqlQueryPage( "SELECT * FROM demo_user", // 原始 SQL null, // 参数 new PageInput // 分页参数 { PageIndex = 1, PageSize = 20, SortField = "CreateTime", SortOrder = "DESC" }); Console.WriteLine($"总记录={sqlPage.TotalCount}, 当前页={sqlPage.Items.Count}"); // 带参数 + 多字段排序 var sqlPage2 = repo.SqlQueryPage( "SELECT * FROM demo_user WHERE Status = @status", new { status = 1 }, new PageInput { PageIndex = 1, PageSize = 10, Sorts = new List { new("CreateTime", isAsc: false), } }); // 异步 var sqlPageAsync = await repo.SqlQueryPageAsync( "SELECT * FROM demo_user", null, page); ``` ### 4.6 存储过程 ```csharp // 执行存储过程 int result = repo.SqlExecuteStoredProcedure( "sp_get_user_count", new SugarParameter("@outCount", 0, true) // output 参数 //, new SugarParameter("@deptId", 100) // input 参数 ); // 异步 int resultAsync = await repo.SqlExecuteStoredProcedureAsync( "sp_get_user_count", new SugarParameter("@outCount", 0, true)); ``` ### 4.7 逻辑删除自动过滤 自定义 SQL 方法默认自动追加 `AND IsDeleted = 0`,可以通过 `autoFilterDeleted` 参数关闭: ```csharp // ★ 默认自动过滤已删除(仅对继承 BaseEntity 的 TResult 类型生效) var activeUsers = repo.SqlQueryList("SELECT * FROM demo_user"); // → 实际执行:SELECT * FROM demo_user WHERE IsDeleted = 0 // ★ 需要查询已删除记录时关闭自动过滤 var allUsers = repo.SqlQueryList( "SELECT * FROM demo_user", autoFilterDeleted: false); // → 实际执行:SELECT * FROM demo_user(不追加过滤条件) // ★ 对非实体类型(如匿名类型、DTO),自动过滤不生效 var names = repo.SqlQueryList("SELECT Name FROM demo_user"); // → 实际执行:SELECT Name FROM demo_user(不追加 IsDeleted,TResult 不是 BaseEntity 子类) ``` --- ## 5. 安全机制详解 ### 5.1 DDL 高危操作拦截 **`DdlGuard`** 类拦截所有 DDL 高危操作: | 拦截的操作 | 检测关键词 | 生产环境 | 开发环境 | |-----------|-----------|---------|---------| | 删除表/库/索引等 | `DROP TABLE / DATABASE / INDEX / VIEW ...` | ❌ 抛出 `DdlBlockedException` | ⚠️ 仅日志告警,允许执行 | | 修改表/库结构 | `ALTER TABLE / DATABASE / INDEX ...` | ❌ 抛出 `DdlBlockedException` | ⚠️ 仅日志告警,允许执行 | | 清空表 | `TRUNCATE TABLE` | ❌ 抛出 `DdlBlockedException` | ⚠️ 仅日志告警,允许执行 | | 重命名 | `RENAME TABLE` | ❌ 抛出 `DdlBlockedException` | ⚠️ 仅日志告警,允许执行 | | 创建表/库 | `CREATE TABLE / DATABASE / INDEX ...` | ❌ 抛出 `DdlBlockedException` | ✅ 允许 | ```csharp var prodSettings = new DbSettings { ConnectionString = "...", IsProduction = true // ★ 生产环境必须设为 true }; var prodRepo = new BaseRepository(prodSettings); try { prodRepo.SqlExecute("DROP TABLE demo_user"); } catch (DdlBlockedException ex) { Console.WriteLine($"DDL 被拦截: {ex.Message}"); // 输出: "生产环境已阻止高危DDL操作: DROP TABLE。如需执行DDL,请通过数据库管理工具手动操作。" } // DdlBlockedException 包含详细信息: // ex.DdlOperation → "DROP TABLE" // ex.DdlStatement → "DROP TABLE demo_user" ``` > 💡 开发环境(`IsProduction = false`)执行 DDL 仅输出安全告警日志,不阻断执行,方便开发调试。 ### 5.2 SQL 注入全特征检测 **`SqlInjectionGuard`** 对所有参数值进行 13 种注入模式检测: | 注入类型 | 检测特征 | 示例 | |----------|---------|------| | DDL 注入 | DROP / ALTER / CREATE / TRUNCATE | 参数值含 `DROP TABLE` | | 恒等式注入 | OR 1=1 / OR '1'='1 | 参数值含 `' OR '1'='1` | | 联合查询 | UNION SELECT / UNION ALL SELECT | 参数值含 `UNION SELECT` | | 延时注入 | WAITFOR DELAY | 参数值含 `WAITFOR DELAY '0:0:5'` | | 文件操作 | LOAD_FILE / INTO OUTFILE / INTO DUMPFILE | 参数值含 `LOAD_FILE('/etc/passwd')` | | 执行命令 | EXEC / EXECUTE / XP_CMDSHELL | 参数值含 `xp_cmdshell` | | 基准测试 | BENCHMARK() | 参数值含 `BENCHMARK(1000000,MD5('a'))` | | 注释注入 | `--` / `#` / `/**/` | 参数值含 `admin'--` | | 数值溢出 | int/long 超出 ±999,999,999 | 参数值 `1000000000` | ```csharp // ★ 以下代码会抛出 SecurityException try { repo.SqlQueryList( "SELECT * FROM demo_user WHERE Name = @name", new { name = "'; DROP TABLE demo_user; --" }); } catch (SecurityException ex) { Console.WriteLine(ex.Message); // 输出: [安全拦截] 检测到SQL注入特征,参数 'name' 包含危险SQL关键字 'DROP TABLE' // SecurityException 包含详细信息: // ex.ParameterName → "name" // ex.ParameterValue → "'; ****** TABLE demo_user; --"(已脱敏) } ``` **DetectConcatSql — 检测直接拼接的 SQL** ```csharp // 检测整个 SQL 字符串中是否存在字符串拼接迹象 try { SqlInjectionGuard.DetectConcatSql( "SELECT * FROM users WHERE id = " + userInput, userInput); } catch (SecurityException) { // 检测到外部输入被直接拼接到 SQL 中 } ``` ### 5.3 日志数据脱敏 **`DataMasker`** 对 SQL 日志中的敏感信息自动脱敏: ``` 脱敏前: 用户手机:13800138000, 身份证:110101199001011234, 银行卡:6222021234567890 脱敏后: 用户手机:138****8000, 身份证:110***********1234, 银行卡:6222 **** **** 7890 脱敏前: password='MySecret123!' 脱敏后: password='******' 脱敏前: 联系邮箱: zhangsan@example.com 脱敏后: 联系邮箱: z***@example.com 脱敏前: ConnectionString='Server=prod;Database=db;Uid=admin;Pwd=RealPassword;' 脱敏后: ConnectionString='******' ``` 直接使用: ```csharp string safeLog = DataMasker.MaskSensitiveData(rawLog); ``` 脱敏覆盖范围: | 数据类型 | 正则模式 | 脱敏效果 | |----------|---------|----------| | 手机号 | `1[3-9]\d{9}` | 138****8000 | | 身份证 | `[1-9]\d{5}(19\|20)\d{2}...` | 110***********1234 | | 银行卡 | `\b\d{16,19}\b` | 6222 **** **** 7890 | | 邮箱 | `\b[A-Za-z0-9._%+-]+@...` | z***@example.com | | 密码参数 | `password\|pwd\|secret\|token\|connstr` | password='******' | ### 5.4 AES 字段加密 **`FieldEncryptor`** 提供 AES-256-CBC 对称加解密(线程安全:使用 `Lazy` 确保高并发下单次初始化)。 **环境变量配置(生产环境推荐):** ```bash # 设置加密密钥(32字节UTF8字符串) set ENCRYPTION_KEY=Your32CharLongEncryptionKey!!! # 设置初始化向量(16字节UTF8字符串) set ENCRYPTION_IV=16CharIVString! ``` **代码中使用:** ```csharp // ★ 方式一:环境变量注入(推荐) // 已在环境变量中设置 ENCRYPTION_KEY 和 ENCRYPTION_IV // 首次调用 Encrypt/Decrypt 时自动读取 // ★ 方式二:开发调试时直接设置 Environment.SetEnvironmentVariable("ENCRYPTION_KEY", "DemoKeyForTestingPurposesOnly!!!"); Environment.SetEnvironmentVariable("ENCRYPTION_IV", "DemoIV16Char!!!"); // 加密 string encrypted = FieldEncryptor.Encrypt("MySecretPassword123!"); Console.WriteLine(encrypted); // Base64编码密文 // 解密 string decrypted = FieldEncryptor.Decrypt(encrypted); Console.WriteLine(decrypted); // "MySecretPassword123!" // 检测是否已加密(启发式判断) bool isEncrypted = FieldEncryptor.IsEncrypted(encrypted); Console.WriteLine(isEncrypted); // true ``` **在实体字段加密业务中的典型用法:** ```csharp // ★ 插入时加密敏感字段 var user = new DemoUser { Name = "张三", Email = FieldEncryptor.Encrypt("zhangsan@test.com"), // 加密存储 Phone = FieldEncryptor.Encrypt("13800138000"), // 加密存储 PasswordHash = FieldEncryptor.Encrypt("myPlainPassword"), // 加密存储 }; repo.Insert(user); // ★ 查询后解密 var saved = repo.GetById(user.Id); if (FieldEncryptor.IsEncrypted(saved.Email)) { saved.Email = FieldEncryptor.Decrypt(saved.Email); } if (FieldEncryptor.IsEncrypted(saved.Phone)) { saved.Phone = FieldEncryptor.Decrypt(saved.Phone); } Console.WriteLine($"真实手机号: {saved.Phone}"); // 13800138000 ``` **字段加密特性标记(预留扩展):** ```csharp // 使用 [EncryptField] 标记需要加密的字段 public class DemoUser : BaseEntity { [EncryptField("用户手机号")] [SugarColumn(ColumnDescription = "手机号", IsNullable = true)] public string Phone { get; set; } } ``` ### 5.5 异常安全输出 **`DbException`** 设计了双层消息机制,防止 SQL 语句和堆栈信息泄露给前端: ```csharp try { // 一些数据库操作 } catch (DbException ex) { // ★ 给前端展示的安全消息(不含SQL和堆栈) string safeMsg = ex.GetSafeMessage(); // 格式: [DB_001] 数据库连接失败 // ★ 写入内部日志的完整消息(含SQL和堆栈) string fullLog = ex.GetFullLogMessage(); // 格式: [DB_001] 数据库连接失败\nSQL: SELECT * FROM ...\nStack: ... // ★ ToString() 默认只输出安全消息 Console.WriteLine(ex.ToString()); // 不会泄露敏感信息 } ``` --- ## 6. 审计系统 ### 6.1 自动字段填充规则 通过 **两层兜底** 确保审计字段绝不遗漏: | 操作 | AOP 层(DbFactory.DataExecuting) | 方法层(BaseRepository 兜底) | |------|-----------------------------------|------------------------------| | **Insert** | Id(雪花ID)、CreateTime、UpdateTime、OperatorId、IsDeleted | 同上(双重保护) | | **Update** | UpdateTime、OperatorId | 同上(双重保护) | | **UpdateColumns** | 无(列级操作不触发 UpdateByObject) | UpdateTime、OperatorId(方法内手动 SetColumns) | | **InsertSelective** | 同 Insert | 同 Insert | | **InsertBulk** | 同 Insert(逐行触发) | 循环中预填充 | | **UpdateBatch** | 同 Update | 循环中预填充 | | **SoftDelete** | 无(列级操作) | IsDeleted=true、UpdateTime、OperatorId | > AOP `OnLogExecuting`/`OnLogExecuted` 还负责:SQL 日志输出(脱敏后)、慢 SQL 检测告警、SQL 执行错误日志。 ### 6.2 用户上下文配置 ```csharp // ★ 在 Web 应用的授权中间件中(比如 ASP.NET Core 的 JWT 中间件之后)调用 UserContext.SetOperatorId(userId, userName); // 读取当前用户 long currentUserId = UserContext.Current?.OperatorId ?? 0; bool isLoggedIn = UserContext.Current?.IsAuthenticated ?? false; // ★ 请求结束时清理(避免 AsyncLocal 跨请求泄漏) UserContext.Clear(); ``` **在 ASP.NET Core 中间件中的典型用法:** ```csharp public class UserContextMiddleware { private readonly RequestDelegate _next; public UserContextMiddleware(RequestDelegate next) => _next = next; public async Task InvokeAsync(HttpContext context) { // 从 JWT Token 中提取用户 ID var userIdClaim = context.User?.FindFirst("UserId")?.Value; if (long.TryParse(userIdClaim, out long userId)) { UserContext.SetOperatorId(userId, context.User.Identity?.Name); } try { await _next(context); } finally { // ★ 必须清理,否则 AsyncLocal 会在线程池复用时泄漏 UserContext.Clear(); } } } ``` --- ## 7. 连接池管理 ### 7.1 单例工厂模式 `DbFactory` 使用 `ConcurrentDictionary>` 实现连接池的**单例缓存**: - 相同的连接字符串复用同一个 SqlSugarClient 实例 - 缓存键 = `"{DatabaseType}_{ConnectionString}"` - `Lazy(ExecutionAndPublication)` 确保高并发下只创建一次 ```csharp // ★ 相同配置多次调用 GetInstance 返回同一个实例 var client1 = DbFactory.GetInstance(settings); var client2 = DbFactory.GetInstance(settings); Console.WriteLine(ReferenceEquals(client1, client2)); // True // ★ 不同配置各自独立实例 var anotherSettings = new DbSettings { DatabaseType = "MySql", ConnectionString = "Server=other;Database=OtherDb;..." }; var client3 = DbFactory.GetInstance(anotherSettings); Console.WriteLine(ReferenceEquals(client1, client3)); // False ``` ### 7.2 连接耗尽告警 ```csharp // ★ 检查当前活跃连接数 int activeCount = DbFactory.GetActiveConnectionCount(); Console.WriteLine($"当前活跃连接: {activeCount}"); // 当活跃连接数超过 MaxPoolSize × ConnectionExhaustionThreshold(默认 80%)时 // 系统自动输出告警日志: // [连接池告警] 当前活跃连接数: 45/50,已超过告警阈值 80% // ★ 紧急恢复:清空所有连接池 DbFactory.ClearAllInstances(); Console.WriteLine($"清空后活跃连接: {DbFactory.GetActiveConnectionCount()}"); // 0 ``` ### 7.3 配置变更重建 ```csharp // 当数据库配置发生变更(如连接字符串更新),需要移除旧的实例 DbFactory.RemoveInstance(oldSettings); // 下一次 GetInstance(oldSettings) 时重新创建连接 var newClient = DbFactory.GetInstance(newSettings); ``` --- ## 8. 雪花 ID 生成器 ### 8.1 分布式部署配置 通过环境变量配置数据中心 ID 和机器 ID,避免多实例部署时 ID 冲突: ```bash # 实例 A set SNOWFLAKE_DATACENTER_ID=1 set SNOWFLAKE_WORKER_ID=1 # 实例 B set SNOWFLAKE_DATACENTER_ID=1 set SNOWFLAKE_WORKER_ID=2 ``` ```csharp // 生成 ID long id = SnowflakeIdGenerator.Instance.NextId(); // 返回 long string idStr = SnowflakeIdGenerator.Instance.NextIdString(); // 返回 string Console.WriteLine(id); // 1745689234567001 // 查看当前实例配置 Console.WriteLine($"DatacenterId={SnowflakeIdGenerator.Instance.DatacenterId}"); Console.WriteLine($"WorkerId={SnowflakeIdGenerator.Instance.WorkerId}"); ``` ### 8.2 从 ID 解析生成时间 ```csharp long id = SnowflakeIdGenerator.Instance.NextId(); DateTime genTime = SnowflakeIdGenerator.GetIdTime(id); Console.WriteLine($"该 ID 的生成时间: {genTime:yyyy-MM-dd HH:mm:ss.fff}"); ``` **ID 结构:** ``` 0 ─── 41位时间戳 ─── 5位数据中心 ─── 5位机器 ─── 12位序列号 符号位 相对 2020-01-01 最大32 最大32 每毫秒4096 ``` **时钟回拨保护:** 检测到时钟回拨时,1 秒内自动等待恢复,超过 1 秒抛异常,确保 ID 绝对不重复。 **为实体生成雪花 ID(扩展方法):** ```csharp // 对于非 BaseEntity 但需要雪花 ID 的类 public class AnyEntity { public long Id { get; set; } } var entity = new AnyEntity(); entity.GenerateSnowflakeId(); // 仅当 Id==0 时生成 Console.WriteLine(entity.Id); // 已赋值 ``` --- ## 9. 多框架兼容说明 项目同时编译 7 个目标框架: | 目标框架 | 条件编译符号 | 专用依赖 | |----------|-------------|---------| | `net461` | `NET461` | SqlSugar, Microsoft.Bcl.AsyncInterfaces | | `net472` | `NET472` | SqlSugar | | `net48` | `NET48` | SqlSugar | | `netcoreapp3.1` | `NETCOREAPP3_1` | SqlSugarCore, Microsoft.Extensions.Configuration 3.1.x | | `net6.0` | `NET6_0` | SqlSugarCore, Microsoft.Extensions.Configuration 8.x | | `net8.0` | `NET8_0` | SqlSugarCore, Microsoft.Extensions.Configuration 8.x | | `net9.0` | `NET9_0` | SqlSugarCore, Microsoft.Extensions.Configuration 8.x | 使用方只需引用 `HJBaseDb.Core`,根据自身目标框架自动选择正确的 SqlSugar 包: ```xml ``` > 注意:`net461` 和 `netcoreapp3.1` 目标某些包版本可能存在兼容性提醒,不影响实际运行。 --- ## 10. 强制规范与禁止操作 ### ✅ 必须遵守 1. **所有业务实体必须继承 `BaseEntity`** — 否则审计字段不填充、软删除过滤器不生效 2. **数据库密码必须通过环境变量或密钥服务注入** — 严禁在配置文件中明文存放 3. **自定义 SQL 必须使用参数化传参** — 禁止字符串拼接,格式:`WHERE Name = @name` 4. **生产环境 `IsProduction` 必须设为 `true`** — 否则 DDL 拦截不生效,存在安全风险 5. **用户登录后立即调用 `UserContext.SetOperatorId()`** — 确保审计字段 OperatorId 可追溯 6. **请求结束后调用 `UserContext.Clear()`** — 防止 AsyncLocal 跨请求泄漏 7. **使用 `SoftDelete` 代替物理删除** — 库不对外暴露 DELETE 执行接口 8. **批量操作单次建议不超过 1000 条** — 过大的批次可能导致数据库锁升级 9. **AES 加密密钥生产环境必须更换** — 默认密钥仅用于本地开发调试 ### ❌ 严禁操作 | 禁止行为 | 原因 | 替代方案 | |---------|------|---------| | 手写 `DELETE FROM table` | 绕过软删除,数据不可恢复 | `SoftDelete()` | | SQL 字符串 `+` 拼接参数 | SQL 注入高危,可能导致数据泄露 | 参数化 `@name` | | 直接修改 `CreateTime` / `OperatorId` | 审计失效,违反安全规范 | AOP 自动填充 | | 生产环境执行 `DROP` / `ALTER` | 数据丢失或服务中断 | DBA 手动通过管理工具操作 | | 配置文件硬编码数据库密码 | 密码泄露,可能导致数据库被入侵 | 环境变量 / Azure Key Vault / 阿里云 KMS | | 嵌套 `UseTransaction` | 事务状态混乱,可能导致脏写 | 合并为一个事务块 | | 用 `SqlExecute` 执行 `CREATE TABLE` | 绕过 DDL 守卫且生产环境不安全 | `CodeFirst.InitTables`(仅限开发环境) | | 手动设置 `UserContext.Current = new UserContext()` | 绕过 SetOperatorId 的安全校验 | 使用 `SetOperatorId()` 静态方法 | --- ## 11. 常见问题 FAQ ### 11.1 提示 "未配置AES加密密钥" 怎么解决? ```bash # 方式一:设置环境变量(推荐,生产环境通过部署脚本注入) set ENCRYPTION_KEY=Your32CharLongEncryptionKey!!! set ENCRYPTION_IV=16CharIVString! # 方式二:代码中设置(仅用于开发调试) Environment.SetEnvironmentVariable("ENCRYPTION_KEY", "DemoKeyForTestingPurposesOnly!!!"); Environment.SetEnvironmentVariable("ENCRYPTION_IV", "DemoIV16Char!!!"); ``` ### 11.2 为什么 GetById 返回 null? 可能的原因: 1. **该记录已被软删除** — 全局查询自动过滤 `IsDeleted = true` 的数据。需要用 `SqlQueryList` 并传 `autoFilterDeleted: false` 来查询 2. **ID 不存在** — 确认 ID 是否正确 3. **表名映射不对** — 检查实体上的 `[SugarTable("正确表名")]` 特性 ### 11.3 InsertSelective 和 Insert 有什么区别? `Insert` 写入所有字段(null 也会写入数据库),`InsertSelective` 只写入非 null 字段。当实体有大量可为 null 的列且只想设置部分字段时使用后者,避免 null 覆盖数据库默认值。 ### 11.4 SqlSugar 连接池耗尽怎么办? ```csharp // 1. 查看当前活跃连接数 int activeCount = DbFactory.GetActiveConnectionCount(); Console.WriteLine($"活跃连接: {activeCount}/{settings.MaxPoolSize}"); // 2. 检查连接字符串最大池大小 // MySQL: "Server=...;Max Pool Size=100;" 默认 100 // SQL Server: "Server=...;Max Pool Size=100;" 默认 100 // 3. 紧急恢复(会断开所有当前连接) DbFactory.ClearAllInstances(); // 4. 确认仓储实例生命周期正确 // Web 应用:使用 DI 容器管理,建议 Scoped 生命周期 // services.AddScoped>(); ``` ### 11.5 SqlExecute 报 "仅支持DML" 是什么原因? `SqlExecute` 允许 `INSERT` / `UPDATE` / `DELETE` / `SELECT` / `SET` / `SHOW` / `CALL` / `BEGIN` / `COMMIT` / `ROLLBACK` / `DECLARE` / `EXEC` / `EXECUTE`。若需要执行 `CREATE TABLE` 等 DDL: - **开发环境**:使用 `repo.Db.CodeFirst.InitTables()`(SqlSugar 自动建表) - **生产环境**:DBA 手动通过数据库管理工具执行 ### 11.6 事务嵌套为什么会报错? 库设计上不支持嵌套事务。`_isInTransaction` 标志会检测嵌套调用并抛出 `InvalidOperationException("不支持嵌套事务")`。原因是: - 大多数业务场景不需要嵌套事务 - 避免因 SavePoint 兼容性问题导致的脏写 - 建议将多个操作放在同一个事务回调中 ### 11.7 如何同时操作多个数据库? ```csharp var mysqlSettings = new DbSettings { DatabaseType = "MySql", ConnectionString = "..." }; var sqliteSettings = new DbSettings { DatabaseType = "Sqlite", ConnectionString = "..." }; var mysqlRepo = new BaseRepository(mysqlSettings); var sqliteRepo = new BaseRepository(sqliteSettings); mysqlRepo.Insert(user); // 写入 MySQL sqliteRepo.Insert(user); // 写入 SQLite ``` ### 11.8 如何在 ASP.NET Core 中注入仓储? ```csharp // Program.cs builder.Services.AddScoped(sp => { var config = sp.GetRequiredService(); var settings = new DbSettings { DatabaseType = config["DbSettings:DatabaseType"], ConnectionString = config["DbSettings:ConnectionString"], IsProduction = true }; return new BaseRepository(settings); }); // Controller 中直接注入 public class UserController { private readonly BaseRepository _repo; public UserController(BaseRepository repo) => _repo = repo; [HttpPost] public IActionResult Create([FromBody] DemoUser user) { UserContext.SetOperatorId(GetCurrentUserId()); // 从 JWT 获取 _repo.Insert(user); return Ok(new { user.Id }); } } ```