434 lines
20 KiB
C#
434 lines
20 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|