加密token
This commit is contained in:
610
docs/api/kitchen.php
Normal file
610
docs/api/kitchen.php
Normal file
@@ -0,0 +1,610 @@
|
||||
<?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';
|
||||
}
|
||||
Reference in New Issue
Block a user