通用的業務編碼規則設計實現

1、背景

每個企業應用中不可避免的都會涉及到業務編碼規則的問題,好比訂單管理系統中的訂單編號,好比商品管理系統中的商品編碼,好比項目管理系統中的項目編碼等等,這一系列的編碼都須要管理起來,那麼它們的應該如何編碼的,具體的編碼規則咱們不少時候都是直接寫在程序當中sql

經常使用的的編碼有:
一、數據庫自增加ID或最大值加1
二、GUID
三、時間戳
四、常量+自增加
五、常量+時間戳+自增加
六、根據單據屬性編碼 好比商品編碼:第X是代碼商品顏色,第Y位是代碼商品產地
七、自定義函數處理返回
八、其它數據庫

添加一張單據時,這個單據的編碼是比較頭疼ide

第一個問題就是單據編碼的時間順序:
一、新增前先預取得新單據編碼
優勢是保存處理很簡單,並且保存後不須要再刷新UI,缺點就是若是放棄表單那麼編碼計數已經跳號,作不到連續的單據號,並且無法實現上面編碼的第6種狀況。
二、保存時才生成單據編碼
缺點是保存比較麻煩點,並且保存後須要再刷新UI中的單據編碼字段,可是若是是須要根據單據屬性編碼,那就必作得使用這種方式了,並且能作到單據連續。函數

第二個問題是這個編碼該怎麼去取,怎麼保證惟一性
這裏要看是使用哪一種編碼方式,好比 max + 1 這種方式 就直接檢索數據庫 select max(id) + 1 from table 取得編碼,若是是GUID則比較方便,可是GUID看着實在是有點噁心,使用時間戳的話若是精確到秒的話是沒辦法保證惟一性的,即便精確到毫秒理論上也是不行的。其它複雜的編碼獲取就更麻煩點了。總之不是很方便就能取得。ui

第三個問題若是這個編碼規則須要更換怎麼辦
這個若是沒有特別設計編碼規則通常都要修改程序,沒辦法說不動程序直接修改配置就能實現this

2、目的及設計

Ⅰ、鑑於以上幾個問題,咱們想設計一個比較通用的業務編碼規則模塊,之後的項目也可複用,它應該要實現如下功能及特色:編碼

一、知足各類編碼規則的需求
    a.背景中提到的那7種都要實現,還要求各類規則能夠自由組合
    b.依賴重置,好比日期變化時序號自動重置爲1
    c.支持SAAS模式的業務需求
二、拓展性強,可添加自定義規則
三、經過配置文件或數據進行配置,修改業務編碼規則只須要修改配置文件或數據
四、使用簡單spa

Ⅱ、咱們先從配置來設計,咱們把規則配置放在數據庫中,能夠考慮之後再作個界面來管理這些配置。設計三張表來保存這些規則
一、單據編碼規則      
二、租戶單據編碼規則 (考慮SAAS多租戶模式)
三、單據編碼規則        用來存儲基礎規則組合,一種單據編碼對應多種規則
image

Ⅲ、基礎的配置及儲存確認,咱們再設計類,我通常設計是從入口開始的,先考慮怎麼用,再考慮怎麼去實現。
好比在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如下是個人一個實現截圖
imagecode

稍微解釋說明下:
一、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);
        }
    }

 

3、配置及使用

a、配置單據規則表sys_sequence

b、根據需求配置租戶單據規則表sys_sequencetenant
image

c、配置編碼規則表
基礎規則包括:
一、const       常量
二、numbering 計數
三、timestamp 時間戳
四、guid         GUID
五、sql           SQL文
六、class        自定義類
你能夠用這些基礎規則自由組合,固然也能夠本身拓展基礎規則
image

使用很簡單
一、取得Ioc容器中的SequenceFactory對象
二、Factory建立具體的Sequence
三、調用Sequence的Next方法

若是不使用Ioc可能更簡單,直接
var result = new Sequence(name).Next();

代碼就這樣就行,而後能夠經過配置改變各單據的業務編碼規則。

4、具體實例

一、採購訂單,在這個頁面點擊新增按鈕
image

這個未保存的表單已經取得一個採購單號:CG20140505002 = (CG + 20140505 + 002)
image

二、保存後生成
image

編輯保存後,即按傳入的數據貨號 顏色 尺寸 生成了一個自定義的商品編碼 171240404781-W-XL
image

固然還有不少其它業務規則,你們均可以經過配置實現

5、後述

一直在項目中忙着都沒動彈過,晚上抽空寫了篇博客,只是把我本身的想法實現出來,若是你們感興趣能夠幫我推薦下,關於編碼規則這塊設計你們有沒有什麼更好的想法,也歡迎你們討論。

相關文章
相關標籤/搜索