Files
Sdf1_login/api/cashier.php
T

310 lines
12 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(独立页面后端)
* 支持:收银员登录/登出/会话、玩家查询、订单列表、收银员管理(仅管理员)
*/
// ★ 清除所有缓冲 + 发送 JSON Content-Type
while (ob_get_level() > 0) { @ob_end_clean(); }
ob_start();
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('memory_limit', '256M');
if (function_exists('opcache_invalidate')) { @opcache_invalidate(__FILE__); }
register_shutdown_function(function() {
while (ob_get_level() > 0) {
@ob_end_flush();
}
});
if (!extension_loaded('sqlite3')) {
exit(json_encode(['success' => false, 'message' => 'Missing SQLite3'], JSON_UNESCAPED_UNICODE));
}
require_once __DIR__ . '/../core.php';
if (!function_exists('error') || !function_exists('success') || !function_exists('getParam')) {
exit(json_encode(['success' => false, 'message' => 'core.php load failed'], JSON_UNESCAPED_UNICODE));
}
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$action = getParam('action', 'status');
try {
switch ($action) {
case 'login': cashierDoLogin(); break;
case 'logout': cashierDoLogout(); break;
case 'session': cashierSession(); break;
case 'player_check': cashierPlayerCheck(); break;
case 'order_list': cashierOrderList(); break;
case 'cashier_list': cashierList(); break;
case 'add': cashierAdd(); break;
case 'edit': cashierEdit(); break;
case 'delete': cashierDelete(); break;
default:
exit(json_encode(['success' => false, 'message' => '未知操作: ' . $action], JSON_UNESCAPED_UNICODE));
}
} catch (Exception $e) {
@error_log('[cashier.php] ' . $e->getMessage());
exit(json_encode(['success' => false, 'message' => '服务器错误: ' . $e->getMessage()], JSON_UNESCAPED_UNICODE));
}
// ===== 收银员登录 =====
function cashierDoLogin() {
$username = trim(getParam('username', ''));
$password = getParam('password', '');
if ($username === '' || $password === '') {
exit(json_encode(['success' => false, 'message' => '请输入账号和密码'], JSON_UNESCAPED_UNICODE));
}
if (cashierLogin($username, $password)) {
$c = getCurrentCashier();
exit(json_encode([
'success' => true,
'data' => [
'username' => $c['username'],
'discount_limit_percent' => $c['discount_limit_percent'],
'can_cash' => (int)($c['can_cash'] ?? 0)
]
], JSON_UNESCAPED_UNICODE));
}
exit(json_encode(['success' => false, 'message' => '账号或密码错误'], JSON_UNESCAPED_UNICODE));
}
// ===== 收银员登出 =====
function cashierDoLogout() {
cashierLogout();
exit(json_encode(['success' => true, 'message' => '已退出登录'], JSON_UNESCAPED_UNICODE));
}
// ===== 当前会话信息 =====
function cashierSession() {
if (isAdminLoggedIn()) {
exit(json_encode([
'success' => true,
'data' => [
'role' => 'admin',
'username' => 'admin',
'discount_limit_percent' => 100
]
], JSON_UNESCAPED_UNICODE));
}
if (isCashierLoggedIn()) {
$c = getCurrentCashier();
exit(json_encode([
'success' => true,
'data' => [
'role' => 'cashier',
'username' => $c['username'],
'discount_limit_percent' => $c['discount_limit_percent'],
'can_cash' => (int)($c['can_cash'] ?? 0)
]
], JSON_UNESCAPED_UNICODE));
}
exit(json_encode(['success' => false, 'message' => '未登录'], JSON_UNESCAPED_UNICODE));
}
// ===== 查询目标玩家余额/在线状态(收银员或管理员均可)=====
function cashierPlayerCheck() {
requireCashierOrAdminSession();
$player = trim(getParam('player'));
if (!$player) exit(json_encode(['success' => false, 'message' => '请输入玩家名'], JSON_UNESCAPED_UNICODE));
if (!preg_match('/^[a-zA-Z0-9_]{3,16}$/', $player)) {
exit(json_encode(['success' => false, 'message' => '玩家名格式不正确(3-16位英文/数字/下划线)'], JSON_UNESCAPED_UNICODE));
}
$db = getDB();
try {
$db->exec("INSERT OR IGNORE INTO bond_cache (player_name, amount, updated_at) VALUES ('" . str_replace("'", "''", $player) . "', 0, " . time() . ")");
$stmt = $db->prepare("SELECT amount FROM bond_cache WHERE player_name = :name");
$stmt->bindValue(':name', $player, SQLITE3_TEXT);
$row = $stmt->execute()->fetchArray(SQLITE3_ASSOC);
$balance = $row ? (int)$row['amount'] : 0;
$online = false;
try {
$ostmt = $db->prepare("SELECT player_name FROM online_players WHERE player_name = :name");
$ostmt->bindValue(':name', $player, SQLITE3_TEXT);
$orow = $ostmt->execute()->fetchArray(SQLITE3_ASSOC);
$online = $orow ? true : false;
} catch (\Throwable $e) {}
exit(json_encode([
'success' => true,
'data' => ['player' => $player, 'exists' => true, 'balance' => $balance, 'online' => $online]
], JSON_UNESCAPED_UNICODE));
} catch (Exception $e) {
@error_log('[cashier.php] Player check error: ' . $e->getMessage());
exit(json_encode(['success' => false, 'message' => '查询失败: ' . $e->getMessage()], JSON_UNESCAPED_UNICODE));
}
}
// ===== 订单列表 =====
function cashierOrderList() {
$role = requireCashierOrAdminSession();
// 收银员只能看自己的订单
$c = null;
if ($role === 'cashier') {
$c = getCurrentCashier();
}
// ★ 尽早释放会话锁:查询期间不再占用 session 文件,避免阻塞同会话并发请求
if (session_status() === PHP_SESSION_ACTIVE) session_write_close();
$db = getDB();
$limit = (int)getParam('limit', 100);
if ($limit <= 0 || $limit > 500) $limit = 100;
$sql = "SELECT * FROM cashier_orders";
$params = [];
if ($role === 'cashier' && $c) {
$sql .= " WHERE operator_name = :op";
$params[':op'] = $c['username'];
}
$sql .= " ORDER BY created_at DESC LIMIT " . (int)$limit;
// ★ 重试规避 database is locked(Java 高频写入时 SQLite 偶发锁,最多重试4次)
$rows = [];
$attempts = 0;
while ($attempts < 4) {
$attempts++;
$stmt = $db->prepare($sql);
if (!$stmt) break;
foreach ($params as $k => $v) {
$stmt->bindValue($k, $v, SQLITE3_TEXT);
}
$result = @$stmt->execute();
if ($result === false) {
$err = $db->lastErrorMsg();
if (stripos($err, 'locked') !== false && $attempts < 4) { usleep(200000); continue; }
exit(json_encode(['success' => false, 'message' => '查询失败: ' . $err], JSON_UNESCAPED_UNICODE));
}
while ($r = $result->fetchArray(SQLITE3_ASSOC)) {
if (!empty($r['items_detail'])) {
$r['items_detail'] = json_decode($r['items_detail'], true) ?: [];
}
$rows[] = $r;
}
break;
}
exit(json_encode(['success' => true, 'data' => $rows], JSON_UNESCAPED_UNICODE));
}
// ===== 收银员列表(仅管理员)=====
function cashierList() {
requireAdminSession();
$db = getDB();
$result = $db->prepare("SELECT id, username, discount_limit_percent, can_cash, created_at, created_by FROM cashiers ORDER BY id ASC")->execute();
$rows = [];
while ($r = $result->fetchArray(SQLITE3_ASSOC)) {
$rows[] = $r;
}
exit(json_encode(['success' => true, 'data' => $rows], JSON_UNESCAPED_UNICODE));
}
// ===== 新增收银员(仅管理员)=====
function cashierAdd() {
requireAdminSession();
$username = trim(getParam('username', ''));
$password = getParam('password', '');
$discountLimit = (int)getParam('discount_limit_percent', 0);
$canCash = (int)getParam('can_cash', 0);
if ($username === '' || $password === '') {
exit(json_encode(['success' => false, 'message' => '账号和密码不能为空'], JSON_UNESCAPED_UNICODE));
}
if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
exit(json_encode(['success' => false, 'message' => '账号格式不正确(3-20位英文/数字/下划线)'], JSON_UNESCAPED_UNICODE));
}
if ($discountLimit < 0 || $discountLimit > 100) {
exit(json_encode(['success' => false, 'message' => '折扣上限需在0-100之间'], JSON_UNESCAPED_UNICODE));
}
if ($canCash < 0 || $canCash > 1) {
exit(json_encode(['success' => false, 'message' => '现金收款权限需为0或1'], JSON_UNESCAPED_UNICODE));
}
$db = getDB();
// 查重
$chk = $db->prepare("SELECT id FROM cashiers WHERE username = :u");
$chk->bindValue(':u', $username, SQLITE3_TEXT);
if ($chk->execute()->fetchArray(SQLITE3_ASSOC)) {
exit(json_encode(['success' => false, 'message' => '账号已存在'], JSON_UNESCAPED_UNICODE));
}
$hash = password_hash($password, PASSWORD_DEFAULT);
$salt = bin2hex(random_bytes(8));
$stmt = $db->prepare("INSERT INTO cashiers (username, password_hash, salt, discount_limit_percent, can_cash, created_at, created_by) VALUES (:u,:h,:s,:d,:c,:t,'admin')");
$stmt->bindValue(':u', $username, SQLITE3_TEXT);
$stmt->bindValue(':h', $hash, SQLITE3_TEXT);
$stmt->bindValue(':s', $salt, SQLITE3_TEXT);
$stmt->bindValue(':d', $discountLimit, SQLITE3_INTEGER);
$stmt->bindValue(':c', $canCash, SQLITE3_INTEGER);
$stmt->bindValue(':t', time(), SQLITE3_INTEGER);
$stmt->execute();
exit(json_encode(['success' => true, 'message' => '收银员创建成功'], JSON_UNESCAPED_UNICODE));
}
// ===== 编辑收银员(仅管理员):可改密码 / 折扣上限 =====
function cashierEdit() {
requireAdminSession();
$id = (int)getParam('id', 0);
if ($id <= 0) exit(json_encode(['success' => false, 'message' => '缺少id'], JSON_UNESCAPED_UNICODE));
$db = getDB();
$chk = $db->prepare("SELECT id FROM cashiers WHERE id = :id");
$chk->bindValue(':id', $id, SQLITE3_INTEGER);
if (!$chk->execute()->fetchArray(SQLITE3_ASSOC)) {
exit(json_encode(['success' => false, 'message' => '收银员不存在'], JSON_UNESCAPED_UNICODE));
}
$sets = [];
$params = [':id' => $id];
$newPassword = getParam('password', '');
if ($newPassword !== '') {
$sets[] = "password_hash = :h";
$params[':h'] = password_hash($newPassword, PASSWORD_DEFAULT);
$sets[] = "salt = :s";
$params[':s'] = bin2hex(random_bytes(8));
}
if (getParam('discount_limit_percent') !== null && getParam('discount_limit_percent') !== '') {
$dl = (int)getParam('discount_limit_percent', 0);
if ($dl < 0 || $dl > 100) {
exit(json_encode(['success' => false, 'message' => '折扣上限需在0-100之间'], JSON_UNESCAPED_UNICODE));
}
$sets[] = "discount_limit_percent = :d";
$params[':d'] = $dl;
}
if (getParam('can_cash') !== null && getParam('can_cash') !== '') {
$cc = (int)getParam('can_cash', 0);
if ($cc < 0 || $cc > 1) {
exit(json_encode(['success' => false, 'message' => '现金收款权限需为0或1'], JSON_UNESCAPED_UNICODE));
}
$sets[] = "can_cash = :c";
$params[':c'] = $cc;
}
if (empty($sets)) {
exit(json_encode(['success' => false, 'message' => '没有可更新的字段'], JSON_UNESCAPED_UNICODE));
}
$sql = "UPDATE cashiers SET " . implode(', ', $sets) . " WHERE id = :id";
$stmt = $db->prepare($sql);
foreach ($params as $k => $v) {
$stmt->bindValue($k, $v, is_int($v) ? SQLITE3_INTEGER : SQLITE3_TEXT);
}
$stmt->execute();
exit(json_encode(['success' => true, 'message' => '收银员信息已更新'], JSON_UNESCAPED_UNICODE));
}
// ===== 删除收银员(仅管理员)=====
function cashierDelete() {
requireAdminSession();
$id = (int)getParam('id', 0);
if ($id <= 0) exit(json_encode(['success' => false, 'message' => '缺少id'], JSON_UNESCAPED_UNICODE));
$db = getDB();
$stmt = $db->prepare("DELETE FROM cashiers WHERE id = :id");
$stmt->bindValue(':id', $id, SQLITE3_INTEGER);
$stmt->execute();
exit(json_encode(['success' => true, 'message' => '收银员已删除'], JSON_UNESCAPED_UNICODE));
}