一、背景

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

常用的的编码有:
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、单据编码规则        用来存储基础规则组合,一种单据编码对应多种规则

Ⅲ、基础的配置及储存确认,我们再设计类,我一般设计是从入口开始的,先考虑怎么用,再考虑怎么去实现。
比如在WebApi的控制器中要採番取得采购订单编码及采购订单明细的行号,代码如下

    public class PurchasingApiController : ApiController{private ISequenceFactory _sequenceFactory;public PurchasingApiController(ISequenceFactory sequenceFactory){_sequenceFactory = sequenceFactory;}//取得主表的BillNopublic string GetNextBillNo(){var sequence = _sequenceFactory.Create("sdx_purchasing");return sequence.Next();}//用BillNo过滤取得从表中的RowIdpublic string GetNextRowId(string key){var sequence = _sequenceFactory.Create("sdx_purchasingLine");sequence.SetValue("BillNo", key)return sequence.Next();}}

通过以上使用,我们大致清楚,Sequence对象中主要就是一个Next()的实现,创建交给SequenceFactory以下是我的一个实现截图

稍微解释说明下:
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

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

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

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

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

四、具体实例

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

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

2、保存后生成

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

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

五、后述

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

通用的业务编码规则设计实现相关推荐

  1. petshop详解之五:PetShop之业务逻辑层设计

    五 PetShop之业务逻辑层设计业务逻辑层(Business Logic Layer)无疑是系统架构中体现核心价值的部分.它的关注点主要集中在业务规则的制定.业务流程的实现等与业务需求有关的系统设计 ...

  2. petshop4.0 详解之五(PetShop之业务逻辑层设计)[转]

    业务逻辑层(Business Logic Layer)无疑是系统架构中体现核心价值的部分.它的关注点主要集中在业务规则的制定.业务流程的实现等与业务需求有关的系统设计,也即是说它是与系统所应对的领域( ...

  3. petshop4.0 详解之五(PetShop之业务逻辑层设计)

    五 PetShop之业务逻辑层设计 业务逻辑层(Business Logic Layer)无疑是系统架构中体现核心价值的部分.它的关注点主要集中在业务规则的制定.业务流程的实现等与业务需求有关的系统设 ...

  4. PetShop之业务逻辑层设计

    <解剖PetShop>系列之五 五 PetShop之业务逻辑层设计 业务逻辑层(Business Logic Layer)无疑是系统架构中体现核心价值的部分.它的关注点主要集中在业务规则的 ...

  5. 详解34家银行对公账号编码规则及其编码分析

    1.工商银行 账号位数19位: 地区代码4位(13为安徽)+网点号4位+账户性质应用号2位(09基本户,19一般户,29专户,39临时户)+账号顺序号7位+校验码2位 编码分析攻略: 中国工商银行对公 ...

  6. [wayfarer]PetShop之业务逻辑层设计

    原文地址: http://www.cnblogs.com/wayfarer/archive/2006/11/05/550723.html <解剖PetShop>系列之五 五 PetShop ...

  7. ASN.1探索 - 3 编码规则与传输语法(3 - PER)

    转自: http://wmfbravo.blog.163.com/ 感谢: wmfbravo 本章主要介绍BER和PER两种编码规则及其衍生规则. 3.3PER BER编码因其在大小上的开销过大而受人 ...

  8. ASN.1 -- 编码规则

    描述ASN.1编码规则的标准 ITU-T Rec. X.690 | ISO/IEC 8825-1 (BER, CER and DER) ITU-T Rec. X.691 | ISO/IEC 8825- ...

  9. 通用对账系统介绍与设计(上)

    转自: http://mp.weixin.qq.com/s/y2I_EYSLlq_239vZXr0OZQ 本文首先介绍了对账的概念.基本内容,其次讲解了对账系统中常见问题及解决方法,最后详细讲解了整个 ...

  10. 【SDCC讲师专访】58同城孙玄:一切抛开业务的架构设计都是耍流氓

    本期我们采访的讲师是来自58同城高级系统架构师&技术负责人孙玄,他是58的技术委员会架构组主任,产品技术学院优秀讲师,代表58同城参与多次对外演讲. 58同城高级系统架构师,技术委员会架构组主 ...

最新文章

  1. 学习下ECharts 异步加载数据
  2. javascript:重新加载js文件
  3. python核心编程——python对象
  4. sublime text2快捷键
  5. Linux 链接文件讲解
  6. Delicious Apples
  7. 7.1 API:GaussianMixture
  8. 【手指识别】基于matlab GUI指尖图像采集与检测【含Matlab源码 585期】
  9. 理解list和vector的区别
  10. android * 工程模式,什么是Android手机的工程模式
  11. 中国数字商业核心产业链分布
  12. Scrapy入门到放弃06:Spider中间件
  13. 发那科2021参数_发那科参数
  14. #使用TF实现海龟机器人跟随
  15. 计算机项目教学法探讨,基于项目教学法的非计算机专业计算机教学的设计和探讨...
  16. 【时间复杂度】你还在担心时间复杂度太高吗?
  17. 基于 AHB 总线的 SRAM 控制器设计
  18. itextpdf 怎么下划线_java – 带有粗体和下划线的Itext新字体
  19. 常用传感器讲解八--土壤湿度传感器(XH-M214)
  20. 基于C的α-β剪枝算法实现的AI五子棋游戏

热门文章

  1. android c callstack,[MTK] 如何在android native code 打callstack
  2. Flash存储的故事
  3. 向股票,外汇,期货投资者推荐的好书
  4. 学习笔记(4):思科CCNA模拟器Packet Tracer使用入门-路由器的使用(2811、2911)
  5. js 随机选取动画_Three.js + GreenSock 模拟简单随机动画
  6. 双栏显示的时候三线格的线太长怎么办?
  7. 三线表(带有行横标目)的绘制方法
  8. 易语言使用超级模块 全局热键
  9. 【最短路】 Johnson 算法
  10. mongoVUE查询