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
}
}