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
|
{
|
/// <summary>
|
/// 条码规则数据库数据缓存
|
/// </summary>
|
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<BAS_CODE_RULE> _Rules = new();
|
public List<BAS_CODE_RULE> 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
|
/// <summary>
|
/// 启动自动更新缓存
|
/// </summary>
|
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");
|
}
|
}
|
|
/// <summary>
|
/// 关闭自动更新缓存
|
/// </summary>
|
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");
|
}
|
}
|
|
/// <summary>
|
/// 更新数据缓存
|
/// </summary>
|
public void Update()
|
{
|
var lastUpdate = Biz.Db.Queryable<BAS_CODE_DTL>().Max(q => q.UPDATE_TIME);
|
if (LastUpdateTime < lastUpdate)
|
{
|
_Rules = Biz.Db.Queryable<BAS_CODE_RULE>().IncludesAllFirstLayer().ToList();
|
LastUpdateTime = lastUpdate;
|
Logger.Console.Info($"Get CodeRule successful, total {Rules.Count}, last update time is {LastUpdateTime:yyyy-MM-dd HH:mm:ss}");
|
}
|
}
|
|
/// <summary>
|
/// 自动更新
|
/// </summary>
|
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);//
|
}
|
|
/// <summary>
|
/// 使用指定规则验证传入编码
|
/// </summary>
|
/// <param name="code">需要验证的条码</param>
|
/// <param name="rule">指定用于验证的条码规则</param>
|
/// <returns>Result.Data is RuleVerifier</returns>
|
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;
|
}
|
|
/// <summary>
|
/// 使用符合传入条件的规则验证传入编码
|
/// </summary>
|
/// <param name="code">需要验证的条码</param>
|
/// <param name="predicate">用于验证条码规则的过滤条件</param>
|
/// <returns>Result.Data is List<RuleVerifier></returns>
|
public Result Verify(string code, Func<BAS_CODE_RULE, bool> 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;
|
}
|
|
/// <summary>
|
/// 条码流水码取号
|
/// </summary>
|
/// <param name="key"></param>
|
/// <param name="rule"></param>
|
/// <param name="hisID">取号的流水号生成历史ID</param>
|
/// <param name="isTry">是否尝试取号,不保存到数据库</param>
|
/// <returns></returns>
|
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<BAS_CODE_GEN>().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<BAS_CODE_DTL.SERIAL_RESETs>())
|
{
|
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);
|
}
|
}
|
}
|
|
/// <summary>
|
/// 生成条码
|
/// </summary>
|
/// <param name="ruleCode"></param>
|
/// <param name="args"></param>
|
/// <returns></returns>
|
public Result Generate(string ruleCode, params object?[] args)
|
{
|
Result result = new(Result.Flags.Success);
|
try
|
{
|
var rule = Biz.Db.Queryable<BAS_CODE_RULE>().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<BAS_CODE_DTL.DATA_TYPEs>())
|
{
|
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;
|
}
|
|
/// <summary>
|
/// 尝试生成条码,不保存到数据库
|
/// </summary>
|
/// <param name="ruleCode"></param>
|
/// <param name="args"></param>
|
/// <returns></returns>
|
public Result TryGenerate(string ruleCode, params object?[] args)
|
{
|
Result result = new(Result.Flags.Success);
|
try
|
{
|
var rule = Biz.Db.Queryable<BAS_CODE_RULE>().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<BAS_CODE_DTL.DATA_TYPEs>())
|
{
|
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
|
}
|
}
|