Files
kitchen/docs/api/kitchen.php
2026-04-17 07:00:26 +08:00

611 lines
17 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 🍽️ 点餐助手API接口
*
* 支持点单CRUD操作JSON文件存储SSE实时推送
* 独立API文件直接访问: /api/kitchen.php?act=xxx
*
* 接口列表:
* GET ?act=create 创建点单POST优先
* POST ?act=create 创建点单JSON body
* GET ?act=get&id=xxx 获取点单
* POST ?act=update 更新点单
* GET ?act=list 点单列表
* GET ?act=delete&id=xxx 删除点单
* GET ?act=cleanup 清理过期数据
* GET ?act=index 接口说明
*
* @file kitchen.php
* @date 2026-04-17
* @version 1.0.0
* @desc 点餐助手API支持CRUD、SSE推送、过期清理
* @lastUpdate 2026-04-17 新增clear_all接口确保只清理kitchen数据
*/
// ─── CORS预检请求处理 ───
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Max-Age: 86400');
http_response_code(204);
exit;
}
$startTime = microtime(true);
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
// ─── POST请求参数解析 ───
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
$postData = array();
if (stripos($contentType, 'application/json') !== false) {
$rawBody = file_get_contents('php://input');
$json = json_decode($rawBody, true);
if (is_array($json)) {
$postData = $json;
}
} else {
$postData = $_POST;
}
foreach ($postData as $key => $value) {
if (!isset($_GET[$key])) {
$_GET[$key] = $value;
}
}
}
$act = strtolower(trim($_GET['act'] ?? 'index'));
// ─── 数据目录配置 ───
$dataDir = dirname(__FILE__) . '/cache/kitchen/';
if (!is_dir($dataDir)) {
if (!@mkdir($dataDir, 0755, true)) {
$tmpDir = sys_get_temp_dir() . '/kitchen/';
if (!is_dir($tmpDir)) {
@mkdir($tmpDir, 0755, true);
}
$dataDir = $tmpDir;
}
}
if (!is_writable($dataDir)) {
$tmpDir = sys_get_temp_dir() . '/kitchen/';
if (!is_dir($tmpDir)) {
@mkdir($tmpDir, 0755, true);
}
$dataDir = $tmpDir;
}
$ordersFile = $dataDir . 'orders.json';
$counterFile = $dataDir . 'counter.json';
$updateFlagFile = $dataDir . 'last_update.json';
// ─── 默认过期天数 ───
$defaultExpireDays = 30;
// ─── 路由分发 ───
$result = array();
switch ($act) {
case 'create':
$result = create_order();
break;
case 'get':
$result = get_order();
break;
case 'update':
$result = update_order();
break;
case 'list':
$result = list_orders();
break;
case 'delete':
$result = delete_order();
break;
case 'cleanup':
$result = cleanup_expired();
break;
case 'clear_all':
$result = clear_all_orders();
break;
case 'stats':
$result = get_stats();
break;
case 'index':
default:
$result = array(
'code' => 200,
'message' => '🍽️ 点餐助手API',
'data' => array(
'version' => '1.0.0',
'description' => '点单CRUD操作JSON文件存储SSE实时推送',
'endpoints' => array(
'create' => 'POST ?act=create {order JSON}',
'get' => 'GET ?act=get&id=订单ID',
'update' => 'POST ?act=update {order JSON}',
'list' => 'GET ?act=list&page=1&limit=20&status=0',
'delete' => 'GET ?act=delete&id=订单ID',
'cleanup' => 'GET ?act=cleanup&days=30',
'clear_all' => 'POST ?act=clear_all&confirm=yes',
'stats' => 'GET ?act=stats',
'sse' => 'GET kitchen_sse.php?order_id=xxx',
),
'storage' => 'JSON文件 (' . $ordersFile . ')',
'expire' => $defaultExpireDays . '天',
)
);
break;
}
$result['_query_time'] = round((microtime(true) - $startTime) * 1000, 2) . 'ms';
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
// ══════════════════════════════════════════════════════════════
// 数据读写函数
// ══════════════════════════════════════════════════════════════
/**
* 读取所有订单数据
*/
function read_orders() {
global $ordersFile;
if (!file_exists($ordersFile)) {
return array();
}
$content = file_get_contents($ordersFile);
$data = json_decode($content, true);
return is_array($data) ? $data : array();
}
/**
* 写入所有订单数据(文件锁)
*/
function write_orders($orders) {
global $ordersFile;
$fp = fopen($ordersFile, 'c');
if (flock($fp, LOCK_EX)) {
ftruncate($fp, 0);
rewind($fp);
fwrite($fp, json_encode($orders, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
flock($fp, LOCK_UN);
}
fclose($fp);
touch_update_flag();
}
/**
* 读取计数器
*/
function read_counter() {
global $counterFile;
if (!file_exists($counterFile)) {
return array('total' => 0, 'today' => 0, 'today_date' => date('Y-m-d'));
}
$content = file_get_contents($counterFile);
$data = json_decode($content, true);
return is_array($data) ? $data : array('total' => 0, 'today' => 0, 'today_date' => date('Y-m-d'));
}
/**
* 写入计数器
*/
function write_counter($counter) {
global $counterFile;
file_put_contents($counterFile, json_encode($counter, JSON_UNESCAPED_UNICODE));
}
/**
* 更新SSE推送标记
*/
function touch_update_flag() {
global $updateFlagFile;
$data = array(
'timestamp' => microtime(true),
'time' => date('Y-m-d H:i:s'),
);
file_put_contents($updateFlagFile, json_encode($data));
}
/**
* 递增计数器
*/
function increment_counter() {
$counter = read_counter();
$today = date('Y-m-d');
if ($counter['today_date'] !== $today) {
$counter['today'] = 0;
$counter['today_date'] = $today;
}
$counter['total']++;
$counter['today']++;
write_counter($counter);
return $counter;
}
// ══════════════════════════════════════════════════════════════
// CRUD函数
// ══════════════════════════════════════════════════════════════
/**
* 创建点单
* POST ?act=create
* Body: {order JSON}
*/
function create_order() {
$orderJson = $_GET['order'] ?? $_GET['data'] ?? null;
if ($orderJson === null) {
$rawBody = file_get_contents('php://input');
$orderData = json_decode($rawBody, true);
} else {
$orderData = json_decode($orderJson, true);
}
if (!is_array($orderData)) {
return array('code' => 400, 'message' => '无效的订单数据需要JSON格式');
}
// 补全字段
if (empty($orderData['id'])) {
$orderData['id'] = uniqid('ord_', true);
}
if (empty($orderData['orderNo'])) {
$orderData['orderNo'] = generate_order_no();
}
if (!isset($orderData['status'])) {
$orderData['status'] = 1; // active
}
if (empty($orderData['createdAt'])) {
$orderData['createdAt'] = date('c');
}
if (empty($orderData['updatedAt'])) {
$orderData['updatedAt'] = date('c');
}
if (empty($orderData['createdBy'])) {
$orderData['createdBy'] = get_client_ip();
}
if (!isset($orderData['recordCount'])) {
$counter = increment_counter();
$orderData['recordCount'] = $counter['total'];
}
if (!isset($orderData['items'])) {
$orderData['items'] = array();
}
$orders = read_orders();
array_unshift($orders, $orderData);
// 限制最大存储量
if (count($orders) > 500) {
$orders = array_slice($orders, 0, 500);
}
write_orders($orders);
return array(
'code' => 200,
'message' => '创建成功',
'data' => $orderData,
);
}
/**
* 获取点单
* GET ?act=get&id=xxx
*/
function get_order() {
$id = trim($_GET['id'] ?? '');
if (empty($id)) {
return array('code' => 400, 'message' => '缺少订单ID参数');
}
$orders = read_orders();
foreach ($orders as $order) {
if (isset($order['id']) && $order['id'] === $id) {
return array(
'code' => 200,
'message' => '获取成功',
'data' => $order,
);
}
}
return array('code' => 404, 'message' => '订单不存在');
}
/**
* 更新点单
* POST ?act=update
* Body: {order JSON with id}
*/
function update_order() {
$orderJson = $_GET['order'] ?? $_GET['data'] ?? null;
if ($orderJson === null) {
$rawBody = file_get_contents('php://input');
$orderData = json_decode($rawBody, true);
} else {
$orderData = json_decode($orderJson, true);
}
if (!is_array($orderData) || empty($orderData['id'])) {
return array('code' => 400, 'message' => '无效的订单数据缺少id字段');
}
$orders = read_orders();
$found = false;
foreach ($orders as $i => $order) {
if (isset($order['id']) && $order['id'] === $orderData['id']) {
$orderData['updatedAt'] = date('c');
$orders[$i] = array_merge($order, $orderData);
$found = true;
break;
}
}
if (!$found) {
// 不存在则创建
array_unshift($orders, $orderData);
}
write_orders($orders);
return array(
'code' => 200,
'message' => $found ? '更新成功' : '订单不存在,已创建',
'data' => $orderData,
);
}
/**
* 点单列表
* GET ?act=list&page=1&limit=20&status=0&type=0
*/
function list_orders() {
$page = max(1, (int)($_GET['page'] ?? 1));
$limit = min(100, max(1, (int)($_GET['limit'] ?? 20)));
$status = $_GET['status'] ?? null;
$type = $_GET['type'] ?? null;
$orders = read_orders();
// 筛选
if ($status !== null) {
$statusVal = (int)$status;
$orders = array_filter($orders, function($o) use ($statusVal) {
return isset($o['status']) && (int)$o['status'] === $statusVal;
});
$orders = array_values($orders);
}
if ($type !== null) {
$typeVal = (int)$type;
$orders = array_filter($orders, function($o) use ($typeVal) {
return isset($o['type']) && (int)$o['type'] === $typeVal;
});
$orders = array_values($orders);
}
$total = count($orders);
$offset = ($page - 1) * $limit;
$list = array_slice($orders, $offset, $limit);
return array(
'code' => 200,
'message' => '获取成功',
'data' => array(
'list' => $list,
'total' => $total,
'page' => $page,
'limit' => $limit,
'pages' => ceil($total / $limit),
),
);
}
/**
* 删除点单
* GET ?act=delete&id=xxx
*/
function delete_order() {
$id = trim($_GET['id'] ?? '');
if (empty($id)) {
return array('code' => 400, 'message' => '缺少订单ID参数');
}
$orders = read_orders();
$originalCount = count($orders);
$orders = array_filter($orders, function($o) use ($id) {
return !isset($o['id']) || $o['id'] !== $id;
});
$orders = array_values($orders);
if (count($orders) === $originalCount) {
return array('code' => 404, 'message' => '订单不存在');
}
write_orders($orders);
return array(
'code' => 200,
'message' => '删除成功',
'data' => array('deleted_id' => $id),
);
}
/**
* 清理过期数据
* GET ?act=cleanup&days=30
*/
function cleanup_expired() {
global $defaultExpireDays;
$days = max(1, (int)($_GET['days'] ?? $defaultExpireDays));
$cutoff = date('c', strtotime("-{$days} days"));
$orders = read_orders();
$originalCount = count($orders);
$orders = array_filter($orders, function($o) use ($cutoff) {
$updatedAt = $o['updatedAt'] ?? $o['createdAt'] ?? '';
return $updatedAt >= $cutoff;
});
$orders = array_values($orders);
$deletedCount = $originalCount - count($orders);
if ($deletedCount > 0) {
write_orders($orders);
}
return array(
'code' => 200,
'message' => "清理完成,删除{$deletedCount}条过期数据",
'data' => array(
'deleted_count' => $deletedCount,
'remaining_count' => count($orders),
'cutoff_date' => $cutoff,
'expire_days' => $days,
),
);
}
/**
* 清空全部点餐助手数据
* POST ?act=clear_all&confirm=yes
* 仅清理 cache/kitchen/ 目录下的文件,不影响其他数据
*/
function clear_all_orders() {
global $dataDir, $ordersFile, $counterFile, $updateFlagFile;
$confirm = strtolower(trim($_GET['confirm'] ?? $_POST['confirm'] ?? ''));
if ($confirm !== 'yes') {
return array(
'code' => 400,
'message' => '需要确认参数 confirm=yes 才能清空数据',
);
}
$deletedFiles = array();
$errors = array();
$kitchenFiles = array(
'orders' => $ordersFile,
'counter' => $counterFile,
'update_flag' => $updateFlagFile,
);
foreach ($kitchenFiles as $name => $file) {
if (file_exists($file)) {
if (@unlink($file)) {
$deletedFiles[] = basename($file);
} else {
$errors[] = '无法删除: ' . basename($file);
}
}
}
$scanDir = $dataDir;
if (is_dir($scanDir)) {
$extraFiles = @scandir($scanDir);
if ($extraFiles !== false) {
foreach ($extraFiles as $f) {
if ($f === '.' || $f === '..') continue;
$fullPath = $scanDir . $f;
if (is_file($fullPath) && !in_array($f, $deletedFiles)) {
if (@unlink($fullPath)) {
$deletedFiles[] = $f;
} else {
$errors[] = '无法删除: ' . $f;
}
}
}
}
}
$message = '点餐助手数据已清空';
if (count($deletedFiles) > 0) {
$message .= ',已删除: ' . implode(', ', $deletedFiles);
}
if (count($errors) > 0) {
$message .= ',错误: ' . implode(', ', $errors);
}
return array(
'code' => 200,
'message' => $message,
'data' => array(
'deleted_files' => $deletedFiles,
'errors' => $errors,
'data_dir' => basename($dataDir),
'scope' => 'kitchen_only',
),
);
}
/**
* 统计信息
* GET ?act=stats
*/
function get_stats() {
$counter = read_counter();
$orders = read_orders();
$statusCount = array(0 => 0, 1 => 0, 2 => 0, 3 => 0);
$typeCount = array(0 => 0, 1 => 0);
foreach ($orders as $o) {
$s = (int)($o['status'] ?? 0);
$t = (int)($o['type'] ?? 0);
if (isset($statusCount[$s])) $statusCount[$s]++;
if (isset($typeCount[$t])) $typeCount[$t]++;
}
return array(
'code' => 200,
'message' => '获取成功',
'data' => array(
'total_orders' => $counter['total'],
'today_orders' => $counter['today'],
'stored_orders' => count($orders),
'by_status' => $statusCount,
'by_type' => $typeCount,
'storage_file' => basename($GLOBALS['ordersFile']),
),
);
}
// ══════════════════════════════════════════════════════════════
// 工具函数
// ══════════════════════════════════════════════════════════════
/**
* 生成订单号
*/
function generate_order_no() {
$ts = substr(microtime(true) . '', 5);
$rand = rand(100, 999);
return 'OD' . $ts . $rand;
}
/**
* 获取客户端IP
*/
function get_client_ip() {
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
return trim($ips[0]);
}
if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
return $_SERVER['HTTP_X_REAL_IP'];
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}