17 Commits

Author SHA1 Message Date
wsh5485 2fb3c70f87 合并SQL文件并移除webhook中的建表代码 2025-12-05 17:51:35 +08:00
wsh5485 5c9f4c1a4f 添加自动编号字段支持:在数据库表和数据存储逻辑中添加auto_number字段 2025-12-05 17:44:57 +08:00
wsh5485 b2ad942b2c 添加对WPS绑定验证请求(bind事件)的处理 2025-12-05 17:38:10 +08:00
wsh5485 0d20bc09bc 修复MySQL 1293错误:将updated_at字段改为DATETIME类型并移除CURRENT_TIMESTAMP属性 2025-12-05 17:30:15 +08:00
wsh5485 8d1612b956 修复MySQL 5.7兼容性问题:将DATETIME字段改回TIMESTAMP字段,使用兼容的默认值 2025-12-05 17:28:28 +08:00
wsh5485 3893065393 更新create_wps_form_table.sql文件,使其与webhook.php中的表结构一致 2025-12-05 17:26:45 +08:00
wsh5485 bafbc8344b 修复webhook.php中的TIMESTAMP限制问题:将TIMESTAMP字段改为DATETIME字段 2025-12-05 17:23:06 +08:00
wsh5485 b4ab3c625a 修复创建表失败的问题:解决多个TIMESTAMP列使用CURRENT_TIMESTAMP的错误 2025-12-05 17:19:08 +08:00
wsh5485 678c79c5a7 fix(test): 修正测试页面中日志文件路径错误
将测试页面中的日志文件路径从相对路径改为绝对路径,确保能正确加载日志文件
2025-12-05 17:17:21 +08:00
wsh5485 016a5bf6b9 fix: 确保日志目录存在以避免写入失败
在写入日志前检查目录是否存在,不存在时自动创建,防止因目录缺失导致的日志写入失败
2025-12-05 17:11:42 +08:00
wsh5485 1a196f968e test: 更新测试网页中的webhook URL为生产环境地址 2025-12-05 17:10:21 +08:00
wsh5485 6a43cec22e 添加配置文件模板config.php.env和安装文档INSTALL.md,从Git中移除config.php 2025-12-05 17:06:43 +08:00
wsh5485 93dfc4e5cd fix: 更新数据库配置并添加404页面
更新生产环境数据库连接配置
添加默认nginx 404错误页面
2025-12-05 17:03:26 +08:00
wsh5485 537a95989e refactor(webhook): 统一使用licensePlate类型的车牌号字段
移除对多个车牌号字段的支持,仅保留qid为t2u2i4的licensePlate类型字段
2025-12-05 16:28:18 +08:00
wsh5485 88346ca1ec refactor(webhook): 根据实际WPS表单字段调整数据库结构和映射
更新数据库表结构和字段映射以匹配实际WPS表单字段
移除不再使用的字段,仅保留实际存在的表单字段
调整SQL语句和绑定逻辑以匹配新字段结构
2025-12-05 15:31:02 +08:00
wsh5485 5d320e0ccf refactor: 将日志文件路径移动到log目录下
修改日志文件存储位置,便于统一管理日志文件
2025-12-05 14:00:35 +08:00
wsh5485 5bbb0877d5 feat: 添加WPS表单处理功能及相关测试
实现WPS表单Webhook接收端点,包括数据解析、验证和存储功能
添加数据库表创建脚本和测试用例
包含Webhook测试页面和日志功能
2025-12-05 13:58:57 +08:00
9 changed files with 960 additions and 7 deletions
+7
View File
@@ -0,0 +1,7 @@
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>
+92
View File
@@ -0,0 +1,92 @@
# 洗车预约系统安装指南
## 1. 环境要求
- PHP 5.6 或更高版本
- MySQL 5.5 或更高版本
- Web服务器(如Apache、Nginx等)
## 2. 安装步骤
### 2.1 克隆项目
首先,将项目克隆到您的本地或服务器上:
```bash
git clone [项目仓库地址]
cd carwash_order
```
### 2.2 配置数据库
1. 创建数据库:
- 使用MySQL客户端(如phpMyAdmin、Navicat等)创建一个新的数据库
- 数据库名称建议使用 `carwash_booking`
2. 导入数据库结构:
- 执行 `carwash_db.sql` 文件,创建所需的表结构和初始数据
### 2.3 配置数据库连接
1. 复制配置文件模板:
-`config.php.env` 重命名为 `config.php`
2. 修改数据库连接信息:
- 使用文本编辑器打开 `config.php` 文件
- 根据您的数据库配置修改以下参数:
```php
// 数据库主机地址
$host = 'localhost'; // 通常为localhost,如使用远程数据库请填写IP地址
// 数据库用户名
$username = 'your_username'; // 替换为您的MySQL用户名
// 数据库密码
$password = 'your_password'; // 替换为您的MySQL密码
// 数据库名称
$database = 'carwash_booking'; // 替换为您创建的数据库名称
```
### 2.4 部署到Web服务器
- 将项目文件上传到Web服务器的根目录或虚拟主机的文档根目录
- 确保Web服务器具有对项目文件的读取权限
- 确保PHP可以执行项目中的PHP文件
## 3. 访问系统
在浏览器中输入您的域名或IP地址,即可访问洗车预约系统。
例如:
```
http://localhost/carwash_order/
```
## 4. 常见问题
### 4.1 数据库连接失败
- 检查 `config.php` 文件中的数据库连接信息是否正确
- 确保MySQL服务正在运行
- 确保数据库用户具有足够的权限
### 4.2 页面显示错误
- 检查PHP版本是否符合要求
- 检查Web服务器的错误日志
- 确保所有项目文件都已正确上传
## 5. 其他配置
- 如果您需要修改时区设置,可以在PHP文件中添加:`date_default_timezone_set('Asia/Shanghai');`
- 如果您需要修改会话超时时间,可以在PHP文件中添加:`ini_set('session.gc_maxlifetime', 3600);`
## 6. 注意事项
- 不要将 `config.php` 文件提交到Git仓库,该文件包含敏感的数据库连接信息
- 定期备份数据库和项目文件
- 确保使用强密码保护数据库和服务器
如有其他问题,请参考项目的README.md文件或联系技术支持。
-7
View File
@@ -1,7 +0,0 @@
<?php
// config.php - 数据库配置文件
$host = 'localhost';
$username = 'root';
$password = '';
$database = 'carwash_booking';
?>
+16
View File
@@ -0,0 +1,16 @@
<?php
// config.php.env - 数据库配置文件模板
// 请将此文件重命名为 config.php 并根据实际情况修改以下配置
// 数据库主机地址
$host = 'localhost';
// 数据库用户名
$username = 'your_username';
// 数据库密码
$password = 'your_password';
// 数据库名称
$database = 'carwash_booking';
?>
+30
View File
@@ -0,0 +1,30 @@
-- 创建WPS表单数据表
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 '车型',
has_car_coat VARCHAR(10) COMMENT '是否有车衣',
car_wash_habit VARCHAR(10) COMMENT '有无自己撸车习惯',
car_wash_experience VARCHAR(50) COMMENT '撸车经验',
wash_frequency VARCHAR(50) COMMENT '洗车频率',
age_group VARCHAR(20) COMMENT '请选择年龄段',
remarks TEXT COMMENT '备注内容',
auto_number VARCHAR(50) COMMENT '自动编号',
status VARCHAR(20) DEFAULT 'pending' COMMENT '状态',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
updated_at DATETIME NOT NULL COMMENT '记录更新时间',
INDEX idx_rid (rid),
INDEX idx_license_plate (license_plate),
INDEX idx_date (date),
INDEX idx_mobile (mobile)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+114
View File
@@ -0,0 +1,114 @@
-- carwash_db.sql - 数据库创建脚本
CREATE DATABASE IF NOT EXISTS carwash_booking;
USE carwash_booking;
-- 创建VIP客户表
CREATE TABLE IF NOT EXISTS vip_customers (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
phone VARCHAR(20) NOT NULL COMMENT '联系电话',
car_model VARCHAR(50) COMMENT '车型',
car_number VARCHAR(20) COMMENT '车牌号',
email VARCHAR(100) COMMENT '邮箱地址',
birthday DATE COMMENT '生日',
notes TEXT COMMENT '备注信息',
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL
);
-- 更新VIP客户表的索引结构
-- 1. 移除phone字段的UNIQUE约束(如果存在)
ALTER TABLE vip_customers DROP INDEX IF EXISTS phone;
-- 2. 添加phone和car_number的复合唯一索引
ALTER TABLE vip_customers ADD UNIQUE INDEX idx_phone_car_number (phone, car_number);
-- 3. 验证索引添加成功
SHOW INDEX FROM vip_customers;
-- 创建套餐表
CREATE TABLE IF NOT EXISTS packages (
id INT AUTO_INCREMENT PRIMARY KEY,
package_name VARCHAR(100) NOT NULL,
description TEXT,
base_duration INT NOT NULL COMMENT '基础服务时长(分钟)',
price DECIMAL(10,2) NOT NULL,
services TEXT NOT NULL COMMENT '包含的服务项目(用逗号分隔)',
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL
);
-- 修改预约表支持时间段、会员类型和来源
DROP TABLE IF EXISTS bookings;
CREATE TABLE IF NOT EXISTS bookings (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_name VARCHAR(100) NOT NULL,
phone VARCHAR(20) NOT NULL,
car_model VARCHAR(50) NOT NULL,
car_number VARCHAR(20) NOT NULL,
package_id INT,
custom_services TEXT COMMENT '自定义服务内容',
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL,
duration INT NOT NULL COMMENT '实际服务时长(分钟)',
total_price DECIMAL(10,2) NOT NULL,
notes TEXT,
status ENUM('待确认', '已确认', '进行中', '已完成', '已取消') DEFAULT '待确认',
member_type ENUM('普通客户', 'VIP会员') DEFAULT '普通客户' COMMENT '会员类型',
source ENUM('抖音', '微信', '快手', '朋友介绍', '其他') DEFAULT '其他' COMMENT '客户来源',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL,
FOREIGN KEY (package_id) REFERENCES packages(id) ON DELETE SET NULL
);
-- 创建WPS表单数据表
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 '车型',
has_car_coat VARCHAR(10) COMMENT '是否有车衣',
car_wash_habit VARCHAR(10) COMMENT '有无自己撸车习惯',
car_wash_experience VARCHAR(50) COMMENT '撸车经验',
wash_frequency VARCHAR(50) COMMENT '洗车频率',
age_group VARCHAR(20) COMMENT '请选择年龄段',
remarks TEXT COMMENT '备注内容',
auto_number VARCHAR(50) COMMENT '自动编号',
status VARCHAR(20) DEFAULT 'pending' COMMENT '状态',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
updated_at DATETIME NOT NULL COMMENT '记录更新时间',
INDEX idx_rid (rid),
INDEX idx_license_plate (license_plate),
INDEX idx_date (date),
INDEX idx_mobile (mobile)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 插入示例套餐数据
INSERT INTO packages (package_name, description, base_duration, price, services) VALUES
('基础洗车', '基础外观清洗', 30, 50.00, '外观冲洗,泡沫清洁,内饰吸尘'),
('精洗套餐', '全面深度清洗', 90, 150.00, '外观精洗,内饰深度清洁,轮胎清洁,打蜡'),
('VIP套餐', '顶级豪华洗护', 180, 300.00, '全套精洗,抛光打蜡,内饰护理,发动机清洁,真皮护理');
-- 插入示例预约数据(包含会员类型和来源)
INSERT INTO bookings (customer_name, phone, car_model, car_number, package_id, start_time, end_time, duration, total_price, notes, member_type, source) VALUES
('张三', '13800138001', '大众朗逸', '京A12345', 1, '2024-12-20 09:00:00', '2024-12-20 09:30:00', 30, 50.00, '第一次来', '普通客户', '抖音'),
('李四', '13800138002', '丰田凯美瑞', '京B67890', 2, '2024-12-20 10:30:00', '2024-12-20 12:00:00', 90, 150.00, '需要特别清洗内饰', 'VIP会员', '朋友介绍'),
('王五', '13800138003', '宝马X3', '沪C88888', 3, '2024-12-21 14:00:00', '2024-12-21 17:00:00', 180, 300.00, 'VIP客户,定期保养', 'VIP会员', '微信'),
('赵六', '13800138004', '本田雅阁', '粤A66666', 1, '2024-12-21 09:30:00', '2024-12-21 10:00:00', 30, 50.00, '快手看到广告来的', '普通客户', '快手');
-- 插入示例VIP客户数据
INSERT INTO vip_customers (customer_name, phone, car_model, car_number, email, birthday, notes) VALUES
('张总', '13900139001', '奔驰S500', '京V88888', 'zhang@example.com', '1980-05-15', '长期VIP客户,商务人士'),
('王女士', '13900139002', '奥迪A6L', '京A66666', 'wang@example.com', '1985-03-20', '女企业家,每月定期保养'),
('李先生', '13900139003', 'BMW X5', '沪B99999', 'li@example.com', '1978-11-10', '房地产公司老板,对服务要求较高');
+124
View File
@@ -0,0 +1,124 @@
<?php
// 测试数据库连接和表创建功能
require_once 'config.php';
// 记录日志的函数
function log_test_message($message, $type = 'info') {
$log_file = 'test_db.log';
$timestamp = date('Y-m-d H:i:s');
$log_entry = "[$timestamp] [$type] $message\n";
file_put_contents($log_file, $log_entry, FILE_APPEND);
echo $log_entry;
}
echo "=== 测试数据库连接和表创建功能 ===\n";
// 测试数据库连接
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);
log_test_message("数据库连接成功");
} catch(PDOException $e) {
log_test_message("数据库连接失败: " . $e->getMessage(), 'error');
exit(1);
}
// 测试创建表
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);
log_test_message("表创建成功");
} catch(PDOException $e) {
log_test_message("表创建失败: " . $e->getMessage(), 'error');
exit(1);
}
// 测试插入数据
try {
$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
) VALUES (
:rid, :form_id, :form_title, :creator_id, :create_time, :update_time,
:mobile, :name, :license_plate, :date, :time_slot, :car_type
)";
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':rid', 'test_rid_' . time());
$stmt->bindValue(':form_id', 'test_form_id');
$stmt->bindValue(':form_title', '测试表单');
$stmt->bindValue(':creator_id', 'test_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', '13800138000');
$stmt->bindValue(':name', '测试用户');
$stmt->bindValue(':license_plate', '京A12345');
$stmt->bindValue(':date', date('Y-m-d'));
$stmt->bindValue(':time_slot', '14:00-15:00');
$stmt->bindValue(':car_type', '轿车');
$stmt->execute();
$insert_id = $pdo->lastInsertId();
log_test_message("测试数据插入成功,ID: " . $insert_id);
} catch(PDOException $e) {
log_test_message("测试数据插入失败: " . $e->getMessage(), 'error');
exit(1);
}
// 测试查询数据
try {
$sql = "SELECT * FROM wps_form_submissions ORDER BY id DESC LIMIT 1";
$stmt = $pdo->query($sql);
$row = $stmt->fetch();
if ($row) {
log_test_message("测试数据查询成功");
log_test_message("查询结果: " . print_r($row, true));
} else {
log_test_message("测试数据查询失败: 没有找到数据", 'error');
}
} catch(PDOException $e) {
log_test_message("测试数据查询失败: " . $e->getMessage(), 'error');
exit(1);
}
echo "\n=== 所有测试完成 ===\n";
+263
View File
@@ -0,0 +1,263 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WPS表单Webhook测试</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.container {
background-color: #f5f5f5;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
.test-section {
margin: 20px 0;
padding: 15px;
background-color: white;
border-radius: 4px;
border: 1px solid #ddd;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover {
background-color: #45a049;
}
.response {
margin-top: 10px;
padding: 10px;
background-color: #f0f0f0;
border-radius: 4px;
white-space: pre-wrap;
font-family: monospace;
}
.log-section {
margin-top: 30px;
}
#log-content {
height: 300px;
overflow-y: auto;
background-color: #2d2d2d;
color: #f0f0f0;
padding: 10px;
border-radius: 4px;
font-family: monospace;
}
</style>
</head>
<body>
<div class="container">
<h1>WPS表单Webhook测试工具</h1>
<div class="test-section">
<h2>1. 验证请求测试</h2>
<p>模拟WPS表单的验证请求,Webhook应返回绑定码</p>
<button onclick="testVerification()">发送验证请求</button>
<div class="response" id="verification-response"></div>
</div>
<div class="test-section">
<h2>2. 表单数据测试</h2>
<p>模拟WPS表单发送数据,Webhook应接收并处理数据</p>
<button onclick="testFormData()">发送表单数据</button>
<div class="response" id="form-data-response"></div>
</div>
<div class="log-section">
<h2>3. Webhook日志</h2>
<button onclick="loadLog()">加载日志</button>
<button onclick="clearLog()">清空日志</button>
<div id="log-content"></div>
</div>
</div>
<script>
// Webhook URL
const webhookUrl = 'http://carwash.com/webhook.php';
// 发送验证请求
async function testVerification() {
const responseDiv = document.getElementById('verification-response');
responseDiv.innerHTML = '发送中...';
try {
const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: '' // 空请求体,模拟验证请求
});
const data = await response.json();
responseDiv.innerHTML = '响应: ' + JSON.stringify(data, null, 2);
} catch (error) {
responseDiv.innerHTML = '错误: ' + error.message;
}
}
// 发送表单数据
async function testFormData() {
const responseDiv = document.getElementById('form-data-response');
responseDiv.innerHTML = '发送中...';
// 模拟WPS实际表单数据格式
const formData = {
"rid": "lAFP6N1Tap",
"formId": "20251205123619412736007",
"formTitle": "张老师撸车(私家车库)预约撸车表单",
"aid": "20251205131620432762589",
"eventTs": Date.now(),
"messageTs": Date.now(),
"creatorId": "344573037",
"creatorName": "吴展鹏",
"event": "create_answer",
"version": 2,
"answerContents": [
{
"qid": "eo513g",
"type": "input",
"title": "怎么称呼您",
"value": "吴展鹏"
},
{
"qid": "xKFUcp",
"type": "input",
"title": "车型",
"value": "比亚迪宋PRODMi"
},
{
"qid": "NFJDDT",
"type": "input",
"title": "车牌号",
"value": "新AF10365-普通输入"
},
{
"qid": "t2u2i4",
"type": "licensePlate",
"title": "车牌号",
"value": "新AF10365-车牌专用"
},
{
"qid": "7jgfmh",
"type": "telphone",
"title": "请输入手机号",
"value": "18699627661"
},
{
"qid": "3j6opi",
"type": "select",
"title": "是否有车衣",
"value": ["否,无车衣"]
},
{
"qid": "ej48lk",
"type": "select",
"title": "有无自己撸车习惯",
"value": ["有"]
},
{
"qid": "1tnljp",
"type": "select",
"title": "撸车经验",
"value": ["我是老司机啦"]
},
{
"qid": "x02g35",
"type": "select",
"title": "洗车频率",
"value": ["每2周一次(每月1-2次)"]
},
{
"qid": "54dbo7",
"type": "select",
"title": "请选择年龄段",
"value": ["26~30"]
},
{
"qid": "rwxgkc",
"type": "autoNum",
"title": "自动编号",
"value": "zlslc202512002"
},
{
"qid": "r3ft9n",
"type": "input",
"title": "备注内容",
"value": "谢谢"
}
]
};
try {
const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData)
});
const data = await response.json();
responseDiv.innerHTML = '响应: ' + JSON.stringify(data, null, 2);
} catch (error) {
responseDiv.innerHTML = '错误: ' + error.message;
}
}
// 加载日志
async function loadLog() {
const logContent = document.getElementById('log-content');
logContent.innerHTML = '加载中...';
try {
const response = await fetch('/log/wps_form_webhook.log');
if (response.ok) {
const text = await response.text();
logContent.innerHTML = text;
} else {
logContent.innerHTML = '无法加载日志文件: ' + response.statusText;
}
} catch (error) {
logContent.innerHTML = '错误: ' + error.message;
}
}
// 清空日志
async function clearLog() {
const logContent = document.getElementById('log-content');
try {
const response = await fetch('webhook.php?action=clear_log', {
method: 'POST'
});
if (response.ok) {
logContent.innerHTML = '日志已清空';
} else {
logContent.innerHTML = '清空日志失败';
}
} catch (error) {
logContent.innerHTML = '错误: ' + error.message;
}
}
</script>
</body>
</html>
+314
View File
@@ -0,0 +1,314 @@
<?php
// WPS表单Webhook接收端点
// 设置响应头
header('Content-Type: application/json; charset=utf-8');
// 绑定码(根据WPS表单要求提供)
$bind_code = '20251205123619412736007';
// 日志文件路径
$log_file = 'log/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;
}
}
// 字段对照表(根据WPS表单实际字段)
$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'),
'车牌号' => array('t2u2i4', 'licensePlate'), // 只使用这个车牌号字段(qid: t2u2i4, type: licensePlate
'车牌号2' => array('7nAOz7', 'input'), // 不使用
'车牌号(普通)' => array('NFJDDT', 'input'), // 不使用
'怎么称呼您' => array('eo513g', 'input'),
'车型' => array('xKFUcp', 'input'),
'请输入手机号' => 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";
// 检查日志目录是否存在,如果不存在则创建
$log_dir = dirname($log_file);
if (!is_dir($log_dir)) {
mkdir($log_dir, 0755, true);
}
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;
}
// 检查是否是绑定验证请求
if (isset($data['event']) && $data['event'] === 'bind') {
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;
}
// 准备SQL语句(只包含实际WPS表单存在的字段)
$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,
has_car_coat, car_wash_habit, car_wash_experience, wash_frequency,
age_group, remarks, auto_number, status, updated_at
) VALUES (
:rid, :form_id, :form_title, :creator_id, :create_time, :update_time,
:mobile, :name, :license_plate, :date, :time_slot, :car_type,
:has_car_coat, :car_wash_habit, :car_wash_experience, :wash_frequency,
:age_group, :remarks, :auto_number, :status, :updated_at
)";
$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(':updated_at', 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']['怎么称呼您'] : '');
// 处理车牌号字段(只使用qid为t2u2i4、type为licensePlate的车牌号字段)
$license_plate = '';
if (isset($form_data['answers_by_qid']['t2u2i4'])) {
$license_plate = $form_data['answers_by_qid']['t2u2i4'];
}
$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(':has_car_coat', isset($form_data['answers_by_title']['是否有车衣']) ? $form_data['answers_by_title']['是否有车衣'] : '');
$stmt->bindValue(':car_wash_habit', isset($form_data['answers_by_title']['有无自己撸车习惯']) ? $form_data['answers_by_title']['有无自己撸车习惯'] : '');
$stmt->bindValue(':car_wash_experience', isset($form_data['answers_by_title']['撸车经验']) ? $form_data['answers_by_title']['撸车经验'] : '');
$stmt->bindValue(':wash_frequency', isset($form_data['answers_by_title']['洗车频率']) ? $form_data['answers_by_title']['洗车频率'] : '');
$stmt->bindValue(':age_group', isset($form_data['answers_by_title']['请选择年龄段']) ? $form_data['answers_by_title']['请选择年龄段'] : '');
$stmt->bindValue(':remarks', isset($form_data['answers_by_title']['备注内容']) ? $form_data['answers_by_title']['备注内容'] : '');
$stmt->bindValue(':auto_number', 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;
}
}
?>