订单号的生成规则

订单号一般具有以下特性

  • 唯一性(编码不重复)
  • 安全性(可校验,不能随意仿造)
  • 易读性(易于识别)
  • 可扩展性(多业务混合使用,可对新增业务提供支持)
  • 防止并发(分布式机器的时间不统一问题,针对编码中包含时间信息的)

编码规则一般设定在10~20位左右,常见编码规则

  • 业务类型 + 时间戳 + 平台 + 渠道 + 随机码(或自增码)+ 用户id(看情况,可以加入部分)+ 校验码(可选)
  • 年月日时分秒 + 用户id
  • 年月日时分秒微妙 + 随机码 + 流水号 + 随机码
  • 数据库主键自增的id
  • 日期+自增长数字的订单号
  • 产生随机的订单号
  • 字母 + 数字字符串
  • twitter的雪花算法,php第三方扩展库php-snowflake(推荐)

以下是我自己的已订单生成规则

const TABLE = "order_id"; // 订单id

    /**
     * 生成订单号 bigint最大支持19位
     * 业务号(2位)+ 日期(ymd 6位) + 毫秒(3位)+ 时间信息(His 6位)+ 用户uid(后1位)+ 校验位(1位)
     * @param $appId
     * @param $time
     * @param $uid4Suffix
     * @return string
     */
    private function _generateOrderId($appId, $ptUid)
    {
        if ($appId < 10) {
            // 取app加大到2位
            $appPre = $appId + 10;
        } else if ($appId <= 99) {
            $appPre = $appId;
        } else {
            // 取前2位
            $appPre = substr(strval($appId), 0, 2);
        }
        // uid后1位
        $uidSuffix = substr(strval($ptUid), -1);
        $micro = explode(" ", microtime());
        
        // 故意将年月日和时分秒错开,显得更无规律
        $orderId = sprintf("%02d%06d%03d%06d%01d", $appPre, date('ymd'), intval($micro[0] * 1000), date('His'), $uidSuffix);
        // 校验位(1位)
        $crcMod = crc32($orderId) % 10;
        return sprintf("%s%01d", $orderId, $crcMod);
    }

    public function createOrderId($appId, $ptUid)
    {
        $orderId = $this->_generateOrderId($appId, $ptUid);
        $res = $this->_insertDb($appId, $orderId);
        if(!$res){
            // 可能冲突重试一次
            $orderId = $this->_generateOrderId($appId, $ptUid);
            $res = $this->_insertDb($appId, $orderId);
            if(!$res){
                // @todo 抛出异常或直接返回错误,或者写错误日志
            }
        }
        return $orderId;
    }

    /**
     * 保存到数据库中,通过设置order_id唯一键,可以防止重复
     * @param $appId
     * @param $orderId
     * @return int
     */
    private function _insertDb($appId, $orderId)
    {
        // 入库
        if(!$appId || !$orderId){
            return 0;
        }
        $data = [
            'app_id' => intval($appId),
            'order_id' => intval($orderId),
        ];
        $res =  $this->dbUser()->insert(self::TABLE, $data);
        if($res === FALSE){
            // 订单id入库失败
            // @todo 抛出异常或直接返回错误,或者写错误日志
        }
        return $orderId;
    }

    public function writeLog($msg, $data = [], $uid = 0, $orderId = 0)
    {
        // @todo write log
    }