1. 项目背景与核心需求
在线投票系统是当前互联网应用中非常典型的业务场景,从校园选举到企业决策,从产品调研到活动评选,几乎涵盖了所有需要群体决策的场合。基于Spring Boot + MyBatis的技术栈实现这样一个系统,既能满足现代Web应用的高并发需求,又能保持代码的简洁性和可维护性。
我在实际开发中发现,一个健壮的在线投票系统需要解决以下几个核心问题:
- 高并发下的数据一致性问题(特别是投票计数场景)
- 灵活的动态问卷配置能力
- 完善的权限控制和防刷机制
- 实时结果展示需求
Spring Boot的自动配置和起步依赖让我们能快速搭建项目骨架,而MyBatis的灵活SQL映射则完美适配需要复杂查询的统计报表功能。这个组合相比传统的SSH框架,在开发效率和运行时性能上都有明显优势。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与架构设计
2.1 为什么选择Spring Boot + MyBatis
Spring Boot的嵌入式Tomcat和约定大于配置的理念,让开发者可以专注于业务逻辑而非环境搭建。我在多个生产项目中验证过,用Spring Boot开发一个基础功能的RESTful接口,代码量能比传统Spring MVC减少40%左右。
MyBatis的优势在于:
- 对复杂SQL的完美支持(特别是多表关联和统计查询)
- 动态SQL能力(通过OGNL表达式)
- 与Spring生态的无缝集成
提示:虽然JPA在简单CRUD场景更便捷,但投票系统后期必然涉及复杂报表查询,这时MyBatis的XML映射文件优势就显现出来了。
2.2 系统分层架构
典型的三层架构设计:
code复制表示层:Thymeleaf + Bootstrap (前后端未分离方案)
业务层:Spring Boot + Spring Security
持久层:MyBatis + PageHelper分页插件
数据库选型建议:
- 中小规模:MySQL 8.0(事务性能优化明显)
- 超大规模:考虑分库分表或引入Redis计数
3. 核心功能实现细节
3.1 动态投票管理
投票主题和选项需要支持动态配置,我的实现方案是:
java复制@Entity
public class VoteTopic {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
@OneToMany(mappedBy = "topic", cascade = CascadeType.ALL)
private List<VoteOption> options;
@Temporal(TemporalType.TIMESTAMP)
private Date endTime;
// 其他字段...
}
对应的MyBatis映射文件要注意collection标签的使用:
xml复制<resultMap id="topicWithOptions" type="VoteTopic">
<id property="id" column="id"/>
<result property="title" column="title"/>
<collection property="options" ofType="VoteOption">
<id property="id" column="option_id"/>
<result property="content" column="content"/>
</collection>
</resultMap>
3.2 高并发投票计数
这是系统最关键的难点,我遇到过两种典型问题:
- 超卖问题:并发时计数不准确
- 性能瓶颈:热门投票的集中访问
解决方案对比:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 数据库乐观锁 | 实现简单 | 高并发时失败率高 |
| Redis原子操作 | 性能极高 | 需要处理持久化 |
| 消息队列削峰 | 系统稳定 | 实时性较差 |
最终我的实现是组合方案:
java复制@Transactional
public void vote(Long userId, Long optionId) {
// 1. 先检查是否已投票(防刷)
if (voteRecordMapper.exists(userId, optionId)) {
throw new IllegalStateException("已投票");
}
// 2. Redis原子递增
long count = redisTemplate.opsForValue()
.increment("vote:count:" + optionId, 1);
// 3. 异步落库
eventPublisher.publishEvent(new VoteEvent(userId, optionId, count));
}
3.3 实时结果展示
对于需要实时显示票数的场景,WebSocket是更好的选择:
java复制@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-vote")
.setAllowedOrigins("*")
.withSockJS();
}
}
前端订阅代码示例:
javascript复制const socket = new SockJS('/ws-vote');
const stompClient = Stomp.over(socket);
stompClient.connect({}, () => {
stompClient.subscribe('/topic/update', (message) => {
const data = JSON.parse(message.body);
updateChart(data.optionId, data.count);
});
});
4. 关键问题与优化实践
4.1 MyBatis缓存踩坑
在开发投票结果查询时,我遇到了MyBatis一级缓存导致的数据不一致问题。场景是:
- 开启事务查询结果
- 期间有其他事务更新了数据
- 再次查询得到旧数据
解决方案:
java复制@Mapper
public interface VoteMapper {
@Options(flushCache = Options.FlushCachePolicy.TRUE)
@Select("SELECT * FROM vote_results WHERE topic_id = #{topicId}")
List<VoteResult> getResultsWithFlush(@Param("topicId") Long topicId);
}
或者直接在application.yml中配置:
yaml复制mybatis:
configuration:
local-cache-scope: statement
4.2 防刷机制设计
为了防止刷票,我实现了多维度的防护:
- IP限流:Guava RateLimiter
java复制private final RateLimiter ipLimiter = RateLimiter.create(5.0); // 每秒5次
public void checkIpRate(HttpServletRequest request) {
String ip = request.getRemoteAddr();
if (!ipLimiter.tryAcquire()) {
throw new RateLimitException("操作过于频繁");
}
}
- 设备指纹:通过JS收集浏览器特征
- 业务规则:同一用户/设备在时间窗口内的投票限制
4.3 性能优化技巧
在大规模投票活动中,我总结了几点有效优化手段:
数据库层面:
- 为vote_record表创建联合索引:(user_id, topic_id)
- 使用覆盖索引优化统计查询
MyBatis层面:
xml复制<!-- 使用延迟加载 -->
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<!-- 大数据量分页优化 -->
<select id="selectLargeData" resultMap="...">
SELECT * FROM large_table
WHERE id > #{lastId}
ORDER BY id ASC
LIMIT #{size}
</select>
5. 部署与监控方案
5.1 生产环境配置
Spring Boot Actuator的安全配置(2.x和3.x版本差异):
yaml复制# 2.x版本
management:
endpoints:
web:
exposure:
include: health,info
endpoint:
health:
show-details: when_authorized
# 3.x版本需要额外配置
management:
endpoint:
health:
show-components: when_authorized
5.2 监控指标设计
关键监控项:
- 投票成功率
- 平均响应时间
- 并发用户数
- Redis命中率
通过Micrometer对接Prometheus:
java复制@Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config()
.commonTags("application", "vote-system");
}
6. 扩展与演进方向
在实际运营过程中,我建议考虑以下扩展点:
- 可视化配置后台:通过拖拽方式创建投票表单
- 多级审核流程:集成Flowable引擎
- 智能防刷:引入机器学习识别异常投票模式
- 多租户支持:SAAS化改造
对于MyBatis的进阶使用,我推荐:
- 自定义TypeHandler处理复杂类型
- 拦截器实现审计日志
- 动态表名映射(适合分表场景)
xml复制<!-- 动态表名示例 -->
<select id="selectByUser" resultType="...">
SELECT * FROM ${tableName}
WHERE user_id = #{userId}
</select>
在Spring Boot 3.x中,尤其要注意Jakarta EE的包名变更问题。我在升级过程中遇到的最常见问题是:
java复制// 旧版本
import javax.servlet.*;
// 新版本
import jakarta.servlet.*;
这个项目从零开始到上线运营,我最大的体会是:技术选型要平衡当下的开发效率和未来的扩展成本。Spring Boot + MyBatis的组合在项目初期能快速出活,在中后期面对复杂需求时也有足够的灵活性。特别是在处理那些需要精细控制SQL的高性能场景时,MyBatis的优势是其他ORM难以替代的。
