简介:这是一套基于PHP开发的EnableQ在线问卷调查引擎完整源码包,面向需要部署在线问卷系统的PHP开发者、站长及调研人员。资源包内共2263个文件,以1317个PHP业务逻辑文件、704个HTML页面模板为核心,辅以CSS/JS前端样式与交互、GIF/PNG/JPG图片素材、SQL数据库脚本以及Swf动态组件等,压缩包整体仅9.57MB,适合快速下载部署。目前已有104人学习下载。解压后可见响应式样式文件(如pad.css、phone.css等),说明系统对移动端与平板访问做了适配;完整目录结构便于二次开发或按模块调用。通过本包可了解在线问卷引擎的UI渲染、数据存储、逻辑跳转、统计分析与发布分享等核心实现,为自建问卷平台或学习PHP项目架构提供可直接运行的参考。
1. 拿到 EnableQ 之后,先知道这是一套什么样的 PHP 问卷引擎
运营周三提了个需求:下周要对三千个客户发满意度问卷。用第三方问卷平台,数据在别人手里,导出还要二次处理;从零自己写,光单选、多选、矩阵、跳题逻辑就能拖半个月。EnableQ 就是这中间出现频率很高的一个选型——一套基于 PHP 的在线问卷调查引擎,源码部署在自己的服务器上,问卷创建、填答、统计都在同一套包里完成,数据自持。这篇东西按拿到源码之后的动手顺序写:先讲清楚它的运行方式和数据模型,再把最小部署跑通,接着讲接入公司登录体系、定制模板和导出答卷,最后补几个老工程师才会注意的坑。目标读者是已经能独立配置 PHP 环境、准备在一两天内上线自托管问卷系统的工程师,新手可以照着命令走,熟手可以重点看参数边界和排障思路。
2. EnableQ 的运行原理与数据模型:先看懂问卷是怎么被跑起来的
拿到一套不熟悉的 PHP 系统,第一件事不是看代码,而是先把它的请求链路和表结构理顺。EnableQ 这类问卷引擎之所以能维持多年稳定,核心原因是它把“问卷配置”“填答过程”“统计结果”三个环节彻底分离了。
2.1 填答、管理、统计三条链路的职责边界
一个用户在浏览器里打开问卷链接,到管理员在后台看到统计图表,中间其实经过了三套互相独立的 PHP 流程。第一套是填答端:用户通过 survey 链接进入,引擎从数据库读出问卷配置,按题目顺序渲染表单,用户提交后写入答卷记录。第二套是管理端:管理员在这里建问卷、维护题目、配置跳题逻辑,这些操作只修改问卷配置表,不碰答卷数据。第三套是统计端:它只做聚合查询,从答卷明细表里按题目维度算出分布和占比。
这三条链路在代码层面通常是三个独立的入口目录。你在源码包会看到类似survey、admin、stat这样的目录划分。写代码的时候要注意,填答端和管理端对 session 的依赖强度完全不同:填答端允许匿名访问,管理员入口必须做权限校验。如果后续要改登录体系,只需要在填答端入口处加一层“识别当前用户是谁”的逻辑,而不需要动问卷引擎本身的题目渲染逻辑。
2.2 核心表结构:问卷配置与答卷数据为什么必须分开存
问卷系统最怕的就是把题目定义和用户答案混在一张表里,那样每改一次问卷结构就要重建数据。EnableQ 的常见做法是分成四类基础表:问卷表、题目表、选项表、答卷表及答卷明细表。问卷表存问卷标题、状态、起止时间;题目表存每个题目的类型、题干、排序;选项表存单选多选的候选项;答卷表存一次完整提交,答卷明细表存每一道题的具体答案。下面的建表片段可以帮你快速理解它们的关联关系。
CREATE TABLE enq_survey ( sid INT UNSIGNED NOT NULL AUTO_INCREMENT, title VARCHAR(200) NOT NULL, status TINYINT DEFAULT 0 COMMENT '0草稿 1发布 2结束', start_time DATETIME, end_time DATETIME, PRIMARY KEY (sid) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE enq_question ( qid INT UNSIGNED NOT NULL AUTO_INCREMENT, sid INT UNSIGNED NOT NULL, qtype TINYINT NOT NULL COMMENT '1单选 2多选 3填空 4矩阵', title VARCHAR(500) NOT NULL, sort_order SMALLINT DEFAULT 0, is_required TINYINT DEFAULT 0, PRIMARY KEY (qid), KEY idx_sid (sid) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;跨表关联的核心字段是sid和qid,所有题目、选项、答卷都通过这两个 ID 串起来。试卷配置表只存“有哪些题”,答卷明细表只存“用户选了哪个选项ID”,统计端最后通过 JOIN 把题目文本和选项文本关联回来。这样设计的好处是:问卷发布后不允许修改题目结构,但允许追加答卷;统计脚本只需要扫描明细表,不用担心题目配置变化影响历史数据。
2.3 为什么这种老牌 PHP 项目还能用:依赖边界与版本注意点
EnableQ 这类系统的代码普遍是面向 PHP 5.x 时代写的,但这并不代表它跑不到 PHP 7.4 或 8.0 上。它依赖的通常只有 mysqli、session、json 这几个基础扩展,而真正的兼容性风险来自三个地方:一是mysql_*函数是否被替换成mysqli_*,二是 Smarty 模板引擎的版本是否过老,三是字符集连接是否显式声明 utf8mb4。在实际部署之前,先确认安装包里的数据库驱动用的是哪套 API,再对照 PHP 版本决定是否需要打兼容补丁。对绝大多数场景,把 PHP 版本固定在 7.4 是最稳妥的选择,既不落后太多,又能绕开 8.x 引入的若干破坏性变更。
3. 把 EnableQ 部署到 PHP 环境:Windows 下的跑通顺序与 Linux 生产配置
部署问卷引擎比部署普通网站多一个关键动作:初始化数据库。所以整个流程分成三个步骤:准备 PHP 运行环境、导入数据库并生成配置文件、验证填答和管理两个入口。先讲本地最快路径,再讲生产环境的差异。
3.1 本地快速跑通:用 PHPStudy 在 Windows 上搭最小环境
Windows 上做开发验证,PHPStudy 这类集成环境仍然是最省事的做法,对应热搜里大量“php安装与配置windows”的需求。下载一个带 Apache + Nginx + MySQL 的版本,把 PHP 版本切到 7.4,然后在站点设置里把域名根目录指向 EnableQ 解压后的目录,开启伪静态(伪静态规则默认在安装包里的nginx.conf或.htaccess中能找到)。浏览器访问http://localhost,进入安装引导页。
安装引导页通常只需要填数据库地址、用户名、密码、数据库名,以及一个管理员账号。安装过程会做两件事:创建数据表,并生成一个配置文件(常见文件名是config.inc.php或setting.php)。下表是安装时必须确认的参数和建议值。
| 参数 | 建议值 | 说明 |
|---|---|---|
| 数据库地址 | 127.0.0.1 | 不推荐写 localhost,避免 PHP 走 socket 导致连接失败 |
| 数据库名 | enableq | 不要用中文库名,避免字符集问题 |
| 表前缀 | enq_ | 如果一台服务器要跑多套问卷,用前缀隔离 |
| PHP 版本 | 7.4 | 兼容性与安全性的折中点 |
| 字符集 | utf8mb4 | 防止生僻字和 emoji 答案写入失败 |
安装完成后立刻做两步验证:访问前台问卷页确认模板能渲染,访问/admin确认后台能登录。如果前台显示乱码,优先检查 MySQL 连接字符集是否设置为utf8mb4,而不是急着改文件编码。
3.2 Linux 生产部署:命令行初始化数据库并调整配置文件
生产环境我一般会用命令行手动初始化,而不是完全依赖可视化安装向导,这样后续迁移和批量部署都容易复制。先建库、导入安装包自带的 SQL 文件,再手动生成配置文件。
mysql -uroot -p -e "CREATE DATABASE enableq DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" mysql -uroot -p enableq < enableq.sql cp config.sample.php config.inc.php chmod 640 config.inc.php安装包内通常自带一个enableq.sql,它比安装向导生成的表结构更完整。手动导入后,编辑config.inc.php,填入数据库连接参数。chmod 640的目的是让 PHP-FPM 进程可读、但同服务器上的其他用户不可读,避免配置泄露。接下来把站点根目录的data目录(存放上传和缓存)设为可写:chown -R www-data:www-data data。
这段命令需要理解两个细节:导入 SQL 时库名必须与配置文件里的库名保持一致,否则后台能打开但所有问卷列表为空;配置文件里的db_prefix项如果改了,必须确认 SQL 文件中的表前缀与之相同,否则会出现“找不到表”的白屏错误。生产环境还建议在 Nginx 里禁止访问.sql和config.inc.php这类敏感文件,典型写法是拒绝~* \.sql$和config\.inc\.php。
3.3 安装失败的三个高发原因:版本、扩展与目录权限
部署季最常见的报错有几种,按照出现频率排序。一是 PHP 版本过高导致mysql_connect()未定义,解决办法是把 PHP 切换到 7.4,或者找到代码里调用旧函数的位置做一次全局替换。二是缺少fileinfo或curl扩展,问卷引擎在导入导出功能里会用到,报错信息通常直接给出函数名,按提示启用对应扩展即可。三是data目录不可写,症状是问卷发布后保存失败或者验证码不显示。
php -m | grep -E "mysqli|curl|fileinfo"在启动服务之前跑一下上面的命令,确认三个扩展都在。如果fileinfo缺失,Debian/Ubuntu 上执行sudo apt install php7.4-fileinfo就能解决。目录权限类问题则用ps aux | grep php-fpm确认运行用户之后,再决定chown给谁,不要直接chmod 777整个站点目录,否则后患无穷。
4. EnableQ 二次开发实战:登录打通、模板定制与答卷导出
部署完成只是开始,实际使用中最常见的三个二次开发需求是:让问卷知道填答人是谁、把页面改成自己公司的样式、把答案数据导出到 Excel。这三件事都不需要动问卷引擎的核心逻辑,而是在它的扩展点做文章。
4.1 从匿名问卷到实名问卷:在提交入口注入用户身份
现在的问卷系统至少要回答一个问题:这份答卷是谁填的。EnableQ 默认是匿名填答,但它的设计保留了一个可用的扩展点——在用户打开问卷时把身份信息注入 session,然后提交时随表单一起写入答卷明细。常见做法是找到问卷前台的入口控制器,在生成问卷页之前先读取当前登录用户。
session_start(); if (!isset($_SESSION['staff_id'])) { header('Location: /login.php?redirect=' . urlencode($_SERVER['REQUEST_URI'])); exit; } $tpl->assign('staff_name', $_SESSION['staff_name']); $tpl->assign('staff_department', $_SESSION['staff_dept']);这段逻辑放在模板渲染之前,作用是强制登录校验,并把用户信息赋值给模板引擎。Enqire 的模板层支持变量输出,你只需要在问卷 HTML 里<input type="hidden" name="ext_staff_name" value="{$staff_name}">,提交后接收端就能拿到这个字段。注意不要用$_SESSION直接拼进 SQL,而是通过参数绑定写入。业务上建议把staff_id作为唯一标识参与查重,部门、姓名只做展示。如果公司已有统一登录接口,替换这里的$_SESSION赋值代码即可,后端存储逻辑完全不用改。
4.2 页面模板定制:找到模板文件与数据变量的对应关系
EnableQ 的页面渲染采用了模板引擎方案,源码解压后能看到templates目录,里面都是.tpl文件,这意味着改页面样式不需要动 PHP 逻辑。模板目录里的目录层级通常和前端页面一一对应:header.tpl控制页头,footer.tpl控制页脚,question_radio.tpl控制单选题的 HTML 结构。下面这段示例展示的是在控制器中给模板赋值,让每个页面都能拿到站点名称和自定义 CSS 路径。
$tpl->assign('site_name', '客户体验中心'); $tpl->assign('site_css', '/assets/custom.css'); $tpl->display('header.tpl');这种“控制器赋值、模板展示”的架构对二次开发很友好。需要维护两套问卷模板时,可以直接复制整个templates/default目录为templates/winter,然后在配置里切换默认模板目录,互不影响。如果模板文件中出现{$tpl_var|escape}这类写法,那是模板引擎的转义语法,不要手动删除escape过滤器,否则用户输入的特殊字符会被直接输出,造成存储型 XSS 风险。另外,返回给前端的接口数据建议统一用array组装后json_encode输出,方便前端算数据,也方便排查字符编码。
4.3 把答卷导出为 Excel:用一条 PHP 脚本搞定 CSV 输出
后台自带的导出功能通常只导出原始文本,遇到多选题答案挤在一个单元格里的情况,二次处理成本很高。更可控的办法是写一个专用导出脚本,通过问卷 ID 拉取明细数据,然后用fputcsv输出 CSV 文件。CSV 的好处是 Excel 和 WPS 都能直接打开,不需要引入沉重的 PHPExcel 依赖。
$fp = fopen('php://output', 'w'); fwrite($fp, "\xEF\xBB\xBF"); // 添加 UTF-8 BOM,防止 Excel 打开乱码 fputcsv($fp, ['工号', '部门', '题目', '答案']); $sql = "SELECT q.title, d.answer_text FROM enq_answer_detail d LEFT JOIN enq_question q ON d.qid = q.qid WHERE d.sid = ? ORDER BY d.aid"; $stmt = $db->prepare($sql); $stmt->execute([$_GET['sid']]); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { fputcsv($fp, [$staff_id, $dept, $row['title'], $row['answer_text']]); } fclose($fp);这段代码的关键在于\xEF\xBB\xBF这 3 个字节。UTF-8 编码的 CSV 文件如果不带 BOM,Excel 会默认按 GBK 解析导致中文全部变成乱码。php://output直接输出流,配合 Nginx 返回application/octet-stream头,用户点击就能下载。数据量大时要分页读取,不要一次性fetchAll,三千份答卷用流式输出几乎不占内存。导出字段按业务需要调整,把多选题的answer_text按分隔符拆成多行也是一种常见做法。
5. 把 EnableQ 当“问卷中台”的进阶技巧:队列落库、上传防护与调试验证
接手任何 PHP 项目都要做三件事:防止重复提交、堵住上传漏洞、留下可观测的日志。放到 EnableQ 场景里也是这样,尤其问卷这种高并发、短时突增(比如全员同时填答)的业务形态,更需要提前设防。
5.1 高并发下答卷写入的常见瓶颈:用 Redis 队列削峰
三千人同时提交问卷时,MySQL 的写入压力会瞬间升高。常见做法是提交接口先写 Redis 队列,再由 CLI 脚本批量消费,对应搜索热词里的“php redis 消费组”。代码层面只需要在提交处理里把原来的insert换成LPUSH,后台用定时任务消费。
$redis->lpush('survey:answer_queue', json_encode($answerData, JSON_UNESCAPED_UNICODE));CLI 脚本使用BRPOP阻塞读取队列,拿到数据后批量写入数据库。这样做的好处是前端提交响应时间降到毫秒级,数据库压力也被摊平。队列消费脚本要加一个去重逻辑,以staff_id + sid为唯一键,消费前先查一次答卷主表,防止网络重试导致的重复记录。如果 Redis 里堆积量持续增长,优先检查 PHP 进程的timeout设置和 MySQL 慢查询日志,而不是盲目增加消费者数量。
5.2 文件上传的安全底线:扩展名白名单与目录禁用执行
问卷系统经常需要用户上传图片附件和附件材料,这是 PHP 应用被植入后门的高发区域,历史上大量真实漏洞都是从“一句话木马 php 文件上传”演化出来的。EnableQ 如果开了附件功能,务必遵守两条底线:上传目录禁止执行 PHP,文件类型做白名单校验。
location ~* /data/uploads/.*\.(php|php5|phtml)$ { deny all; }Nginx 里把上传目录的 PHP 解析关掉,关键是不让data/uploads下的文件命中fastcgi_pass。同时在后端代码里校验扩展名和 MIME 类型,不要相信用户传的Content-Type,也不要只靠前端 JS 校验。比较稳妥的做法是保存文件时强制改名为随机字符串,并去掉原始文件名中的特殊字符,从根上杜绝.php文件进入可执行目录。切记关闭安装目录里的测试脚本,部署完毕立即删除install目录,否则攻击者可以通过重放安装流程直接覆盖数据库配置。
5.3 排错技巧:用 Xdebug 和日志验证一份答卷的完整流向
问卷答完了,数据也在库里,但统计报表数字对不上,这种问题最让人头疼。排查思路是从入口到落库逐段验证,不要盯着统计报表猜。先在submit.php入口加一行error_log,输出取到的$_POST数组和生成的答卷 ID,再在 Nginx access log 里过滤出这个答卷 ID,确认请求到达了哪台机器。如果用了 Redis 队列,还要确认消费脚本是否执行成功。
本地开发可以用 Xdebug 打断点跟踪一次提交请求,IDE 里观察$_SESSION和提交数据的变化过程,这一步对理解 EnableQ 的填答生命周期帮助很大。热词里“idea php debug”对应的就是这种场景:CLI 模式下直接用 PHP 内置服务器跑一个测试脚本,把 Xdebug 的xdebug.mode=debug打开,IDE 里监听 9003 端口,就能看到每行代码的实际变量值。线上环境不要开 Xdebug,靠error_log和慢查询日志足够定位大多数问题。最后说一个性价比很高的验证技巧:发布问卷后先填一份测试答卷,然后直接查enq_answer_detail表里是否能同时找到sid、qid、answer_text三个字段的内容,如果答案文本有值但题目对不上,优先检查跳题逻辑的配置而非代码。
本文还有配套的精品资源,点击获取