using Newtonsoft.Json.Linq; using Rhea.Common; using SqlSugar; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Data; using System.Data.Entity; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Tiger.IBusiness; using Tiger.Model; namespace Tiger.Business.DbCache { /// /// 条码规则数据库数据缓存 /// public class CodeRuleCache : ICodeRuleCache { #region Variables private WhileThread AutoUpdateThread; private DateTime LastUpdateTime = DateTime.MinValue; #endregion #region Propertys public string Id { get; set; } = Guid.NewGuid().ToString("N"); public string Tag { get; set; } = "CodeRuleCache"; public string Name { get; set; } = "CodeRuleCache"; public bool IsRunning { get; set; } private List _Rules = new(); public List Rules { get { Update(); return _Rules; } set => _Rules = value; } public BAS_CODE_RULE this[string ruleCode] { get { Update(); return _Rules.FirstOrDefault(q => q.RULE_CODE == ruleCode); } } #endregion #region Functions /// /// 启动自动更新缓存 /// public void Start() { try { AutoUpdateThread = new(AutoUpdate); AutoUpdateThread.Start(); Logger.Default.Info("Start CodeRule Cache Auto Update Thread"); } catch (System.Exception ex) { Logger.Default.Fatal(ex, "Start CodeRule Cache Auto Update Thread Exception"); } } /// /// 关闭自动更新缓存 /// public void Stop() { try { AutoUpdateThread?.Stop(); Logger.Console.Info("Stop CodeRule Cache Auto Update Thread"); } catch (System.Exception ex) { Logger.Console.Fatal(ex, "Stop CodeRule Cache Auto Update Thread Exception"); } } /// /// 更新数据缓存 /// public void Update() { var lastUpdate = Biz.Db.Queryable().Max(q => q.UPDATE_TIME); if (LastUpdateTime < lastUpdate) { _Rules = Biz.Db.Queryable().IncludesAllFirstLayer().ToList(); LastUpdateTime = lastUpdate; Logger.Console.Info($"Get CodeRule successful, total {Rules.Count}, last update time is {LastUpdateTime:yyyy-MM-dd HH:mm:ss}"); } } /// /// 自动更新 /// private void AutoUpdate() { try { Update(); } catch (System.Exception ex) { Logger.Console.Fatal(ex, "CodeRule Cache Auto Update Exception"); LastUpdateTime = DateTime.MinValue; } //休眠30分钟 Thread.Sleep(30 * 60 * 1000);// } /// /// 使用指定规则验证传入编码 /// /// 需要验证的条码 /// 指定用于验证的条码规则 /// Result.Data is RuleVerifier public Result Verify(string code, BAS_CODE_RULE rule) { Result result = new(Result.Flags.Success); try { var rv = new RuleVerifier(rule); rv.Verify(code); result.Flag = rv.IsMatch ? Result.Flags.Success : Result.Flags.Failed; result.Data = rv; } catch (System.Exception ex) { result.CatchExceptionWithLog(ex, Biz.L("BAS.CodeRule.VerifyException", code)); } return result; } /// /// 使用符合传入条件的规则验证传入编码 /// /// 需要验证的条码 /// 用于验证条码规则的过滤条件 /// Result.Data is List<RuleVerifier> public Result Verify(string code, Func predicate = null) { Result result = new(Result.Flags.Success); try { var cv = new CodeVerifier(code, predicate); cv.DoVerify(); result.Flag = cv.IsMatch ? Result.Flags.Success : Result.Flags.Failed; result.Data = cv.Matches; } catch (System.Exception ex) { result.CatchExceptionWithLog(ex, Biz.L("BAS.CodeRule.VerifyException", code)); } return result; } /// /// 条码流水码取号 /// /// /// /// 取号的流水号生成历史ID /// 是否尝试取号,不保存到数据库 /// private int FetchSerialNo(string key, BAS_CODE_DTL rule, string hisID, bool isTry = false) { var db = Biz.Db; var failCount = 0; while (true) { try { var next = 0; db.BeginTran(); //查询条件记录后加行等待锁 var serialGen = db.Queryable().TranLock(DbLockType.Wait).Where(q => q.RULE_DTL_ID == rule.ID && q.SERIAL_KEY == key).First(); if (serialGen.IsNullOrEmpty()) { serialGen = new() { RULE_ID = rule.RULE_ID, RULE_DTL_ID = rule.ID, SERIAL_KEY = key, SERIAL_DATE = DateTime.Now, SERIAL_VALUE = rule.SERIAL_MIN, LAST_GEN_DATE = DateTime.Now, LAST_HIS_ID = hisID, }; if (!isTry) { db.Insertable(serialGen).ExecuteCommand(); } next = serialGen.SERIAL_VALUE; //Console.WriteLine($"{hisID}: {DateTime.Now:HH:mm:ss.fff} > 第一次取号成功[{next}],休息5000ms"); //Thread.Sleep(5000); } else { //流水号重置 switch (rule.SERIAL_RESET.GetEnum()) { case BAS_CODE_DTL.SERIAL_RESETs.Default: if (serialGen.SERIAL_VALUE + 1 > rule.SERIAL_MAX) { serialGen.SERIAL_VALUE = rule.SERIAL_MIN - 1; serialGen.SERIAL_DATE = DateTime.Now; } break; case BAS_CODE_DTL.SERIAL_RESETs.Year: if (serialGen.SERIAL_DATE.Year != DateTime.Now.Year) { serialGen.SERIAL_VALUE = rule.SERIAL_MIN - 1; serialGen.SERIAL_DATE = DateTime.Now; } break; case BAS_CODE_DTL.SERIAL_RESETs.Month: if (serialGen.SERIAL_DATE.Month != DateTime.Now.Month) { serialGen.SERIAL_VALUE = rule.SERIAL_MIN - 1; serialGen.SERIAL_DATE = DateTime.Now; } break; case BAS_CODE_DTL.SERIAL_RESETs.Week: if ((serialGen.SERIAL_DATE.Date.DayOfWeek.GetValue() > 0 && DateTime.Now.Date.DayOfWeek.GetValue() == 0) || (DateTime.Now.Date - serialGen.SERIAL_DATE.Date).TotalDays >= 7) { serialGen.SERIAL_VALUE = rule.SERIAL_MIN - 1; serialGen.SERIAL_DATE = DateTime.Now; } break; case BAS_CODE_DTL.SERIAL_RESETs.Day: if (serialGen.SERIAL_DATE.Date != DateTime.Now.Date) { serialGen.SERIAL_VALUE = rule.SERIAL_MIN - 1; serialGen.SERIAL_DATE = DateTime.Now; } break; case BAS_CODE_DTL.SERIAL_RESETs.NotReset: break; } //流水号取号 if (serialGen.SERIAL_VALUE + 1 <= rule.SERIAL_MAX) { serialGen.SERIAL_VALUE++; serialGen.LAST_GEN_DATE = DateTime.Now; serialGen.LAST_HIS_ID = hisID; } else { throw new Exception($"The serial number has reached its max value[{rule.SERIAL_MAX}], waiting for reset"); } if (!isTry) { db.Updateable(serialGen, "system").UpdateColumns(q => new { q.SERIAL_VALUE, q.SERIAL_DATE, q.LAST_GEN_DATE, q.LAST_HIS_ID }).ExecuteCommand(); } next = serialGen.SERIAL_VALUE; //Debug.WriteLine($"{hisID}: {DateTime.Now:HH:mm:ss.fff} > 继续取号成功[{next}],休息1000ms"); //Thread.Sleep(1000); } db.CommitTran(); return next; } catch (Exception ex) { db.RollbackTran(); //Debug.WriteLine($"{hisID}: {DateTime.Now:HH:mm:ss.fff} > 第{failCount + 1}次尝试取号异常:{ex.Message}"); if (++failCount >= 10) { throw; } Thread.Sleep(500); } } } /// /// 生成条码 /// /// /// /// public Result Generate(string ruleCode, params object?[] args) { Result result = new(Result.Flags.Success); try { var rule = Biz.Db.Queryable().Where(q => q.RULE_CODE == ruleCode).IncludesAllFirstLayer().First(); if (rule.IsNullOrEmpty()) { result.Flag = Result.Flags.Failed; result.LocaleMsg = new("BAS.CodeRule.GenerateFailed.NotFound", ruleCode, string.Join(", ", args)); } else { var argsIndex = 0; var his = new BAS_CODE_HIS(); foreach (var item in rule.Details.OrderBy(q => q.RULE_SEQ)) { switch (item.DATA_TYPE.GetEnum()) { case BAS_CODE_DTL.DATA_TYPEs.Character: case BAS_CODE_DTL.DATA_TYPEs.Numeric: case BAS_CODE_DTL.DATA_TYPEs.FixedCode: case BAS_CODE_DTL.DATA_TYPEs.DbCheck: case BAS_CODE_DTL.DATA_TYPEs.NoCheck: if (argsIndex >= args.Length) { result.Flag = Result.Flags.Failed; result.LocaleMsg = new("BAS.CodeRule.GenerateFailed.NotEnoughArgs", ruleCode, string.Join(", ", args), item.RULE_SEQ); return result; } var value = args[argsIndex++].ToString();//.Substring(0, item.CHECK_LENGTH); if (item.Regex.IsMatch(value) && RuleVerifier.CheckDB(value, item)) { for (int i = 0; i < item.REPEAT_TIMES; i++) { item.CodeValue += value; } } else { result.Flag = Result.Flags.Failed; result.LocaleMsg = new("BAS.CodeRule.GenerateFailed.NotPassValidation", ruleCode, string.Join(", ", args), value, item.RULE_SEQ); return result; } break; case BAS_CODE_DTL.DATA_TYPEs.DateCode: case BAS_CODE_DTL.DATA_TYPEs.TimeCode: value = ""; switch (item.DATA_CASE) { case "yyyy": case "yy": case "dd": case "ss": case "fff": value = DateTime.Now.ToString(item.DATA_CASE); break; case "q": value = Math.Ceiling(DateTime.Now.Month * 1.0 / 3).ToString(); break; case "hh": case "mm": value = DateTime.Now.ToString(item.DATA_CASE.ToUpper()); break; case "m": var month = DateTime.Now.Month; value = month == 10 ? "A" : (month == 11 ? "B" : (month == 12 ? "C" : DateTime.Now.Month.ToString())); break; case "ww": value = Math.Ceiling((DateTime.Now.DayOfYear + new DateTime(DateTime.Now.Year, 1, 1).DayOfWeek.ToInt32()) * 1.0 / 7).ToString("00"); break; case "dy": value = DateTime.Now.DayOfWeek.ToInt32().ToString(); break; case "mi": value = DateTime.Now.ToString("mm"); break; } for (int i = 0; i < item.REPEAT_TIMES; i++) { item.CodeValue += value; } break; } } //流水号生成 var serialRules = rule.Details.Where(q => q.DATA_TYPE == BAS_CODE_DTL.DATA_TYPEs.SerialCode.GetValue()).OrderBy(q => q.RULE_SEQ); var serialKey = string.Join("|", rule.Details.Where(q => q.DATA_TYPE == BAS_CODE_DTL.DATA_TYPEs.DbCheck.GetValue()).Select(q => q.CodeValue)); foreach (var item in serialRules) { var serialGen = FetchSerialNo(serialKey, item, his.ID); var value = serialGen.ToString().PadLeft(item.CHECK_LENGTH, '0'); for (int i = 0; i < item.REPEAT_TIMES; i++) { item.CodeValue += value; } } his.RULE_ID = rule.ID; his.GEN_CODE = string.Concat(rule.Details.OrderBy(q => q.RULE_SEQ).Select(q => q.CodeValue)); his.GEN_DATE = DateTime.Now; try { Biz.Db.Insertable(his).ExecuteCommand(); } catch (System.Exception ex) { throw new DataException(his.GEN_CODE, ex); } result.Data = his.GEN_CODE; } } catch (DataException ex) { result.CatchExceptionWithLog(ex.InnerException, Biz.L("BAS.CodeRule.GenerateSnHisException", ruleCode, ex.Message)); } catch (System.Exception ex) { result.CatchExceptionWithLog(ex, Biz.L("BAS.CodeRule.GenerateException", ruleCode, string.Join(", ", args))); } return result; } /// /// 尝试生成条码,不保存到数据库 /// /// /// /// public Result TryGenerate(string ruleCode, params object?[] args) { Result result = new(Result.Flags.Success); try { var rule = Biz.Db.Queryable().Where(q => q.RULE_CODE == ruleCode).IncludesAllFirstLayer().First(); if (rule.IsNullOrEmpty()) { result.Flag = Result.Flags.Failed; result.LocaleMsg = new("BAS.CodeRule.GenerateFailed.NotFound", ruleCode, string.Join(", ", args)); } else { var his = new BAS_CODE_HIS(); var argsIndex = 0; foreach (var item in rule.Details.OrderBy(q => q.RULE_SEQ)) { switch (item.DATA_TYPE.GetEnum()) { case BAS_CODE_DTL.DATA_TYPEs.Character: case BAS_CODE_DTL.DATA_TYPEs.Numeric: case BAS_CODE_DTL.DATA_TYPEs.FixedCode: case BAS_CODE_DTL.DATA_TYPEs.DbCheck: case BAS_CODE_DTL.DATA_TYPEs.NoCheck: if (argsIndex >= args.Length) { result.Flag = Result.Flags.Failed; result.LocaleMsg = new("BAS.CodeRule.GenerateFailed.NotEnoughArgs", ruleCode, string.Join(", ", args), item.RULE_SEQ); return result; } var value = args[argsIndex++].ToString();//.Substring(0, item.CHECK_LENGTH); if (item.Regex.IsMatch(value) && RuleVerifier.CheckDB(value, item)) { for (int i = 0; i < item.REPEAT_TIMES; i++) { item.CodeValue += value; } } else { result.Flag = Result.Flags.Failed; result.LocaleMsg = new("BAS.CodeRule.GenerateFailed.NotPassValidation", ruleCode, string.Join(", ", args), value, item.RULE_SEQ); return result; } break; case BAS_CODE_DTL.DATA_TYPEs.DateCode: case BAS_CODE_DTL.DATA_TYPEs.TimeCode: value = ""; switch (item.DATA_CASE) { case "yyyy": case "yy": case "dd": case "ss": case "fff": value = DateTime.Now.ToString(item.DATA_CASE); break; case "q": value = Math.Ceiling(DateTime.Now.Month * 1.0 / 3).ToString(); break; case "hh": case "mm": value = DateTime.Now.ToString(item.DATA_CASE.ToUpper()); break; case "m": var month = DateTime.Now.Month; value = month == 10 ? "A" : (month == 11 ? "B" : (month == 12 ? "C" : DateTime.Now.Month.ToString())); break; case "ww": value = Math.Ceiling((DateTime.Now.DayOfYear + new DateTime(DateTime.Now.Year, 1, 1).DayOfWeek.ToInt32()) * 1.0 / 7).ToString("00"); break; case "dy": value = DateTime.Now.DayOfWeek.ToInt32().ToString(); break; case "mi": value = DateTime.Now.ToString("mm"); break; } for (int i = 0; i < item.REPEAT_TIMES; i++) { item.CodeValue += value; } break; } } //流水号生成 var serialRules = rule.Details.Where(q => q.DATA_TYPE == BAS_CODE_DTL.DATA_TYPEs.SerialCode.GetValue()).OrderBy(q => q.RULE_SEQ); var serialKey = string.Join("|", rule.Details.Where(q => q.DATA_TYPE == BAS_CODE_DTL.DATA_TYPEs.DbCheck.GetValue()).Select(q => q.CodeValue)); foreach (var item in serialRules) { var serialGen = FetchSerialNo(serialKey, item, his.ID, true); var value = serialGen.ToString().PadLeft(item.CHECK_LENGTH, '0'); for (int i = 0; i < item.REPEAT_TIMES; i++) { item.CodeValue += value; } } his.RULE_ID = rule.ID; his.GEN_CODE = string.Concat(rule.Details.OrderBy(q => q.RULE_SEQ).Select(q => q.CodeValue)); his.GEN_DATE = DateTime.Now; result.Data = his.GEN_CODE; } } catch (System.Exception ex) { result.CatchExceptionWithLog(ex, Biz.L("BAS.CodeRule.GenerateException", ruleCode, string.Join(", ", args))); } return result; } #endregion } }