`Laravel`版`小丑路人社区`改版中,与`Hyperf版小丑路人社区`数据互动,此版本改版中……尚未彻底完结!

Q:

PHP生成订单号(旧方法导致订单号重复的问题)

最新发现经常使用的order_no生成订单号直接重复,且很多项目都使用的是如下的方式生成。

这里不实际说明与数据库效验的唯一性,根据需要自行验证处理即可。

function order_no()
{
    return date('Ymd') . substr(implode(null, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}

var_dump(order_no()); ===> string(16) "2023071297101571"
var_dump(order_no()); ===> string(16) "2023071297101571"
var_dump(order_no()); ===> string(16) "2023071297101571"

解析实现过程

  1. uniqid(prefix, more_entropy)

    uniqid() 函数基于以微秒计的当前时间,生成一个唯一的 ID 相对而言,足以支持系统生成唯一性的ID,但是大型并发系统不能够100%保证。

    • prefix 可选参数。规定唯一 ID 的前缀。如果两个脚本恰好在相同的微秒生成 ID,该参数很有用。
    • more_entropy 可选。规定位于返回值末尾的更多的熵。这将让结果更具唯一性。当设置为 TRUE,返回字符串为 23 个字符。默认是 FALSE,返回字符串为 13 个字符。
  2. substr(uniqid(), 7, 13)

    uniqid 不传参数,则默认长度为13位,获取7 - 13的字符,可保证唯一性

  3. str_split

    函数把字符串分割到数组中:字符串按照每个字符归类到数组

  4. array_map('ord', str_split(substr(uniqid(), 7, 13), 1))

    array_map 将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新的值的数组 ord 返回字符串中第一个字符的 ASCII 值

    • 通过array_map调用ord方法,获取str_split(substr(uniqid(), 7, 13)为数组中的每个值转换为ASCII
  5. implode(null, array_map('ord', str_split(substr(uniqid(), 7, 13), 1)))

    返回一个由数组元素组合成的字符串,即把数组中的值按照顺序拼接成一个字符串。

  6. substr(implode(null, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8)

    最主要的原因是这个逻辑导致订单重复!!! substr 返回字符串的一部分

    • substr(string, 0, 8) 说明是截取前8位,偶吼?什么意思.implode转换array_map为字符串之后,字符串长度基本上为12位,但是同时触发这个逻辑
      string(12) "535498984854"
      string(12) "535498984899"
      string(13) "5354989848100"
    • 发现,最后的一个字符是13位。
    • 发现规则:字符串的前面部分基本上是一致,唯一的区别就是后面几位数是不一致的;而substr截取逻辑确实仅截取前8位,那么有很大可能性拿到的字符就是一致的,而正确的做法应该截取后8位。
    • 应使用 substr(string, -8, 8)的方式截取。 这里只说截取的,因为限定了本身订单的长度;也可移除截取逻辑,保证单号唯一性的问题。

调整之后的生成订单编号order_no方法

function order_no()
{
    return date('Ymd') . substr(implode(null, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), -8, 8);
}

var_dump(order_no()); ===> string(16) "2023071210010253"
var_dump(order_no()); ===> string(16) "2023071210010298"
var_dump(order_no()); ===> string(16) "2023071200102100"

本文在这里仅说明此方法的优化方式,也可使用雪花算法等。

追加随机数、机器码等组合的方式生成订单编号。

PHP
订阅

评论记录

慕」

不错不错||ヽ( ̄▽ ̄)ノミ|Ю


评论/回复