C# 订单流水号生成

例如流水号格式如下:XX201604120001,2位前缀加8位日期加4位流水号

首先各种搜索出现如下解决方案

    public class SerialNoHelper
    {
        /// <summary>
        /// 生成流水号
        /// </summary>
        /// <param name="serialno">从数据库读取最大的流水号</param>
        /// <returns></returns>
        public String Generate(String serialno)
        {
            var today = DateTime.Today.ToString("yyyyMMdd");

            if (String.IsNullOrEmpty(serialno))
                return $"XX{today}0001";

            var date = serialno.Substring(2, 8);
            if (date == today)
            {
                var no = Convert.ToInt32(serialno.Substring(10));
                return $"XX{today}{++no:0000}";
            }

            return $"XX{today}0001";
        }
    }

然后测试

  class Program
    {
        static void Main(string[] args)
        {
            //模拟数据库
            var array = new List<String>();

            //模拟订单号生成
            var tasks = new Task[1000];
            for (var i = 0; i < tasks.Length; i++)
            {
                tasks[i] = Task.Run(() =>
                {
                    var helper = new SerialNoHelper();

                    var sno = array.LastOrDefault();//模拟从数据库读取最大的流水号

                    var serialno = helper.Generate(sno);

            //各种逻辑操作
array.Add(serialno);//模拟保存到数据库 Console.WriteLine(serialno); }); } //等待执行完成 Task.WaitAll(tasks); Console.WriteLine("-----------------------------------分割线-----------------------------------"); //测试是否重复 var repeat = array.GroupBy(m => m).Where(m => m.Count() > 1).Select(m => m.Key).ToList(); foreach (var item in repeat) Console.WriteLine(item); Console.ReadLine(); } }

测试后不难发现,在高并发下很容易就出现重复。

好像哪里不对啊,修改SerialNoHelper类实现单例,然后给Generate方法加锁。这下应该可以了吧。

    public sealed class SerialNoHelper
    {
        private static volatile SerialNoHelper helper;
        private static readonly Object syncRoot = new Object();

        private SerialNoHelper()
        {
        }

        public static SerialNoHelper Helper
        {
            get
            {
                if (helper == null)
                {
                    lock (syncRoot)
                    {
                        if (helper == null)
                            helper = new SerialNoHelper();
                    }
                }
                return helper;
            }
        }

        /// <summary>
        /// 生成流水号
        /// </summary>
        /// <param name="serialno">从数据库读取最大的流水号</param>
        /// <returns></returns>
        public String Generate(String serialno)
        {
            lock (syncRoot)
            {
                var today = DateTime.Today.ToString("yyyyMMdd");

                if (String.IsNullOrEmpty(serialno))
                    return $"XX{today}0001";

                var date = serialno.Substring(2, 8);
                if (date == today)
                {
                    var no = Convert.ToInt32(serialno.Substring(10));
                    return $"XX{today}{++no:0000}";
                }

                return $"XX{today}0001";
            }
        }
    }

心情忐忑的按下F5,WTF,居然还是有重复。

不慌,走到窗口猛吸两口雾霾压压惊。接下来分析一下为什么还是会出现重复呢?

生成序列号的时候依赖的是从数据库获取最大的流水号,但是在生成序列号之后,到保存序列号到数据库这之间一般会有一些逻辑操作。

这就导致在高并发的时候,前一个流水号还没有保存到数据库,那就有可能从数据库获取到的流水号是相同的,那么生成的流水号自然就会出现重复。

怎么解决这个问题呢?在Generate方法内就把生成的流水号保存到数据库?这显然不太合适,上面提到保存流水号到数据库一般会有一些逻辑操作。

最终版本

public sealed class SerialNoHelper
    {
        private static volatile SerialNoHelper helper;
        private static readonly Object syncRoot = new Object();

        private static String lastdate;
        private static Int32 lastno;

        private SerialNoHelper()
        {
        }

        public static SerialNoHelper Helper
        {
            get
            {
                if (helper == null)
                {
                    lock (syncRoot)
                    {
                        if (helper == null)
                            helper = new SerialNoHelper();
                    }
                }
                return helper;
            }
        }

        /// <summary>
        /// 生成流水号
        /// </summary>
        /// <param name="serialno">从数据库读取最大的流水号</param>
        /// <returns></returns>
        public String Generate(String serialno)
        {
            lock (syncRoot)
            {
                var today = DateTime.Today.ToString("yyyyMMdd");

                if (today == lastdate)
                    return $"XX{today}{++lastno:0000}";

                lastdate = today;
                lastno = 0;
if (!String.IsNullOrEmpty(serialno) && serialno.Substring(2, 8) == today) lastno = Convert.ToInt32(serialno.Substring(10)); return $"XX{today}{++lastno:0000}"; } } }

终于成功了。

当然这种处理方式也有不好的地方。

比如当生成流水号最终没有使用,会造成浪费。

 

最后

感谢阅读,希望可以帮到你。也欢迎留言指正文中的错误与不足,大家共同进步。

 

posted @ 2016-04-12 14:52  写代码的小2B  阅读(5420)  评论(0编辑  收藏  举报