Files
psychological-counseling/api/bootstrap.php
litianming 08ce60344d Initial commit: 心理咨询预约系统
PHP 前后端一体:首页咨询师列表、预约表单、管理后台、二维码准入与邮件通知;敏感配置与提交数据通过 .gitignore 排除。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 10:17:09 +08:00

179 lines
4.5 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
declare(strict_types=1);
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
ini_set('display_errors', '0');
define('ROOT_DIR', dirname(__DIR__));
define('DATA_DIR', ROOT_DIR . '/data');
define('CONFIG_FILE', ROOT_DIR . '/config/app.json');
define('SUBMISSIONS_FILE', DATA_DIR . '/submissions.json');
function loadAppConfig(): array
{
static $config = null;
if ($config !== null) {
return $config;
}
if (!is_file(CONFIG_FILE)) {
jsonError('配置文件不存在', 500);
}
$raw = file_get_contents(CONFIG_FILE);
$config = json_decode($raw ?: '{}', true);
if (!is_array($config)) {
jsonError('配置文件格式错误', 500);
}
return $config;
}
function jsonResponse($data, int $code = 200): void
{
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_UNESCAPED_UNICODE);
exit;
}
function jsonError(string $message, int $code = 400): void
{
jsonResponse(['ok' => false, 'message' => $message], $code);
}
function readJsonFile(string $path): array
{
if (!is_file($path)) {
jsonError('数据文件不存在: ' . basename($path), 500);
}
$data = json_decode(file_get_contents($path) ?: '[]', true);
return is_array($data) ? $data : [];
}
function getRequestBody(): array
{
$raw = file_get_contents('php://input');
$data = json_decode($raw ?: '{}', true);
return is_array($data) ? $data : [];
}
function requireAdmin(): void
{
if (empty($_SESSION['admin'])) {
jsonError('未登录或会话已过期', 401);
}
}
function readSubmissions(): array
{
if (!is_file(SUBMISSIONS_FILE)) {
return [];
}
$data = json_decode(file_get_contents(SUBMISSIONS_FILE) ?: '[]', true);
return is_array($data) ? $data : [];
}
function getDataWriteError(): ?string
{
if (!is_dir(DATA_DIR)) {
if (!@mkdir(DATA_DIR, 0755, true) && !is_dir(DATA_DIR)) {
return 'data 目录无法创建。请在宝塔:文件 → data → 权限设为 755属主 www';
}
}
if (!is_writable(DATA_DIR)) {
return 'data 目录不可写。请在宝塔:文件 → data → 权限 755属主 www勾选「应用到子目录」';
}
if (is_file(SUBMISSIONS_FILE) && !is_writable(SUBMISSIONS_FILE)) {
return 'submissions.json 不可写。请在宝塔data/submissions.json → 权限 664属主 www';
}
return null;
}
function saveSubmissions(array $submissions): bool
{
$writeError = getDataWriteError();
if ($writeError !== null) {
error_log('[data] ' . $writeError);
return false;
}
$json = json_encode($submissions, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
$written = @file_put_contents(SUBMISSIONS_FILE, $json, LOCK_EX);
if ($written === false && !is_file(SUBMISSIONS_FILE)) {
$written = @file_put_contents(SUBMISSIONS_FILE, $json, LOCK_EX);
}
return $written !== false;
}
function getSaveSubmissionsError(): string
{
$writeError = getDataWriteError();
if ($writeError !== null) {
return $writeError;
}
return '保存失败,请稍后重试';
}
function validatePhone(string $phone): bool
{
return (bool) preg_match('/^1[3-9]\d{9}$/', $phone);
}
function getAssetVersion(): string
{
static $version = null;
if ($version !== null) {
return $version;
}
try {
$config = loadAppConfig();
if (!empty($config['assetVersion'])) {
$version = (string) $config['assetVersion'];
return $version;
}
} catch (Throwable $e) {
// 页面未就绪时回退到文件时间戳
}
$max = 0;
foreach (['css', 'js', 'assets', 'assets/avatars'] as $dirName) {
$dir = ROOT_DIR . '/' . $dirName;
if (!is_dir($dir)) {
continue;
}
$files = glob($dir . '/*');
if ($files === false) {
continue;
}
foreach ($files as $file) {
if (is_file($file)) {
$max = max($max, (int) filemtime($file));
}
}
}
$version = $max > 0 ? (string) $max : date('YmdHis');
return $version;
}
function asset(string $path): string
{
$path = ltrim($path, '/');
$sep = str_contains($path, '?') ? '&' : '?';
return $path . $sep . 'v=' . rawurlencode(getAssetVersion());
}
function sendHtmlNoCacheHeaders(): void
{
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
}