Files
2026-10-15 16:31:09 +08:00

434 lines
20 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using CloudBuilder.Core.DatabaseAccessor;
using CloudBuilder.Core.DatabaseAccessor.Entity;
using CloudBuilder.Core.Extensions;
using CloudBuilder.Core.Report;
using CloudBuilder.Core.Service;
using CloudBuilder.Security.Data;
using CloudBuilder.Security.Entity;
using CloudBuilder.Security.Policy;
using CloudBuilder.Security.SearchCriteria;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace CloudBuilder.Security.Service
{
public class SecurityGeneratorNumberService : ISecurityGeneratorNumberService
{
private readonly IApplicationService service;
private IRepository<SecurityGeneratorNumberEntity> repository;
private readonly YesNoPolicy yesNoPolicy;
public SecurityGeneratorNumberService(IApplicationService service)
{
this.service = service;
yesNoPolicy = service.ServiceProvider.GetRequiredKeyedService<YesNoPolicy>(nameof(YesNoPolicy));
repository = service.GetRepository<IRepository<SecurityGeneratorNumberEntity>>();
}
[PermissionOperate(name: SecurityPermissionPolicy.SecurityGeneratorNumber, operate: Operate.Find)]
public SecurityGeneratorNumberEntity FindByKey(int key)
{
return repository.DetachedEntities.Where(x => x.GeneratorId == key).FirstOrDefault()!;
}
[PermissionOperate(name: SecurityPermissionPolicy.SecurityGeneratorNumber, operate: Operate.Find)]
public SecurityGeneratorNumberResultData FindBySearchCriteria(SecurityGeneratorNumberSearchCriteria criteria)
{
SecurityGeneratorNumberResultData data = new SecurityGeneratorNumberResultData();
SecurityGeneratorNumberEntity[] list = null;
Int64 count = 0;
string sqlWhere = null;
if (criteria.Filter != null && criteria.Filter.Length > 0)
{
sqlWhere = SearchFilter.ComposeWhereClause(criteria.Filter);
}
SecurityGeneratorNumberEntity entity = SecurityGeneratorNumberEntity.GetInstance(repository.GetDbContext().Database.ProviderName);
string sql = entity.GetSelectTopFrom(criteria.limit, criteria.page, criteria.orderby.StringToDbField(), criteria.orderbytag, sqlWhere);
string err = null;
try
{
list = repository.FromSql(sql).ToArray();
}
catch (System.Exception ex)
{
err = ex.Message;
}
if (list != null && list.Length > 0)
{
sql = entity.GetSelectCountFrom(sqlWhere);
count = Convert.ToInt64(repository.ExecuteScalar(sql));
}
data.Limit = criteria.limit;
data.Page = criteria.page;
data.Datas = list;
data.Total = count;
//data.Sql = sql;
data.ErrorMessage = err;
return data;
}
[PermissionOperate(name: SecurityPermissionPolicy.SecurityGeneratorNumber, operate: Operate.Print)]
public dynamic PrintBySearchCriteria(SecurityGeneratorNumberSearchCriteria criteria)
{
string sqlWhere = string.Empty;
List<TemplateHeader> headers = new List<TemplateHeader>();
Dictionary<string, Dictionary<string, object>> mappings = new Dictionary<string, Dictionary<string, object>>();
Dictionary<string, object> validDic = new Dictionary<string, object>();
foreach (string v in yesNoPolicy.GetValues())
{
validDic.Add(v, yesNoPolicy.ComposeMessage(v, service.Locale));
}
mappings.Add(SecurityGeneratorNumberEntity.REFRESH_PERIOD, validDic);
List<string> searCriteria = criteria.PackSearchCriteriaDisplay(ref headers, ref sqlWhere, mappings);
SecurityGeneratorNumberEntity entity = SecurityGeneratorNumberEntity.GetInstance(repository.GetDbContext().Database.ProviderName);
string sql = entity.GetSelectFrom() + " where 1=1 " + sqlWhere;
SecurityGeneratorNumberEntity[] list = repository.FromSql(sql).ToArray();
return new DataToExcelHelper<SecurityGeneratorNumberEntity>(service).ToExcel(criteria.ReportName, criteria.ReportName, searCriteria, headers.ToArray(), list, mappings);
}
[PermissionOperate(name: SecurityPermissionPolicy.SecurityGeneratorNumber, operate: Operate.Insert)]
[UnitOfWork]
public SecurityGeneratorNumberEntity Insert(SecurityGeneratorNumberEntity data)
{
throw new NotImplementedException();
}
[PermissionOperate(name: SecurityPermissionPolicy.SecurityGeneratorNumber, operate: Operate.Update)]
[UnitOfWork]
public SecurityGeneratorNumberEntity Update(SecurityGeneratorNumberEntity data)
{
SecurityGeneratorNumberEntity entity = repository.Entities.Where(x => x.GeneratorId == data.GeneratorId).FirstOrDefault()!;
if (entity == null)
{
throw new ValidatedException("记录不存在.");
}
entity.RefreshPeriod = data.RefreshPeriod;
entity.NextId = data.NextId;
entity.Format = data.Format;
repository.UpdateNow(entity);
return entity;
}
[PermissionOperate(name: SecurityPermissionPolicy.SecurityGeneratorNumber, operate: Operate.Delete)]
[UnitOfWork]
public void Delete(int key)
{
SecurityGeneratorNumberEntity entity = repository.Entities.Where(x => x.GeneratorId == key).FirstOrDefault()!;
if (entity == null)
{
throw new ValidatedException("记录不存在.");
}
repository.DeleteNow(entity);
}
public string GetNo(string systemId, string subsystemId)
{
return GetNo(systemId, subsystemId, null);
}
public string GetNo(string systemId, string subsystemId, string prefix)
{
return GetNo(systemId, subsystemId, prefix, null, null);
}
//public string GetNo(string systemId, string subsystemId, string prefix, string refreshPeriod, string format)
//{
// SecurityGeneratorNumberEntity number = null;
// if (!string.IsNullOrEmpty(systemId) && !string.IsNullOrEmpty(subsystemId))
// {
// SecurityGeneratorNumberEntity[] tps = repository.Entities.Where(x => x.SystemId == systemId && x.SubsystemId == subsystemId).ToArray();
// if (tps != null && tps.Length > 0 && !string.IsNullOrEmpty(prefix))
// {
// tps = tps.Where(x => x.Prefix == prefix).ToArray();
// }
// if (tps != null && tps.Length > 0) number = tps[0];
// }
// if (number == null)
// {
// number = new SecurityGeneratorNumberEntity();
// number.SystemId = string.IsNullOrEmpty(systemId) ? string.Empty : systemId;
// number.SubsystemId = string.IsNullOrEmpty(subsystemId) ? string.Empty : subsystemId;
// number.Prefix = string.IsNullOrEmpty(prefix) ? string.Empty : prefix;
// number.RefreshPeriod = string.IsNullOrEmpty(refreshPeriod) ? YesNoPolicy.YES : refreshPeriod;
// number.Format = string.IsNullOrEmpty(format) ? "yyMMdd000000" : format;
// number.LastGeneratorDate = DateTime.Now;
// number.NextId = 2;
// repository.InsertNow(number);
// }
// else
// {
// number.NextId += 1;
// number.LastGeneratorDate = DateTime.Now;
// repository.UpdateNow(number);
// }
// format = number.Format;
// if (number.LastGeneratorDate.Date.Subtract(DateTime.Now.Date).TotalDays != 0 && number.RefreshPeriod == YesNoPolicy.YES)
// {
// number.NextId = 1;
// }
// string temp = format.Replace("0", "").Replace("yyyy", DateTime.Now.ToString("yyyy"), StringComparison.OrdinalIgnoreCase).Replace("yy", DateTime.Now.ToString("yy"), StringComparison.OrdinalIgnoreCase)
// .Replace("MM", DateTime.Now.ToString("MM"), StringComparison.OrdinalIgnoreCase).Replace("dd", DateTime.Now.ToString("dd"));
// string zero = format.Replace("y", "").Replace("Y", "").Replace("M", "").Replace("m", "").Replace("d", "").Replace("D", "");
// string code = number.Prefix + temp + string.Format("{0:" + zero + "}", number.NextId - 1);
// Int64 max = Convert.ToInt64(zero.Replace("0", "9"));
// if (max == number.NextId)
// {
// throw new Exception("系统编号已达到最大值.");
// }
// return code;
//}
public string GetNo(string systemId, string subsystemId, string prefix, string refreshPeriod, string format)
{
SecurityGeneratorNumberEntity number = null;
if (!string.IsNullOrEmpty(systemId) && !string.IsNullOrEmpty(subsystemId))
{
// 这里只用来「定位计数行 + 读取 format/prefix 等静态配置」;自增后的取值不再依赖这份内存拷贝,
// 用 DetachedEntities 避免跟踪实例干扰后面回读到的最新 next_id。
SecurityGeneratorNumberEntity[] tps = repository.DetachedEntities.Where(x => x.SystemId == systemId && x.SubsystemId == subsystemId).ToArray();
if (tps != null && tps.Length > 0 && !string.IsNullOrEmpty(prefix))
{
tps = tps.Where(x => x.Prefix == prefix).ToArray();
}
if (tps != null && tps.Length > 0) number = tps[0];
}
// pointer 等价于旧实现里「自增/建行之后、用于发号与最大值校验」的 number.NextId
int pointer;
if (number == null)
{
// 首次建行:NextId=2,本次发号 = pointer-1 = 1(与旧实现完全一致,建行分支不参与原子自增)
number = new SecurityGeneratorNumberEntity();
number.SystemId = string.IsNullOrEmpty(systemId) ? string.Empty : systemId;
number.SubsystemId = string.IsNullOrEmpty(subsystemId) ? string.Empty : subsystemId;
number.Prefix = string.IsNullOrEmpty(prefix) ? string.Empty : prefix;
number.RefreshPeriod = string.IsNullOrEmpty(refreshPeriod) ? YesNoPolicy.YES : refreshPeriod;
number.Format = string.IsNullOrEmpty(format) ? "yyMMdd000000" : format;
number.LastGeneratorDate = DateTime.Now;
number.NextId = 2;
repository.InsertNow(number);
pointer = number.NextId;
}
else
{
// 治本:把旧的「读旧值→内存+1→写回」换成数据库端单条原子 UPDATE(next_id = next_id + 1 由 DB 计算)。
// 并发事务会在该行的排他锁上排队,后到者只能在前者提交后基于新值再加,彻底杜绝丢失更新/重号。
// 走 EF 的 ExecuteSqlRaw 而非裸 Connection,确保自动登记当前 [UnitOfWork] 事务(否则 SqlServer 报"需要事务")。
string providerName = repository.GetDbContext().Database.ProviderName;
string nowFunc = providerName.Contains("MySql") ? "NOW()" : "GETDATE()";
string incrementSql = $"UPDATE {SecurityGeneratorNumberEntity.DB_NAME_SECURITY_GENERATOR_NUMBER} "
+ $"SET {SecurityGeneratorNumberEntity.DB_NEXT_ID} = {SecurityGeneratorNumberEntity.DB_NEXT_ID} + 1, "
+ $"{SecurityGeneratorNumberEntity.DB_LAST_GENERATOR_DATE} = {nowFunc} "
+ $"WHERE {SecurityGeneratorNumberEntity.DB_GENERATOR_ID} = {number.GeneratorId}";
repository.GetDbContext().Database.ExecuteSqlRaw(incrementSql);
// 回读自增后的值:同一事务能读到自己的写;此刻并发事务正阻塞在上面那条 UPDATE 的行锁上
pointer = repository.DetachedEntities.Where(x => x.GeneratorId == number.GeneratorId).Select(x => x.NextId).First();
}
format = number.Format;
string temp = format.Replace("0", "").Replace("yyyy", DateTime.Now.ToString("yyyy"), StringComparison.OrdinalIgnoreCase).Replace("yy", DateTime.Now.ToString("yy"), StringComparison.OrdinalIgnoreCase)
.Replace("MM", DateTime.Now.ToString("MM"), StringComparison.OrdinalIgnoreCase).Replace("dd", DateTime.Now.ToString("dd"));
string zero = format.Replace("y", "").Replace("Y", "").Replace("M", "").Replace("m", "").Replace("d", "").Replace("D", "");
string code = number.Prefix + temp + string.Format("{0:" + zero + "}", pointer - 1);
Int64 max = Convert.ToInt64(zero.Replace("0", "9"));
if (max == pointer)
{
throw new Exception("系统编号已达到最大值.");
}
return code;
}
public int GetNextId(string systemId, string subsystemId)
{
return GetNextId(systemId, subsystemId, null);
}
public int GetNextId(string systemId, string subsystemId, string prefix)
{
return GetNextId(systemId, subsystemId, prefix, null);
}
public int GetNextId(string systemId, string subsystemId, string prefix, string format)
{
return GetNextId(systemId, subsystemId, prefix, format, 1);
}
//public int GetNextId(string systemId, string subsystemId, string prefix, string format, int length)
//{
// SecurityGeneratorNumberEntity number = null;
// if (!string.IsNullOrEmpty(systemId) && !string.IsNullOrEmpty(subsystemId))
// {
// SecurityGeneratorNumberEntity[] tps = repository.DetachedEntities.Where(x => x.SystemId == systemId && x.SubsystemId == subsystemId).ToArray();
// if (tps != null && tps.Length > 0 && !string.IsNullOrEmpty(prefix))
// {
// tps = tps.Where(x => x.Prefix == prefix).ToArray();
// }
// if (tps != null && tps.Length > 0) number = tps[0];
// }
// if (number == null)
// {
// number = new SecurityGeneratorNumberEntity();
// number.SystemId = string.IsNullOrEmpty(systemId) ? string.Empty : systemId;
// number.SubsystemId = string.IsNullOrEmpty(subsystemId) ? string.Empty : subsystemId;
// number.Prefix = string.IsNullOrEmpty(prefix) ? string.Empty : prefix;
// number.Format = string.IsNullOrEmpty(format) ? "yyMMdd000000" : format;
// number.LastGeneratorDate = DateTime.Now;
// number.NextId = 1 + length;
// number.RefreshPeriod = YesNoPolicy.NO;
// repository.Insert(number);
// }
// else
// {
// number.NextId = number.NextId + length;
// if (number.LastGeneratorDate.Date.Subtract(DateTime.Now.Date).TotalDays != 0 && number.RefreshPeriod == YesNoPolicy.YES)
// {
// number.NextId = 1 + length;
// }
// number.LastGeneratorDate = DateTime.Now;
// repository.Update(number);
// }
// string zero = number.Format.Replace("y", "").Replace("Y", "").Replace("M", "").Replace("m", "").Replace("d", "").Replace("D", "");
// Int64 max = Convert.ToInt64(zero.Replace("0", "9"));
// if (max == number.NextId - length)
// {
// throw new Exception("系统编号已达到最大值.");
// }
// return number.NextId - length - 1;
//}
public int GetNextId(string systemId, string subsystemId, string prefix, string format, int length)
{
SecurityGeneratorNumberEntity number = null;
if (!string.IsNullOrEmpty(systemId) && !string.IsNullOrEmpty(subsystemId))
{
SecurityGeneratorNumberEntity[] tps = repository.DetachedEntities.Where(x => x.SystemId == systemId && x.SubsystemId == subsystemId).ToArray();
if (tps != null && tps.Length > 0 && !string.IsNullOrEmpty(prefix))
{
tps = tps.Where(x => x.Prefix == prefix).ToArray();
}
if (tps != null && tps.Length > 0) number = tps[0];
}
// pointer 等价于旧实现里「自增/建行/重置之后、用于发号与最大值校验」的 number.NextId
int pointer;
if (number == null)
{
number = new SecurityGeneratorNumberEntity();
number.SystemId = string.IsNullOrEmpty(systemId) ? string.Empty : systemId;
number.SubsystemId = string.IsNullOrEmpty(subsystemId) ? string.Empty : subsystemId;
number.Prefix = string.IsNullOrEmpty(prefix) ? string.Empty : prefix;
number.Format = string.IsNullOrEmpty(format) ? "yyMMdd000000" : format;
number.LastGeneratorDate = DateTime.Now;
number.NextId = 1 + length;
number.RefreshPeriod = YesNoPolicy.NO;
repository.Insert(number);
pointer = number.NextId;
}
else
{
// 与 GetNo 同款治本:把「读旧值→内存+length→(可能重置)→写回」整体交给数据库端一条原子 UPDATE。
// 注意与 GetNo 的差别:GetNextId 的每日重置不是死代码(它在比较之后才把 last_generator_date 置为 now),
// 所以这里把「跨天且 refresh_period=YES 时重置为 1+length」并入 CASE,靠该行排他锁串行化,
// 后到事务读到的是前者提交后的新值,既杜绝丢失更新,也顺带修好「同一天首个号被并发重复发放」的隐患。
string providerName = repository.GetDbContext().Database.ProviderName;
bool isMySql = providerName.Contains("MySql");
string nowFunc = isMySql ? "NOW()" : "GETDATE()";
string dateChanged = isMySql
? $"DATE({SecurityGeneratorNumberEntity.DB_LAST_GENERATOR_DATE}) <> DATE(NOW())"
: $"CAST({SecurityGeneratorNumberEntity.DB_LAST_GENERATOR_DATE} AS date) <> CAST(GETDATE() AS date)";
string incrementSql = $"UPDATE {SecurityGeneratorNumberEntity.DB_NAME_SECURITY_GENERATOR_NUMBER} "
+ $"SET {SecurityGeneratorNumberEntity.DB_NEXT_ID} = CASE WHEN {SecurityGeneratorNumberEntity.DB_REFRESH_PERIOD} = '{YesNoPolicy.YES}' AND {dateChanged} "
+ $"THEN 1 + {length} ELSE {SecurityGeneratorNumberEntity.DB_NEXT_ID} + {length} END, "
+ $"{SecurityGeneratorNumberEntity.DB_LAST_GENERATOR_DATE} = {nowFunc} "
+ $"WHERE {SecurityGeneratorNumberEntity.DB_GENERATOR_ID} = {number.GeneratorId}";
repository.GetDbContext().Database.ExecuteSqlRaw(incrementSql);
// 回读自增后的值:同一事务能读到自己的写;此刻并发事务正阻塞在上面那条 UPDATE 的行锁上
pointer = repository.DetachedEntities.Where(x => x.GeneratorId == number.GeneratorId).Select(x => x.NextId).First();
}
string zero = number.Format.Replace("y", "").Replace("Y", "").Replace("M", "").Replace("m", "").Replace("d", "").Replace("D", "");
Int64 max = Convert.ToInt64(zero.Replace("0", "9"));
if (max == pointer - length)
{
throw new Exception("系统编号已达到最大值.");
}
return pointer - length - 1;
}
}
}