本文目录导读:

在PHP项目中接收硬件设备上报的数据,通常涉及构建一个HTTP API端点,硬件设备(如传感器、工业控制器、物联网网关)会通过POST请求将数据发送到这个端点。
下面是一个完整的技术方案,涵盖接收、验证、存储和安全防护。
核心流程
- 设备发送请求:设备通过HTTP POST请求,将数据以JSON、XML或表单格式发送到指定URL。
- PHP接收请求:脚本读取请求体,解析数据格式。
- 数据验证与处理:验证签名、时间戳、数据完整性。
- 存储与响应:将数据存入数据库,并给设备返回
200 OK或失败状态码。
基础接收示例(JSON格式)
这是最常见的方式,设备以 Content-Type: application/json 发送数据。
<?php
// receive.php
// 设置响应类型为 JSON
header('Content-Type: application/json; charset=utf-8');
// 1. 获取原始请求体(不依赖 $_POST,因为 $_POST 只解析表单数据)
$input = file_get_contents('php://input');
// 2. 记录日志(调试和审计用)
$logFile = '/var/log/device_data.log';
file_put_contents($logFile, date('Y-m-d H:i:s') . " - Received: " . $input . PHP_EOL, FILE_APPEND | LOCK_EX);
// 3. 解析 JSON 数据
$data = json_decode($input, true);
// 4. 校验 JSON 是否有效
if (json_last_error() !== JSON_ERROR_NONE) {
http_response_code(400);
echo json_encode(['code' => 400, 'message' => 'Invalid JSON format']);
exit;
}
// 5. 检查必要字段(假设设备ID和数据是必须的)
if (empty($data['device_id']) || !isset($data['temperature'])) {
http_response_code(400);
echo json_encode(['code' => 400, 'message' => 'Missing required fields: device_id or temperature']);
exit;
}
// 6. 数据清洗与类型转换(重要:防止 SQL 注入或 XSS)
$deviceId = htmlspecialchars(strip_tags(trim($data['device_id'])));
$temperature = floatval($data['temperature']); // 强制转为浮点数
$humidity = isset($data['humidity']) ? floatval($data['humidity']) : null;
$timestamp = isset($data['timestamp']) ? $data['timestamp'] : date('Y-m-d H:i:s');
// 7. 这里可以存储到数据库(下面详述)
// saveToDatabase($deviceId, $temperature, $humidity, $timestamp);
// 8. 成功响应
http_response_code(200);
echo json_encode(['code' => 200, 'message' => 'Data received successfully']);
?>
接收其他格式
表单数据(application/x-www-form-urlencoded)
如果设备使用类似 curl -d "device_id=123&temp=25.5" 发送,PHP 会直接解析到 $_POST。
$deviceId = $_POST['device_id'] ?? ''; $temperature = $_POST['temp'] ?? '';
XML 数据
$xml = simplexml_load_string(file_get_contents('php://input'));
$deviceId = (string)$xml->device_id;
$temperature = (float)$xml->temperature;
安全性保障
硬件设备上报接口容易被利用,必须加入安全机制。
A. 签名验证(推荐)
设备在发送数据时,使用预共享密钥(Secret Key)生成签名,服务端用同样算法验证。
// 假设预共享密钥
$secretKey = 'your_shared_secret_abc123';
// 设备发送时需附带 sign 和 timestamp
// devices sends: {"device_id":"001","temperature":25.5,"timestamp":1700000000,"sign":"xxxxx"}
$signature = $data['sign'] ?? '';
$timestamp = $data['timestamp'] ?? 0;
// 1. 防重放攻击:检查时间戳是否在允许窗口内(±5分钟)
if (abs(time() - $timestamp) > 300) {
http_response_code(403);
echo json_encode(['code' => 403, 'message' => 'Timestamp expired']);
exit;
}
// 2. 计算签名(需要与设备端算法一致)
$params = [
'device_id' => $data['device_id'],
'temperature' => $data['temperature'],
'timestamp' => $timestamp
];
ksort($params); // 按键名排序
$signStr = http_build_query($params) . $secretKey;
$expectedSign = md5($signStr); // 或 sha256
if ($signature !== $expectedSign) {
http_response_code(403);
echo json_encode(['code' => 403, 'message' => 'Invalid signature']);
exit;
}
B. IP白名单
如果设备IP固定,可在入口处过滤。
$allowedIps = ['192.168.1.100', '10.0.0.50'];
if (!in_array($_SERVER['REMOTE_ADDR'], $allowedIps)) {
http_response_code(403);
exit('Access denied');
}
C. 限流(Rate Limiting)
使用 Redis 记录每个设备最近1分钟内的请求次数,超过阈值则拒绝。
D. 仅允许 POST 请求
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
exit('Method Not Allowed');
}
数据库存储示例(MySQL)
创建数据表:
CREATE TABLE device_reports (
id INT AUTO_INCREMENT PRIMARY KEY,
device_id VARCHAR(50) NOT NULL,
temperature DECIMAL(5,2),
humidity DECIMAL(5,2),
reported_at DATETIME,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_device_time (device_id, reported_at)
);
使用 PDO 安全插入:
function saveToDatabase($deviceId, $temperature, $humidity, $timestamp) {
$host = '127.0.0.1';
$db = 'iot_db';
$user = 'iot_user';
$pass = 'secure_password';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
$stmt = $pdo->prepare(
"INSERT INTO device_reports (device_id, temperature, humidity, reported_at)
VALUES (:device_id, :temperature, :humidity, :reported_at)"
);
$stmt->execute([
':device_id' => $deviceId,
':temperature' => $temperature,
':humidity' => $humidity,
':reported_at' => date('Y-m-d H:i:s', $timestamp)
]);
} catch (PDOException $e) {
// 记录错误日志,但不要暴露给客户端
error_log('Database error: ' . $e->getMessage());
http_response_code(500);
echo json_encode(['code'=>500, 'message'=>'Internal server error']);
exit;
}
}
完整示例文件结构
api/
├── receive.php # 主接收入口(上面代码集合)
├── config.php # 数据库配置、密钥、IP白名单
├── lib/
│ ├── Auth.php # 签名验证类
│ └── Logger.php # 日志记录类
└── .htaccess # Apache 重写规则(可选)
Nginx 配置示例:
location /api/receive.php {
# 限制请求大小,防止大包攻击
client_max_body_size 10k;
# 如果使用 PHP-FPM
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
include fastcgi_params;
}
常见问题与解决建议
| 问题 | 原因 | 解决方案 |
|---|---|---|
接收不到 $_POST |
设备发送的是 JSON/XML 而非表单数据 | 改用 file_get_contents('php://input') |
| 请求超时 | 设备数量多或处理慢 | 异步处理:先将数据写入消息队列(RabbitMQ/Kafka),再由后台进程消费写入数据库 |
| 数据重复 | 设备重试机制导致同一数据保存多次 | 数据库加唯一索引(如设备ID+时间戳)或使用 INSERT ... ON DUPLICATE KEY UPDATE |
| 安全隐患 | 未验证签名、未限流 | 实施上述安全措施,并定期更换密钥 |
总结步骤
- 确定数据格式:推荐 JSON,易于解析和扩展。
- 安全优先:必加签名验证和IP白名单,使用HTTPS。
- 日志记录:记录所有原始请求和异常,便于调试和审计。
- 数据库存储:使用 PDO 参数化查询,防止注入。
- 响应明确:总是返回
200表示成功,4xx表示客户端错误,5xx表示服务端错误。
如果你能提供更具体的细节(设备上报频率、数据量大小、协议类型等),我可以给出更针对性的优化建议,比如使用长连接(WebSocket/CoAP)或消息队列方案。