1. 项目概述
这个基于SpringBoot的网上投票系统是一个功能完善的在线投票平台,采用前后端分离架构设计。系统支持从活动创建、用户管理到结果统计的全流程投票管理,适用于企业内投票、校园选举、活动评选等多种场景。
我在实际开发中发现,这类系统最核心的价值在于其灵活的可配置性和严谨的权限控制。系统内置了四种角色权限(系统管理员、内容管理员、普通用户和访客),通过RBAC模型实现了精细化的功能权限划分。比如内容管理员可以管理投票活动但无法操作用户账号,这种设计既保证了系统安全,又方便了多人协作管理。
2. 技术架构解析
2.1 核心技术栈
系统采用SpringBoot 2.7 + Vue 3的组合方案,这种技术选型主要基于以下考虑:
- SpringBoot的自动配置特性大幅简化了后端服务搭建
- Vue的响应式数据绑定非常适合动态投票结果展示
- 前后端分离架构有利于团队分工和独立部署
数据库选用MySQL 5.7,主要考虑到:
- 事务完整性对投票数据准确性的保障
- 成熟的索引优化方案应对高并发查询
- 与Spring Data JPA的良好兼容性
2.2 关键组件设计
投票核心模块采用领域驱动设计(DDD),主要包含:
- 活动聚合根(VoteActivity)
- 包含基础信息、时间范围、投票规则等
- 采用JPA的@Embedded处理复杂值对象
- 选项实体(VoteOption)
- 使用@ManyToOne建立与活动的关联
- 投票记录值对象(VoteRecord)
- 记录用户投票行为,包含时间戳防重
特别注意:投票记录表需要建立(user_id,activity_id)联合唯一索引,防止重复投票
3. 核心功能实现
3.1 投票活动创建
活动创建接口采用三步提交模式:
java复制@PostMapping("/create")
public Result createActivity(@Valid @RequestBody ActivityDTO dto) {
// 1. 校验时间有效性
if(dto.getEndTime().isBefore(LocalDateTime.now())) {
throw new BusinessException("结束时间不能早于当前时间");
}
// 2. 转换DTO为领域对象
VoteActivity activity = convertToDomain(dto);
// 3. 持久化并返回结果
return Result.success(activityService.save(activity));
}
关键参数说明:
- maxVotesPerUser:单用户最大投票数
- anonymousFlag:是否匿名投票
- resultVisible:结果是否立即可见
3.2 实时统计展示
使用Redis的Sorted Set实现实时排名:
java复制public void recordVote(Long activityId, Long optionId) {
String key = "vote:rank:" + activityId;
redisTemplate.opsForZSet().incrementScore(key, optionId.toString(), 1);
// 异步持久化到MySQL
asyncExecutor.execute(() -> {
voteRecordRepository.save(new VoteRecord(...));
});
}
统计页面前端采用ECharts实现动态图表,关键配置:
javascript复制option = {
tooltip: { trigger: 'item' },
series: [{
type: 'pie',
data: [
{value: 1048, name: '选项A'},
{value: 735, name: '选项B'}
]
}]
}
4. 安全与性能优化
4.1 防刷机制
采用多维度防护策略:
- IP限流:Guava RateLimiter实现每分钟20次调用限制
- 验证码:关键操作前进行人机验证
- 令牌桶:投票接口采用分布式令牌桶控制
4.2 缓存策略
采用多级缓存架构:
- 本地缓存(Caffeine):存储活动基础信息
- 最大500条,过期时间5分钟
- Redis缓存:
- 投票结果:1小时自动过期
- 用户投票记录:持久化存储
缓存更新策略:
java复制@CacheEvict(value = "activities", key = "#activityId")
public void updateActivity(VoteActivity activity) {
// 先更新数据库
activityRepository.save(activity);
// 再清除相关缓存
redisTemplate.delete("vote:rank:" + activity.getId());
}
5. 部署实践
5.1 环境准备
推荐使用Docker Compose一键部署:
yaml复制version: '3'
services:
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: vote123
ports:
- "3306:3306"
redis:
image: redis:6-alpine
ports:
- "6379:6379"
5.2 性能调优
JVM参数建议配置:
code复制-Xms512m -Xmx1024m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
MySQL优化建议:
sql复制ALTER TABLE vote_record
ADD INDEX idx_user_activity (user_id, activity_id);
ALTER TABLE vote_option
ADD INDEX idx_activity (activity_id);
6. 常见问题排查
6.1 并发投票异常
现象:高并发时出现重复投票记录
解决方案:
- 数据库层面添加唯一约束
- 采用分布式锁控制:
java复制public boolean tryVote(Long userId, Long activityId) {
String lockKey = "vote:lock:" + activityId + ":" + userId;
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
6.2 结果统计延迟
现象:投票后结果更新不及时
排查步骤:
- 检查Redis是否正常运行
- 验证消息队列消费情况
- 查看定时任务日志
我在实际部署中发现,当投票量突增时,建议:
- 调整Redis内存淘汰策略为volatile-lru
- 增加结果计算任务的执行频率
- 考虑引入Flink进行实时计算
7. 扩展建议
7.1 微信集成
通过微信公众号接口实现:
- 网页授权获取用户openid
- 使用模板消息发送投票通知
- 生成带参数的二维码直接跳转
7.2 可视化配置
可扩展的功能点:
- 投票模板市场
- 拖拽式表单设计器
- 自定义结果展示模板
这个项目最值得关注的其实是其权限系统的设计思路。我在多个项目中复用这套RBAC实现时,发现通过注解+拦截器的方式可以极大简化权限控制代码:
java复制@PreAuthorize("hasRole('CONTENT_ADMIN')")
@PostMapping("/create")
public Result createActivity(...) {
// 方法实现
}
对于想要深入学习的开发者,建议重点研究:
- Spring Security的权限决策流程
- Redis在秒杀场景中的应用
- Vue的响应式原理与性能优化
