2.1 版本文件
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
<?php
|
||||
/**
|
||||
* 商城统一 API 端点(AJAX/XHR)
|
||||
*
|
||||
* 所有请求通过 action 参数路由,返回 JSON。
|
||||
* 需登录的操作会校验 session。
|
||||
* 用法: api.php?action=xxx [&参数]
|
||||
*/
|
||||
require __DIR__ . '/includes/init.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
|
||||
// ─── 通用响应函数 ───
|
||||
function json_ok($data = null, $msg = '') {
|
||||
echo json_encode(['ok' => true, 'msg' => $msg, 'data' => $data], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function json_err($code = 400, $msg = '操作失败') {
|
||||
http_response_code($code);
|
||||
echo json_encode(['ok' => false, 'msg' => $msg], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ─── CSRF 校验(写操作必须带 token)─────────────────────────────
|
||||
function requireCsrf() {
|
||||
$token = trim($_SERVER['HTTP_X_CSRF_TOKEN'] ?? $_POST['csrf_token'] ?? '');
|
||||
if (!$token || !verifyCsrfToken($token)) {
|
||||
json_err(403, 'CSRF 验证失败,请刷新页面重试');
|
||||
}
|
||||
}
|
||||
|
||||
$action = trim($_GET['action'] ?? $_POST['action'] ?? '');
|
||||
|
||||
// ─── 路由表 ───
|
||||
$ROUTES = [
|
||||
// 购物车(需登录)
|
||||
'cart_get' => true, // GET - 获取购物车
|
||||
'cart_add' => true, // POST - 加入购物车
|
||||
'cart_update' => true, // POST - 修改数量
|
||||
'cart_remove' => true, // POST - 删除一项
|
||||
'cart_clear' => true, // POST - 清空购物车
|
||||
|
||||
// 用户操作(需登录)
|
||||
'sign_in' => true, // POST - 每日签到
|
||||
'ticket_submit'=> true, // POST - 提交工单
|
||||
|
||||
// 公开读取
|
||||
'search' => false, // GET - 搜索商品
|
||||
'products' => false, // GET - 分类筛选商品
|
||||
'product_info' => false, // GET - 商品详情
|
||||
'announcements'=> false, // GET - 公告列表
|
||||
];
|
||||
|
||||
if (!isset($ROUTES[$action])) {
|
||||
json_err(404, '未知操作: ' . h($action));
|
||||
}
|
||||
|
||||
$needAuth = $ROUTES[$action];
|
||||
if ($needAuth && empty($_SESSION['user_id'])) {
|
||||
json_err(401, '请先登录');
|
||||
}
|
||||
|
||||
$user = null;
|
||||
if ($needAuth) {
|
||||
$user = currentUser();
|
||||
if (!$user) { json_err(401, '登录已过期'); }
|
||||
}
|
||||
|
||||
// ─── 写操作统一 CSRF ───
|
||||
$WRITE_ACTIONS = ['cart_add', 'cart_update', 'cart_remove', 'cart_clear', 'sign_in', 'ticket_submit'];
|
||||
if (in_array($action, $WRITE_ACTIONS)) {
|
||||
requireCsrf();
|
||||
}
|
||||
|
||||
// ─── 分发处理 ───
|
||||
switch ($action) {
|
||||
|
||||
/* ================================================================
|
||||
* 购物车
|
||||
* ================================================================ */
|
||||
|
||||
case 'cart_get':
|
||||
$items = [];
|
||||
$total = 0;
|
||||
if (!empty($_SESSION['cart']) && is_array($_SESSION['cart'])) {
|
||||
$ids = array_keys($_SESSION['cart']);
|
||||
if (!empty($ids)) {
|
||||
$inPlaceholders = str_repeat('?', count($ids));
|
||||
$stmt = db()->prepare(
|
||||
'SELECT id,name,image,icon,category,points_price,stock,status FROM ' . tn('products') .
|
||||
' WHERE id IN (' . $inPlaceholders . ') AND status=1'
|
||||
);
|
||||
$stmt->execute($ids);
|
||||
while ($p = $stmt->fetch()) {
|
||||
$qty = (int)($_SESSION['cart'][$p['id']] ?? 0);
|
||||
if ($qty <= 0) continue;
|
||||
$price = !empty($p['points_price']) ? (int)$p['points_price'] : 0;
|
||||
$items[] = [
|
||||
'id' => (int)$p['id'],
|
||||
'name' => $p['name'],
|
||||
'image' => $p['image'] ?: '',
|
||||
'icon' => $p['icon'] ?: '',
|
||||
'category' => $p['category'],
|
||||
'qty' => $qty,
|
||||
'points_price' => $price,
|
||||
'stock' => (int)$p['stock'],
|
||||
'subtotal' => $price * $qty,
|
||||
];
|
||||
$total += $price * $qty;
|
||||
}
|
||||
}
|
||||
}
|
||||
json_ok(['items' => $items, 'count' => count($items), 'total_points' => $total]);
|
||||
|
||||
case 'cart_add':
|
||||
$pid = (int)($_POST['product_id'] ?? 0);
|
||||
$qty = max(1, min(99, (int)($_POST['qty'] ?? 1)));
|
||||
if ($pid <= 0) { json_err(400, '商品 ID 无效'); }
|
||||
$stmt = db()->prepare('SELECT id,status,stock FROM ' . tn('products') . ' WHERE id=?');
|
||||
$stmt->execute([$pid]);
|
||||
$p = $stmt->fetch();
|
||||
if (!$p || (int)$p['status'] !== 1) { json_err(400, '商品不存在或已下架'); }
|
||||
if (!isset($_SESSION['cart'])) { $_SESSION['cart'] = []; }
|
||||
$current = (int)($_SESSION['cart'][$pid] ?? 0);
|
||||
$newQty = $current + $qty;
|
||||
if ((int)$p['stock'] >= 0 && $newQty > (int)$p['stock']) {
|
||||
json_err(400, '库存不足(剩余 ' . (int)$p['stock'] . ' 件)');
|
||||
}
|
||||
$_SESSION['cart'][$pid] = $newQty;
|
||||
$count = array_sum(array_map('intval', $_SESSION['cart']));
|
||||
json_ok(['cart_count' => $count, 'item_qty' => $newQty], '已加入购物车');
|
||||
|
||||
case 'cart_update':
|
||||
$pid = (int)($_POST['product_id'] ?? 0);
|
||||
$qty = max(1, min(99, (int)($_POST['qty'] ?? 1)));
|
||||
if ($pid <= 0) { json_err(400, '商品 ID 无效'); }
|
||||
if (!isset($_SESSION['cart'][$pid])) { json_err(400, '购物车中没有该商品'); }
|
||||
$stmt = db()->prepare('SELECT stock FROM ' . tn('products') . ' WHERE id=? AND status=1');
|
||||
$stmt->execute([$pid]);
|
||||
$p = $stmt->fetch();
|
||||
if (!$p) { json_err(400, '商品不存在或已下架'); }
|
||||
if ((int)$p['stock'] >= 0 && $qty > (int)$p['stock']) {
|
||||
json_err(400, '库存不足');
|
||||
}
|
||||
$_SESSION['cart'][$pid] = $qty;
|
||||
$count = array_sum(array_map('intval', $_SESSION['cart']));
|
||||
json_ok(['cart_count' => $count], '数量已更新');
|
||||
|
||||
case 'cart_remove':
|
||||
$pid = (int)($_POST['product_id'] ?? 0);
|
||||
if ($pid <= 0) { json_err(400, '商品 ID 无效'); }
|
||||
unset($_SESSION['cart'][$pid]);
|
||||
$count = isset($_SESSION['cart']) ? array_sum(array_map('intval', $_SESSION['cart'])) : 0;
|
||||
json_ok(['cart_count' => $count], '已移除');
|
||||
|
||||
case 'cart_clear':
|
||||
$_SESSION['cart'] = [];
|
||||
json_ok([], '购物车已清空');
|
||||
|
||||
/* ================================================================
|
||||
* 每日签到
|
||||
* ================================================================ */
|
||||
|
||||
case 'sign_in':
|
||||
$enabled = getSetting('points_enabled', '1') === '1';
|
||||
if (!$enabled) { json_err(400, '积分系统未启用'); }
|
||||
$today = date('Y-m-d');
|
||||
$stmt = db()->prepare('SELECT id FROM ' . tn('sign_logs') . ' WHERE user_id=? AND sign_date=?');
|
||||
$stmt->execute([$user['id'], $today]);
|
||||
if ($stmt->fetch()) { json_err(400, '今天已经签到了'); }
|
||||
$points = max(0, (int)getSetting('points_sign', 5));
|
||||
db()->beginTransaction();
|
||||
try {
|
||||
db()->prepare('INSERT INTO ' . tn('sign_logs') . ' (user_id,sign_date,points) VALUES (?,?,?)')
|
||||
->execute([$user['id'], $today, $points]);
|
||||
db()->prepare('UPDATE ' . tn('users') . ' SET points=points+? WHERE id=?')
|
||||
->execute([$points, $user['id']]);
|
||||
db()->commit();
|
||||
} catch (Exception $e) {
|
||||
db()->rollBack();
|
||||
json_err(500, '签到失败: 数据库错误');
|
||||
}
|
||||
$newTotal = getUserPoints($user['id']);
|
||||
json_ok(['points_gained' => $points, 'points_total' => $newTotal], "签到成功!+{$points} 积分");
|
||||
|
||||
/* ================================================================
|
||||
* 提交工单
|
||||
* ================================================================ */
|
||||
|
||||
case 'ticket_submit':
|
||||
$deptId = (int)($_POST['dept_id'] ?? 0);
|
||||
$title = trim($_POST['title'] ?? '');
|
||||
$body = trim($_POST['body'] ?? '');
|
||||
$priority = in_array($_POST['priority'] ?? '', ['low','normal','high']) ? $_POST['priority'] : 'normal';
|
||||
if ($title === '' || strlen($title) < 3) { json_err(400, '标题至少 3 个字'); }
|
||||
if ($body === '' || strlen($body) < 5) { json_err(400, '描述至少 5 个字'); }
|
||||
|
||||
// 验证部门是否存在
|
||||
if ($deptId > 0) {
|
||||
$ds = db()->prepare('SELECT id FROM ' . tn('departments') . ' WHERE id=?');
|
||||
$ds->execute([$deptId]);
|
||||
if (!$ds->fetch()) { $deptId = 0; }
|
||||
}
|
||||
|
||||
$orderId = !empty($_POST['order_id']) ? (int)$_POST['order_id'] : null;
|
||||
|
||||
db()->prepare(
|
||||
'INSERT INTO ' . tn('tickets') . ' (user_id,dept_id,title,body,priority,order_id,status,created_at) VALUES (?,?,?,?,?,?,\'open\',NOW())'
|
||||
)->execute([$user['id'], $deptId, $title, $body, $priority, $orderId]);
|
||||
|
||||
$ticketId = (int)db()->lastInsertId();
|
||||
|
||||
// 通知处理人员
|
||||
notifyAdmins('新工单 #' . $ticketId . ': ' . $title, "用户 {$user['username']} 提交了新工单,请及时处理。");
|
||||
|
||||
json_ok(['ticket_id' => $ticketId], '工单提交成功!我们会尽快处理。');
|
||||
|
||||
/* ================================================================
|
||||
* 商品搜索 / 分类筛选(公开)
|
||||
* ================================================================ */
|
||||
|
||||
case 'search':
|
||||
$q = trim($_GET['q'] ?? '');
|
||||
if ($q === '') { json_err(400, '请输入搜索关键词'); }
|
||||
$where = ['status = 1', 'name LIKE ?'];
|
||||
$params = ['%' . $q . '%'];
|
||||
$sql = 'SELECT id,name,category,image,icon,points_price,stock,description FROM ' . tn('products') .
|
||||
' WHERE ' . implode(' AND ', $where) . ' ORDER BY created_at DESC LIMIT 50';
|
||||
$stmt = db()->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$products = [];
|
||||
while ($r = $stmt->fetch()) {
|
||||
$products[] = [
|
||||
'id' => (int)$r['id'],
|
||||
'name' => $r['name'],
|
||||
'category' => $r['category'],
|
||||
'image' => $r['image'] ?: '',
|
||||
'icon' => $r['icon'] ?: '',
|
||||
'points_price' => !empty($r['points_price']) ? (int)$r['points_price'] : null,
|
||||
'stock' => (int)$r['stock'],
|
||||
'description' => mb_substr($r['description'] ?? '', 0, 100),
|
||||
];
|
||||
}
|
||||
json_ok(['products' => $products, 'query' => $q, 'count' => count($products)]);
|
||||
|
||||
case 'products':
|
||||
$cat = trim($_GET['cat'] ?? '');
|
||||
$where = ['status = 1'];
|
||||
$params = [];
|
||||
if ($cat !== '') {
|
||||
$where[] = 'category = ?';
|
||||
$params[] = $cat;
|
||||
}
|
||||
$sql = 'SELECT id,name,category,image,icon,points_price,stock FROM ' . tn('products') .
|
||||
' WHERE ' . implode(' AND ', $where) . ' ORDER BY created_at DESC LIMIT 100';
|
||||
$stmt = db()->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$products = [];
|
||||
while ($r = $stmt->fetch()) {
|
||||
$products[] = [
|
||||
'id' => (int)$r['id'],
|
||||
'name' => $r['name'],
|
||||
'category' => $r['category'],
|
||||
'image' => $r['image'] ?: '',
|
||||
'icon' => $r['icon'] ?: '',
|
||||
'points_price' => !empty($r['points_price']) ? (int)$r['points_price'] : null,
|
||||
'stock' => (int)$r['stock'],
|
||||
];
|
||||
}
|
||||
json_ok(['products' => $products, 'category' => $cat, 'count' => count($products)]);
|
||||
|
||||
case 'product_info':
|
||||
$pid = (int)($_GET['id'] ?? 0);
|
||||
if ($pid <= 0) { json_err(400, '商品 ID 无效'); }
|
||||
$stmt = db()->prepare(
|
||||
'SELECT id,name,category,image,icon,points_price,cash_price,stock,period,description,created_at FROM ' . tn('products') . ' WHERE id=? AND status=1'
|
||||
);
|
||||
$stmt->execute([$pid]);
|
||||
$p = $stmt->fetch();
|
||||
if (!$p) { json_err(404, '商品不存在'); }
|
||||
json_ok([
|
||||
'id' => (int)$p['id'],
|
||||
'name' => $p['name'],
|
||||
'category' => $p['category'],
|
||||
'image' => $p['image'] ?: '',
|
||||
'icon' => $p['icon'] ?: '',
|
||||
'points_price' => !empty($p['points_price']) ? (int)$p['points_price'] : null,
|
||||
'cash_price' => !empty($p['cash_price']) ? floatval($p['cash_price']) : null,
|
||||
'stock' => (int)$p['stock'],
|
||||
'period' => $p['period'] ?: '',
|
||||
'description' => $p['description'] ?: '',
|
||||
]);
|
||||
|
||||
case 'announcements':
|
||||
$limit = min(20, max(1, (int)($_GET['limit'] ?? 10)));
|
||||
$stmt = db()->query(
|
||||
'SELECT id,title,pinned,content,created_at FROM ' . tn('announcements') .
|
||||
' WHERE status=1 ORDER BY pinned DESC, created_at DESC LIMIT ' . $limit
|
||||
);
|
||||
$list = [];
|
||||
while ($a = $stmt->fetch()) {
|
||||
$list[] = [
|
||||
'id' => (int)$a['id'],
|
||||
'title' => $a['title'],
|
||||
'pinned' => (int)$a['pinned'],
|
||||
'excerpt' => mb_substr(strip_tags($a['content'] ?? ''), 0, 120),
|
||||
'created_at' => $a['created_at'],
|
||||
];
|
||||
}
|
||||
json_ok(['announcements' => $list]);
|
||||
|
||||
default:
|
||||
json_err(404, '未知操作: ' . h($action));
|
||||
}
|
||||
Reference in New Issue
Block a user