You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

$wpdb->insert仅本地生效,线上主机无法保存数据问题排查

WordPress插件AJAX数据插入失败排查与修复(20i主机环境)

问题现象

开发了一款捕获访客数据的WordPress插件,通过AJAX调用$wpdb->insert将数据存入自定义表:

  • 本地环境运行正常,$wpdb->insert返回true
  • 部署到20i主机后,AJAX提交的数据无记录,后台可正常显示phpMyAdmin插入的数据
  • 无错误日志、控制台无报错

核心排查方向与修复步骤

1. 强制开启调试日志

20i主机默认可能关闭了WordPress调试,先开启日志排查具体错误:
在wp-config.php中添加:

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);

错误会写入wp-content/debug.log,查看是否有数据库插入相关的报错。

2. 补全AJAX请求的Nonce验证

原代码未做Nonce验证,部分主机的安全机制会拦截无验证的AJAX请求:

后端修改(保存函数)

function io_tarot_readings_visitors_database_save() {
    global $wpdb;

    // 验证请求合法性
    if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'io_tarot_visitors_nonce')) {
        wp_send_json_error('请求验证失败');
        wp_die();
    }

    $email    = sanitize_email($_POST['email']);
    $url      = sanitize_url($_POST['url']);
    $datetime = $_POST['datetime'];

    // 校验数据有效性
    if (empty($email) || !is_email($email)) {
        wp_send_json_error('邮箱格式无效');
        wp_die();
    }
    if (empty($datetime) || !preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $datetime)) {
        wp_send_json_error('日期格式错误,需为YYYY-MM-DD HH:MM:SS');
        wp_die();
    }

    $table = $wpdb->prefix.'io_tarot_reading_visitors';

    $data = array(
        'email' => $email,
        'url'   => $url,
        'date'  => $datetime
    );

    // 执行插入并返回结果
    $result = $wpdb->insert($table, $data);
    if ($result !== false) {
        wp_send_json_success('数据保存成功');
    } else {
        wp_send_json_error('保存失败: ' . $wpdb->last_error);
    }

    wp_die(); // 必须终止AJAX请求
}
add_action('wp_ajax_io_tarot_readings_visitors_database_save', 'io_tarot_readings_visitors_database_save');
add_action('wp_ajax_nopriv_io_tarot_readings_visitors_database_save', 'io_tarot_readings_visitors_database_save');

前端修改(添加Nonce字段)

在提交表单或AJAX触发页面添加Nonce:

<?php wp_nonce_field('io_tarot_visitors_nonce', 'nonce'); ?>

AJAX请求携带Nonce(以jQuery为例):

jQuery.post(
    ajaxurl,
    {
        action: 'io_tarot_readings_visitors_database_save',
        email: jQuery('#visitor-email').val(),
        url: window.location.href,
        datetime: new Date().toISOString().slice(0, 19).replace('T', ' '),
        nonce: jQuery('input[name="nonce"]').val()
    },
    function(response) {
        console.log(response.data); // 查看响应结果,方便调试
    }
);

3. 修复数据库表字段限制

原建表语句中url字段长度仅为55字符,若访客URL超过长度会导致插入失败:

// 修改建表SQL中的url字段
$sql = "CREATE TABLE $table_name (
    id mediumint(9) NOT NULL AUTO_INCREMENT,
    email varchar(250) NOT NULL,
    url varchar(255) DEFAULT '' NOT NULL, // 改为255或更长
    date datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, // 替换无效默认值,适配MySQL严格模式
    PRIMARY KEY  (id)
) $charset_collate;";

注意:修改表结构后需要重新激活插件,或直接在phpMyAdmin中修改字段。

4. 检查主机安全机制

20i主机自带ModSecurity等安全防护,可能拦截了AJAX请求:

  • 登录20i主机后台,查看安全日志是否有相关拦截记录
  • 临时关闭ModSecurity测试,若恢复正常则添加规则白名单

5. 验证数据库写入权限

虽然后台能读取数据,但写入权限可能受限:

  • 通过phpMyAdmin使用WordPress数据库用户执行INSERT语句,测试是否能写入
  • 若写入失败,联系20i主机客服调整数据库用户权限

原代码参考

// Create Visitors database table on activation
function io_tarot_readings_visitors_database_create() {
    
    // Allow access to the $wpdb variable
    global $wpdb;

    // Set the table name
    $table_name = $wpdb->prefix . 'io_tarot_reading_visitors';
    
    // Set the charset
    $charset_collate = $wpdb->get_charset_collate();

    // Create the SQL query
    $sql = "CREATE TABLE $table_name (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        email varchar(250) NOT NULL,
        url varchar(55) DEFAULT '' NOT NULL,
        date datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
        PRIMARY KEY  (id)
    ) $charset_collate;";

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta($sql);

}
register_activation_hook(__FILE__, 'io_tarot_readings_visitors_database_create');


// Visitor capture database save
function io_tarot_readings_visitors_database_save() {

    // Allow access to the $wpdb variable
    global $wpdb;

    // Set $_POST variables
    $email    = sanitize_email($_POST['email']);
    $url      = sanitize_url($_POST['url']);
    $datetime = $_POST['datetime'];

    // Set table name
    $table = $wpdb->prefix.'io_tarot_reading_visitors';

    // Set data to save
    $data = array(
        'email' => $email,
        'url'   => $url,
        'date'  => $datetime
    );

    // Save to database
    $wpdb->insert($table, $data);

}
add_action('wp_ajax_io_tarot_readings_visitors_database_save', 'io_tarot_readings_visitors_database_save');
add_action('wp_ajax_nopriv_io_tarot_readings_visitors_database_save', 'io_tarot_readings_visitors_database_save');


// Export visitor capture data as CSV
function io_tarot_readings_visitors_export_csv() {

    // Check if export button is pressed
    if(isset($_POST['visitor-capture-export'])) {

        // Allow access to $wpdb variable
        global $wpdb;
    
        // Set the SQL query
        $sql = "SELECT * FROM {$wpdb->prefix}io_tarot_reading_visitors";
    
        // Get the database rows
        $rows = $wpdb->get_results($sql, 'ARRAY_A');
    
        // Check if there are existing rows
        if($rows) {
    
            $csv_fields         = array();
            $csv_fields[]       = "first_column";
            $csv_fields[]       = 'second_column';
    
            // Set filename
            $output_filename    = get_bloginfo('name').' - IO Tarot Readings Visitor Data - '.date('Y-m-d H:i:s').'.csv';
            $output_handle      = @fopen('php://output', 'w');
    
            // Set headers
            header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
            header('Content-Description: File Transfer');
            header('Content-type: text/csv');
            header('Content-Disposition: attachment; filename=' . $output_filename);
            header('Expires: 0');
            header('Pragma: public');
    
            $first = true;
            foreach($rows as $row) {
    
                // Process the header row
                if($first) {
                   $titles = array();
                    foreach ($row as $key => $val) {
                        $titles[] = $key;
                    }
                    fputcsv($output_handle, $titles);
                    $first = false;
                }
    
                $leadArray = (array) $row;
                
                // Add row to file
                fputcsv($output_handle, $leadArray);
                
            }
        
            // Close output file stream
            fclose($output_handle);
    
            die();
        }

    }
}
add_action('admin_init', 'io_tarot_readings_visitors_export_csv');


// Visitor capture database delete
function io_tarot_readings_visitors_delete_data() {

    // Check if delete button is pressed
    if(isset($_POST['visitor-capture-delete'])) {

        // Allow access to the $wpdb variable
        global $wpdb;

        // Set table name
        $table = $wpdb->prefix.'io_tarot_reading_visitors';

        // Run the SQL query to truncate the table
        $delete = $wpdb->query("TRUNCATE TABLE $table");

    }

}
add_action('admin_init', 'io_tarot_readings_visitors_delete_data');

内容的提问来源于stack exchange,提问作者Lee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 18:25:01