FJY.BaseModule 1.2.1

dotnet add package FJY.BaseModule --version 1.2.1
                    
NuGet\Install-Package FJY.BaseModule -Version 1.2.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FJY.BaseModule" Version="1.2.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FJY.BaseModule" Version="1.2.1" />
                    
Directory.Packages.props
<PackageReference Include="FJY.BaseModule" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FJY.BaseModule --version 1.2.1
                    
#r "nuget: FJY.BaseModule, 1.2.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FJY.BaseModule@1.2.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FJY.BaseModule&version=1.2.1
                    
Install as a Cake Addin
#tool nuget:?package=FJY.BaseModule&version=1.2.1
                    
Install as a Cake Tool

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 没有自动建表?

检查:

  1. 实体所在 DLL 文件名必须匹配配置 DbConnection.Model(默认 *.Models.dll)或位于入口程序集
  2. 实体必须有 [SugarTable] 特性
  3. 没有 [IgnoreTable]
  4. TableSettings.EnableInitTable = true
  5. 如果加了 [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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.1 104 8/25/2026
1.2.0 99 8/25/2026
1.1.0 120 7/28/2026
1.0.7 148 8/23/2025
1.0.6 194 8/9/2025
1.0.5 139 8/3/2025
1.0.4 95 8/2/2025
1.0.2 166 7/30/2025