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

一、背景


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

常用的的编码有:
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
   
b.支持SAAS模式的业务需求
2、拓展性强,可添加自定义规则

3、通过配置文件或数据进行配置,修改业务编码规则只需要修改配置文件或数据
4、使用简单

Ⅱ、我们先从配置来设计,我们把规则配置放在数据库中,可以考虑以后再做个界面来管理这些配置。设计三张表来保存这些规则

1、单据编码规则      
2、租户单据编码规则 (考虑SAAS多租户模式)

3、单据编码规则        用来存储基础规则组合,一种单据编码对应多种规则



Ⅲ、基础的配置及储存确认,我们再设计类,我一般设计是从入口开始的,先考虑怎么用,再考虑怎么去实现。

比如在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以下是我的一个实现截图

稍微解释说明下:

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

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

五、后述

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

.NET框架交流群(四) 294214497

通用的业务编码规则设计实现,布布扣,bubuko.com

时间: 2024-11-18 17:42:17

通用的业务编码规则设计实现的相关文章

通用的业务编码规则设计实现[转:http://www.cnblogs.com/xqin/p/3708367.html]

一.背景 每一个企业应用中不可避免的都会涉及到业务编码规则的问题,比如订单管理系统中的订单编号,比如商品管理系统中的商品编码,比如项目管理系统中的项目编码等等,这一系列的编码都需要管理起来,那么它们的应该如何编码的,具体的编码规则我们很多时候都是直接写在程序当中 常用的的编码有: 1.数据库自增长ID或最大值加1 2.GUID 3.时间戳 4.常量+自增长 5.常量+时间戳+自增长 6.根据单据属性编码 比如商品编码:第X是代码商品颜色,第Y位是代码商品产地 7.自定义函数处理返回 8.其它 添

构建一个较为通用的业务技术架构

1.通用架构概述 创业之初,我们往往会为了快速迭代出产品,而选择最简单的技术架构,比如LAMP架构,SSH三层架构.这些架构可以适应初期业务的快速发展,但是,随着业务变得越来越复杂,我们会发现这些架构越来越难支撑业务的发展,出现在一个类中写好几千行代码,一个方法中到处都是if else语句,如果中间遇到主程序猿离职,后面介入的程序猿几乎无法理解这些代码,到最后,产品越来越难迭代,只能推翻重做.如果我们在创业初始就以一种适应性较强的架构去写代码,后面就会少走很多弯路.下面的文章是我自己总结出来的一

如何构建一个较为通用的业务技术架构

1.通用架构概述 创业之初,我们往往会为了快速迭代出产品,而选择最简单的技术架构,比如LAMP架构,SSH三层架构.这些架构可以适应初期业务的快速发展,但是,随着业务变得越来越复杂,我们会发现这些架构越来越难支撑业务的发展,出现在一个类中写好几千行代码,一个方法中到处都是if else语句,如果中间遇到主程序猿离职,后面介入的程序猿几乎无法理解这些代码,到最后,产品越来越难迭代,只能推翻重做.如果我们在创业初始就以一种适应性较强的架构去写代码,后面就会少走很多弯路.下面的文章是我自己总结出来的一

在阿里架构师眼中构建一个较为通用的业务技术架构就是如此简单

1.通用架构概述 创业之初,我们往往会为了快速迭代出产品,而选择最简单的技术架构,比如LAMP架构,SSH三层架构.这些架构可以适应初期业务的快速发展,但是,随着业务变得越来越复杂,我们会发现这些架构越来越难支撑业务的发展,出现在一个类中写好几千行代码,一个方法中到处都是if else语句,如果中间遇到主程序猿离职,后面介入的程序猿几乎无法理解这些代码,到最后,产品越来越难迭代,只能推翻重做.如果我们在创业初始就以一种适应性较强的架构去写代码,后面就会少走很多弯路.下面的文章是我自己总结出来的一

在架构师眼中构建一个较为通用的业务技术架构就是如此简单

1.通用架构概述 创业之初,我们往往会为了快速迭代出产品,而选择最简单的技术架构,比如LAMP架构,SSH三层架构.这些架构可以适应初期业务的快速发展,但是,随着业务变得越来越复杂,我们会发现这些架构越来越难支撑业务的发展,出现在一个类中写好几千行代码,一个方法中到处都是if else语句,如果中间遇到主程序猿离职,后面介入的程序猿几乎无法理解这些代码,到最后,产品越来越难迭代,只能推翻重做.如果我们在创业初始就以一种适应性较强的架构去写代码,后面就会少走很多弯路.下面的文章是我自己总结出来的一

转:从开源项目学习 C 语言基本的编码规则

从开源项目学习 C 语言基本的编码规则 每个项目都有自己的风格指南:一组有关怎样为那个项目编码约定.一些经理选择基本的编码规则,另一些经理则更偏好非常高级的规则,对许多项目而言则没有特定的编码规则,项目中的每个开发者使用他自己的风格. 所有代码都保持一致风格的大型库,更容易让人理解. 有许多资源是关于能让人采取的更好的编码规则的,我们可以通过以下方式学到好的编码规则: 阅读书或杂志 浏览网站 与同事交流 参加培训 另一个更有趣的方法是通过研究一个成熟的知名开源项目来得知其开发者是怎样编写代码的.

驰骋工作流引擎设计系列10时效考核规则设计

第1节. 关键字 驰骋工作流引擎 流程快速开发平台 workflow ccflow jflow 第1节. 时效考核规则设计 考核是流程运行的副产品,业务搬到了计算机上,整个运行轨迹就会被有效的记录下来.CCBPM的考核分为时效考核.质量考核.时效考核是对工作及时程度的一种考核,而质量考核是一个节点对上一个节点工作完成好坏的一个考核. 1.1.1: 时效考核的系统配置 系统配置分为工作日信息设置,节假日信息设置. 上下班&午休时间配置,该设置在全局变量里:JFlow的配置: CCFlow的配置:

Python编码规则

1. 命名规则 1.1 变量名.包名.模块名 变量名通常有字母.数字和下划线组成,且首字母必须是字母或下划线,并且不能使用python的保留字:包名.模块名通常用小写字母 1.2 类名.对象名 类名首字母用大写,其他字母采用小写:对象名用小写字母.类的属性和方法名以对象作为前缀,对象通过操作符"."访问属性和方法.类的私有变量.私有方法以两个下划线作为前缀. l.3 函数名     函数名通常采用小写,并用下划线或单词首字母大写来增加名称的可读性,导入的函数以模块名作为前缀. 2. 模

排球记分员计分程序(六)————Views视图的编码与设计

一.Views视图的编码与设计 1.在上一步创建新的 DuiWuController控制器时,系统在Views\DuiWu文件夹中创建新的Create.cshtml. Delete.cshtml. Details.cshtml. Edit.cshtml和Index.cshtml 文件. 2.查看原来的Index.cshtml 文件,代码如下图所示: @model IEnumerable<排球计分程序.Models.DuiWu> @{    ViewBag.Title = "Index