本文目录导读:

- 数据库设计(MySQL / PostgreSQL)
- 后端核心逻辑(PHP)
- 前端展示(HTML + JavaScript + AJAX)
- 结果统计与分析(后台管理)
- 增强功能建议
- 完整项目结构示例
- 安全注意事项
- 第三方库(可选)
在PHP项目中实现用户满意度调查,通常需要涵盖前端展示、后端逻辑、数据存储和结果分析四个部分,以下是完整的实现方案:
数据库设计(MySQL / PostgreSQL)
首先创建一个调查表和反馈记录表。
-- 调查问卷表
CREATE TABLE surveys (
id INT AUTO_INCREMENT PRIMARY KEY,VARCHAR(255) NOT NULL, -- 调查标题
description TEXT, -- 描述
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at DATETIME NULL, -- 过期时间(可选)
status TINYINT DEFAULT 1 -- 1=启用 0=禁用
);
-- 调查问题表
CREATE TABLE survey_questions (
id INT AUTO_INCREMENT PRIMARY KEY,
survey_id INT NOT NULL,
question_text TEXT NOT NULL, -- 问题内容
question_type VARCHAR(50) NOT NULL, -- rating(评分), radio(单选), text(文本)
options JSON NULL, -- 选项(JSON格式,如["1分","2分","3分"])
sort_order INT DEFAULT 0,
FOREIGN KEY (survey_id) REFERENCES surveys(id) ON DELETE CASCADE
);
-- 用户反馈记录表
CREATE TABLE survey_responses (
id INT AUTO_INCREMENT PRIMARY KEY,
survey_id INT NOT NULL,
user_id INT NULL, -- 可选:关联用户(匿名则不填)
ip_address VARCHAR(45) NULL, -- 防止刷票
submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (survey_id) REFERENCES surveys(id) ON DELETE CASCADE
);
-- 问题答案表(一对多:一条反馈对应多个答案)
CREATE TABLE survey_answers (
id INT AUTO_INCREMENT PRIMARY KEY,
response_id INT NOT NULL,
question_id INT NOT NULL,
answer_value TEXT, -- 答案值(评分值/选项文本/用户输入)
FOREIGN KEY (response_id) REFERENCES survey_responses(id) ON DELETE CASCADE,
FOREIGN KEY (question_id) REFERENCES survey_questions(id) ON DELETE CASCADE
);
后端核心逻辑(PHP)
1 加载调查问卷
// get_survey.php
<?php
function getActiveSurvey(PDO $pdo) {
$stmt = $pdo->prepare("
SELECT * FROM surveys
WHERE status = 1
AND (expires_at IS NULL OR expires_at > NOW())
ORDER BY created_at DESC
LIMIT 1
");
$stmt->execute();
$survey = $stmt->fetch(PDO::FETCH_ASSOC);
if ($survey) {
// 获取问题列表
$stmtQ = $pdo->prepare("
SELECT * FROM survey_questions
WHERE survey_id = ?
ORDER BY sort_order ASC
");
$stmtQ->execute([$survey['id']]);
$survey['questions'] = $stmtQ->fetchAll(PDO::FETCH_ASSOC);
}
return $survey;
}
2 提交反馈
// submit_survey.php
<?php
session_start();
function submitFeedback(PDO $pdo, array $answers, int $surveyId, ?int $userId = null) {
try {
$pdo->beginTransaction();
// 1. 记录反馈
$ip = $_SERVER['REMOTE_ADDR'] ?? null;
$stmt = $pdo->prepare("
INSERT INTO survey_responses (survey_id, user_id, ip_address)
VALUES (?, ?, ?)
");
$stmt->execute([$surveyId, $userId, $ip]);
$responseId = $pdo->lastInsertId();
// 2. 逐条保存答案
$answerStmt = $pdo->prepare("
INSERT INTO survey_answers (response_id, question_id, answer_value)
VALUES (?, ?, ?)
");
foreach ($answers as $questionId => $value) {
$answerStmt->execute([$responseId, $questionId, $value]);
}
$pdo->commit();
return ['success' => true, 'response_id' => $responseId];
} catch (Exception $e) {
$pdo->rollBack();
return ['success' => false, 'error' => $e->getMessage()];
}
}
3 防止重复提交(示例)
// 在提交函数前检查(基于IP+用户ID+时间窗口)
function canSubmit(PDO $pdo, int $surveyId, ?int $userId, string $ip): bool {
$minInterval = 60 * 60; // 1小时
$stmt = $pdo->prepare("
SELECT COUNT(*) FROM survey_responses
WHERE survey_id = ?
AND (user_id = ? OR ip_address = ?)
AND submitted_at > DATE_SUB(NOW(), INTERVAL ? SECOND)
");
$stmt->execute([$surveyId, $userId, $ip, $minInterval]);
return $stmt->fetchColumn() == 0;
}
前端展示(HTML + JavaScript + AJAX)
1 问卷页面(异步加载)
<!DOCTYPE html>
<html>
<head>满意度调查</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div id="survey-container"></div>
<script>
$(document).ready(function() {
$.get('api/get_survey.php', function(response) {
if (response.error) {
$('#survey-container').html('<p>暂无调查问卷</p>');
return;
}
let html = `<h2>${response.title}</h2>
<p>${response.description}</p>
<form id="survey-form">`;
response.questions.forEach(function(q) {
html += `<div class="question-block">
<label>${q.question_text}</label>`;
if (q.question_type === 'rating') {
// 1-5分评分
html += '<div class="rating">';
for (let i = 1; i <= 5; i++) {
html += `<input type="radio" name="q_${q.id}" value="${i}"> ${i}分 `;
}
html += '</div>';
} else if (q.question_type === 'radio') {
const options = JSON.parse(q.options);
options.forEach(function(opt) {
html += `<label><input type="radio" name="q_${q.id}" value="${opt}"> ${opt}</label>`;
});
} else { // text
html += `<textarea name="q_${q.id}" rows="3"></textarea>`;
}
html += '</div>';
});
html += '<button type="submit">提交反馈</button></form>';
$('#survey-container').html(html);
}, 'json');
// 提交处理
$(document).on('submit', '#survey-form', function(e) {
e.preventDefault();
const formData = $(this).serialize();
$.post('api/submit_survey.php', formData, function(response) {
if (response.success) {
alert('感谢您的反馈!');
$('#survey-container').html('<p>已提交,谢谢参与!</p>');
} else {
alert('提交失败:' + (response.error || '请稍后重试'));
}
}, 'json');
});
});
</script>
</body>
</html>
结果统计与分析(后台管理)
// get_survey_stats.php
<?php
function getSurveyStats(PDO $pdo, int $surveyId): array {
// 总参与人数
$total = $pdo->prepare("SELECT COUNT(*) FROM survey_responses WHERE survey_id=?");
$total->execute([$surveyId]);
$stats['total_responses'] = $total->fetchColumn();
// 每题统计
$stmt = $pdo->prepare("
SELECT q.id, q.question_text, q.question_type, q.options,
a.answer_value
FROM survey_questions q
LEFT JOIN survey_answers a ON q.id = a.question_id
WHERE q.survey_id = ?
");
$stmt->execute([$surveyId]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 按问题分组统计
foreach ($results as $row) {
$qid = $row['id'];
if (!isset($stats['questions'][$qid])) {
$stats['questions'][$qid] = [
'text' => $row['question_text'],
'type' => $row['question_type'],
'counts' => [],
'texts' => [] // 文本答案
];
}
$val = $row['answer_value'];
if ($row['question_type'] === 'text') {
$stats['questions'][$qid]['texts'][] = $val;
} else {
if (!isset($stats['questions'][$qid]['counts'][$val])) {
$stats['questions'][$qid]['counts'][$val] = 0;
}
$stats['questions'][$qid]['counts'][$val]++;
}
}
// 计算评分平均分(rating类型)
foreach ($stats['questions'] as $qid => &$q) {
if ($q['type'] === 'rating') {
$sum = array_sum(array_map(function($v, $k) {
return $v * $k;
}, $q['counts'], array_keys($q['counts'])));
$total = array_sum($q['counts']);
$q['average'] = $total > 0 ? round($sum / $total, 2) : 0;
}
}
return $stats;
}
增强功能建议
| 功能 | 实现思路 |
|---|---|
| 验证码 | 使用 google/recaptcha 或自定义验证码,在提交前验证 |
| 匿名模式 | 提交时移除user_id/IP记录,仅保留时间戳 |
| 分页/多页问卷 | 前端使用步骤切换,后端一次性提交所有数据 |
| 自动触发 | 在用户完成某个操作(如订单完成、客服结束)后,弹窗或自动跳转 |
| 报告导出 | 使用 PhpSpreadsheet 生成 Excel 或 CSV 文件 |
完整项目结构示例
project/
├── index.php # 前端问卷页面
├── admin/
│ ├── stats.php # 后台统计页面
│ └── export.php # 数据导出
├── api/
│ ├── get_survey.php # 获取问卷接口
│ └── submit_survey.php # 提交反馈接口
├── includes/
│ ├── db.php # 数据库连接
│ └── functions.php # 公共函数
└── assets/
└── styles.css # 样式文件
安全注意事项
- SQL注入:使用预处理语句(如 PDO 的
prepare+ 参数绑定) - XSS攻击:输出答案时使用
htmlspecialchars()转义 - 重复提交:结合 IP + User-Agent + 时间窗口限制
- CSRF保护:在表单中嵌入 Token 并验证
- 数据加密:敏感信息(如用户ID)建议加密存储
第三方库(可选)
- PHP:
phpmailer(发送调查邀请邮件)、mpdf(生成PDF报告) - 前端:
Chart.js(绘制统计图表)、Bootstrap(美化界面)
如果你需要更具体的代码实现(如带图表展示的统计页面或自动发送邮件提醒),欢迎继续追问!