This repository has been archived on 2026-06-20. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
carwash_order/webhook.php
T
wsh5485 5bbb0877d5 feat: 添加WPS表单处理功能及相关测试
实现WPS表单Webhook接收端点,包括数据解析、验证和存储功能
添加数据库表创建脚本和测试用例
包含Webhook测试页面和日志功能
2025-12-05 13:58:57 +08:00

359 lines
16 KiB
PHP

<?php
// WPS表单Webhook接收端点
// 设置响应头
header('Content-Type: application/json; charset=utf-8');
// 绑定码(根据WPS表单要求提供)
$bind_code = '20251205123619412736007';
// 日志文件路径
$log_file = 'wps_form_webhook.log';
// 加载数据库配置
require_once 'config.php';
// 数据库连接函数
function get_db_connection() {
global $host, $username, $password, $database;
try {
$pdo = new PDO("mysql:host=$host;dbname=$database;charset=utf8mb4", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
return $pdo;
} catch(PDOException $e) {
log_message("数据库连接失败: " . $e->getMessage(), 'error');
return null;
}
}
// 字段对照表(根据提供的文档)
$field_mapping = array(
// 题目标题 => array(qid, type)
'日期' => array('0AmAeI', 'date'),
'单选项' => array('y0Rvqm', 'select'),
'图片和附件' => array('FOS5GT', 'file'),
'等级' => array('0wJSrH', 'star'),
'填写ID' => array('vwS7ci', 'input'),
'提交时间' => array('yjbQYS', 'date'),
'答题时间(秒)' => array('AA2kq8', 'input'),
'车牌号2' => array('7nAOz7', 'input'),
'车牌号' => array('NFJDDT', 'input'), // 注意:有两个车牌号字段,这里是第一个
'怎么称呼您' => array('eo513g', 'input'),
'车型' => array('xKFUcp', 'input'),
'车牌号' => array('t2u2i4', 'licensePlate'), // 注意:有两个车牌号字段,这里是第二个
'请输入手机号' => array('7jgfmh', 'input'),
'是否有车衣' => array('3j6opi', 'select'),
'有无自己撸车习惯' => array('ej48lk', 'select'),
'撸车经验' => array('1tnljp', 'select'),
'洗车频率' => array('x02g35', 'select'),
'请选择年龄段' => array('54dbo7', 'select'),
'提交者' => array('T9OiCe', 'contact'),
'自动编号' => array('rwxgkc', 'autoNum'),
'备注内容' => array('r3ft9n', 'input')
);
// 反向映射:qid => array(title, type)
$qid_mapping = array();
foreach ($field_mapping as $title => $info) {
$qid = $info[0];
$type = $info[1];
$qid_mapping[$qid] = array('title' => $title, 'type' => $type);
}
// 记录日志的函数
function log_message($message, $type = 'info') {
global $log_file;
$timestamp = date('Y-m-d H:i:s');
$log_entry = "[$timestamp] [$type] $message\n";
file_put_contents($log_file, $log_entry, FILE_APPEND);
}
// 获取请求方法
$method = $_SERVER['REQUEST_METHOD'];
// 获取请求数据
$request_body = file_get_contents('php://input');
// 记录所有请求
log_message("收到请求 - 方法: $method");
log_message("请求内容: $request_body");
// 处理验证请求
if ($method == 'POST') {
// 尝试解析请求体
$data = json_decode($request_body, true);
// 如果解析失败,可能是验证请求
if ($data === null) {
// 返回绑定码进行验证
log_message("返回绑定码进行验证");
echo json_encode(array('bind_code' => $bind_code));
exit;
}
// 处理实际的表单数据
log_message("处理表单数据");
log_message("原始表单数据: " . print_r($data, true), 'data');
// 解析WPS表单数据结构
$form_data = array(
'rid' => isset($data['rid']) ? $data['rid'] : '',
'form_id' => isset($data['formId']) ? $data['formId'] : '',
'form_title' => isset($data['formTitle']) ? $data['formTitle'] : '',
'aid' => isset($data['aid']) ? $data['aid'] : '',
'event_ts' => isset($data['eventTs']) ? $data['eventTs'] : 0,
'message_ts' => isset($data['messageTs']) ? $data['messageTs'] : 0,
'creator_id' => isset($data['creatorId']) ? $data['creatorId'] : '',
'creator_name' => isset($data['creatorName']) ? $data['creatorName'] : '',
'event' => isset($data['event']) ? $data['event'] : '',
'version' => isset($data['version']) ? $data['version'] : 1,
'answers' => array()
);
// 解析表单字段
if (isset($data['answerContents']) && is_array($data['answerContents'])) {
foreach ($data['answerContents'] as $content) {
$qid = isset($content['qid']) ? $content['qid'] : '';
$type = isset($content['type']) ? $content['type'] : '';
$title = isset($content['title']) ? $content['title'] : '';
$value = isset($content['value']) ? $content['value'] : '';
// 将数组值转换为字符串,便于处理
if (is_array($value)) {
$value = implode(', ', $value);
}
$field = array(
'qid' => $qid,
'type' => $type,
'title' => $title,
'value' => $value
);
$form_data['answers'][] = $field;
// 按标题索引存储,便于直接访问
if (!empty($title)) {
$form_data['answers_by_title'][$title] = $value;
}
// 按qid索引存储,便于精确访问
if (!empty($qid)) {
$form_data['answers_by_qid'][$qid] = $value;
}
// 使用字段对照表检查并标准化字段
if (!empty($qid) && isset($qid_mapping[$qid])) {
$standard_title = $qid_mapping[$qid]['title'];
$standard_type = $qid_mapping[$qid]['type'];
// 存储标准化字段
$form_data['standardized_answers'][$standard_title] = array(
'qid' => $qid,
'type' => $standard_type,
'title' => $title, // 保留原始标题
'value' => $value,
'standard_title' => $standard_title
);
}
}
}
log_message("解析后的表单数据: " . print_r($form_data, true), 'data');
// 在这里可以添加数据处理逻辑,例如:
// 1. 将数据保存到数据库
// 2. 发送通知
// 3. 其他业务逻辑
// 示例:访问特定字段
// 通过标题访问
if (isset($form_data['answers_by_title']['怎么称呼您'])) {
$name = $form_data['answers_by_title']['怎么称呼您'];
log_message("客户姓名: $name");
}
// 注意:由于有两个"车牌号"字段,通过标题访问可能获取到任意一个
// 建议使用qid或标准化字段访问
if (isset($form_data['answers_by_title']['车牌号'])) {
$license_plate = $form_data['answers_by_title']['车牌号'];
log_message("车牌号(通过标题): $license_plate");
}
// 通过qid精确访问特定的车牌号字段
if (isset($form_data['answers_by_qid']['t2u2i4'])) {
$license_plate_standard = $form_data['answers_by_qid']['t2u2i4'];
log_message("车牌号(通过qid t2u2i4 - 标准): $license_plate_standard");
}
// 通过标准化字段访问
if (isset($form_data['standardized_answers']['怎么称呼您'])) {
$name_standard = $form_data['standardized_answers']['怎么称呼您']['value'];
log_message("客户姓名(标准化): $name_standard");
}
if (isset($form_data['standardized_answers']['车型'])) {
$car_model = $form_data['standardized_answers']['车型']['value'];
log_message("车型: $car_model");
}
if (isset($form_data['standardized_answers']['请输入手机号'])) {
$phone = $form_data['standardized_answers']['请输入手机号']['value'];
log_message("手机号: $phone");
}
// 返回成功响应
$response = array(
'code' => 200,
'message' => '数据接收成功',
'received_at' => date('Y-m-d H:i:s'),
'processed_fields' => count($form_data['answers'])
);
// 将数据存储到数据库
store_form_data_to_db($form_data);
log_message("返回成功响应");
echo json_encode($response);
} else {
// 处理非POST请求
log_message("不支持的请求方法: $method", 'error');
http_response_code(405); // 方法不允许
echo json_encode(array('error' => '只支持POST请求'));
}
log_message("请求处理完成\n" . str_repeat('-', 50) . "\n");
// 将表单数据存储到数据库
function store_form_data_to_db($form_data) {
try {
$pdo = get_db_connection();
if (!$pdo) {
return false;
}
// 创建表(如果不存在)
create_wps_form_table($pdo);
// 准备SQL语句
$sql = "INSERT INTO wps_form_submissions (
rid, form_id, form_title, creator_id, create_time, update_time,
mobile, name, license_plate, date, time_slot, car_type,
wash_type, wash_packages, wash_services, additional_services, need_wax,
wax_type, cleaning_needs, interior_cleaning, exterior_cleaning, vip_member,
vip_number, payment_method, special_requests, technician, status
) VALUES (
:rid, :form_id, :form_title, :creator_id, :create_time, :update_time,
:mobile, :name, :license_plate, :date, :time_slot, :car_type,
:wash_type, :wash_packages, :wash_services, :additional_services, :need_wax,
:wax_type, :cleaning_needs, :interior_cleaning, :exterior_cleaning, :vip_member,
:vip_number, :payment_method, :special_requests, :technician, :status
)";
$stmt = $pdo->prepare($sql);
// 绑定参数
$stmt->bindValue(':rid', $form_data['rid']);
$stmt->bindValue(':form_id', $form_data['form_id']);
$stmt->bindValue(':form_title', $form_data['form_title']);
$stmt->bindValue(':creator_id', $form_data['creator_id']);
$stmt->bindValue(':create_time', date('Y-m-d H:i:s'));
$stmt->bindValue(':update_time', date('Y-m-d H:i:s'));
// 绑定表单字段值
$stmt->bindValue(':mobile', isset($form_data['answers_by_title']['请输入手机号']) ? $form_data['answers_by_title']['请输入手机号'] : '');
$stmt->bindValue(':name', isset($form_data['answers_by_title']['怎么称呼您']) ? $form_data['answers_by_title']['怎么称呼您'] : '');
// 处理车牌号字段(优先使用licensePlate类型的)
$license_plate = '';
if (isset($form_data['answers_by_qid']['t2u2i4'])) {
$license_plate = $form_data['answers_by_qid']['t2u2i4'];
} elseif (isset($form_data['answers_by_title']['车牌号'])) {
$license_plate = $form_data['answers_by_title']['车牌号'];
}
$stmt->bindValue(':license_plate', $license_plate);
// 日期处理
$date = isset($form_data['answers_by_title']['日期']) ? $form_data['answers_by_title']['日期'] : '';
$stmt->bindValue(':date', $date ? date('Y-m-d', strtotime($date)) : null);
// 其他字段
$stmt->bindValue(':time_slot', isset($form_data['answers_by_title']['时间段']) ? $form_data['answers_by_title']['时间段'] : '');
$stmt->bindValue(':car_type', isset($form_data['answers_by_title']['车型']) ? $form_data['answers_by_title']['车型'] : '');
$stmt->bindValue(':wash_type', isset($form_data['answers_by_title']['洗车类型']) ? $form_data['answers_by_title']['洗车类型'] : '');
$stmt->bindValue(':wash_packages', isset($form_data['answers_by_title']['洗车套餐']) ? $form_data['answers_by_title']['洗车套餐'] : '');
$stmt->bindValue(':wash_services', isset($form_data['answers_by_title']['洗车服务']) ? $form_data['answers_by_title']['洗车服务'] : '');
$stmt->bindValue(':additional_services', isset($form_data['answers_by_title']['额外服务']) ? $form_data['answers_by_title']['额外服务'] : '');
$stmt->bindValue(':need_wax', isset($form_data['answers_by_title']['是否需要打蜡']) ? $form_data['answers_by_title']['是否需要打蜡'] : '');
$stmt->bindValue(':wax_type', isset($form_data['answers_by_title']['蜡的类型']) ? $form_data['answers_by_title']['蜡的类型'] : '');
$stmt->bindValue(':cleaning_needs', isset($form_data['answers_by_title']['清洁需求']) ? $form_data['answers_by_title']['清洁需求'] : '');
$stmt->bindValue(':interior_cleaning', isset($form_data['answers_by_title']['是否需要内饰清洁']) ? $form_data['answers_by_title']['是否需要内饰清洁'] : '');
$stmt->bindValue(':exterior_cleaning', isset($form_data['answers_by_title']['是否需要外观清洁']) ? $form_data['answers_by_title']['是否需要外观清洁'] : '');
$stmt->bindValue(':vip_member', isset($form_data['answers_by_title']['是否为VIP会员']) ? $form_data['answers_by_title']['是否为VIP会员'] : '');
$stmt->bindValue(':vip_number', isset($form_data['answers_by_title']['VIP卡号']) ? $form_data['answers_by_title']['VIP卡号'] : '');
$stmt->bindValue(':payment_method', isset($form_data['answers_by_title']['支付方式']) ? $form_data['answers_by_title']['支付方式'] : '');
$stmt->bindValue(':special_requests', isset($form_data['answers_by_title']['特殊要求']) ? $form_data['answers_by_title']['特殊要求'] : '');
$stmt->bindValue(':technician', isset($form_data['answers_by_title']['指定技师']) ? $form_data['answers_by_title']['指定技师'] : '');
$stmt->bindValue(':status', 'pending');
// 执行SQL
$stmt->execute();
log_message("表单数据成功存储到数据库,插入ID: " . $pdo->lastInsertId());
return true;
} catch (Exception $e) {
log_message("数据存储失败: " . $e->getMessage(), 'error');
return false;
}
}
// 创建WPS表单数据表
function create_wps_form_table($pdo) {
try {
$sql = "CREATE TABLE IF NOT EXISTS wps_form_submissions (
id INT AUTO_INCREMENT PRIMARY KEY,
rid VARCHAR(50) NOT NULL COMMENT '表单提交ID',
form_id VARCHAR(50) NOT NULL COMMENT '表单ID',
form_title VARCHAR(255) NOT NULL COMMENT '表单标题',
creator_id VARCHAR(50) NOT NULL COMMENT '创建者ID',
create_time DATETIME NOT NULL COMMENT '创建时间',
update_time DATETIME NOT NULL COMMENT '更新时间',
mobile VARCHAR(20) COMMENT '请输入手机号',
name VARCHAR(255) COMMENT '怎么称呼您',
license_plate VARCHAR(20) COMMENT '车牌号',
date DATE COMMENT '日期',
time_slot VARCHAR(20) COMMENT '时间段',
car_type VARCHAR(50) COMMENT '车型',
wash_type VARCHAR(100) COMMENT '洗车类型',
wash_packages VARCHAR(255) COMMENT '洗车套餐',
wash_services TEXT COMMENT '洗车服务',
additional_services TEXT COMMENT '额外服务',
need_wax VARCHAR(10) COMMENT '是否需要打蜡',
wax_type VARCHAR(50) COMMENT '蜡的类型',
cleaning_needs TEXT COMMENT '清洁需求',
interior_cleaning VARCHAR(10) COMMENT '是否需要内饰清洁',
exterior_cleaning VARCHAR(10) COMMENT '是否需要外观清洁',
vip_member VARCHAR(10) COMMENT '是否为VIP会员',
vip_number VARCHAR(50) COMMENT 'VIP卡号',
payment_method VARCHAR(50) COMMENT '支付方式',
special_requests TEXT COMMENT '特殊要求',
technician VARCHAR(50) COMMENT '指定技师',
status VARCHAR(20) DEFAULT 'pending' COMMENT '状态',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
INDEX idx_rid (rid),
INDEX idx_license_plate (license_plate),
INDEX idx_date (date),
INDEX idx_mobile (mobile)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
$pdo->exec($sql);
return true;
} catch (Exception $e) {
log_message("创建表失败: " . $e->getMessage(), 'error');
return false;
}
}
?>