FJY.BaseModule
1.2.1
dotnet add package FJY.BaseModule --version 1.2.1
NuGet\Install-Package FJY.BaseModule -Version 1.2.1
<PackageReference Include="FJY.BaseModule" Version="1.2.1" />
<PackageVersion Include="FJY.BaseModule" Version="1.2.1" />
<PackageReference Include="FJY.BaseModule" />
paket add FJY.BaseModule --version 1.2.1
#r "nuget: FJY.BaseModule, 1.2.1"
#:package FJY.BaseModule@1.2.1
#addin nuget:?package=FJY.BaseModule&version=1.2.1
#tool nuget:?package=FJY.BaseModule&version=1.2.1
IMBaseModule
综合管理系统基础模块,封装了数据库操作、缓存、模块化依赖注入、对象映射、API 统一返回、控制台扩展等常用基础能力。
功能概览
| 模块 | 说明 |
|---|---|
| SQL Sugar 数据库封装 | 多库/多租户支持、CodeFirst 建表、AOP SQL 日志、基础仓储 BaseRepository<T> 与分页查询 |
| Redis 缓存封装 | 基于 FreeRedis 的 ICaching 接口,支持内存/Redis 自动切换、同步/异步全量方法 |
| 模块化系统 | IBaseModule + AddModule<T>() 模块注册、[InjectModule] 模块依赖、[InjectOn] 自动 DI |
| 实体特性 | [Tenant] / [LogTable] / [PermissionsTable] / [WmsTable] / [IncrementTable] / [IgnoreTable] |
| API 统一返回 | ApiResult + ApiResultHelper 标准响应结构(Code / Message / Data / Result) |
| 服务定位器 | App 静态类:App.GetService<T>()、App.GetConfig<T>()、App.User、App.Configuration |
| Console 扩展 | ConsoleExtension 彩色输出(Success / Warning / Info / Error) |
| 对象映射 | AutoMapper / Mapster Helper 封装 |
| 通用工具 | JsonHelper、Md5Helper、AppSettingsHelper、ConvertUtil |
安装
方式一:NuGet 包管理器 CLI
dotnet add package FJY.BaseModule
方式二:PackageReference
<PackageReference Include="FJY.BaseModule" Version="1.2.0" />
目标框架:net10.0
快速开始(宿主 Program.cs)
1. Serilog 预热(重要)
基础模块在 AddModule<BaseModule>() 阶段会输出日志,而 builder.Host.UseSerilog() 要在 builder.Build() 后才会设置 Log.Logger。必须在注册模块前提前预热 Serilog,否则初始化阶段日志会丢失:
using Serilog;
// 1. 临时引导日志(在 builder 之前)
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.CreateBootstrapLogger();
try
{
var builder = WebApplication.CreateBuilder(args);
// 2. 正式配置 Serilog(自动覆盖引导日志)
builder.Host.UseSerilog((ctx, cfg) => cfg
.ReadFrom.Configuration(ctx.Configuration)
.Enrich.FromLogContext());
// 3. 注册基础模块
builder.Services.AddModule<BaseModule>();
builder.Services.AddControllers();
var app = builder.Build();
// 4. 标记应用已运行(必须)
IMBaseModule.Apps.App.IsRun = true;
app.UseSerilogRequestLogging();
app.MapControllers();
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
2. appsettings.json 配置
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.EntityFrameworkCore": "Information"
}
},
"WriteTo": [
{ "Name": "Console" },
{
"Name": "File",
"Args": {
"path": "Logs/log-.txt",
"rollingInterval": "Day",
"retainedFileCountLimit": 30
}
}
]
},
"SnowId": {
"WorkerId": 1
},
"Redis": {
"Enable": true,
"ConnectionString": "127.0.0.1:6379,password=,defaultDatabase=0",
"InstanceName": "IMBase:",
"MaxPoolSize": 50,
"MinPoolSize": 5,
"SendTimeout": 10000,
"ReceiveTimeout": 10000,
"ConnectTimeout": 15000
},
"DbConnection": {
"EnableConsoleSql": true,
"Model": "Models",
"ConnectionStrings": [
{
"ConfigId": "Main",
"DbType": "MySql",
"ConnectionString": "server=127.0.0.1;port=3306;database=im_main;uid=root;pwd=123456;AllowLoadLocalInfile=true;",
"DbSettings": {
"EnableInitDb": true,
"EnableDiffLog": false,
"EnableUnderLine": true
},
"TableSettings": {
"EnableInitTable": true,
"EnableIncrementTable": true
},
"SeedSettings": {
"EnableInitSeed": true,
"EnableIncrementSeed": false
}
},
{
"ConfigId": "Log",
"DbType": "MySql",
"ConnectionString": "server=127.0.0.1;port=3306;database=im_log;uid=root;pwd=123456;",
"DbSettings": {
"EnableInitDb": true,
"EnableDiffLog": false,
"EnableUnderLine": true
},
"TableSettings": {
"EnableInitTable": true,
"EnableIncrementTable": true
},
"SeedSettings": {
"EnableInitSeed": false,
"EnableIncrementSeed": false
}
}
]
}
}
一、数据库操作(SQL Sugar)
1.1 定义实体
using IMBaseModule.Attributes.DataTableAttributes;
using SqlSugar;
/// <summary>
/// 用户表(主数据库,ConfigId = "Main")
/// </summary>
[SugarTable("sys_user")]
[Tenant("Main")] // 指定使用哪个数据库连接(ConfigId)
public class SysUser
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = false)]
public long Id { get; set; } // 使用 SnowId
[SugarColumn(ColumnDataType = "varchar(64)")]
public string UserName { get; set; } = string.Empty;
[SugarColumn(ColumnDataType = "varchar(128)")]
public string Password { get; set; } = string.Empty;
public DateTime CreatedTime { get; set; } = DateTime.Now;
}
/// <summary>
/// 审计日志表(自动走日志库 ConfigId = "Log")
/// </summary>
[SugarTable("audit_log")]
[LogTable] // 不需要写 [Tenant("Log")]
public class AuditLog
{
[SugarColumn(IsPrimaryKey = true)]
public long Id { get; set; }
public string Message { get; set; } = string.Empty;
public DateTime CreatedTime { get; set; }
}
可用实体特性:
| 特性 | 作用 |
|---|---|
[Tenant("ConfigId")] |
显式指定租户/数据库连接 |
[LogTable] |
自动路由到 ConfigId = "Log" 的日志库 |
[PermissionsTable] |
自动路由到权限库 ConfigId = "PermissionsMain" |
[WmsTable] |
自动路由到 WMS 库 ConfigId = "WmsMain" |
[IncrementTable] |
表结构增量更新时才会处理该实体 |
[IgnoreTable] |
跳过 CodeFirst 建表 |
1.2 使用基础仓储
BaseRepository<T> 已自动注册为 Scoped,直接构造函数注入使用:
public class UserService
{
private readonly IBaseRepository<SysUser> _userRepo;
public UserService(IBaseRepository<SysUser> userRepo)
{
_userRepo = userRepo;
}
// 新增
public async Task AddUser(SysUser user)
{
user.Id = YitIdHelper.NextId(); // 雪花Id(模块已自动配置 WorkerId)
await _userRepo.InsertAsync(user);
}
// 按Id查询
public Task<SysUser?> GetById(long id)
{
return _userRepo.GetByIdAsync(id);
}
// 条件查询
public Task<List<SysUser>> GetByName(string name)
{
return _userRepo.GetListAsync(u => u.UserName.Contains(name));
}
// 更新
public Task Update(SysUser user)
{
return _userRepo.UpdateAsync(user);
}
// 删除
public Task Delete(long id)
{
return _userRepo.DeleteByIdAsync(id);
}
// ======== 分页查询 ========
public Task<BasePageInfoDto<SysUser>> GetPage(int current, int size)
{
return _userRepo.QueryAll(
filterCondition: u => u.Id > 0,
orderByExpression: u => u.CreatedTime,
current: current,
size: size,
isAsc: false);
}
// 投影分页(只取需要的列)
public Task<BasePageInfoDto<UserDto>> GetPageDto(int current, int size)
{
return _userRepo.PageQueryAsync<SysUser, UserDto>(
// 注意:此处应使用方法自带的selector参数,而不是嵌套Select
// 正确的用法见下一节
filterCondition: null,
current, size,
orderByExpression: u => u.Id,
isAsc: true);
}
}
1.3 使用 SqlSugarSetup.Tenant 原生操作
多库场景下可直接使用静态租户对象:
var db = SqlSugarSetup.Tenant.GetConnectionScope("Main");
// 原生 SqlSugar 语法
var list = db.Queryable<SysUser>()
.Where(u => u.UserName == "admin")
.ToList();
// 事务
SqlSugarSetup.Tenant.BeginTran();
// ... 业务操作
SqlSugarSetup.Tenant.CommitTran();
二、缓存操作(ICaching)
2.1 启用配置
| Redis:Enable | 行为 |
|---|---|
true |
使用 FreeRedis 分布式缓存 |
false |
使用 IMemoryCache / IDistributedMemoryCache 本地内存缓存 |
2.2 使用方式
ICaching 已注册为 Singleton:
public class ProductService
{
private readonly ICaching _cache;
public ProductService(ICaching cache)
{
_cache = cache;
}
public async Task Demo()
{
// ===== 写缓存 =====
await _cache.SetValueAsync("product:1", new Product { Id = 1, Name = "Test" },
expirationTime: TimeSpan.FromMinutes(30));
// 永久缓存
await _cache.SetPermanentAsync("sys:dict", dictList);
// 字符串
await _cache.SetStringAsync("key", "value", TimeSpan.FromHours(1));
// ===== 读缓存 =====
var product = await _cache.GetValueAsync<Product>("product:1");
var str = await _cache.GetValueAsync("key");
var exists = await _cache.CacheIsExistsAsync("product:1");
// ===== 删缓存 =====
await _cache.RemoveAsync("product:1");
await _cache.DeleteByPatternAsync("product:*"); // 按模式删除(Redis下才高效)
await _cache.RemoveAllAsync();
}
}
三、模块化与自动依赖注入
3.1 模块依赖
宿主项目可以像 BaseModule 一样定义自己的模块,并通过 [InjectModule] 声明依赖:
using IMBaseModule.Modules;
using IMBaseModule.Attributes.ModuleAttributes;
// 业务模块依赖基础模块
[InjectModule<BaseModule>] // 自动先初始化 BaseModule
public class BusinessModule : IBaseModule
{
public void DependencyInjectionServices(DiServiceContext context)
{
// 注册本模块自定义服务
context.Services.AddScoped<IUserService, UserService>();
context.Services.AddCacheSetup(); // 如果业务模块也需要 Redis 缓存显式注册
}
}
Program.cs 中注册:
builder.Services.AddModule<BusinessModule>(); // 只注册最上层业务模块即可
模块依赖树会自动检测循环引用并抛出异常
3.2 自动注入服务([InjectOn] 特性)
无需手动写 services.AddXXX,在实现类上打特性即可被模块启动时扫描并注册:
using IMBaseModule.Attributes.ModuleAttributes;
// 默认:Transient、按接口注入
[InjectOn]
public class UserService : IUserService
{
// ...
}
// 自定义:Scoped、注入自身+所有接口
[InjectOn(ServiceLifetime.Scoped, InjectScheme.Any, Own = true)]
public class OrderService : IOrderService, IDomainService
{
// ...
}
// 只注入特定接口/类型
[InjectOn(ServiceLifetime.Singleton, InjectScheme.Some,
ServicesType = new[] { typeof(ICacheService) })]
public class CacheService : ICacheService, IAnotherInterface
{
// ...
}
InjectScheme 枚举:
| 值 | 说明 |
|---|---|
OnlyInterfaces |
默认,注册该类的所有接口(排除 IDisposable) |
OnlyBaseClass |
只注册父类 |
Any |
同时注册接口和父类 |
Some |
只注册 ServicesType 指定的类型数组 |
None |
不按接口/父类注入(配合 Own = true 仅注入自身) |
四、服务定位器 App
在不方便构造函数注入的场景(静态类、启动早期、实体工厂等)使用:
using IMBaseModule.Apps;
// 获取服务(单例/Scoped都会自动解析HttpContext或新建Scope)
var userRepo = App.GetService<IBaseRepository<SysUser>>();
var caching = App.GetService<ICaching>();
// 读取配置(按 IOptions 约定的类名去 Section)
var redisOpt = App.GetConfig<RedisOptions>(); // 读 "Redis" 节
var dbOpt = App.GetConfig<DbConnectionOption>(); // 读 "DbConnection" 节
// 当前用户(需宿主实现并注册 IAppUser)
var userName = App.User.Name;
var userId = App.User.Id;
// 配置根对象
var conn = App.Configuration.GetConnectionString("Default");
// 环境
bool isDev = App.HostEnvironment.IsDevelopment();
string root = App.WebHostEnvironment.WebRootPath;
⚠️
App.GetService<T>()要在WebApplication.Build()之后调用,或传参mustBuild: false允许启动阶段临时构建。必须在app.Run()前设置App.IsRun = true
五、API 统一返回
using IMBaseModule.ApiResults;
using IMBaseModule.ApiController;
// 继承 BaseApiController 即可使用统一封装
[ApiRoutePrefix("api/[controller]")]
public class UserController : BaseApiController
{
private readonly IUserService _userService;
public UserController(IUserService userService)
{
_userService = userService;
}
[HttpGet("{id}")]
public async Task<ApiResult> Get(long id)
{
var user = await _userService.GetById(id);
return user == null
? ApiResultHelper.NotFound("用户不存在")
: ApiResultHelper.Success(user);
}
[HttpGet("page")]
public async Task<ApiResult> Page(int current = 1, int size = 10)
{
var page = await _userService.GetPage(current, size);
return ApiResultHelper.Success(page.Data, page.TotalCount);
}
[HttpPost]
public async Task<ApiResult> Create([FromBody] SysUser user)
{
await _userService.AddUser(user);
return ApiResultHelper.Success("创建成功");
}
}
ApiResult 结构:
{
"code": 200,
"message": "操作成功",
"data": { },
"dataCount": 100,
"result": 1
}
| result | 含义 |
|---|---|
1 |
成功 |
0 |
失败 |
2 |
异常 |
六、对象映射
6.1 AutoMapper
IMapper 已注册为 Scoped,构造函数注入即可。模块约定宿主可以在任何程序集放 Profile 子类,AutoMapper 会自动扫描
public class UserProfile : Profile
{
public UserProfile()
{
CreateMap<SysUser, UserDto>();
CreateMap<UserCreateDto, SysUser>();
}
}
// 使用
public class UserService
{
private readonly IMapper _mapper;
public UserService(IMapper mapper) => _mapper = mapper;
public UserDto ToDto(SysUser user) => _mapper.Map<UserDto>(user);
}
6.2 Mapster
项目也引用了 Mapster,可以用 NewAutoMapperHelper.Adapt<TSource, TDestination>():
var dto = user.Adapt<UserDto>();
var list = users.Adapt<List<UserDto>>();
七、Console 彩色输出扩展
using IMBaseModule.Extensions;
using IMBaseModule.Helpers;
// 扩展方法
"操作成功".Success(); // 绿色
"注意一下".Warning(); // 黄色
"发生错误".Error(); // 红色
"普通信息".Info(); // 青色
// 或使用 ConsoleHelper
ConsoleHelper.Success("启动完成");
ConsoleHelper.Warning("配置缺失");
ConsoleHelper.Error("异常:{0}", ex.Message);
ConsoleHelper.WriteLine("【{0:HH:mm:ss}】就绪", DateTime.Now);
八、Helpers 工具类
| 类 | 说明 |
|---|---|
JsonHelper |
Newtonsoft.Json 封装(Serialize / Deserialize ) |
Md5Helper |
MD5 加密(32位/16位) |
AppSettingsHelper |
读取配置节点,支持 AppSettingsHelper.Get<T>("Section") |
ConvertUtil |
类型安全转换(ToInt / ToLong / ToDecimal / ToDateTime 等,异常返回默认值) |
AutoMapperHelper |
旧版 Mapper 静态封装(不推荐,改用注入 IMapper) |
LogHelper |
Serilog 静态日志封装(推荐直接用 Serilog.Log.* 或 DI ILogger<T>) |
九、多库约定 ConfigId
BaseSqlSugarConfig 内置常量:
| 常量 | 值 | 匹配特性 |
|---|---|---|
MainConfigId |
Main |
[Tenant("Main")] 或默认(非其它特性实体) |
LogConfigId |
Log |
[LogTable] |
PermissionsMainConfigId |
PermissionsMain |
[PermissionsTable] |
WmsMainConfigId |
WmsMain |
[WmsTable] |
在 appsettings.json 的 DbConnection:ConnectionStrings 中增加数组元素并把 ConfigId 设为对应值即可
十、常见问题
Q1:为什么 AddModule 时日志没输出?
Serilog 默认的 Log.Logger 在 builder.Build() 之前是 SilentLogger。请按本文 快速开始第 1 步 使用 CreateBootstrapLogger() 提前预热,或在 AddModule 之前手动 Log.Logger = new LoggerConfiguration()...CreateLogger()
Q2:App.GetService<T> 抛 "当前不可用,必须要等到 WebApplication Build后"?
- 需要在
builder.Build()之后调用 - 启动阶段确实要用,传
App.GetService<T>(mustBuild: false),但此时 Scoped 服务只能拿到临时 Scope - 记得在
app.Run()之前设置App.IsRun = true
Q3:CodeFirst 没有自动建表?
检查:
- 实体所在 DLL 文件名必须匹配配置
DbConnection.Model(默认*.Models.dll)或位于入口程序集 - 实体必须有
[SugarTable]特性 - 没有
[IgnoreTable] TableSettings.EnableInitTable = true- 如果加了
[IncrementTable]且EnableIncrementTable = false会被过滤
Q4:Redis 配置关闭(Enable=false)后 ICaching 还能用吗?
可以。内部会自动走 IMemoryCache + IDistributedMemoryCache,接口完全一致。但 DeleteByPattern 在内存模式下效果有限,建议仅用精确 key
依赖项
| 包 | 版本 |
|---|---|
| SqlSugarCore | 5.1.4.217 |
| FreeRedis | 1.5.5 |
| Serilog.AspNetCore | 10.0.0 |
| AutoMapper | 16.1.1 |
| Mapster | 10.0.7 |
| Microsoft.Extensions.Caching.StackExchangeRedis | 10.0.8 |
| Microsoft.Extensions.DependencyInjection | 10.0.8 |
| MiniProfiler.Shared | 4.5.4 |
| Newtonsoft.Json | 13.0.4 |
本模块仅限于 综合管理系统 业务线内使用
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- AutoMapper (>= 16.1.1)
- FreeRedis (>= 1.5.5)
- Mapster (>= 10.0.7)
- Microsoft.Extensions.Caching.StackExchangeRedis (>= 10.0.8)
- Microsoft.Extensions.DependencyInjection (>= 10.0.8)
- MiniProfiler.Shared (>= 4.5.4)
- Newtonsoft.Json (>= 13.0.4)
- Serilog.AspNetCore (>= 10.0.0)
- SqlSugarCore (>= 5.1.4.217)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.