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 repository; private readonly YesNoPolicy yesNoPolicy; public SecurityGeneratorNumberService(IApplicationService service) { this.service = service; yesNoPolicy = service.ServiceProvider.GetRequiredKeyedService(nameof(YesNoPolicy)); repository = service.GetRepository>(); } [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 headers = new List(); Dictionary> mappings = new Dictionary>(); Dictionary validDic = new Dictionary(); foreach (string v in yesNoPolicy.GetValues()) { validDic.Add(v, yesNoPolicy.ComposeMessage(v, service.Locale)); } mappings.Add(SecurityGeneratorNumberEntity.REFRESH_PERIOD, validDic); List 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(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; } } }