// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 // // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! using Microsoft.AspNetCore.Mvc.Rendering; namespace Admin.NET.Core.Service; /// /// 系统流水号服务 🧩 /// [ApiDescriptionSettings(Order = 100, Description = "系统流水号")] public class SysSerialService : IDynamicApiController, ITransient { private readonly SqlSugarRepository _sysSerialRep; private readonly SysCacheService _sysCacheService; private readonly UserManager _userManager; private readonly ISqlSugarClient _db; /// /// 因为缓存的是代理,无法序列化,所以只能用字典缓存 /// private readonly ConcurrentDictionary>> _slotTempMap = new(); public SysSerialService(SqlSugarRepository sysSerialRep, SysCacheService sysCacheService, ISqlSugarClient db, UserManager userManager) { _db = db; _userManager = userManager; _sysSerialRep = sysSerialRep; _sysCacheService = sysCacheService; } /// /// 分页查询本地序列 🔖 /// /// /// [DisplayName("分页查询本地序列")] [ApiDescriptionSettings(Name = "Page"), HttpPost] public async Task> Page(PageSerialInput input) { input.Keyword = input.Keyword?.Trim(); var query = _sysSerialRep.AsQueryable() .Where(u => u.TenantId == _userManager.TenantId) .WhereIF(input.Type != null, u => u.Type == input.Type) .WhereIF(input.Status != null, u => u.Status == input.Status) .Select(); return await query.MergeTable().OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize); } /// /// 获取本地序列详情 ℹ️ /// /// /// [DisplayName("获取本地序列详情")] [ApiDescriptionSettings(Name = "Detail"), HttpGet] public async Task Detail([FromQuery] BaseIdInput input) { return await _sysSerialRep.GetFirstAsync(u => u.Id == input.Id); } /// /// 增加本地序列 ➕ /// /// /// [DisplayName("增加本地序列")] [ApiDescriptionSettings(Name = "Add"), HttpPost] public async Task Add(AddSerialInput input) { var typeList = GetTypeList(); if (typeList.All(u => u.Value != input.Type)) throw Oops.Oh(ErrorCodeEnum.S0007, input.Type); if (await _sysSerialRep.IsAnyAsync(u => u.Type == input.Type && u.TenantId == _userManager.TenantId)) throw Oops.Oh(ErrorCodeEnum.D1006); var entity = input.Adapt(); entity.Expy = DateTime.Now; entity.Seq = 1; return await _sysSerialRep.InsertAsync(entity) ? entity.Id : default; } /// /// 更新本地序列 ✏️ /// /// /// [DisplayName("更新本地序列")] [ApiDescriptionSettings(Name = "Update"), HttpPost] public async Task Update(UpdateSerialInput input) { var typeList = GetTypeList(); if (typeList.All(u => u.Value != input.Type)) throw Oops.Oh(ErrorCodeEnum.S0007, input.Type); var seq = await _sysSerialRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002); if (await _sysSerialRep.IsAnyAsync(u => u.Type == input.Type && u.TenantId == _userManager.TenantId && input.Id != seq.Id)) throw Oops.Oh(ErrorCodeEnum.D1006); var entity = input.Adapt(); await _sysSerialRep.AsUpdateable(entity).IgnoreColumns(u => new { u.Expy, u.Seq }).ExecuteCommandAsync(); } /// /// 删除本地序列 ❌ /// /// /// [DisplayName("删除本地序列")] [ApiDescriptionSettings(Name = "Delete"), HttpPost] public async Task Delete(BaseIdInput input) { var entity = await _sysSerialRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002); await _sysSerialRep.DeleteAsync(entity); } /// /// 设置本地序列状态 🚫 /// /// /// [DisplayName("设置本地序列状态")] [ApiDescriptionSettings(Name = "SetStatus"), HttpPost] public async Task SetStatus(BaseStatusInput input) { await _sysSerialRep.AsUpdateable().SetColumns(u => u.Status, input.Status).Where(u => u.Id == input.Id).ExecuteCommandAsync(); } /// /// 获取流水号类型 /// /// [DisplayName("获取流水号类型"), HttpGet] public List GetTypeList() { var typeList = _sysCacheService.Get>(CacheConst.KeySysSerialType); if (typeList != null) return typeList; typeList = App.EffectiveTypes .Where(u => !u.IsInterface && u.IsClass && u.HasImplementedRawGeneric(typeof(ISerialTypeProvider))) .SelectMany(u => u.GetFields()) .Select(u => new SelectListItem(u.GetCustomAttribute()?.Description ?? u.Name, u.Name)) .ToList(); if (typeList.GroupBy(u => u.Value).Any(u => u.Count() > 1)) throw Oops.Oh(ErrorCodeEnum.S0008); _sysCacheService.Set(CacheConst.KeySysSerialType, typeList); return typeList; } /// /// 获取流水号 /// /// [DisplayName("获取流水号"), HttpPost] public async Task NextSeqNo(GetNextSeqInput input) { return await NextSeqNo(input.Type); } /// /// 获取全局流水号 /// /// [DisplayName("获取全局流水号"), HttpPost] public async Task GlobalNextSeqNo(GetNextSeqInput input) { return await NextSeqNo(input.Type, true); } /// /// 预览流水号 /// /// [DisplayName("预览流水号"), HttpPost] public string Preview(PreviewSysSerialInput input) { return FormatSeqNo(new() { Formater = input.Formater, Seq = input.Seq, Max = input.Max, }, DateTime.Now); } /// /// 获取插槽列表 /// /// [DisplayName("获取插槽列表")] public List GetSlotList() { return GetSlotMap(0, 1, DateTime.Now).Select(k => k.Key).ToList(); } /// /// 获取流水号 /// /// 类型 /// 是否是全局唯一流水号 /// 是否开启事务 /// [NonAction] public async Task NextSeqNo(string type, bool isGlobal = false, bool isTran = true) { // 获取租户Id, 以及分布式锁缓存键名 long? tenantId = isGlobal ? SqlSugarConst.DefaultTenantId : _userManager.TenantId; string cacheKey = $"{CacheConst.KeySerialLock}:{tenantId}:{type}"; // 获取分布式锁 using var distributedLock = _sysCacheService.BeginCacheLock(cacheKey, 1000) ?? throw Oops.Oh(ErrorCodeEnum.S0001); // 记录流水号生成耗时 var stopWatch = Stopwatch.StartNew(); try { // 查询系统流水号配置 var seq = await _sysSerialRep.AsQueryable().ClearFilter().FirstAsync(u => u.Type == type && u.Status == StatusEnum.Enable && u.TenantId == tenantId) ?? throw Oops.Oh(ErrorCodeEnum.S0002); // 尝试重置系统流水号配置 var nowDate = DateTime.Now; TryRestSeqNo(seq, nowDate); // 检查是否超出最大值 seq.Seq++; if (seq.Seq > seq.Max) throw Oops.Oh(ErrorCodeEnum.S0004); // 开启事务 using var uow = _db.CreateContext(isTran); // 更新失败,抛出异常 await _db.RunWithoutFilter(async () => { if (!await _sysSerialRep.UpdateAsync(u => new() { Expy = seq.Expy, Seq = seq.Seq }, u => u.Id == seq.Id)) throw Oops.Oh(ErrorCodeEnum.S0005); }); uow.Commit(); return FormatSeqNo(seq, nowDate); } finally { stopWatch.Stop(); Console.WriteLine($"流水号【{type.GetDescription()}】生成耗时:{stopWatch.ElapsedMilliseconds}毫秒"); } } /// /// 重置系统流水号配置 /// /// /// private void TryRestSeqNo(SysSerial seq, DateTime nowDate) { switch (seq.ResetInterval) { case ResetIntervalEnum.Never: break; case ResetIntervalEnum.Day: if (nowDate.Date != seq.Expy.Date) { seq.Expy = nowDate.Date; seq.Seq = seq.Min; } break; case ResetIntervalEnum.Month: if (nowDate.Month != seq.Expy.Month || nowDate.Year != seq.Expy.Year) { seq.Expy = nowDate.Date; seq.Seq = seq.Min; } break; case ResetIntervalEnum.Year: if (nowDate.Year != seq.Expy.Year) { seq.Expy = nowDate.Date; seq.Seq = seq.Min; } break; default: throw Oops.Oh(ErrorCodeEnum.S0006); } } /// /// 按格式渲染流水号 /// /// /// /// private string FormatSeqNo(SysSerial seq, DateTime nowDate) { var replacements = GetSlotMap(seq.Seq, seq.Max, nowDate); return Regex.Replace(seq.Formater, @"\{.*?\}", match => { string key = match.Value; if (replacements.TryGetValue(key, out var valueFunc)) return valueFunc(); throw Oops.Oh("无效插槽: {0}", key); }); } /// /// 获取插槽映射表 /// /// [NonAction] private Dictionary> GetSlotMap(long seq, long max, DateTime nowDate) { // 获取插槽逻辑模板 var slotLogic = _slotTempMap.GetOrAdd(CacheConst.KeySysSerialSlot, _ => { // 默认插槽逻辑 var logicMap = new Dictionary> { // 序号格式化 ["{SEQ}"] = (s, m, _, _) => s.ToString($"D{m.ToString().Length}"), // 时间相关(依赖 nowDate) ["{yyyy}"] = (_, _, d, _) => d.Year.ToString(), ["{yy}"] = (_, _, d, _) => d.Year.ToString().Substring(2), ["{MM}"] = (_, _, d, _) => d.Month.ToString("D2"), ["{dd}"] = (_, _, d, _) => d.Day.ToString("D2"), ["{HH}"] = (_, _, d, _) => d.Hour.ToString("D2"), ["{mm}"] = (_, _, d, _) => d.Minute.ToString("D2"), ["{ss}"] = (_, _, d, _) => d.Second.ToString("D2"), ["{TenantId}"] = (_, _, _, u) => u?.TenantId.ToString(), ["{UserId}"] = (_, _, _, u) => u?.UserId.ToString(), ["{OrgId}"] = (_, _, _, u) => u?.OrgId.ToString(), ["{OrgType}"] = (_, _, _, u) => u?.OrgType, }; // 扩展插槽提供者 var providers = App.EffectiveTypes .Where(u => !u.IsInterface && u.IsClass && u.HasImplementedRawGeneric(typeof(ISerialSlotProvider))) .Select(u => (ISerialSlotProvider)Activator.CreateInstance(u)) .ToList(); foreach (var provider in providers) { var providerLogic = provider.GetSlotMap(); foreach (var kv in providerLogic) logicMap[kv.Key] = kv.Value; } return logicMap; }); // 将逻辑模板绑定到当前参数,生成最终的 Func var result = new Dictionary>(); foreach (var kv in slotLogic) result.TryAdd(kv.Key, () => kv.Value(seq, max, nowDate, _userManager)); return result; } }