织梦me递增函数,如何实现数据稳定增长?

99ANYc3cd6
预计阅读时长 24 分钟
位置: 首页 织梦建站 正文

这个需求非常常见,通常用于为文章、产品等生成唯一的、有序的编号,NO.202505210001NO.202505210002

在织梦的二次开发中,实现“递增”功能最核心、最可靠的方法是利用数据库,直接在PHP代码中用一个变量来递增是不可行的,因为并发请求会导致数据错乱。

下面我将提供两种最主流的实现方案,并给出完整的代码示例。


核心思想

无论是哪种方案,核心思想都是:

  1. 锁定:在读取和更新编号时,必须锁定相关的资源,防止其他进程同时操作,导致编号重复或跳跃。
  2. 读取:从数据库中读取当前已经用到的最大编号。
  3. 计算:将最大编号加一,得到新的编号。
  4. 更新:将新的编号写回数据库,并释放锁。

使用独立的数据表(推荐,最稳定)

这是最规范、最稳定的方法,我们创建一个专门用于存储和管理序列号的表。

步骤 1:创建数据表

在你的数据库中(建议使用 dede_ 前缀,根据你的实际情况修改),执行以下SQL语句,创建一个 dede_serial_number 表:

CREATE TABLE `dede_serial_number` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type` varchar(50) NOT NULL COMMENT '编号类型, article, product',
  `prefix` varchar(20) NOT NULL COMMENT '编号前缀, NO, PROD',
  `current_num` int(11) NOT NULL DEFAULT '0' COMMENT '当前已使用的最大编号',
  `date` date DEFAULT NULL COMMENT '日期,可用于按天/月/年重置',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_type_date` (`type`, `date`) -- 联合唯一索引,确保同一天同类型的编号唯一
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

表结构说明:

  • type: 用于区分不同业务的编号,比如文章编号、产品编号。
  • prefix: 编号的前缀,方便识别。
  • current_num: 存储当前递增的数字。
  • date: 存储日期,如果需要按天重置编号,这个字段就很有用。
  • uk_type_date: 这个唯一索引是关键,它保证了在同一个 typedate 下,只能有一条记录,防止重复插入。

步骤 2:编写PHP递增函数

在你的织梦程序中,找到 include 目录,创建一个新文件,inc_serial_functions.php,然后把下面的函数代码放进去。

<?php
/**
 * 获取一个递增的序列号
 * @param string $type 编号类型 ( 'article', 'product')
 * @param string $prefix 编号前缀 ( 'NO', 'PROD')
 * @param bool $resetDaily 是否按天重置
 * @return string 返回格式化后的序列号, NO.202505210001
 */
function getIncrementalNumber($type, $prefix = 'NO', $resetDaily = false)
{
    global $dsql;
    $date = '';
    if ($resetDaily) {
        $date = date('Ymd');
    }
    // 表名,根据你的前缀修改
    $table = 'dede_serial_number';
    // 使用事务和锁来确保原子性
    $dsql->ExecuteNoneQuery("START TRANSACTION");
    // 尝试获取锁,防止并发
    $dsql->ExecuteNoneQuery("SELECT GET_LOCK('serial_number_{$type}_{$date}', 10)"); // 锁定10秒
    try {
        // 1. 尝试更新现有记录(乐观锁)
        $current_num = 0;
        $sql = "SELECT current_num FROM `{$table}` WHERE type = '{$type}' AND date = '{$date}'";
        $row = $dsql->GetOne($sql);
        if ($row) {
            // 如果记录存在,则更新
            $current_num = $row['current_num'];
            $new_num = $current_num + 1;
            $dsql->ExecuteNoneQuery("UPDATE `{$table}` SET current_num = {$new_num} WHERE type = '{$type}' AND date = '{$date}'");
        } else {
            // 如果记录不存在(第一次或日期已变更),则插入
            $new_num = 1;
            $dsql->ExecuteNoneQuery("INSERT INTO `{$table}` (type, prefix, current_num, date) VALUES ('{$type}', '{$prefix}', {$new_num}, '{$date}')");
        }
        // 2. 生成最终的编号字符串
        $serialNumber = '';
        if ($resetDaily) {
            $serialNumber = $prefix . '.' . $date . str_pad($new_num, 4, '0', STR_PAD_LEFT);
        } else {
            // 如果不按天重置,日期可以为空或使用其他逻辑
            $serialNumber = $prefix . '.' . str_pad($new_num, 6, '0', STR_PAD_LEFT);
        }
        // 提交事务
        $dsql->ExecuteNoneQuery("COMMIT");
        return $serialNumber;
    } catch (Exception $e) {
        // 发生异常,回滚事务
        $dsql->ExecuteNoneQuery("ROLLBACK");
        // 释放锁
        $dsql->ExecuteNoneQuery("SELECT RELEASE_LOCK('serial_number_{$type}_{$date}')");
        return 'ERROR: ' . $e->getMessage();
    } finally {
        // 确保锁被释放
        $dsql->ExecuteNoneQuery("SELECT RELEASE_LOCK('serial_number_{$type}_{$date}')");
    }
}
?>

步骤 3:在模板或PHP文件中调用

方法A:在文章发布页(article_add.php)中自动生成

打开 /dede/article_add.php 文件,找到 if($dopost=='save') 部分,在保存文章之前调用这个函数。

// 引入我们的函数文件
require_once(DEDEINC.'/inc_serial_functions.php');
// ... 其他代码 ...
if($dopost=='save')
{
    // 获取递增的编号
    $articleSerialNumber = getIncrementalNumber('article', 'ARTICLE', true); // true表示按天重置
    // 将编号存入一个自定义字段,或者直接存入标题等
    // 假设你有一个自定义字段叫 `serial_number`
    $_POST['serial_number'] = $articleSerialNumber;
    // ... 继续原有的保存逻辑 ...
    // $arc->Save(); 这行代码不变
}

方法B:在内容模板(article_article.htm)中显示

如果你的编号已经保存在文章的自定义字段 serial_number 中,那么在模板里直接调用即可。

{dede:field.serial_number/}

如果是在PHP代码里处理,也可以直接使用 $arc->Fields['serial_number']


利用系统自带的 sys_enum 表(简单,但不够灵活)

织梦有一个系统表 dede_sys_enum,常用来存储一些枚举值(如性别、栏目类型等),我们可以“借用”它来存储一个简单的计数器。此方法不推荐用于高并发或需要复杂逻辑的场景,因为它的设计初衷并非如此。

步骤 1:准备数据

手动在 dede_sys_enum 表中插入一条记录作为计数器的起点。

INSERT INTO `dede_sys_enum` (
    `id`, `evalue`, `ename`, `eorder`, `group`, `issign`
) VALUES (
    NULL, '0', 'article_counter', '0', 'serial_counter', '0'
);
  • evalue: 存储当前的数字,我们从0开始。
  • ename: 一个唯一的名称,用来标识这个计数器。
  • group: 分组,用来区分不同的计数器。

步骤 2:编写PHP递增函数

同样,在 include/inc_serial_functions.php 中添加如下函数:

/**
 * 使用sys_enum表获取递增编号(简单版,不推荐高并发)
 * @param string $counter_name 计数器名称
 * @param string $prefix 编号前缀
 * @return string
 */
function getIncrementalNumberFromEnum($counter_name, $prefix = 'NO')
{
    global $dsql;
    // 1. 获取当前计数 (使用锁防止并发读取到旧值)
    $dsql->ExecuteNoneQuery("SELECT GET_LOCK('enum_counter_{$counter_name}', 5)");
    $row = $dsql->GetOne("SELECT evalue FROM `dede_sys_enum` WHERE ename = '{$counter_name}'");
    $current_num = isset($row['evalue']) ? intval($row['evalue']) : 0;
    $new_num = $current_num + 1;
    // 2. 更新计数
    $dsql->ExecuteNoneQuery("UPDATE `dede_sys_enum` SET evalue = '{$new_num}' WHERE ename = '{$counter_name}'");
    // 3. 释放锁
    $dsql->ExecuteNoneQuery("SELECT RELEASE_LOCK('enum_counter_{$counter_name}')");
    // 4. 格式化并返回
    return $prefix . '.' . str_pad($new_num, 6, '0', STR_PAD_LEFT);
}

步骤 3:调用方法

调用方式与方案一类似,只需调用 getIncrementalNumberFromEnum 函数即可。

require_once(DEDEINC.'/inc_serial_functions.php');
$number = getIncrementalNumberFromEnum('article_counter', 'ARTICLE');

方案对比与总结

特性 方案一(独立数据表) 方案二(sys_enum表)
稳定性 极高,专为计数设计,结构清晰。 一般,非设计用途,可能受系统更新影响。
灵活性 极高,支持按天/月/年重置、多类型管理等。 较低,逻辑简单,难以实现复杂规则。
并发安全性 ,通过事务和行级锁(或MySQL应用锁)保证。 中等,依赖MySQL GET_LOCK,但表结构不适合高并发更新。
维护性 ,表结构独立,易于理解和维护。 ,数据与系统配置混在一起,不易管理。
推荐度 强烈推荐 不推荐,仅用于非常简单的、低频的内部需求。

对于任何正式的、有一定业务量的网站,请务必选择方案一,它虽然多建了一张表,但带来的稳定性、灵活性和可维护性是完全值得的,方案二可以作为临时或简单的测试方案,但请勿在生产环境中依赖它。

-- 展开阅读全文 --
头像
网页如何嵌套织梦后台
« 上一篇 03-27
c语言 a a-aa
下一篇 » 03-27

相关文章

取消
微信二维码
支付宝二维码

目录[+]