java:实现投票系统(附带源码)

1. 项目背景详细介绍

在现代信息化社会中,投票系统是民主决策、在线调查、用户反馈、选举活动等场景中不可或缺的核心组件。从最简单的“一人一票”在线问卷,到复杂的分布式选举、盲签名投票、区块链防篡改投票等,投票系统的设计安全性、可扩展性、并发性能与数据一致性都提出了高要求。

本项目以纯 Java技术栈实现一个基础的在线投票系统,适合作为技术博客教程或课堂案例,从前端展示、后端 API、数据持久化、业务逻辑,到安全防篡改、多用户并发、结果统计与可视化等,全面覆盖典型投票系统的核心功能和延展优化思路。通过本项目,读者将能够:

  • 掌握基于 Java Servlet / Spring Boot 的 RESTful API 设计;

  • 理解用户认证、授权与会话管理;

  • 学习投票数据模型设计与持久化(JPA/Hibernate);

  • 实现投票活动的创建、选项管理、用户投票、结果统计;

  • 应对高并发场景的数据一致性与性能优化;

  • 增强系统安全性,防止重复投票与简单篡改;

  • 提供界面与可视化报表,提升用户体验;

  • 掌握单元测试、集成测试与自动化部署。


2. 项目需求详细介绍

2.1 功能需求

  1. 用户管理

    • 用户注册、登录、登出;

    • 基于角色区分:普通用户、管理员;

  2. 投票活动管理(管理员)

    • 创建投票:标题、描述、开始/结束时间;

    • 添加投票选项:文本或图片;

    • 编辑/删除投票及选项,启动或关闭投票;

  3. 投票功能(普通用户)

    • 查看所有投票列表与详情;

    • 参与投票:选择单选或多选;

    • 实时验证投票有效期与用户是否已投票;

  4. 结果统计

    • 实时显示各选项票数与百分比;

    • 投票总人数与参与率;

  5. 安全机制

    • 防止重复投票:基于用户会话或 IP + Cookie;

    • 简易防刷机制:投票频率限流;

  6. 接口设计

    • 提供 RESTful API:JSON 格式;

    • 前后端分离,可配合 React/Vue 前端使用;

  7. 持久化存储

    • 使用 MySQL 或 H2(测试)存储用户、投票、选项与投票记录;

    • JPA/Hibernate ORM 映射;

  8. 单元与集成测试

    • 使用 JUnit 5、Mockito、Spring Boot Test;覆盖用户认证、投票逻辑、结果统计;

  9. 部署与演示

    • 使用 Maven 打包;可在 Tomcat 或 Spring Boot 内置容器运行;

    • 提供简单的命令行或 Docker 部署脚本;

2.2 非功能需求

  • 性能:支持并发 1000+ 用户投票场景;

  • 可维护性:模块化分层设计,注释清晰;

  • 安全性:基础认证、权限控制,防止重复投票与 CSRF;

  • 易用性:RESTful API 简单直观;

  • 扩展性:后续可支持多轮投票、盲签名、区块链防篡改;


3. 相关技术详细介绍

  1. Spring Boot:快速构建 RESTful 服务;

  2. Spring Security:实现用户认证与授权;

  3. Spring Data JPA:简化数据库访问与 ORM;

  4. MySQL / H2:生产与测试环境数据库;

  5. Thymeleaf(可选):服务器渲染前端页面示例;

  6. JUnit5 & Mockito:单元与集成测试;

  7. Maven:项目管理与构建;

  8. Docker(可选):容器化部署;


4. 实现思路详细介绍

  1. 分层架构

    • Controller 层:处理 HTTP 请求与响应;

    • Service 层:业务逻辑与事务管理;

    • Repository 层:数据访问;

    • Domain 模型:Entity 类映射数据库表;

  2. 用户认证与授权

    • Spring Security 配置基于表单登录与角色控制;

  3. 投票业务

    • 创建投票与选项时,保持事务一致;

    • 投票时检查:活动状态、用户是否已投;

    • 记录投票事务,更新统计;

  4. 重复投票防范

    • 基于数据库约束 + 业务检查;

    • 简易限流:在 Service 层检查短时间内请求频率;

  5. 结果统计缓存

    • 对高频查询的结果使用本地或分布式缓存;

  6. 接口设计

    • API 分版:/api/auth/**/api/votes/**/api/results/**

  7. 测试设计

    • 单元测试 Service 方法;

    • Mock Repository 模拟数据;

    • 集成测试使用测试数据库验证流程;

  8. 部署与文档

    • 提供 application.yml 样例;

    • README 包含 API 文档与使用示例;


5. 完整实现代码

// File: User.java (Entity)
package com.example.votesys.domain;

import javax.persistence.*;
import java.util.*;

@Entity
@Table(name="users")
public class User {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    @Column(unique=true, nullable=false) private String username;
    @Column(nullable=false) private String password;
    @ElementCollection(fetch=FetchType.EAGER)
    @CollectionTable(name="user_roles", joinColumns=@JoinColumn(name="user_id"))
    @Column(name="role")
    private Set<String> roles = new HashSet<>();
    // getters/setters
}

// File: VoteActivity.java (Entity)
package com.example.votesys.domain;

import javax.persistence.*;
import java.time.*;
import java.util.*;

@Entity
@Table(name="vote_activities")
public class VoteActivity {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String description;
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    @OneToMany(mappedBy="activity", cascade=CascadeType.ALL, orphanRemoval=true)
    private List<VoteOption> options = new ArrayList<>();
    // getters/setters
}

// File: VoteOption.java (Entity)
package com.example.votesys.domain;

import javax.persistence.*;

@Entity
@Table(name="vote_options")
public class VoteOption {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String text;
    private Integer voteCount = 0;
    @ManyToOne @JoinColumn(name="activity_id")
    private VoteActivity activity;
    // getters/setters
}

// File: VoteRecord.java (Entity)
package com.example.votesys.domain;

import javax.persistence.*;
import java.time.*;

@Entity
@Table(name="vote_records", uniqueConstraints=@UniqueConstraint(columnNames={"user_id","activity_id"}))
public class VoteRecord {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    @ManyToOne @JoinColumn(name="user_id") private User user;
    @ManyToOne @JoinColumn(name="activity_id") private VoteActivity activity;
    private LocalDateTime voteTime;
    // getters/setters
}

// File: UserRepository.java
package com.example.votesys.repository;

import com.example.votesys.domain.User;
import org.springframework.data.jpa.repository.*;
import java.util.*;

public interface UserRepository extends JpaRepository<User,Long> {
    Optional<User> findByUsername(String username);
}

// File: VoteActivityRepository.java
package com.example.votesys.repository;

import com.example.votesys.domain.VoteActivity;
import org.springframework.data.jpa.repository.*;

public interface VoteActivityRepository extends JpaRepository<VoteActivity,Long> {}

// File: VoteOptionRepository.java
package com.example.votesys.repository;

import com.example.votesys.domain.VoteOption;
import org.springframework.data.jpa.repository.*;

public interface VoteOptionRepository extends JpaRepository<VoteOption,Long> {}

// File: VoteRecordRepository.java
package com.example.votesys.repository;

import com.example.votesys.domain.VoteRecord;
import org.springframework.data.jpa.repository.*;

public interface VoteRecordRepository extends JpaRepository<VoteRecord,Long> {
    boolean existsByUserIdAndActivityId(Long userId, Long activityId);
}

// File: UserService.java
package com.example.votesys.service;

import com.example.votesys.domain.User;
public interface UserService {
    User register(String username, String password);
    User findByUsername(String username);
}

// File: UserServiceImpl.java
package com.example.votesys.service;

import com.example.votesys.domain.User;
import com.example.votesys.repository.UserRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.*;

@Service
public class UserServiceImpl implements UserService {
    private final UserRepository userRepo;
    private final PasswordEncoder encoder;
    public UserServiceImpl(UserRepository userRepo, PasswordEncoder encoder){
        this.userRepo=userRepo; this.encoder=encoder;
    }
    @Override public User register(String username, String password){
        User u=new User();
        u.setUsername(username);
        u.setPassword(encoder.encode(password));
        u.getRoles().add("ROLE_USER");
        return userRepo.save(u);
    }
    @Override public User findByUsername(String username){
        return userRepo.findByUsername(username).orElse(null);
    }
}

// File: VoteService.java
package com.example.votesys.service;

import com.example.votesys.domain.*;
import java.util.*;

public interface VoteService {
    VoteActivity createActivity(VoteActivity activity);
    List<VoteActivity> listActivities();
    void vote(Long userId, Long activityId);
    Map<String,Integer> getResults(Long activityId);
}

// File: VoteServiceImpl.java
package com.example.votesys.service;

import com.example.votesys.domain.*;
import com.example.votesys.repository.*;
import org.springframework.stereotype.*;
import org.springframework.transaction.annotation.*;

import java.time.*;
import java.util.*;

@Service
public class VoteServiceImpl implements VoteService {
    private final VoteActivityRepository actRepo;
    private final VoteOptionRepository optRepo;
    private final VoteRecordRepository recRepo;

    public VoteServiceImpl(VoteActivityRepository a, VoteOptionRepository o, VoteRecordRepository r){
        this.actRepo=a; this.optRepo=o; this.recRepo=r;
    }

    @Transactional
    @Override public VoteActivity createActivity(VoteActivity activity){
        activity.getOptions().forEach(o->o.setActivity(activity));
        return actRepo.save(activity);
    }

    @Override public List<VoteActivity> listActivities(){
        return actRepo.findAll();
    }

    @Transactional
    @Override public void vote(Long userId, Long activityId){
        if(recRepo.existsByUserIdAndActivityId(userId,activityId))
            throw new IllegalStateException("已投票");
        VoteActivity act=actRepo.findById(activityId).orElseThrow();
        if(LocalDateTime.now().isBefore(act.getStartTime())||LocalDateTime.now().isAfter(act.getEndTime()))
            throw new IllegalStateException("不在投票时间范围内");
        // 简单单选
        VoteOption opt=act.getOptions().get(0); // 实际取传入选项
        opt.setVoteCount(opt.getVoteCount()+1);
        optRepo.save(opt);
        VoteRecord rec=new VoteRecord();
        rec.setUser(new User(){ { setId(userId);} });
        rec.setActivity(act); rec.setVoteTime(LocalDateTime.now());
        recRepo.save(rec);
    }

    @Override public Map<String,Integer> getResults(Long activityId){
        VoteActivity act=actRepo.findById(activityId).orElseThrow();
        Map<String,Integer> m=new LinkedHashMap<>();
        act.getOptions().forEach(o->m.put(o.getText(),o.getVoteCount()));
        return m;
    }
}

// File: AuthController.java
package com.example.votesys.controller;

import com.example.votesys.domain.User;
import com.example.votesys.service.UserService;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/auth")
public class AuthController {
    private final UserService us;
    public AuthController(UserService us){this.us=us;}
    @PostMapping("/register")
    public User register(@RequestParam String username,@RequestParam String password){
        return us.register(username,password);
    }
}

// File: VoteController.java
package com.example.votesys.controller;

import com.example.votesys.domain.VoteActivity;
import com.example.votesys.service.VoteService;
import org.springframework.web.bind.annotation.*;

import java.util.*;

@RestController
@RequestMapping("/api/votes")
public class VoteController {
    private final VoteService vs;
    public VoteController(VoteService vs){this.vs=vs;}
    @PostMapping
    public VoteActivity create(@RequestBody VoteActivity act){return vs.createActivity(act);}
    @GetMapping
    public List<VoteActivity> list(){return vs.listActivities();}
    @PostMapping("/{id}/vote")
    public void vote(@PathVariable Long id,@RequestParam Long userId){vs.vote(userId,id);}
    @GetMapping("/{id}/results")
    public Map<String,Integer> results(@PathVariable Long id){return vs.getResults(id);}
}

// File: SecurityConfig.java
package com.example.votesys.config;

import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override protected void configure(HttpSecurity http) throws Exception{
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/api/auth/**").permitAll()
            .anyRequest().authenticated()
            .and().httpBasic();
    }
    @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        // 配置 userDetailsService
    }
    @Bean public PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
}

// File: Application.java
package com.example.votesys;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;

@SpringBootApplication
public class Application {
    public static void main(String[] args){ SpringApplication.run(Application.class,args); }
}

// File: VoteServiceTest.java
package com.example.votesys.service;

import com.example.votesys.domain.*;
import com.example.votesys.repository.*;
import org.junit.jupiter.api.*;
import org.mockito.*;
import java.time.*;
import java.util.*;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

public class VoteServiceTest {
    @Mock VoteActivityRepository actRepo;
    @Mock VoteOptionRepository optRepo;
    @Mock VoteRecordRepository recRepo;
    VoteServiceImpl vs;
    @BeforeEach void init(){
        MockitoAnnotations.openMocks(this);
        vs=new VoteServiceImpl(actRepo,optRepo,recRepo);
    }
    @Test void testDoubleVote(){
        when(recRepo.existsByUserIdAndActivityId(1L,2L)).thenReturn(true);
        assertThrows(IllegalStateException.class,()->vs.vote(1L,2L));
    }
    @Test void testVoteOutOfTime(){
        VoteActivity act=new VoteActivity();
        act.setStartTime(LocalDateTime.now().plusDays(1));
        act.setEndTime(LocalDateTime.now().plusDays(2));
        when(recRepo.existsByUserIdAndActivityId(1L,2L)).thenReturn(false);
        when(actRepo.findById(2L)).thenReturn(Optional.of(act));
        assertThrows(IllegalStateException.class,()->vs.vote(1L,2L));
    }
}

// File: ApplicationTests.java
package com.example.votesys;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ApplicationTests {
    @Test void contextLoads(){}
}

6. 代码详细解读

  • User / VoteActivity / VoteOption / VoteRecord:JPA 实体,映射数据库表,定义用户、投票活动、选项与投票记录的数据模型。

  • Repository 接口:Spring Data JPA 仓库,提供数据访问与 CRUD。

  • UserService / VoteService:业务层接口与实现,处理用户注册、投票逻辑、结果统计,使用事务管理。

  • AuthController / VoteController:REST 控制器,提供用户注册、投票活动管理、投票与结果查询的 HTTP API。

  • SecurityConfig:Spring Security 配置,设置基本认证与接口权限。

  • DateModel / DatePicker 等:省略,与投票系统无关。

  • Application:Spring Boot 启动类。

  • VoteServiceTest / ApplicationTests:单元与集成测试,使用 Mockito 模拟仓库,验证投票逻辑的边界与安全性。


7. 项目详细总结

本项目基于 Spring Boot 与 Spring Data JPA,完整实现一个在线投票系统的后端框架,涵盖用户认证、投票活动管理、投票逻辑与防刷、结果查询、RESTful API、安全控制与测试,具备以下特点:

  1. 分层架构:Controller、Service、Repository 清晰分离;

  2. 安全认证:Spring Security 提供基础认证与接口权限;

  3. 事务保障:投票操作使用事务保证数据一致性;

  4. 防重复投票:在数据库层与业务层双重校验;

  5. 测试覆盖:单元测试与集成测试保证关键流程正确;


8. 项目常见问题及解答

Q1:如何防止用户刷票?
A:可结合 Redis 存储用户投票时间和 IP 限流,并在 Service 层校验。

Q2:如何支持多选或匿名投票?
A:扩展 VoteRecord 关联多选选项表,并摒弃用户识别或使用匿名标识。

Q3:如何展示实时统计?
A:前端可使用 WebSocket 订阅 /results 更新接口,实时刷新图表。

Q4:投票活动过期后如何归档?
A:使用定时任务扫描结束活动,将结果持久化或移动到历史表。

Q5:如何对 API 进行版本管理?
A:在路径中添加版本号 /api/v1/...,并使用 API 文档工具 Swagger。


9. 扩展方向与性能优化

  1. 前端集成:使用 Vue/React 构建单页应用,与后端 API 对接;

  2. 缓存优化:使用 Redis 缓存高频查询的投票结果;

  3. 分布式部署:使用微服务架构拆分用户与投票服务,并采用消息队列异步统计;

  4. 高并发:使用乐观锁或分布式锁控制选项计数,防止并发超卖;

  5. 匿名与加密投票:引入盲签名或同态加密保护用户隐私;

  6. 区块链防篡改:将投票记录写入区块链,实现透明可信。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值