# 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 });
}
}
```