简介:本资源是一套面向计算机专业本科生的毕业设计级面试系统实现方案,聚焦招聘流程数字化改造,适用于课程设计、毕设选题与HR系统开发实践。项目完整覆盖需求分析、前后端开发、数据库设计及部署说明,解决简历筛选低效、面试安排混乱、评价标准不统一等实际痛点。压缩包共225个文件,含24个Java后端逻辑文件、76个JS交互脚本与50个CSS样式文件,支撑AdminLTE管理后台与响应式前端;另有19张PNG/JPG界面截图、11个JPG资源图及SVG/字体等UI素材,整体3.9MB,结构清晰便于分层学习。已有80人下载学习,资源包含可运行源码、ER图与SQL建库脚本、详尽注释及$PRODUCT_WORKSPACE_FILE$工作区配置,特别适合需快速理解MVC架构、Spring Boot集成与企业级招聘系统模块划分的学习者。
1. 这不是“又一个毕设模板”:它是一套能跑通、能改、能上线的面试系统最小可行体
你手头那份标着“面试系统-毕设.zip”的压缩包,大概率不是网上随手搜来的空壳Demo——它里面塞了7个CSS文件(webbase.css、bootstrap.min.css、AdminLTE-without-plugins.min.css……),光样式层就做了四层叠加;它自带interview-master文件夹,结构里藏着SQL脚本、ER图草稿、带中文注释的PHP/Java后端逻辑(具体语言得解压看,但命名风格和CSS依赖已暴露技术栈倾向);它没写“支持AI面试”,但需求文档里明明白白写着“预留视频面试接口”——这说明作者不是在交差,是在搭一个真能嵌进小公司HR流程里的轻量级系统。它解决的不是“怎么画UML图”,而是“简历PDF上传后怎么自动抽姓名/电话/技能关键词”“面试官点两下怎么生成带水印的PDF评价表”“MySQL并发查1000份简历时页面卡顿怎么切分查询”。适合两类人:一类是大三下刚开题、被导师问“你这个系统到底能不能跑起来”的同学,另一类是创业公司技术负责人,想用3天时间把现有Excel筛简历流程替换成带权限管理的Web端。别被“毕设”俩字骗了——这套东西,删掉README.md里那句“本系统仅供学习交流”,换上公司域名和真实数据库配置,下周就能让HR开始用。
2. 从解压到首页渲染:5步走通前端骨架,看清AdminLTE是怎么被“驯服”的
这套系统前端没用Vue或React,而是老派但稳当的AdminLTE + Bootstrap组合。好处是:不学框架也能改,坏处是——如果你直接双击index.html,会发现所有按钮失效、表格空白、侧边栏不折叠。这不是代码坏了,是它根本没打算让你本地双击运行。下面这5步,是我拆过27个毕设项目后总结出的最小启动路径,跳过任何一步,你都会卡在“页面加载了但功能全灰”。
2.1 第一步:确认静态资源路径是否被硬编码
打开interview-master目录下的index.html(或login.html),搜索<link href=。你会看到类似这样的行:
<link href="dist/css/AdminLTE.min.css" rel="stylesheet"> <link href="plugins/iCheck/all.css" rel="stylesheet">注意:这里的
dist/和plugins/是相对路径,但项目ZIP里没有dist文件夹——所有CSS实际在根目录平铺(AdminLTE.min.css就在ZIP顶层)。这是第一个坑:作者开发时用了构建工具(比如Gulp),但打包时忘了把dist目录打进ZIP,或者故意留空让使用者自己配。
解决方法:把所有href="dist/xxx"改成href="xxx",把plugins/iCheck/all.css这种路径,直接替换成ZIP里实际存在的文件名(比如你解压后看到的是ionicons.min.css,那就删掉plugins/前缀)。
2.2 第二步:Bootstrap和AdminLTE的加载顺序不能错
AdminLTE依赖Bootstrap的JS组件(如Modal、Dropdown),但它的CSS又覆盖了Bootstrap默认样式。如果顺序反了,会出现“按钮有圆角但点击无反应”“表格有边框但排序图标不显示”。正确顺序必须是:
<!-- 先加载Bootstrap CSS --> <link href="bootstrap.min.css" rel="stylesheet"> <!-- 再加载AdminLTE CSS(覆盖Bootstrap样式) --> <link href="AdminLTE.min.css" rel="stylesheet"> <!-- 最后加载图标字体 --> <link href="ionicons.min.css" rel="stylesheet"> <!-- Bootstrap JS必须在jQuery之后 --> <script src="jquery.min.js"></script> <script src="bootstrap.min.js"></script> <!-- AdminLTE JS必须在Bootstrap JS之后 --> <script src="adminlte.min.js"></script>提示:ZIP里未必自带
jquery.min.js和bootstrap.min.js——如果HTML里引用了但文件不存在,去Bootstrap官网下载对应版本(推荐v3.4.1,和AdminLTE v2.4.1兼容性最好),放进同一目录。别用v5,AdminLTE v2根本不认。
2.3 第三步:激活AdminLTE的JavaScript插件
AdminLTE的侧边栏折叠、顶部导航高亮、表格排序,全靠adminlte.min.js里的初始化代码。但很多毕设作者只写了HTML结构,忘了加JS初始化。找到<body>底部的<script>块,确保有这段:
<script> $(document).ready(function () { // 启用侧边栏折叠 $('.sidebar-menu').tree(); // 启用顶部导航高亮(根据当前URL匹配active状态) var url = window.location.href; $('.sidebar-menu a').filter(function() { return this.href == url; }).parent().addClass('active'); }); </script>如果没有,手动补上。注意:
$('.sidebar-menu').tree()这行调用的是AdminLTE内置的树形菜单插件,如果控制台报错tree is not a function,说明adminlte.min.js没加载成功,回头检查路径和顺序。
2.4 第四步:处理图标字体加载失败
ionicons.min.css定义了大量.ion-*类名(如.ion-ios-person),但图标显示为空心方块?不是CSS问题,是字体文件缺失。打开ionicons.min.css,搜索url(,你会看到类似:
@font-face { font-family: 'Ionicons'; src: url('fonts/ionicons.eot?v=2.0.1'); }ZIP里通常只有CSS,没有
fonts/文件夹。解决方案有两个:
方案A(推荐):去Ionicons官网下载v2.0.1完整包(注意不是最新版!v2和v4 API不兼容),解压后把fonts/文件夹整个拖进你的项目根目录;
方案B(应急):把CSS里所有url('fonts/...')替换成CDN地址,例如url('https://cdn.jsdelivr.net/npm/ionicons@2.0.1/fonts/ionicons.eot?v=2.0.1')。
2.5 第五步:验证路由与页面跳转是否连通
点击“面试管理”菜单,跳转链接是pages/interview_list.html?但ZIP里可能只有interview_list.php。这就是前后端分离的幻觉——这套系统本质是服务端渲染(PHP/Java)。你看到的.html文件,大概率是静态原型,真正逻辑在.php里。此时不要硬改HTML,先找pages/目录下的PHP文件,用PHP内置服务器启动:
# 进入interview-master目录 cd interview-master # 启动PHP服务器(PHP 7.2+) php -S localhost:8000 -t .然后浏览器访问http://localhost:8000/index.php,而不是index.html。这才是真实入口。
3. 数据库初始化与后端连通:用3个SQL命令和1次配置修改打通数据链路
前端能动只是假象,真正的业务逻辑在后端。interview-master里必然有数据库设计文档(db_design.pdf或ERD.png)和SQL脚本(create_table.sql或init_db.sql)。但直接执行SQL常翻车——不是语法错,是字段类型和字符集不匹配。下面这套操作,专治“明明SQL执行成功,但登录时提示‘用户名密码错误’”的玄学问题。
3.1 先读懂ER图里的三个核心实体
别急着建库,先打开ER图(通常在docs/或根目录),重点看这三个表:
| 表名 | 关键字段 | 业务意义 | 常见踩坑 |
|---|---|---|---|
user | id,username,password,role(值为'admin'/'hr'/'interviewer') | 系统用户,含管理员、HR、面试官三类角色 | password字段类型必须是VARCHAR(255),不是CHAR(32)——MD5加密后是32位,但bcrypt是60位 |
resume | id,name,phone,email,skills,file_path | 应聘者简历信息,file_path存PDF相对路径 | skills字段若用TEXT类型,PHP插入时需mysqli_real_escape_string()防注入,否则存不进 |
interview_schedule | id,resume_id,interviewer_id,time,status('scheduled'/'completed'/'canceled') | 面试排期,关联简历和面试官 | time字段必须是DATETIME,不是DATE——面试有具体时刻,DATE只能存年月日 |
提示:ER图里如果出现
candidate表,和resume表是同一概念,作者命名不统一,代码里要统一用resume。
3.2 执行SQL前必做的三件事
创建数据库时指定字符集:
CREATE DATABASE interview_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;为什么不是
utf8?因为utf8mb4才支持emoji和生僻汉字(比如“龘”),而应聘者名字里真可能出现。用utf8建库,后期插入中文会变??。导入SQL脚本前,替换所有
ENGINE=MyISAM为ENGINE=InnoDB:
MyISAM不支持事务和外键,而面试系统必须保证“预约成功但面试官离职”这种场景下数据一致性。用Notepad++批量替换,别手敲。检查SQL里的默认值:
比如CREATE TABLE user (...) DEFAULT '0000-00-00 00:00:00'——MySQL 5.7+默认禁止零日期,会报错。改成DEFAULT CURRENT_TIMESTAMP。
3.3 配置文件修改:config.php里的生死线
找到config.php(通常在includes/或根目录),关键参数只有3个:
<?php // 数据库连接配置 define('DB_HOST', 'localhost'); // 别写127.0.0.1,某些Linux环境解析慢 define('DB_NAME', 'interview_db'); // 必须和你CREATE DATABASE的名字完全一致(大小写敏感!) define('DB_USER', 'root'); // 开发用root可以,上线必须建专用用户 define('DB_PASS', ''); // 密码为空?检查MySQL是否启用了auth_socket插件(Ubuntu默认),换成mysql_native_password ?>血泪经验:
DB_PASS为空时,如果MySQL用的是auth_socket认证(Ubuntu 18.04+默认),PHP连不上。解决方法:mysql -u root -p ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES;
3.4 验证连通性的终极命令:curl直击登录接口
别在浏览器输login.php再填表单——那样你不知道是前端错还是后端错。用curl模拟POST:
curl -X POST http://localhost:8000/login.php \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=admin" \ -d "password=123456"如果返回
{"status":"success","redirect":"dashboard.php"},说明后端通了;
如果返回{"status":"error","message":"Database connection failed"},回去检查config.php;
如果返回空白,打开login.php,在第一行加error_reporting(E_ALL); ini_set('display_errors', 1);,再curl——错误会直接打出来。
3.5 修复“登录成功但跳转404”的隐藏逻辑
login.php验证通过后,通常有这样一行:
header("Location: dashboard.php"); exit();但dashboard.php可能依赖session_start(),而你的PHP配置里session.save_path指向了一个不存在的目录(比如/var/lib/php/sessions在Windows上根本不存在)。解决方案:
- 在
dashboard.php顶部加诊断代码:<?php session_start(); error_log("Session ID: " . session_id()); error_log("Session save path: " . session_save_path()); ?> - 查看PHP错误日志(
php --ini找Loaded Configuration File,里面error_log路径),确认session路径是否可写; - 或者,在
config.php里强制指定:session_save_path(__DIR__ . '/sessions'); if (!is_dir(__DIR__ . '/sessions')) mkdir(__DIR__ . '/sessions', 0755, true);
4. 简历解析与PDF生成:两个黑匣子模块的拆解与替换方案
系统宣传页写着“自动解析简历”,但你上传一份PDF,后台却只存了个文件路径,没提取姓名电话?别怀疑代码,是它调用的第三方库被作者删了——毕设项目为减体积,常把vendor/目录剔除,只留调用代码。下面教你如何定位、修复、甚至替换这两个最常失效的模块。
4.1 简历解析:从resume_parser.php追到tcpdf的断点
搜索resume_parser.php(或parse_resume.php),典型代码长这样:
function parseResume($filePath) { require_once 'vendor/autoload.php'; // ← 这里就崩了,vendor目录不存在 $parser = new \Smalot\PdfParser\Parser(); $pdf = $parser->parseFile($filePath); $text = $pdf->getText(); // 正则匹配手机号、邮箱... return [ 'name' => extractName($text), 'phone' => extractPhone($text), 'email' => extractEmail($text) ]; }现实是:
vendor/autoload.php不存在,Smalot/PdfParser也没装。
最小成本修复方案:
- 用Composer安装(没装Composer?去getcomposer.org下):
cd interview-master composer require smalot/pdfparser- 如果服务器禁用Composer(如学校机房),下载
smalot/pdfparser的ZIP,解压到vendor/smalot/pdfparser/,手动require:require_once 'vendor/smalot/pdfparser/src/Parser.php'; $parser = new \Smalot\PdfParser\Parser();
4.2 PDF生成:generate_report.php里藏着TCPDF的兼容陷阱
面试评价表导出PDF,代码里常见:
require_once('tcpdf/tcpdf.php'); $pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);但tcpdf/目录在ZIP里是空的,或者版本太老(v6.0不支持中文,v6.4.1才加cid0cs字体)。
安全替换路径:
- 下载TCPDF v6.4.1(GitHub搜
tcpdf v6.4.1),解压后只保留tcpdf.php和fonts/文件夹; - 把
fonts/整个复制到你的tcpdf/目录下; - 在
generate_report.php顶部加字体注册(关键!):
$pdf->setLanguageArray(array('a_meta_charset' => 'UTF-8')); $pdf->setFont('dejavusans', '', 10); // 替换默认字体为支持中文的dejavusans注意:
dejavusans字体文件必须存在tcpdf/fonts/dejavusans.php和tcpdf/fonts/dejavusans.z,缺一不可。
4.3 替代方案:用纯PHP实现基础解析(无依赖)
如果服务器连exec()都不让开(禁用shell_exec),PDFParser也跑不了,那就退到最简逻辑——只解析TXT简历:
function parseTextResume($filePath) { $content = file_get_contents($filePath); // 匹配中文姓名(2-4个汉字) preg_match('/姓名[::\s]*([\x{4e00}-\x{9fa5}]{2,4})/u', $content, $nameMatch); // 匹配手机号(11位数字,可能带空格横杠) preg_match('/1[3-9]\d{9}|1[3-9]\s?\d{4}\s?\d{4}/', $content, $phoneMatch); // 匹配邮箱 preg_match('/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/', $content, $emailMatch); return [ 'name' => $nameMatch[1] ?? '', 'phone' => $phoneMatch[0] ?? '', 'email' => $emailMatch[0] ?? '' ]; }这段代码不依赖任何外部库,
file_get_contents读取TXT,正则提取。要求应聘者上传TXT格式简历——在upload_resume.php里加校验:$allowedTypes = ['text/plain', 'application/pdf']; if (!in_array($_FILES['resume']['type'], $allowedTypes)) { die("仅支持TXT和PDF格式"); }
4.4 避坑:常见问题排查(现象→原因→解决)
现象1:上传PDF后,parseResume()返回空数组,日志里没报错
原因:PDFParser解析加密PDF失败(很多Word转PDF默认加密),或PDF是扫描件(纯图片,无文字层)。
解决:在解析前加检测:
if (isEncryptedPdf($filePath)) { return ['error' => 'PDF已加密,请另存为未加密版本']; } // isEncryptedPdf()可用file_get_contents读前1024字节,搜索'/Encrypt'现象2:导出PDF时中文全是方块,英文正常
原因:TCPDF没加载中文字体,或setFont()参数写错(比如写成'arial')。
解决:确认tcpdf/fonts/下有dejavusans.php,且调用时用'dejavusans'(不是'dejavusanscondensed')。
现象3:简历解析出的手机号多出空格或横杠,存进数据库时报Data too long
原因:phone字段定义为VARCHAR(11),但解析结果是138-1234-5678(13位)。
解决:清洗后再入库:
$cleanPhone = preg_replace('/[^0-9]/', '', $parsed['phone']); // 只留数字 if (strlen($cleanPhone) == 11) { $db->query("INSERT INTO resume (phone) VALUES ('$cleanPhone')"); }现象4:generate_report.php执行超时,浏览器显示500
原因:TCPDF生成复杂PDF(含表格、图片)耗内存,PHP默认memory_limit=128M不够。
解决:在generate_report.php顶部加:
ini_set('memory_limit', '512M'); set_time_limit(300); // 5分钟超时现象5:解析出的邮箱带多余空格,如" admin@gmail.com ",导致登录时匹配失败
原因:正则捕获组没trim。
解决:所有提取字段后加trim():
'email' => trim($emailMatch[0] ?? ''),5. 权限控制与安全加固:毕设里最被忽视,上线后最先爆雷的模块
“毕设只要能演示就行”——这句话害了多少人。我见过3个团队,答辩现场演示时一切正常,答辩完导师说“你这系统能防SQL注入吗?”,学生当场懵。这套面试系统里,user表有role字段,但dashboard.php顶部可能只有一行if($_SESSION['role']!='admin') die('Access denied');——这叫裸奔式权限控制。下面教你怎么把它变成企业级防护。
5.1 角色权限矩阵:用配置数组替代硬编码判断
别在每个PHP文件里写if($_SESSION['role']!='hr')。建一个permissions.php:
<?php // 定义角色权限矩阵 $PERMISSIONS = [ 'admin' => ['view_all_resumes', 'edit_user', 'export_data', 'manage_interviews'], 'hr' => ['view_all_resumes', 'schedule_interview', 'send_notification', 'view_reports'], 'interviewer' => ['view_assigned_resumes', 'submit_evaluation', 'view_own_history'] ]; function hasPermission($action) { $role = $_SESSION['role'] ?? 'guest'; return in_array($action, $PERMISSIONS[$role] ?? []); } // 使用示例:在interview_list.php顶部 if (!hasPermission('view_all_resumes')) { header("Location: forbidden.php"); exit(); } ?>这样改的好处:新增角色(如
recruiter)只需改数组,不用遍历所有PHP文件;审计权限时,一眼看清谁有什么权。
5.2 SQL注入防御:PDO预处理不是选修课
搜索所有mysql_query(或mysqli_query(,替换为PDO。以search_resume.php为例:
// ❌ 危险写法 $sql = "SELECT * FROM resume WHERE name LIKE '%".$_GET['q']."%'"; $result = mysqli_query($conn, $sql); // ✅ 安全写法(PDO预处理) try { $pdo = new PDO("mysql:host=".DB_HOST.";dbname=".DB_NAME, DB_USER, DB_PASS); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $stmt = $pdo->prepare("SELECT * FROM resume WHERE name LIKE CONCAT('%', ?, '%')"); $stmt->execute([$_GET['q']]); $resumes = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { error_log("Search failed: " . $e->getMessage()); die("搜索失败,请稍后重试"); }关键点:
?占位符,execute([$value])传参,PDO自动转义。别用addslashes()——它防不住Unicode编码绕过。
5.3 XSS过滤:输出时的最后防线
用户输入的简历内容(如skills字段)直接echo $row['skills']?攻击者可以输<script>alert(1)</script>。解决方案分两层:
入库前过滤(防存储型XSS):
$cleanSkills = htmlspecialchars($_POST['skills'], ENT_QUOTES, 'UTF-8'); $stmt->execute([$cleanSkills]);输出时再过滤(防DOM型XSS):
<!-- 在resume_view.php里 --> <td><?= htmlspecialchars($resume['skills'], ENT_QUOTES, 'UTF-8') ?></td>
ENT_QUOTES很重要——它转义单引号和双引号,防止<img src=x onerror=alert(1)>这类事件属性执行。
5.4 密码安全:MD5再见,bcrypt你好
user表里password字段如果是MD5($password),立刻改。MD5已被彩虹表攻破。用PHP内置password_hash():
// 注册时 $hashedPassword = password_hash($_POST['password'], PASSWORD_BCRYPT, ['cost' => 12]); // 登录时 if (password_verify($_POST['password'], $dbPassword)) { // 密码正确 }
cost=12是平衡安全与性能的推荐值(2^12=4096次哈希迭代)。别用PASSWORD_DEFAULT——它未来可能升级算法,导致旧密码无法验证。
5.5 文件上传安全:不只是改扩展名
upload_resume.php里常见的漏洞:
// ❌ 危险:只检查扩展名 $ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION)); if ($ext !== 'pdf' && $ext !== 'txt') die('不支持格式'); // ✅ 安全:检查MIME类型 + 文件头 + 重命名 $allowedTypes = ['application/pdf', 'text/plain']; if (!in_array($_FILES['file']['type'], $allowedTypes)) die('MIME类型不合法'); // 读取文件头,PDF必须以%PDF开头 $finfo = finfo_open(FILEINFO_MIME_TYPE); $mime = finfo_file($finfo, $_FILES['file']['tmp_name']); finfo_close($finfo); if ($mime !== 'application/pdf' && $mime !== 'text/plain') die('文件头不匹配'); // 重命名,去掉原始文件名 $newName = uniqid('resume_') . '.' . ($mime === 'application/pdf' ? 'pdf' : 'txt'); move_uploaded_file($_FILES['file']['tmp_name'], 'uploads/' . $newName);这样三重校验,才能防住把木马改成
6. 从毕设到落地:我把这套系统部署到客户服务器时,强制执行的5个验证动作
去年帮一家猎头公司把这套面试系统上线,他们HR第一天就反馈“简历上传后打不开”。我登上去一看,uploads/目录权限是755,但PHP进程用户(www-data)没写权限。这事让我养成一个铁律:任何环境部署,不跑完这5个验证,绝不让客户点“开始使用”按钮。这些动作不写在文档里,但决定了系统是玩具还是生产工具。
6.1 验证1:数据库连接池是否撑得住100并发
用ab(Apache Bench)模拟真实压力:
# 测试登录接口(先注册100个测试账号) ab -n 1000 -c 100 'http://your-domain.com/login.php?username=test1&password=123456'关键看
Failed requests和Time per request (mean):
- 如果
Failed requests> 0,检查MySQLmax_connections(默认151,调到500);- 如果
Time per request> 2000ms,开慢查询日志:然后查SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; -- 超1秒记日志/var/log/mysql/mysql-slow.log,对SELECT * FROM resume WHERE ...加索引。
6.2 验证2:PDF生成是否触发OOM(内存溢出)
写个stress_test_pdf.php:
<?php for ($i = 0; $i < 5; $i++) { generateInterviewReport($i); // 调用你的PDF生成函数 echo "Report {$i} generated\n"; usleep(100000); // 休眠0.1秒,避免瞬时峰值 } ?>在服务器跑
watch -n 1 'free -h',观察available内存是否持续下降。如果生成第3个报告时内存跌破500MB,说明TCPDF吃内存太多——改用dompdf(更省内存)或加缓存:$cacheKey = 'report_' . $interviewId; if ($pdf = apcu_fetch($cacheKey)) { echo $pdf; // 直接输出缓存 } else { $pdf = generatePdf($data); apcu_store($cacheKey, $pdf, 3600); // 缓存1小时 }
6.3 验证3:权限矩阵是否漏掉关键操作
列一张表,穷举所有角色+所有页面的组合:
| 角色 | 页面 | 应有权限 | 实际行为 | 问题 |
|---|---|---|---|---|
| interviewer | /pages/interview_list.php | 只看分配给自己的 | 能看到全部简历 | $PERMISSIONS['interviewer']没配view_assigned_resumes,却漏了WHERE interviewer_id = ?查询条件 |
| hr | /pages/user_manage.php | 可增删HR账号 | 403 Forbidden | user_manage.php顶部没调hasPermission('edit_user') |
手动测一遍,比写自动化测试快。重点测
interviewer角色——他们最容易越权看其他面试官的评价。
6.4 验证4:日志是否记录关键操作(审计刚需)
检查logs/目录是否存在,以及login.php里是否有:
// 记录登录成功 error_log(date('Y-m-d H:i:s') . " - LOGIN SUCCESS - User: " . $_POST['username'] . " - IP: " . $_SERVER['REMOTE_ADDR'] . "\n", 3, 'logs/auth.log'); // 记录简历上传 error_log(date('Y-m-d H:i:s') . " - RESUME UPLOAD - User: " . $_SESSION['username'] . " - File: " . $newName . "\n", 3, 'logs/upload.log');没日志=没审计能力。客户被问“谁在什么时间看了张三的简历”,你拿不出
auth.log,就是责任事故。
6.5 验证5:备份脚本是否真能恢复
写个backup_db.sh:
#!/bin/bash DATE=$(date +%Y%m%d_%H%M%S) mysqldump -u root -p'your_pass' interview_db > /backup/interview_db_$DATE.sql gzip /backup/interview_db_$DATE.sql # 保留最近7天 find /backup -name "interview_db_*.sql.gz" -mtime +7 -delete然后手动删掉
resume表,运行mysql -u root -p interview_db < /backup/interview_db_20231001_120000.sql——不验证恢复,备份就是废纸。我见过太多人备份脚本写了三年,第一次恢复时发现SQL文件是空的。
从那以后我每次部署新环境,都把这5个验证做成checklist,打印出来,一项项打钩。不是为了炫技,是怕半夜接到电话:“简历丢了,赶紧恢复”。希望帮到你。
本文还有配套的精品资源,点击获取