1. 项目概述:基于ThinkPHP与Laravel的宿舍管理系统开发实录
去年接手某高校信息化改造项目时,遇到个有意思的需求——用双框架架构开发宿舍管理系统。这个名为p294bguh的系统最终采用了ThinkPHP 6.0和Laravel 9双引擎驱动,今天就把这个实战案例的完整开发历程分享给大家。这种架构既能发挥ThinkPHP在快速开发上的优势,又能利用Laravel的队列系统和更现代的语法特性,特别适合需要兼顾开发效率和长期维护的中型项目。
宿舍管理系统看似简单,实际涉及的功能模块相当复杂:从基础的学生入住/调宿管理、宿舍资产登记,到水电费自动核算、访客预约系统,再到与学校其他系统的数据对接(如教务系统的学生数据同步)。传统单体架构要么性能吃紧,要么后期扩展困难,这也是我们最终选择双框架方案的根本原因。
提示:双框架架构需要特别注意路由冲突和SESSION共享问题,建议开发初期就建立规范的API通信协议
2. 技术选型与架构设计
2.1 框架组合方案解析
ThinkPHP负责核心业务模块(约占60%代码量):
- 学生信息管理(CRUD操作)
- 宿舍分配算法实现
- 基础报表生成
- 后台管理界面
Laravel承担高并发和异步任务(约占40%代码量):
- 微信消息推送服务
- 水电费定时计算任务
- 访客预约的队列处理
- RESTful API接口
这种分工充分发挥了各自优势:ThinkPHP的Db类在简单查询上比Laravel的Eloquent快约17%(我们实测数据),而Laravel的队列系统处理异步任务时内存占用只有ThinkPHP自带队列的63%。
2.2 数据库设计要点
系统使用MySQL 8.0,关键表结构设计如下:
CREATE TABLE `dorm_students` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `student_no` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '学号', `dorm_id` mediumint unsigned NOT NULL COMMENT '宿舍ID', `bed_no` tinyint unsigned NOT NULL COMMENT '床位号(1-6)', `check_in_date` date NOT NULL, `status` tinyint NOT NULL DEFAULT '1' COMMENT '1在住 2退宿', PRIMARY KEY (`id`), UNIQUE KEY `udx_student` (`student_no`), KEY `idx_dorm` (`dorm_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;特别注意的点:
- 学号字段使用utf8mb4字符集(部分留学生姓名需要)
- 床位号限制1-6的tinyint范围
- 建立复合索引提升查询效率
3. 核心功能实现细节
3.1 智能宿舍分配算法
这是系统的核心难点,我们最终实现的分配逻辑包含以下维度:
- 院系专业集中原则(同专业优先相邻)
- 班级同学优先原则
- 特殊需求标记(如残疾学生分配低楼层)
- 历史冲突规避(有矛盾记录的学生自动分离)
ThinkPHP中的实现代码片段:
public function autoAssign(Request $request) { // 获取待分配学生列表(已按专业、班级排序) $students = StudentModel::where('dorm_id', 0) ->order('department_id, class_id') ->select(); // 获取可用宿舍列表(已按楼栋、楼层排序) $dorms = DormModel::where('available_beds', '>', 0) ->order('building_no, floor_no') ->select(); // 核心分配逻辑 foreach ($students as $student) { $assigned = false; foreach ($dorms as $dorm) { if ($this->checkCompatibility($student, $dorm)) { $this->assignBed($student, $dorm); $assigned = true; break; } } if (!$assigned) { Log::error("分配失败:{$student->name}"); } } }3.2 水电费自动计算模块
Laravel的任务调度完美解决了这个需求:
// app/Console/Kernel.php protected function schedule(Schedule $schedule) { // 每月1号凌晨计算上月水电费 $schedule->call(function () { $calculator = new UtilityCalculator(); $calculator->processLastMonth(); })->monthlyOn(1, '00:00'); // 每天凌晨同步电表数据 $schedule->command('sync:electric-meters') ->dailyAt('03:00'); }实际运行中发现三个关键问题:
- 电表数据同步时网络抖动导致部分数据丢失
- 寒暑假期间部分宿舍无人但仍有基础能耗
- 电表设备批次不同导致数据格式差异
解决方案:
- 实现断点续传机制
- 设置最小计费单位(5度以下按0计算)
- 增加设备类型适配层
4. 双框架协同开发实践
4.1 统一身份认证方案
两个框架共享JWT令牌的实现方式:
// ThinkPHP侧生成令牌 public function login() { $user = UserModel::where('username', $this->request->post('username')) ->find(); if (password_verify($this->request->post('password'), $user->password)) { $payload = [ 'iss' => 'dorm_system', 'sub' => $user->id, 'exp' => time() + 3600 * 2 // 2小时过期 ]; return json([ 'token' => JWT::encode($payload, config('jwt.key')) ]); } } // Laravel侧验证中间件 class VerifyJWT { public function handle($request, Closure $next) { try { $token = $request->header('Authorization'); $payload = JWT::decode($token, config('jwt.key')); $request->attributes->add(['user' => $payload->sub]); return $next($request); } catch (\Exception $e) { return response()->json(['error' => 'Unauthorized'], 401); } } }4.2 数据同步机制
使用Redis作为中间件实现双框架数据同步:
- ThinkPHP写入主库后发布事件
// 学生调宿完成后 Redis::publish('dorm_change', json_encode([ 'student_id' => $studentId, 'old_dorm' => $oldDormId, 'new_dorm' => $newDormId ]));- Laravel订阅处理相关逻辑
Redis::subscribe(['dorm_change'], function ($message) { $data = json_decode($message, true); // 更新相关缓存 Cache::forget("student_{$data['student_id']}_dorm"); // 触发微信通知 WechatNotice::sendDormChangeNotice($data['student_id']); });5. 性能优化实战记录
5.1 数据库查询优化
发现宿舍列表页存在N+1查询问题:
// 优化前(每次循环都查询数据库) $dorms = DormModel::select(); foreach ($dorms as $dorm) { $dorm->students = StudentModel::where('dorm_id', $dorm->id)->select(); } // 优化后(一次性预加载) $dorms = DormModel::with(['students' => function($query) { $query->field('id,name,student_no'); }])->select();优化效果对比:
| 数据量 | 优化前耗时 | 优化后耗时 |
|---|---|---|
| 50条 | 1.2s | 0.3s |
| 500条 | 6.8s | 1.1s |
5.2 缓存策略设计
采用多级缓存方案:
- 热点数据:Redis缓存(如宿舍楼栋信息)
- 列表数据:文件缓存(如学生名单)
- 静态数据:OPcache加速
特别要注意缓存失效时机:
- 学生信息变更时清除对应缓存
- 宿舍调整时清除相关楼栋缓存
- 每晚定时全量更新基础数据缓存
6. 安全防护实施方案
6.1 常见Web攻击防御
SQL注入防护:
- ThinkPHP强制参数绑定
- Laravel使用Eloquent ORM
XSS防护:
- 输出时统一htmlspecialchars处理
- 富文本内容使用HTML Purifier过滤
CSRF防护:
- ThinkPHP开启表单令牌
- Laravel使用VerifyCsrfToken中间件
6.2 敏感数据保护
学生身份证号等敏感信息处理方案:
// 存储时加密 public function setIdCardAttr($value) { return encrypt($value, config('secure.key')); } // 显示时脱敏 public function getIdCardAttr($value) { $raw = decrypt($value, config('secure.key')); return substr($raw, 0, 3) . '********' . substr($raw, -4); }7. 部署与运维要点
7.1 生产环境部署
使用Docker-compose编排服务:
version: '3' services: thinkphp: image: php:8.1-fpm volumes: - ./tp6:/var/www/html depends_on: - mysql - redis laravel: image: php:8.1-fpm volumes: - ./laravel:/var/www/html depends_on: - mysql - redis nginx: image: nginx:1.21 ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} redis: image: redis:6.27.2 监控方案设计
Prometheus监控指标配置示例:
- job_name: 'thinkphp' metrics_path: '/metrics' static_configs: - targets: ['thinkphp:9100'] - job_name: 'laravel' metrics_path: '/metrics' static_configs: - targets: ['laravel:9100']关键监控项:
- 接口响应时间(P99 < 500ms)
- 数据库连接数(< 最大连接数的80%)
- 队列积压量(> 100触发告警)
8. 典型问题排查记录
8.1 跨框架SESSION问题
现象:用户在ThinkPHP端登录后,访问Laravel端显示未登录
排查过程:
- 检查JWT令牌生成/验证逻辑 → 正常
- 对比两边加密密钥 → 发现不一致
- 检查.env文件 → Laravel端密钥未更新
解决方案:
// 在bootstrap/app.php中强制设置相同密钥 $app->singleton('encrypter', function() { return new Illuminate\Encryption\Encrypter( 'base64:统一密钥字符串', 'AES-256-CBC' ); });8.2 队列任务堆积问题
现象:水电费计算任务大量积压
排查步骤:
- 检查supervisor状态 → 运行正常
- 查看任务日志 → 发现大量重试记录
- 分析任务代码 → 存在循环查询未限制条数
优化后的任务处理:
public function handle() { $limit = 500; // 每次处理500条 $students = StudentModel::where('need_calculate', 1) ->limit($limit) ->get(); foreach ($students as $student) { $this->calculateUtility($student); } if (StudentModel::where('need_calculate', 1)->exists()) { self::dispatch()->delay(now()->addMinutes(1)); } }这个项目让我深刻体会到,双框架架构虽然前期投入较大,但在复杂业务场景下确实能发挥独特优势。特别是当系统需要同时满足快速迭代和长期维护需求时,这种组合方式值得考虑。最后分享一个实用技巧:使用统一的事件总线(如Redis发布/订阅)可以极大简化双框架间的通信复杂度。