• 通用的业务编码规则设计实现


    一、背景

    每一个企业应用中不可避免的都会涉及到业务编码规则的问题,比如订单管理系统中的订单编号,比如商品管理系统中的商品编码,比如项目管理系统中的项目编码等等,这一系列的编码都需要管理起来,那么它们的应该如何编码的,具体的编码规则我们很多时候都是直接写在程序当中

    常用的的编码有:
    1、数据库自增长ID或最大值加1
    2、GUID
    3、时间戳
    4、常量+自增长
    5、常量+时间戳+自增长
    6、根据单据属性编码 比如商品编码:第X是代码商品颜色,第Y位是代码商品产地
    7、自定义函数处理返回
    8、其它

    添加一张单据时,这个单据的编码是比较头疼

    第一个问题就是单据编码的时间顺序:
    1、新增前先预取得新单据编码
    优点是保存处理很简单,而且保存后不需要再刷新UI,缺点就是如果放弃表单那么编码计数已经跳号,做不到连续的单据号,而且没法实现上面编码的第6种情况。
    2、保存时才生成单据编码
    缺点是保存比较麻烦点,而且保存后需要再刷新UI中的单据编码字段,但是如果是需要根据单据属性编码,那就必做得使用这种方式了,而且能做到单据连续。

    第二个问题是这个编码该怎么去取,怎么保证唯一性
    这里要看是使用哪种编码方式,比如 max + 1 这种方式 就直接检索数据库 select max(id) + 1 from table 取得编码,如果是GUID则比较方便,但是GUID看着实在是有点恶心,使用时间戳的话如果精确到秒的话是没办法保证唯一性的,即使精确到毫秒理论上也是不行的。其它复杂的编码获取就更麻烦点了。总之不是很方便就能取得。

    第三个问题如果这个编码规则需要更换怎么办
    这个如果没有特别设计编码规则一般都要修改程序,没办法说不动程序直接修改配置就能实现

    二、目的及设计

    Ⅰ、鉴于以上几个问题,我们想设计一个比较通用的业务编码规则模块,以后的项目也可复用,它应该要实现以下功能及特点:

    1、满足各种编码规则的需求
        a.背景中提到的那7种都要实现,还要求各种规则可以自由组合
        b.依赖重置,比如日期变化时序号自动重置为1
        c.支持SAAS模式的业务需求
    2、拓展性强,可添加自定义规则
    3、通过配置文件或数据进行配置,修改业务编码规则只需要修改配置文件或数据
    4、使用简单

    Ⅱ、我们先从配置来设计,我们把规则配置放在数据库中,可以考虑以后再做个界面来管理这些配置。设计三张表来保存这些规则
    1、单据编码规则      
    2、租户单据编码规则 (考虑SAAS多租户模式)
    3、单据编码规则        用来存储基础规则组合,一种单据编码对应多种规则
    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以下是我的一个实现截图
    image

    稍微解释说明下:
    1、DefaultSequenceFacotry          继承自接口ISequenceFactory负责构建Squence
    2、Sequence                              继承自ISeqence是採番的主要处理类
    3、SequenceContext                   Sequence上下文
    4、Resets文件夹中类                    继承自ISequenceReset,由SequenceResetFactory构建
    5、Rules文件夹中类                      继承自抽象类SequenceRuleBase,由SequenceRuleFactory构建
    6、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
    image

    c、配置编码规则表
    基础规则包括:
    1、const       常量
    2、numbering 计数
    3、timestamp 时间戳
    4、guid         GUID
    5、sql           SQL文
    6、class        自定义类
    你可以用这些基础规则自由组合,当然也可以自己拓展基础规则
    image

    使用很简单
    1、取得Ioc容器中的SequenceFactory对象
    2、Factory创建具体的Sequence
    3、调用Sequence的Next方法

    如果不使用Ioc可能更简单,直接
    var result = new Sequence(name).Next();

    代码就这样就行,然后可以通过配置改变各单据的业务编码规则。

    四、具体实例

    1、采购订单,在这个页面点击新增按钮
    image

    这个未保存的表单已经取得一个采购单号:CG20140505002 = (CG + 20140505 + 002)
    image

    2、保存后生成
    image

    编辑保存后,即按传入的数据货号 颜色 尺寸 生成了一个自定义的商品编码 171240404781-W-XL
    image

    当然还有很多其它业务规则,大家都可以通过配置实现

    五、后述

    一直在项目中忙着都没动弹过,晚上抽空写了篇博客,只是把我自己的想法实现出来,如果大家感兴趣可以帮我推荐下,关于编码规则这块设计大家有没有什么更好的想法,也欢迎大家讨论。

  • 相关阅读:
    Python3编写网络爬虫11-数据存储方式四-关系型数据库存储
    Python3编写网络爬虫10-数据存储方式三-CSV文件存储
    Python3编写网络爬虫09-数据存储方式二-JSON文件存储
    Python3编写网络爬虫08-数据存储方式一-文件存储
    Python3编写网络爬虫07-基本解析库pyquery的使用
    Python3编写网络爬虫06-基本解析库Beautiful Soup的使用
    Python3编写网络爬虫05-基本解析库XPath的使用
    Python3编写网络爬虫04-爬取猫眼电影排行实例
    LeetCode455 分发饼干(简单贪心—Java优先队列简单应用)
    LeetCode874 模拟行走机器人(简单模拟—Java之HashSet简单应用)
  • 原文地址:https://www.cnblogs.com/xqin/p/3708367.html
Copyright © 2020-2023  润新知