本文目录导读:

- 数据库设计(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(美化界面)
如果你需要更具体的代码实现(如带图表展示的统计页面或自动发送邮件提醒),欢迎继续追问!
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播