1 Star 1 Fork 0

FreeWind/简易的PHP图书后台管理系统

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
denlurizhi.php 13.39 KB
一键复制 编辑 原始数据 按行查看 历史
FreeWind 提交于 2025-05-29 17:20 +08:00 . 简易的PHP图书后台管理系统
<?php
session_start();
$config = [
'log_file' => __DIR__ . '/logs/app.log',
'max_log_size' => 10 * 1024 * 1024, // 10MB
'log_levels' => ['debug', 'info', 'warning', 'error', 'critical'],
'db_host' => 'www.20.com',
'db_name' => 'book',
'db_user' => 'root',
'db_password' => 'root'
];
if (!file_exists(dirname($config['log_file']))) {
mkdir(dirname($config['log_file']), 0777, true);
}
class Logger {
private $logFile;
private $maxSize;
public function __construct($logFile, $maxSize) {
$this->logFile = $logFile;
$this->maxSize = $maxSize;
}
public function log($level, $message) {
if (in_array($level, $config['log_levels'])) {
$timestamp = date('Y-m-d H:i:s');
$logEntry = "[{$timestamp}] [{$level}] {$message}\n";
file_put_contents($this->logFile, $logEntry, FILE_APPEND);
if (filesize($this->logFile) > $this->maxSize) {
$this->rotateLog();
}
}
}
private function rotateLog() {
$backupFile = $this->logFile . '.' . date('YmdHis') . '.bak';
rename($this->logFile, $backupFile);
file_put_contents($this->logFile, '');
}
public function getLogs($limit = null) {
return file($this->logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
}
}
$logger = new Logger($config['log_file'], $config['max_log_size']);
try {
$pdo = new PDO("mysql:host={$config['db_host']};dbname={$config['db_name']}", $config['db_user'], $config['db_password']);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("Database connection failed: " . $e->getMessage());
}
// 处理登录日志(新增模拟登录提交)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login_username'])) {
$username = $_POST['login_username'];
$ip = $_SERVER['REMOTE_ADDR'];
$message = "用户 {$username} 登录,IP: {$ip}";
try {
$stmt = $pdo->prepare("INSERT INTO librarian (username, ip, login_time) VALUES (:username, :ip, NOW())");
$stmt->execute(['username' => $username, 'ip' => $ip]);
$logger->log('info', $message);
header('Content-Type: application/json');
echo json_encode(['status' => 'success', 'message' => '登录日志已记录']);
exit;
} catch (PDOException $e) {
header('Content-Type: application/json');
echo json_encode(['status' => 'error', 'message' => '数据库错误: ' . $e->getMessage()]);
exit;
}
}
// 原日志提交逻辑不变
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
$level = $_POST['level'];
$message = $_POST['message'];
$context = isset($_POST['context']) ? json_decode($_POST['context'], true) : [];
if ($context !== null) {
$message .= ' ' . json_encode($context);
}
$logger->log($level, $message);
header('Content-Type: application/json');
echo json_encode(['status' => 'success', 'message' => '日志已记录']);
exit;
}
// 日志检索功能不变
if (isset($_GET['action']) && $_GET['action'] === 'fetch') {
$limit = isset($_GET['limit']) ? intval($_GET['limit']) : 100;
try {
$stmt = $pdo->query("SELECT username, ip, login_time FROM librarian ORDER BY login_time DESC LIMIT $limit");
$logs = $stmt->fetchAll(PDO::FETCH_ASSOC);
header('Content-Type: application/json');
echo json_encode(['status' => 'success', 'logs' => $logs]);
exit;
} catch (PDOException $e) {
header('Content-Type: application/json');
echo json_encode(['status' => 'error', 'message' => '数据库错误: ' . $e->getMessage()]);
exit;
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="font-inter bg-neutral-100 text-neutral-700 min-h-screen flex flex-col">
<header class="bg-white border-b border-neutral-200 py-4 px-6 flex items-center justify-between">
<div class="flex items-center gap-4">
<span class="text-sm text-neutral-500">
<i class="fa fa-clock-o mr-1"></i>
<span id="live-time"></span>
</span>
<button id="refresh-logs" class="btn btn-outline text-sm">
<i class="fa fa-refresh"></i>
<span>刷新</span>
</button>
</div>
</header>
<main class="flex-1 p-6">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- 左侧记录新日志模块不变 -->
<div class="bg-white rounded-xl shadow-sm p-5 border border-neutral-200 lg:col-span-1">
<h2 class="text-lg font-semibold mb-4 flex items-center gap-2">
<i class="fa fa-plus-circle text-primary"></i>
<span>记录新日志</span>
</h2>
<form id="log-form" class="space-y-4">
<div>
<label class="block text-sm font-medium text-neutral-700 mb-1">日志级别</label>
<select name="level" class="w-full px-3 py-2 border border-neutral-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary">
<?php foreach ($config['log_levels'] as $level) { ?>
<option value="<?php echo $level; ?>"><?php echo ucfirst($level); ?></option>
<?php } ?>
</select>
</div>
<div>
<label class="block text-sm font-medium text-neutral-700 mb-1">日志消息</label>
<textarea name="message" rows="3" class="w-full px-3 py-2 border border-neutral-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary" placeholder="输入日志内容..."></textarea>
</div>
<div>
<label class="block text-sm font-medium text-neutral-700 mb-1">上下文信息 (JSON)</label>
<textarea name="context" rows="3" class="w-full px-3 py-2 border border-neutral-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary" placeholder='{"key": "value"}'></textarea>
</div>
<button type="submit" class="btn btn-primary w-full">
<i class="fa fa-save"></i>
<span>记录日志</span>
</button>
</form>
<div id="form-status" class="mt-4 hidden"></div>
</div>
<!-- 右侧新增登录日志模块 -->
<div class="bg-white rounded-xl shadow-sm p-5 border border-neutral-200 lg:col-span-2">
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-semibold flex items-center gap-2">
<i class="fa fa-user-circle text-primary"></i>
<span>登录日志</span>
</h2>
<div class="flex gap-2">
<input type="text" id="login-search" placeholder="搜索登录记录..." class="pl-9 pr-4 py-2 rounded-lg border border-neutral-300 focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary text-sm">
<select id="login-filter" class="border border-neutral-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary text-sm">
<option value="">所有级别</option>
<?php foreach ($config['log_levels'] as $level) { ?>
<option value="<?php echo $level; ?>"><?php echo ucfirst($level); ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="h-[calc(100%-60px)] overflow-auto">
<div id="login-logs-container" class="space-y-2">
<div class="text-center text-neutral-400 py-10">
<i class="fa fa-spinner fa-spin text-2xl mb-2"></i>
<p>加载登录日志中...</p>
</div>
</div>
</div>
</div>
</div>
</main>
<footer class="bg-white border-t border-neutral-200 py-3 px-6 text-center text-sm text-neutral-500">
<p>© 2025 日志系统 | 系统时间:<span id="footer-time"></span></p>
</footer>
<script>
// 实时时间更新函数
function updateTime() {
const now = new Date();
const timeStr = now.toISOString().slice(0, 19).replace('T', ' ');
document.getElementById('live-time').textContent = timeStr;
document.getElementById('footer-time').textContent = timeStr;
}
document.addEventListener('DOMContentLoaded', () => {
updateTime();
setInterval(updateTime, 1000); // 每秒更新时间
// 原日志加载逻辑(左侧记录新日志)
function loadLogs() {
fetch('log_system.php?action=fetch&limit=100')
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
renderLogs(data.logs);
}
});
}
function renderLogs(logs) {
const container = document.getElementById('logs-container');
container.innerHTML = '';
logs.forEach(log => {
const logEntry = document.createElement('div');
logEntry.className = 'p-3 rounded-lg border border-neutral-200 bg-white hover:shadow-sm transition-shadow';
logEntry.innerHTML = `
<div class="flex justify-between items-start">
<span class="text-xs text-neutral-500">${log.login_time}</span>
<span class="px-2 py-0.5 rounded-full text-xs font-medium ${getLogLevelClass(log.level)}">${ucfirst(log.level)}</span>
</div>
<div class="mt-2 text-sm break-words">${log.message}</div>
`;
container.appendChild(logEntry);
});
}
// 新增:登录日志加载逻辑(右侧登录日志)
function loadLoginLogs() {
fetch('log_system.php?action=fetch&limit=100')
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
renderLoginLogs(data.logs);
}
});
}
function renderLoginLogs(logs) {
const container = document.getElementById('login-logs-container');
container.innerHTML = '';
logs.forEach(log => {
if (log.username && log.ip) { // 过滤出登录相关日志
const logEntry = document.createElement('div');
logEntry.className = 'p-3 rounded-lg border border-neutral-200 bg-white hover:shadow-sm transition-shadow';
logEntry.innerHTML = `
<div class="flex justify-between items-start">
<span class="text-xs text-neutral-500">${log.login_time}</span>
<span class="px-2 py-0.5 rounded-full text-xs font-medium log-level-info">登录</span>
</div>
<div class="mt-2 text-sm break-words">用户 ${log.username} 登录,IP: ${log.ip}</div>
`;
container.appendChild(logEntry);
}
});
}
// 登录日志搜索过滤(示例:仅搜索用户名或 IP)
document.getElementById('login-search').addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase();
const entries = document.querySelectorAll('#login-logs-container div');
entries.forEach(entry => {
const message = entry.textContent.toLowerCase();
entry.style.display = message.includes(searchTerm) ? 'block' : 'none';
});
});
// 初始加载日志和登录日志
loadLogs();
loadLoginLogs();
// 刷新按钮同时更新两类日志
document.getElementById('refresh-logs').addEventListener('click', () => {
loadLogs();
loadLoginLogs();
});
});
</script>
</body>
</html>
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
PHP
1
https://gitee.com/FreeWind2333/Simple-PHP-Library-Management-System.git
git@gitee.com:FreeWind2333/Simple-PHP-Library-Management-System.git
FreeWind2333
Simple-PHP-Library-Management-System
简易的PHP图书后台管理系统
master

搜索帮助