每個企業應用中不可避免的都會涉及到業務編碼規則的問題,好比訂單管理系統中的訂單編號,好比商品管理系統中的商品編碼,好比項目管理系統中的項目編碼等等,這一系列的編碼都須要管理起來,那麼它們的應該如何編碼的,具體的編碼規則咱們不少時候都是直接寫在程序當中sql
經常使用的的編碼有:
一、數據庫自增加ID或最大值加1
二、GUID
三、時間戳
四、常量+自增加
五、常量+時間戳+自增加
六、根據單據屬性編碼 好比商品編碼:第X是代碼商品顏色,第Y位是代碼商品產地
七、自定義函數處理返回
八、其它數據庫
添加一張單據時,這個單據的編碼是比較頭疼ide
第一個問題就是單據編碼的時間順序:
一、新增前先預取得新單據編碼
優勢是保存處理很簡單,並且保存後不須要再刷新UI,缺點就是若是放棄表單那麼編碼計數已經跳號,作不到連續的單據號,並且無法實現上面編碼的第6種狀況。
二、保存時才生成單據編碼
缺點是保存比較麻煩點,並且保存後須要再刷新UI中的單據編碼字段,可是若是是須要根據單據屬性編碼,那就必作得使用這種方式了,並且能作到單據連續。函數
第二個問題是這個編碼該怎麼去取,怎麼保證惟一性
這裏要看是使用哪一種編碼方式,好比 max + 1 這種方式 就直接檢索數據庫 select max(id) + 1 from table 取得編碼,若是是GUID則比較方便,可是GUID看着實在是有點噁心,使用時間戳的話若是精確到秒的話是沒辦法保證惟一性的,即便精確到毫秒理論上也是不行的。其它複雜的編碼獲取就更麻煩點了。總之不是很方便就能取得。ui
第三個問題若是這個編碼規則須要更換怎麼辦
這個若是沒有特別設計編碼規則通常都要修改程序,沒辦法說不動程序直接修改配置就能實現this
Ⅰ、鑑於以上幾個問題,咱們想設計一個比較通用的業務編碼規則模塊,之後的項目也可複用,它應該要實現如下功能及特色:編碼
一、知足各類編碼規則的需求
a.背景中提到的那7種都要實現,還要求各類規則能夠自由組合
b.依賴重置,好比日期變化時序號自動重置爲1
c.支持SAAS模式的業務需求
二、拓展性強,可添加自定義規則
三、經過配置文件或數據進行配置,修改業務編碼規則只須要修改配置文件或數據
四、使用簡單spa
Ⅱ、咱們先從配置來設計,咱們把規則配置放在數據庫中,能夠考慮之後再作個界面來管理這些配置。設計三張表來保存這些規則
一、單據編碼規則
二、租戶單據編碼規則 (考慮SAAS多租戶模式)
三、單據編碼規則 用來存儲基礎規則組合,一種單據編碼對應多種規則
Ⅲ、基礎的配置及儲存確認,咱們再設計類,我通常設計是從入口開始的,先考慮怎麼用,再考慮怎麼去實現。
好比在WebApi的控制器中要採番取得采購訂單編碼及採購訂單明細的行號,代碼以下設計
public class PurchasingApiController : ApiController { private ISequenceFactory _sequenceFactory; public PurchasingApiController(ISequenceFactory sequenceFactory) { _sequenceFactory = sequenceFactory; } //取得主表的BillNo public string GetNextBillNo() { var sequence = _sequenceFactory.Create("sdx_purchasing"); return sequence.Next(); } //用BillNo過濾取得從表中的RowId public string GetNextRowId(string key) { var sequence = _sequenceFactory.Create("sdx_purchasingLine"); sequence.SetValue("BillNo", key) return sequence.Next(); } }
經過以上使用,咱們大體清楚,Sequence對象中主要就是一個Next()的實現,建立交給SequenceFactory如下是個人一個實現截圖
code
稍微解釋說明下:
一、DefaultSequenceFacotry 繼承自接口ISequenceFactory負責構建Squence
二、Sequence 繼承自ISeqence是採番的主要處理類
三、SequenceContext Sequence上下文
四、Resets文件夾中類 繼承自ISequenceReset,由SequenceResetFactory構建
五、Rules文件夾中類 繼承自抽象類SequenceRuleBase,由SequenceRuleFactory構建
六、IClassSequenceHandler 自定義類規則接口,實現這個添口可添加自定義規則,SequenceHandler中是兩個自定義類規則的實現
Ⅳ、下面貼出代碼
ISequenceFactory.cs
public interface ISequenceFactory { ISequence Create(string name); }
DefaultSequenceFactory.cs
public class DefaultSequenceFactory : ISequenceFactory { public ISequence Create(string name) { return new Sequence(name); } }
ISequence.cs
public interface ISequence { ISequence SetDbContext(IDbContext db); ISequence SetTenantID(string tenantId); ISequence SetValues(Dictionary<string, object> row); ISequence SetValues(JToken row); ISequence SetValue(string name, object value); string Next(); string Next(int qty); }
Sequence.cs
public class Sequence : ISequence { private SequenceContext _context; public Sequence(string name) { _context = new SequenceContext(); _context.TenantID = SdxLoginer.TenantID; _context.SequenceName = name; } public ISequence SetDbContext(IDbContext db) { _context.db = db; return this; } public ISequence SetTenantID(string tenantId) { _context.TenantID = tenantId; return this; } public ISequence SetValues(Dictionary<string, object> row) { _context.row = row; return this; } public ISequence SetValues(JToken row) { if (row != null) foreach (JProperty item in row.Children()) if (item != null) _context.row[item.Name] = ((JValue)item.Value).Value; return this; } public ISequence SetValue(string name, object value) { if (!string.IsNullOrEmpty(name)) _context.row[name] = value; return this; } public string Next() { return Next(1); } public string Next(int qty) { bool IsCreateDb = false; var result = string.Empty; try { if (_context.db == null) { _context.db = Db.Context(App.DefaultConnectionName??App.GetDefaultConnectionName()); _context.db.UseTransaction(true); _context.db.UseSharedConnection(true); IsCreateDb = true; } //初始化Sequence數據 …
//加載Sequence重置依賴 …
//加載Sequence規則 …
//生成Sequence處理 for (var i = 0; i < qty; i++) { _context.CurrentCode = string.Empty; foreach (var rule in _context.Rules) _context.CurrentCode += (_context.CurrentCode.Length > 0 ? _context.SequenceDelimiter : string.Empty)
+ rule.Series(_context); result += (result.Length > 0 ? "," : string.Empty) + _context.CurrentCode; } //更新 CurrentNo …
} catch (Exception e) { if (IsCreateDb) _context.db.Rollback(); throw e; } finally { if (IsCreateDb) { _context.db.Commit(); _context.db.Dispose(); } } return result; } }
SequenceContext.cs
public class SequenceContext { public IDbContext db { get; set; } public ISequenceReset SequenceReset { get; set; } public List<SequenceRuleBase> Rules { get; set; } public string TenantID { get; set; } public string SequenceName { get; set; } public string SequenceDelimiter { get; set; } public int Step { get; set; } public int CurrentNo { get; set; }
public string CurrentCode { get; set; } public string CurrentReset { get; set; } public bool IsMultipleTenant { get; set; } public Dictionary<string,object> row { get; set; } public SequenceContext() { db = null; SequenceReset = new NullSequenceReset(); Rules = new List<SequenceRuleBase>(); TenantID = ""; SequenceName = ""; SequenceDelimiter = ""; Setp = 0;
CurrentNo = 0; CurrentCode = ""; IsMultipleTenant = true; row = new Dictionary<string, object>(); } }
SequenceResetFactory.cs
public class SequenceResetFactory { public static ISequenceReset CreateReset(string sequenceReset) { if (string.IsNullOrEmpty(sequenceReset)) return new NullSequenceReset(); var type = Assembly.GetExecutingAssembly().GetTypes() .Where(t => t.GetInterface("ISequenceReset")!=null && t.Name.Equals(sequenceReset + "SequenceReset", StringComparison.CurrentCultureIgnoreCase)) .FirstOrDefault(); if (type == null) throw new Exception(string.Format("沒法建立重置依賴[{0}],找不到類{0}SequenceReset", sequenceReset)); return (ISequenceReset)Activator.CreateInstance(type); } }
ISequenceReset.cs
public interface ISequenceReset { string Dependency(SequenceContext context); }
DateSequenceReset.cs
public class DateSequenceReset:ISequenceReset { public string Dependency(SequenceContext context) { return DateTime.Now.ToString("yyyyMMdd"); } }
NullSequenceReset.cs
public class NullSequenceReset:ISequenceReset { public string Dependency(SequenceContext context) { return string.Empty; } }
PaddingSide.cs
public enum PaddingSide { Left, Right, None }
SequenceRuleFactory.cs
public class SequenceRuleFactory { public static SequenceRuleBase CreateRule(string ruleName) { var type = Assembly.GetExecutingAssembly().GetTypes() .Where(t => t.BaseType == typeof(SequenceRuleBase) && t.Name.Equals(ruleName + "SequenceRule", StringComparison.CurrentCultureIgnoreCase)) .FirstOrDefault(); if (type == null) throw new Exception(string.Format("沒法建立編碼規則[{0}],找不到類{0}SequenceRule", ruleName)); return (SequenceRuleBase)Activator.CreateInstance(type); } }
SequenceRuleBase.cs
public abstract class SequenceRuleBase { public int PaddingWidth { get; set; } public char PaddingChar { get; set; } public PaddingSide PaddingSide { get; set; } public string RuleValue { get; set; } public SequenceRuleBase() { PaddingWidth = 0; PaddingChar = char.MinValue; PaddingSide = PaddingSide.None; RuleValue = ""; } public string Series(SequenceContext data) { var result = Handle(data); result = result ?? string.Empty; if (PaddingSide == PaddingSide.Left && PaddingWidth > 0) { if (PaddingChar == char.MinValue) throw new Exception(string.Format("取得Sequence[{0}]處理中未設置填充的字符", data.SequenceName)); result = result.PadLeft(PaddingWidth, PaddingChar); } else if (PaddingSide == PaddingSide.Right && PaddingWidth > 0) { if (PaddingChar == char.MinValue) throw new Exception(string.Format("取得Sequence[{0}]處理中未設置填充的字符", data.SequenceName)); result = result.PadRight(PaddingWidth, PaddingChar); } return result; } protected abstract string Handle(SequenceContext data); }
ConstSequenceRule.cs
public class ConstSequenceRule : SequenceRuleBase { protected override string Handle(SequenceContext data) { return RuleValue ?? string.Empty; } }
GuidSequenceRule.cs
public class GuidSequenceRule : SequenceRuleBase { protected override string Handle(SequenceContext data) { return Guid.NewGuid().ToString(RuleValue); } }
NumberingSequenceRule.cs
public class NumberingSequenceRule : SequenceRuleBase { protected override string Handle(SequenceContext data) { data.CurrentNo = data.CurrentNo + data.Setp; return data.CurrentNo.ToString(); } }
SQLSequenceRule.cs
public class SQLSequenceRule : SequenceRuleBase { protected override string Handle(SequenceContext data) { return data.db.Sql(RuleValue).QuerySingle<string>(); } }
TimeStampSequenceRule.cs
public class TimeStampSequenceRule : SequenceRuleBase { protected override string Handle(SequenceContext data) { return DateTime.Now.ToString(RuleValue); } }
IClassSequenceHandler.cs
public interface IClassSequenceHandler { string Handle(SequenceContext data); }
ClassSequenceRule.cs
public class ClassSequenceRule : SequenceRuleBase { private IClassSequenceHandler handler; protected override string Handle(SequenceContext data) { if (handler == null) { var type = Type.GetType(RuleValue); if (type == null) throw new Exception(string.Format("取得Sequence[{0}]函數處理規則中類名設置不正確", data.SequenceName)); if (type.GetInterface("IClassSequenceHandler") == null) throw new Exception(string.Format("取得Sequence[{0}]函數處理{0}未實現接口IClassSequenceHandler", type.Name)); handler = (IClassSequenceHandler)Activator.CreateInstance(type); } return handler.Handle(data); } }
GoodsNoSequenceRule.cs 商品編碼自定義處理示例
public class GoodsNoSequenceRule : IClassSequenceHandler { public string Handle(SequenceContext data) { if (!data.row.ContainsKey("ArtNo")) throw new Exception("缺乏參數ArtNo"); if (!data.row.ContainsKey("Color")) throw new Exception("缺乏參數Color"); if (!data.row.ContainsKey("Size")) throw new Exception("缺乏參數Size"); var list = new List<string>(); list.Add(data.row["ArtNo"].ToString()); list.Add(data.row["Color"].ToString()); list.Add(data.row["Size"].ToString()); return string.Join("-", list); } }
a、配置單據規則表sys_sequence
b、根據需求配置租戶單據規則表sys_sequencetenant
c、配置編碼規則表
基礎規則包括:
一、const 常量
二、numbering 計數
三、timestamp 時間戳
四、guid GUID
五、sql SQL文
六、class 自定義類
你能夠用這些基礎規則自由組合,固然也能夠本身拓展基礎規則
使用很簡單
一、取得Ioc容器中的SequenceFactory對象
二、Factory建立具體的Sequence
三、調用Sequence的Next方法
若是不使用Ioc可能更簡單,直接
var result = new Sequence(name).Next();
代碼就這樣就行,而後能夠經過配置改變各單據的業務編碼規則。
一、採購訂單,在這個頁面點擊新增按鈕
這個未保存的表單已經取得一個採購單號:CG20140505002 = (CG + 20140505 + 002)
編輯保存後,即按傳入的數據貨號 顏色 尺寸 生成了一個自定義的商品編碼 171240404781-W-XL
固然還有不少其它業務規則,你們均可以經過配置實現
一直在項目中忙着都沒動彈過,晚上抽空寫了篇博客,只是把我本身的想法實現出來,若是你們感興趣能夠幫我推薦下,關於編碼規則這塊設計你們有沒有什麼更好的想法,也歡迎你們討論。