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}"; } } }

終於成功了。

當然這種處理方式也有不好的地方。

比如當生成流水號最終沒有使用,會造成浪費。

 

最后

感謝閱讀,希望可以幫到你。也歡迎留言指正文中的錯誤與不足,大家共同進步。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM