PHP毕设项目避坑指南:从MVC架构到安全实践的完整技术路径
面向计算机专业本科生的技术科普,全文可直接作为毕设脚手架参考。
1. 背景痛点:为什么“能跑”≠“能毕业”
过去三年帮校内同学 Review 了 120 多份 PHP 毕设,发现大家踩的坑高度重合:
- 所有业务代码挤在
*.php顶部,HTML 与 SQL 交错,调试时像“考古”。 - 接收
$_GET['id']后直接拼进 SQL,被导师用 SQLMap 一扫直接爆库。 - 把 Apache 根目录指向项目根路径,
.git与config.php裸露,外网可下载。 - 本地用 WAMP 跑通就交稿,上服务器才发现
session.save_path不可写,登录状态秒失效。 - 代码里到处是
md5($_POST['pwd']),还自我安慰“哈希过了”。
这些问题导致功能越丰富,演示翻车概率越大。下文给出一条“低学习成本 + 高安全底线”的完整技术路径,让你把有限时间花在“亮点功能”而非救火。
2. 技术选型:原生、CodeIgniter、Laravel 如何权衡
| 方案 | 学习曲线 | 功能完备度 | 本地部署速度 | 适用场景 |
|---|---|---|---|---|
| 原生 PHP | 最低 | 需手写 | 最快 | 页面数 <10、无用户体系、演示一次就封存 |
| CodeIgniter 4 | 中 | 路由、ORM、验证器齐全 | 解压即用 | 功能模块 5~8 个,需前后台,时间 3~4 周 |
| Laravel 11 | 高 | 队列/事件/通知全家桶 | 需调整 PHP 版本、Redis | 团队开发、想秀“企业级”但肯啃文档 |
结论:80 % 的本科毕设用 CodeIgniter 4 最划算,路由+ORM+验证器开箱即用,又比 Laravel 轻量;下文示例以 CI4 为蓝本,原生开发者可把目录结构平移到简单 MVC 自行实现。
3. 核心实现:MVC + PDO + CSRF 三板斧
3.1 目录结构(CodeIgniter 4 为例)
project_root/ ├─ app/ │ ├─ Controllers/ // 接收请求 │ ├─ Models/ // 业务逻辑与数据库交互 │ ├─ Views/ // 纯 HTML + 少量 echo │ └─ Filters/ // 统一 CSRF、Auth 检查 ├─ public/ // 仅入口 index.php 与静态资源 ├─ writable/ // 日志、上传、session ├─ env // 复制自 env.example,数据库密钥放这里 └─ composer.json把
public/设为 Apache/Nginx 根目录,即可一键屏蔽上层源码。
3.2 路由与控制器拆分
app/Config/Routes.php
$routes->get('/', 'Home::index'); $routes->post('login', 'Auth::login'); // 显式指定 POSTapp/Controllers/Auth.php
namespace App\Controllers; use App\Models\UserModel; class Auth extends BaseController { public function login() { // 1. 只收 POST if (! $this->request->is('post')) return redirect()->back(); // 2. 内置验证器:账号必填、密码长度 6-20 $rules = [ 'email' => 'required|valid_email', 'password' => 'required|min_length[6]' ]; if (! $this->validate($rules)) { return redirect()->back()->withInput()->with('errors', $this->validator->getErrors()); } // 3. 模型层验证用户 $model = new UserModel(); $user = $model->verify( $this->request->getPost('email'), $this->request->getPost('password') ); if (! $user) { return redirect()->back()->with('error', '账号或密码错误'); } // 4. 重建会话防固定攻击 session()->regenerate(); session()->set('uid', $user['id']); return redirect()->to('/dashboard'); } }3.3 模型层:PDO 参数化查询
app/Models/UserModel.php
namespace App\Models; use CodeIgniter\Model; class UserModel extends Model { protected $table = 'users'; protected $primaryKey = 'id'; protected $allowedFields = ['email', 'password_hash']; /** * 验证用户,返回用户数组或 null */ public function verify(string $email, string $plainPwd): ?array { $user = $this->where('email', $email)->first(); if ($user && password_verify($plainPwd, $user['password_hash'])) { return $user; } return null; } }框架已默认使用 PDO 预处理,
where()方法内部自动参数化,彻底杜绝拼接注入。
3.4 视图层:只负责渲染,业务零渗透
app/Views/login.php
<?= csrf_field() ?> <!-- CI4 一键生成隐藏 token --> <div class="mb-3"> <label>邮箱</label> <input type="email" name="email" value="<?= old('email') ?>"> </div>3.5 全局 CSRF 过滤
app/Filters/CsrfFilter.php
public function before(RequestInterface $request, $arguments = null) { if (in_array($request->getMethod(), ['POST','PUT','PATCH','DELETE'])) { if (! $request->hasHeader('X-Requested-With') && ! $this->security->verify($request)) { throw SecurityException::forDisallowedAction(); } } }在app/Config/Filters.php把该过滤器绑定到所有路由,即可一键开启 CSRF 防护。
4. 完整登录模块示例(Clean Code 版)
以下代码可直接复制到 CI4 运行,也可作为原生 MVC 的伪代码模板。
路由
见 3.2 节,不再赘述。控制器
已含输入校验、会话重建、失败重定向,符合单一职责。模型
仅暴露verify()一个语义化方法,隐藏密码比对细节。视图
通过old()辅助函数保留输入,错误信息用session()->get('error')展示,无 JavaScript 依赖。密码存储
注册时调用password_hash($plain, PASSWORD_DEFAULT),成本因子让服务器在 100 ms 左右完成计算即可。
5. 安全性与性能再深挖
5.1 会话固定与劫持
- 登录成功必须
session_regenerate_id(true);CI4 已封装为session()->regenerate()。 - 给 Cookie 加
HttpOnly+Secure+SameSite=Lax,可在php.ini或框架app/Config/Session.php统一开启。
5.2 N+1 查询
常见场景:在foreach里循环查关联表。解决思路:
- 使用 ORM 的
with()预加载(CI4 叫join()或eager loading)。 - 手写 SQL 时先
IN (...)一次性取回,再用 PHP 数组重索引。
5.3 错误报告
开发阶段env中CI_ENVIRONMENT = development,上线前必须改为production,否则 Whoops 页面会把数据库账号抛给评委。
6. 生产环境避坑清单
关闭目录索引
Options -Indexes写进public/.htaccess,防止列目录泄露 git 备份。Composer 依赖锁定
提交composer.lock,服务器执行composer install --no-dev,确保版本与本地一致。配置分离
数据库、SMTP 密钥统一放env,并在 Git 里忽略真实文件,只保留env.example作为模板。上传目录可写但不可执行
writable/uploads/给予 755,.htaccess内加php_flag engine off,防止恶意上传 getshell。日志分级
业务日志写writable/logs/app-2024-06-12.log,错误日志走error_log,演示时评委想看的“数据流向”一目了然。
7. 效果展示
下图是某同学按本文重构后的后台仪表盘,目录清晰、路由统一、所有 POST 表单自带 CSRF token,一次性通过外网安全扫描。
8. 结语:把时间花在“亮点”而非救火
毕设周期通常只有 60~90 天,先让代码“安全可维护”,再谈功能创新。建议你:
- 用 1 小时把现有脚本按上文目录拆成 MVC;
- 用 30 分钟把 SQL 改成参数化查询,开启 CSRF;
- 用 2 小时把密码哈希、会话固定、N+1 查一遍;
- 剩余时间专注“算法/可视化/接口”这些评委愿意提问的亮点。
思考留给你:在截止倒计时面前,如何用最少的代码换取最大的质量感?答案往往不是“再写一万行”,而是“让每一行都经得起提问”。祝你毕设一遍过,答辩不翻车。