一、需求分析与技术选型
在开发投票统计系统前,首先要明确核心需求。一般来说,这类系统需要支持以下基础功能:创建投票活动、用户提交投票、统计投票结果、查看投票详情。如果是面向多用户的场景,还需要考虑防刷票、权限控制等扩展需求。针对中小规模的投票场景,我们选择如下技术栈:后端使用Spring Boot作为基础框架,简化配置和快速开发;持久层使用MyBatis-Plus操作数据库,减少重复CRUD代码;数据库选用MySQL存储投票相关数据;如果需要实时统计,可以引入Redis做缓存,提升查询效率。
二、数据库设计
数据库设计是系统的核心基础,合理的表结构能减少后续开发的复杂度。我们设计四张核心表:投票活动表、投票选项表、投票记录表、用户表(可选,根据实际场景是否需要用户体系)。
1. 投票活动表(vote_activity)
存储投票活动的基本信息,包括活动名称、开始时间、结束时间、活动状态等。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| activity_name | varchar(100) | 活动名称 |
| start_time | datetime | 活动开始时间 |
| end_time | datetime | 活动结束时间 |
| status | tinyint | 状态:0未开始,1进行中,2已结束 |
| create_time | datetime | 创建时间 |
2. 投票选项表(vote_option)
存储每个投票活动对应的选项信息,一个活动可以对应多个选项。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| activity_id | bigint | 关联投票活动ID |
| option_name | varchar(100) | 选项名称 |
| vote_count | int | 当前得票数,默认0 |
3. 投票记录表(vote_record)
存储用户的投票记录,用于防刷票和统计明细。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| activity_id | bigint | 关联投票活动ID |
| option_id | bigint | 关联投票选项ID |
| user_id | bigint | 投票用户ID,匿名场景可存设备标识 |
| vote_time | datetime | 投票时间 |
三、核心功能实现
1. 创建投票活动
首先实现创建投票活动的功能,包括活动基本信息和对应选项的批量添加。我们使用Spring Boot的Controller层接收请求,Service层处理业务逻辑,Mapper层操作数据库。
实体类代码示例:
// 投票活动实体类
public class VoteActivity {
private Long id;
private String activityName;
private Date startTime;
private Date endTime;
private Integer status;
private Date createTime;
// 省略getter、setter方法
}
// 投票选项实体类
public class VoteOption {
private Long id;
private Long activityId;
private String optionName;
private Integer voteCount;
// 省略getter、setter方法
}创建活动的Service层实现:
@Service
public class VoteActivityService {
@Autowired
private VoteActivityMapper activityMapper;
@Autowired
private VoteOptionMapper optionMapper;
/**
* 创建投票活动,同时添加对应选项
* @param activity 活动信息
* @param optionList 选项列表
* @return 是否创建成功
*/
public boolean createActivity(VoteActivity activity, List<VoteOption> optionList) {
// 设置活动初始状态为未开始
activity.setStatus(0);
activity.setCreateTime(new Date());
// 插入活动基本信息
int activityInsert = activityMapper.insert(activity);
if (activityInsert <= 0) {
return false;
}
// 批量设置选项关联的活动ID
for (VoteOption option : optionList) {
option.setActivityId(activity.getId());
option.setVoteCount(0);
}
// 批量插入选项
int optionInsert = optionMapper.batchInsert(optionList);
return optionInsert == optionList.size();
}
}2. 提交投票功能
提交投票时需要做几个校验:活动是否在有效期内、用户是否已经投过票(防刷票)、提交的选项是否属于当前活动。如果是需要登录的场景,可以通过用户ID校验;如果是匿名场景,可以通过设备标识或者IP地址做简单防刷。
投票提交的Service实现:
@Service
public class VoteService {
@Autowired
private VoteRecordMapper recordMapper;
@Autowired
private VoteOptionMapper optionMapper;
@Autowired
private VoteActivityMapper activityMapper;
/**
* 提交投票
* @param activityId 活动ID
* @param optionId 选项ID
* @param userId 用户ID,匿名场景可传入设备标识
* @return 投票结果
*/
public String submitVote(Long activityId, Long optionId, Long userId) {
// 1. 校验活动状态
VoteActivity activity = activityMapper.selectById(activityId);
if (activity == null) {
return "活动不存在";
}
Date now = new Date();
if (now.before(activity.getStartTime())) {
return "活动尚未开始";
}
if (now.after(activity.getEndTime())) {
return "活动已经结束";
}
if (activity.getStatus() != 1) {
return "活动状态异常";
}
// 2. 校验选项是否属于当前活动
VoteOption option = optionMapper.selectById(optionId);
if (option == null || !option.getActivityId().equals(activityId)) {
return "选项不存在或不属于当前活动";
}
// 3. 校验用户是否已经投过票
int recordCount = recordMapper.countByActivityAndUser(activityId, userId);
if (recordCount > 0) {
return "您已经参与过本次投票,无法重复投票";
}
// 4. 插入投票记录
VoteRecord record = new VoteRecord();
record.setActivityId(activityId);
record.setOptionId(optionId);
record.setUserId(userId);
record.setVoteTime(now);
int recordInsert = recordMapper.insert(record);
if (recordInsert <= 0) {
return "投票提交失败,请重试";
}
// 5. 更新选项得票数
int updateCount = optionMapper.incrementVoteCount(optionId);
if (updateCount <= 0) {
// 这里实际开发需要加事务回滚,避免数据不一致
return "投票统计更新失败";
}
return "投票成功";
}
}3. 投票统计功能
投票统计分为实时统计和结果查询两种场景。实时统计可以在用户投票后直接返回当前各选项的得票数和占比,结果查询则支持按活动ID查询所有选项的统计信息。
统计功能的Service实现:
@Service
public class VoteStatisticsService {
@Autowired
private VoteOptionMapper optionMapper;
@Autowired
private VoteRecordMapper recordMapper;
/**
* 查询指定活动的投票统计结果
* @param activityId 活动ID
* @return 统计结果列表
*/
public List<Map<String, Object>> getVoteStatistics(Long activityId) {
// 查询活动下所有选项
List<VoteOption> optionList = optionMapper.selectByActivityId(activityId);
if (optionList == null || optionList.isEmpty()) {
return new ArrayList<>();
}
// 查询总投票数
int totalVote = recordMapper.countByActivityId(activityId);
List<Map<String, Object>> resultList = new ArrayList<>();
for (VoteOption option : optionList) {
Map<String, Object> optionStat = new HashMap<>();
optionStat.put("optionId", option.getId());
optionStat.put("optionName", option.getOptionName());
optionStat.put("voteCount", option.getVoteCount());
// 计算占比,避免除零错误
double percentage = totalVote == 0 ? 0.0 : (option.getVoteCount() * 100.0) / totalVote;
optionStat.put("percentage", String.format("%.2f", percentage));
resultList.add(optionStat);
}
return resultList;
}
}4. 接口层实现
最后通过Controller层暴露HTTP接口,供前端调用。这里使用RESTful风格设计接口,符合常规的接口开发规范。
@RestController
@RequestMapping("/vote")
public class VoteController {
@Autowired
private VoteService voteService;
@Autowired
private VoteStatisticsService statisticsService;
@Autowired
private VoteActivityService activityService;
/**
* 创建投票活动接口
*/
@PostMapping("/create")
public Map<String, Object> createActivity(@RequestBody Map<String, Object> params) {
Map<String, Object> result = new HashMap<>();
// 这里省略参数校验逻辑,实际开发需要补充
VoteActivity activity = new VoteActivity();
activity.setActivityName((String) params.get("activityName"));
// 省略时间转换等逻辑
List<VoteOption> optionList = (List<VoteOption>) params.get("optionList");
boolean success = activityService.createActivity(activity, optionList);
result.put("code", success ? 200 : 500);
result.put("msg", success ? "活动创建成功" : "活动创建失败");
return result;
}
/**
* 提交投票接口
*/
@PostMapping("/submit")
public Map<String, Object> submitVote(@RequestParam Long activityId,
@RequestParam Long optionId,
@RequestParam Long userId) {
Map<String, Object> result = new HashMap<>();
String msg = voteService.submitVote(activityId, optionId, userId);
if ("投票成功".equals(msg)) {
result.put("code", 200);
} else {
result.put("code", 500);
}
result.put("msg", msg);
return result;
}
/**
* 查询投票统计结果接口
*/
@GetMapping("/statistics")
public Map<String, Object> getStatistics(@RequestParam Long activityId) {
Map<String, Object> result = new HashMap<>();
List<Map<String, Object>> statistics = statisticsService.getVoteStatistics(activityId);
result.put("code", 200);
result.put("data", statistics);
return result;
}
}四、扩展优化建议
以上实现的是基础的投票统计系统,实际生产环境中还需要根据场景做扩展优化。首先是防刷票能力增强,除了用户ID校验,还可以结合IP限制、设备指纹、验证码等方式,避免恶意刷票。其次是性能优化,如果投票并发量较高,投票记录的插入和票数更新可以引入消息队列异步处理,避免数据库压力过大,同时可以用Redis缓存投票统计数据,减少数据库查询次数。另外如果需要更复杂的统计,比如按时间段统计投票趋势,可以扩展投票记录表,增加更多维度字段,再做对应的聚合查询。如果需要可视化展示统计结果,可以在前端使用图表组件渲染后端返回的统计数据,实现更直观的展示效果。
Java投票系统统计功能Spring_BootMySQL修改时间:2026-05-24 20:44:32