// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
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;
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)
{
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 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("获取流水号"), 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(SerialTypeEnum? 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().IgnoreTenant().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)
{
// 使用 "插槽名 - 代理方法" 映射表,这样匹配到的插槽才渲染,减少不必要的运算
return new Dictionary>
{
["{yyyy}"] = () => nowDate.Year.ToString(),
["{yy}"] = () => nowDate.Year.ToString()[2..],
["{MM}"] = () => nowDate.Month.ToString("D2"),
["{dd}"] = () => nowDate.Day.ToString("D2"),
["{HH}"] = () => nowDate.Hour.ToString("D2"),
["{mm}"] = () => nowDate.Minute.ToString("D2"),
["{ss}"] = () => nowDate.Second.ToString("D2"),
["{SEQ}"] = () => seq.ToString("D" + max.ToString().Length),
["{TenantId}"] = () => _userManager.TenantId.ToString(),
};
}
}