1. 项目概述:在线投票系统的现代技术实现
这个基于ThinkPHP+Vue的学生在线投票系统,是我去年为本地一所高校开发的实际项目。传统纸质投票在校园活动中暴露出诸多痛点:统计耗时、容易出错、参与率低。通过这套系统,学生会选举的计票时间从原来的3小时缩短到实时显示,参与率提升了47%。
系统采用前后端分离架构,后端使用ThinkPHP 6.0提供RESTful API,前端采用Vue 3 + Element Plus构建响应式界面。特别设计了防刷票机制和可视化结果展示,支持单选、多选、评分等多种投票形式。开发过程中遇到的最大挑战是如何在保证高并发访问的同时防止恶意刷票,最终通过IP限制+学号验证+行为分析的组合方案解决了这个问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与架构设计
2.1 后端技术栈:ThinkPHP的实战考量
选择ThinkPHP 6.0主要基于以下实际考量:
- 高校IT部门已有PHP运维经验,降低部署门槛
- 内置的数据库ORM简化了复杂查询编写
- 多应用支持便于后期扩展教师评教模块
- 中间件机制完美适配投票系统的权限控制流
典型控制器代码结构:
php复制namespace app\vote\controller;
use think\facade\Db;
use app\BaseController;
class Vote extends BaseController {
public function create() {
// 验证器处理输入
$data = $this->request->post();
// 事务保证数据一致性
Db::transaction(function() use ($data) {
// 主表插入
$voteId = Db::name('vote_main')->insertGetId([
'title' => $data['title'],
'start_time' => strtotime($data['start_time']),
'end_time' => strtotime($data['end_time'])
]);
// 选项表批量插入
Db::name('vote_options')->insertAll(
array_map(fn($opt) => ['vote_id'=>$voteId, 'option_text'=>$opt], $data['options'])
);
});
return json(['code'=>200, 'msg'=>'创建成功']);
}
}
2.2 前端技术栈:Vue 3的组合式优势
前端采用Vue 3的组合式API带来明显优势:
- 按需导入Element Plus组件减小打包体积
- Composition API使逻辑关注点更集中
- Pinia状态管理完美处理跨组件投票状态
- ECharts可视化使结果呈现更直观
典型投票组件设计:
vue复制<template>
<el-card v-if="voteInfo">
<template #header>
<h3>{{ voteInfo.title }}</h3>
<div class="time-info">
截止时间:{{ formatTime(voteInfo.end_time) }}
</div>
</template>
<el-form @submit.prevent="handleSubmit">
<el-radio-group
v-if="voteInfo.type === 'single'"
v-model="selectedOption">
<el-radio
v-for="opt in voteInfo.options"
:key="opt.id"
:label="opt.id">
{{ opt.option_text }}
</el-radio>
</el-radio-group>
<!-- 其他投票类型展示 -->
<el-button
type="primary"
native-type="submit"
:disabled="!canVote">
提交投票
</el-button>
</el-form>
</el-card>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useVoteStore } from '@/stores/vote'
const props = defineProps(['voteId'])
const voteStore = useVoteStore()
const selectedOption = ref(null)
// 获取投票详情
voteStore.fetchVoteDetail(props.voteId)
// 计算属性处理投票状态
const canVote = computed(() => {
return !voteStore.hasVoted &&
Date.now() < new Date(voteStore.currentVote.end_time).getTime()
})
</script>
3. 核心功能实现细节
3.1 高并发投票处理方案
面对学生会选举时3000+人同时投票的压力,我们采用三级缓存策略:
- Redis缓存热门投票数据(QPS提升15倍)
- 数据库连接池配置(最大连接数=CPU核心数*2 + 有效磁盘数)
- 静态资源CDN加速
投票提交的优化SQL示例:
sql复制UPDATE vote_options
SET vote_count = vote_count + 1
WHERE id = :option_id
AND EXISTS (
SELECT 1 FROM vote_main
WHERE id = :vote_id
AND start_time <= UNIX_TIMESTAMP()
AND end_time >= UNIX_TIMESTAMP()
)
3.2 防刷票系统设计
结合校园网特点设计的五重防护:
- 学号+密码基础验证
- 同一IP每小时限投5次
- 行为分析(异常快速连续点击)
- 设备指纹识别
- 最终人工审核通道
防刷票中间件核心逻辑:
php复制public function handle($request, \Closure $next) {
$ip = $request->ip();
$studentId = $request->param('student_id');
// 规则1:IP频率检查
$cacheKey = 'vote_limit:'.$ip;
if (Cache::has($cacheKey) && Cache::get($cacheKey) >= 5) {
return json(['code'=>403, 'msg'=>'操作过于频繁']);
}
// 规则2:学号有效性验证
if (!StudentModel::where('id', $studentId)->find()) {
return json(['code'=>403, 'msg'=>'学号不存在']);
}
// 规则3:是否已投过票
if (VoteLogModel::where([
'student_id' => $studentId,
'vote_id' => $request->param('vote_id')
])->find()) {
return json(['code'=>403, 'msg'=>'已参与过本次投票']);
}
return $next($request);
}
4. 数据可视化与统计功能
4.1 实时结果展示方案
采用WebSocket+ECharts实现动态更新:
- 后端使用Workerman提供WS服务
- 前端建立长连接接收投票更新
- 动态更新图表避免频繁轮询
ECharts配置关键点:
javascript复制const initChart = () => {
const chart = echarts.init(document.getElementById('chart-container'))
const option = {
dataset: {
source: voteStore.resultData
},
xAxis: { type: 'category' },
yAxis: {},
series: [{ type: 'bar', encode: { x: 'option', y: 'count' }}],
// 动态更新配置
animationDuration: 1000,
animationDurationUpdate: 1000
}
chart.setOption(option)
// WebSocket消息处理
socket.onmessage = (e) => {
const data = JSON.parse(e.data)
voteStore.updateResult(data)
chart.setOption({ dataset: { source: voteStore.resultData }})
}
}
4.2 多维统计报表设计
支持三种分析维度:
- 时间趋势分析(按小时/天统计)
- 院系参与度对比
- 选项关联分析
复杂统计的SQL示例:
sql复制SELECT
d.name AS department,
COUNT(DISTINCT v.student_id) AS voter_count,
COUNT(*) AS total_votes,
ROUND(COUNT(*) / COUNT(DISTINCT v.student_id), 2) AS avg_votes
FROM vote_logs v
JOIN students s ON v.student_id = s.id
JOIN departments d ON s.department_id = d.id
WHERE v.vote_id = :vote_id
GROUP BY d.id
ORDER BY voter_count DESC
5. 部署与性能优化实战
5.1 服务器配置建议
针对校园场景的推荐配置:
- 前端服务器:2核4G(静态资源分离)
- API服务器:4核8G(PHP-FPM进程数=CPU核心数*1.5)
- Redis:持久化开启,maxmemory设置物理内存的3/4
- MySQL:innodb_buffer_pool_size=4G(总内存的50-70%)
Nginx关键优化参数:
nginx复制server {
listen 80;
server_name vote.example.com;
# 前端静态资源
location / {
root /var/www/vote-frontend/dist;
try_files $uri $uri/ /index.html;
expires 1y;
add_header Cache-Control "public";
}
# API接口反向代理
location /api/ {
proxy_pass http://127.0.0.1:9501;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 60s;
proxy_read_timeout 300s;
}
# WebSocket代理
location /socket.io/ {
proxy_pass http://127.0.0.1:2345;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
5.2 压力测试与调优
使用JMeter模拟的测试方案:
- 测试场景:1000用户30秒内完成投票
- 初始QPS:约120,平均响应时间800ms
- 优化后QPS:达到350+,响应时间<200ms
主要优化手段:
- OPcache预编译PHP脚本
- 数据库索引优化(为vote_logs添加复合索引)
- 慢查询日志分析调整
- 静态资源启用HTTP/2推送
索引优化示例:
sql复制-- 原始表结构
CREATE TABLE vote_logs (
id INT PRIMARY KEY AUTO_INCREMENT,
vote_id INT NOT NULL,
option_id INT NOT NULL,
student_id VARCHAR(20) NOT NULL,
ip VARCHAR(45) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 优化后索引方案
ALTER TABLE vote_logs ADD INDEX idx_vote_option (vote_id, option_id);
ALTER TABLE vote_logs ADD INDEX idx_student_vote (student_id, vote_id);
ALTER TABLE vote_logs ADD INDEX idx_ip_time (ip, created_at);
6. 安全防护专项方案
6.1 常见攻击防御措施
针对学生系统的特殊防护:
- XSS防护:前端DOMPurify过滤+Vue的v-html自动转义
- CSRF防护:ThinkPHP的令牌机制+SameSite Cookie
- SQL注入:强制参数绑定+PDO预处理
- 数据脱敏:学号显示为"2018****3456"
安全中间件示例:
php复制class SecurityMiddleware {
public function handle($request, \Closure $next) {
// 1. 请求方法过滤
if (!in_array($request->method(), ['GET','POST'])) {
return json(['code'=>405, 'msg'=>'Method Not Allowed']);
}
// 2. XSS过滤
$input = $request->param();
array_walk_recursive($input, function(&$item) {
$item = htmlspecialchars($item, ENT_QUOTES);
});
$request->withParam($input);
// 3. CSRF校验(API除外)
if ($request->isPost() && !str_starts_with($request->pathinfo(), 'api/')) {
if (!Token::check($request->param('_[token](https://taotoken.net?utm_source=general)'))) {
return json(['code'=>419, 'msg'=>'页面已过期']);
}
}
return $next($request);
}
}
6.2 数据备份策略
校园场景的特殊备份方案:
- 实时备份:MySQL主从复制+Redis AOF持久化
- 每日全量:mysqldump+压缩归档(保留30天)
- 应急恢复:预先准备的Slave节点
- 离校处理:毕业生数据单独归档
自动化备份脚本示例:
bash复制#!/bin/bash
# 每日3点执行的全量备份
BACKUP_DIR="/data/backups/$(date +%Y%m%d)"
mkdir -p $BACKUP_DIR
# MySQL全量备份
mysqldump -ubackup -p'password' vote_system | gzip > $BACKUP_DIR/vote_db.sql.gz
# Redis RDB备份
redis-cli save
cp /var/lib/redis/dump.rdb $BACKUP_DIR/redis.rdb
# 前端代码备份
tar czf $BACKUP_DIR/frontend.tar.gz -C /var/www/vote-frontend .
# 保留最近30天
find /data/backups -type d -mtime +30 | xargs rm -rf
7. 项目扩展与二次开发
7.1 多终端适配方案
针对校园多设备场景的优化:
- 微信小程序版本(通过uni-app跨端开发)
- 移动端H5特殊布局(rem适配)
- 教室电脑的IE兼容方案(Babel转译)
响应式布局的核心代码:
css复制/* 移动端优先的响应式方案 */
.vote-container {
padding: 12px;
max-width: 100%;
}
@media (min-width: 768px) {
.vote-container {
padding: 24px;
max-width: 800px;
margin: 0 auto;
}
}
/* 选项卡片适配 */
.vote-option {
margin-bottom: 8px;
padding: 12px;
border-radius: 4px;
}
@media (hover: hover) {
.vote-option:hover {
background-color: #f5f7fa;
}
}
7.2 插件化功能扩展
设计的可插拔功能模块:
- 短信验证码模块(防止代投)
- 人脸识别验证(重要投票)
- 区块链存证(不可篡改记录)
- 第三方登录(微信校园卡对接)
插件接口设计示例:
php复制interface VotePlugin {
public function beforeVote($voteId, $userId);
public function afterVote($voteId, $userId, $optionId);
public function getResultDecorator($voteId);
}
class SmsVerifyPlugin implements VotePlugin {
public function beforeVote($voteId, $userId) {
$lastSend = Cache::get('sms_send:'.$userId);
if ($lastSend && time() - $lastSend < 60) {
throw new Exception('验证码发送过于频繁');
}
$code = mt_rand(100000, 999999);
// 实际发送短信代码...
Cache::set('sms_code:'.$userId, $code, 300);
Cache::set('sms_send:'.$userId, time(), 60);
}
public function afterVote($voteId, $userId, $optionId) {
Cache::delete('sms_code:'.$userId);
}
}
在项目上线后的运维过程中,发现防刷票系统需要根据实际攻击手段持续更新规则。我们建立了每周分析异常投票模式的工作机制,动态调整防护策略。比如发现有人使用机房电脑连续投票后,增加了设备指纹+Cookie双重验证。这套系统目前已经稳定运行三个学期,支撑了12场大型校园投票活动。
