commit 40cb5c49dfb69077a7a6031a9e8aa075c867db3e Author: lenghui Date: Mon Dec 16 11:02:28 2024 +0800 初始化 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c91a2fb --- /dev/null +++ b/pom.xml @@ -0,0 +1,106 @@ + + + 4.0.0 + com.lh + vote_system + 0.0.1-SNAPSHOT + vote_system + Demo project for Spring Boot + + 1.8 + UTF-8 + UTF-8 + 2.4.2 + + + + org.springframework.boot + spring-boot-starter-web + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.1.4 + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + mysql + mysql-connector-java + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-cache + + + + org.springframework.kafka + spring-kafka + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + com.lh.VoteSystemApplication + true + + + + repackage + + repackage + + + + + + + + diff --git a/src/main/java/com/lh/VoteSystemApplication.java b/src/main/java/com/lh/VoteSystemApplication.java new file mode 100644 index 0000000..f7313ff --- /dev/null +++ b/src/main/java/com/lh/VoteSystemApplication.java @@ -0,0 +1,13 @@ +package com.lh; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = "com.lh") +public class VoteSystemApplication { + + public static void main(String[] args) { + SpringApplication.run(VoteSystemApplication.class, args); + } + +} diff --git a/src/main/java/com/lh/bean/Candidate.java b/src/main/java/com/lh/bean/Candidate.java new file mode 100644 index 0000000..b0d2740 --- /dev/null +++ b/src/main/java/com/lh/bean/Candidate.java @@ -0,0 +1,19 @@ +package com.lh.bean; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Candidate { + private Long id; + private String jwCode; + private String name; + private String avatar; + private Integer votes = 0; + + //是否被当前用户投票 + private boolean isVoted; +} diff --git a/src/main/java/com/lh/bean/RespBean.java b/src/main/java/com/lh/bean/RespBean.java new file mode 100644 index 0000000..774e401 --- /dev/null +++ b/src/main/java/com/lh/bean/RespBean.java @@ -0,0 +1,82 @@ +package com.lh.bean; + +public class RespBean { + //状态码 10000-成功 10001-失败 + private Integer code; + //返回的附件信息 + private String msg; + //返回的数据 + private Object data; + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } + + public static RespBean ok() { + RespBean respBean = new RespBean(); + respBean.setCode(10000); + + return respBean; + } + + public static RespBean ok(String msg) { + RespBean respBean = new RespBean(); + respBean.setCode(10000); + respBean.setMsg(msg); + + return respBean; + } + + public static RespBean ok(String msg, Object data) { + RespBean respBean = new RespBean(); + respBean.setCode(10000); + respBean.setMsg(msg); + respBean.setData(data); + + return respBean; + } + + public static RespBean error() { + RespBean respBean = new RespBean(); + respBean.setCode(10001); + + return respBean; + } + + public static RespBean error(String msg) { + RespBean respBean = new RespBean(); + respBean.setCode(10001); + respBean.setMsg(msg); + + return respBean; + } + + public static RespBean error(String msg, Object data) { + RespBean respBean = new RespBean(); + respBean.setCode(10001); + respBean.setMsg(msg); + respBean.setData(data); + + return respBean; + } +} \ No newline at end of file diff --git a/src/main/java/com/lh/bean/voter.java b/src/main/java/com/lh/bean/voter.java new file mode 100644 index 0000000..ff9fcab --- /dev/null +++ b/src/main/java/com/lh/bean/voter.java @@ -0,0 +1,18 @@ +package com.lh.bean; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class voter { + private Long id; + private String jwCode; + private String name; + private LocalDateTime voteTime; + private String candidateJwCode; +} diff --git a/src/main/java/com/lh/config/ExceptionHandler.java b/src/main/java/com/lh/config/ExceptionHandler.java new file mode 100644 index 0000000..a79e2fc --- /dev/null +++ b/src/main/java/com/lh/config/ExceptionHandler.java @@ -0,0 +1,22 @@ +package com.lh.config; + +import com.lh.bean.RespBean; +import com.lh.exception.MyException; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Configuration +@RestControllerAdvice +public class ExceptionHandler { + @org.springframework.web.bind.annotation.ExceptionHandler(MyException.class) + public RespBean myException(MyException e) { + e.printStackTrace(); + return RespBean.error(e.getMessage()); + } + + @org.springframework.web.bind.annotation.ExceptionHandler(Exception.class) + public RespBean exception(Exception e) { + e.printStackTrace(); + return RespBean.error("未知错误,请联系管理员"); + } +} \ No newline at end of file diff --git a/src/main/java/com/lh/config/RedisConfig.java b/src/main/java/com/lh/config/RedisConfig.java new file mode 100644 index 0000000..115232e --- /dev/null +++ b/src/main/java/com/lh/config/RedisConfig.java @@ -0,0 +1,51 @@ +package com.lh.config; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.time.Duration; + +@Configuration +@EnableCaching +public class RedisConfig { + @Bean + public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { + // 基本配置 + RedisCacheConfiguration defaultCacheConfiguration = + RedisCacheConfiguration + .defaultCacheConfig() + //设置key为String + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) + //设置value为自动转Json的Object + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) + //不缓存null + .disableCachingNullValues() + //缓存数据保存10分钟 + .entryTtl(Duration.ofMinutes(10)); + + //创建一个redis缓存管理器 + RedisCacheManager redisCacheManager = + RedisCacheManager.RedisCacheManagerBuilder + //Redis连接工厂 + .fromConnectionFactory(redisConnectionFactory) + //缓存配置 + .cacheDefaults(defaultCacheConfiguration) + .build(); + + return redisCacheManager; + } + + @Bean + public StringRedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + return new StringRedisTemplate(connectionFactory); + } +} \ No newline at end of file diff --git a/src/main/java/com/lh/controller/VoteController.java b/src/main/java/com/lh/controller/VoteController.java new file mode 100644 index 0000000..1058101 --- /dev/null +++ b/src/main/java/com/lh/controller/VoteController.java @@ -0,0 +1,31 @@ +package com.lh.controller; + +import com.lh.bean.RespBean; +import com.lh.service.VoteService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@CrossOrigin +@RestController +@RequestMapping +public class VoteController { + @Autowired + private VoteService voteService; + //投票 + @PostMapping("/vote") + public RespBean vote(String voterJwcode, String candidateJwcode, String voterName) throws Exception { + Integer result =voteService.insertVote(voterJwcode, candidateJwcode, voterName); + return RespBean.ok("投票成功!今日还可以投" + result + "次"); + } + //获取所有候选人 + @GetMapping ("/getCandidates/{voterJwcode}") + public RespBean getCandidates(@PathVariable("voterJwcode") String voterJwcode) { + return RespBean.ok("获取成功",voteService.getCandidates(voterJwcode)); + } + + //获取某个候选人的被投票记录 + @GetMapping("/getVotesByCandidate/{candidateJwcode}") + public RespBean getVotesByCandidate(@PathVariable("candidateJwcode") String candidateJwcode) { + return RespBean.ok("获取成功",voteService.getVotesByCandidate(candidateJwcode)); + } +} diff --git a/src/main/java/com/lh/exception/MyException.java b/src/main/java/com/lh/exception/MyException.java new file mode 100644 index 0000000..d4e1f5b --- /dev/null +++ b/src/main/java/com/lh/exception/MyException.java @@ -0,0 +1,7 @@ +package com.lh.exception; + +public class MyException extends Exception{ + public MyException(String msg){ + super(msg); + } +} \ No newline at end of file diff --git a/src/main/java/com/lh/mapper/CandidatesMapper.java b/src/main/java/com/lh/mapper/CandidatesMapper.java new file mode 100644 index 0000000..e8773c6 --- /dev/null +++ b/src/main/java/com/lh/mapper/CandidatesMapper.java @@ -0,0 +1,17 @@ +package com.lh.mapper; + +import com.lh.bean.Candidate; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface CandidatesMapper { + //获取所有候选人 + List getCandidates(); + //通过精网号搜索候选人 + Candidate getByCandidateJwcode(@Param("jwcode") String jwcode); + //候选人票数+1 + boolean addVotes(@Param("jwcode") String jwcode); +} diff --git a/src/main/java/com/lh/mapper/VoterMapper.java b/src/main/java/com/lh/mapper/VoterMapper.java new file mode 100644 index 0000000..df8a33d --- /dev/null +++ b/src/main/java/com/lh/mapper/VoterMapper.java @@ -0,0 +1,17 @@ +package com.lh.mapper; + +import com.lh.bean.voter; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface VoterMapper { + // 查询用户今日投票信息 + List countVotesToday(@Param("jwcode") String jwcode); + //插入投票记录 + void insertVote(@Param("jwcode") String jwcode, @Param("candidateJwcode") String candidateJwcode,@Param("name") String namne); + //根据候选人的jwcode查询投票记录 + List getVotesByCandidate(@Param("candidateJwcode") String candidateJwcode); +} diff --git a/src/main/java/com/lh/service/VoteService.java b/src/main/java/com/lh/service/VoteService.java new file mode 100644 index 0000000..4dd7ca8 --- /dev/null +++ b/src/main/java/com/lh/service/VoteService.java @@ -0,0 +1,16 @@ +package com.lh.service; + +import com.lh.bean.Candidate; +import com.lh.bean.voter; +import com.lh.exception.MyException; + +import java.util.List; + +public interface VoteService { + //投票 + Integer insertVote(String voterJwcode, String candidateJwcode, String voterName) throws MyException; + //获取所有候选人 + List getCandidates(String VoterJwcode); + //获取某个候选人的被投票记录 + List getVotesByCandidate(String candidateJwcode); +} diff --git a/src/main/java/com/lh/service/VoteServiceImpl.java b/src/main/java/com/lh/service/VoteServiceImpl.java new file mode 100644 index 0000000..02ce317 --- /dev/null +++ b/src/main/java/com/lh/service/VoteServiceImpl.java @@ -0,0 +1,101 @@ +package com.lh.service; + +import com.lh.bean.Candidate; +import com.lh.bean.voter; +import com.lh.exception.MyException; +import com.lh.mapper.CandidatesMapper; +import com.lh.mapper.VoterMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Service +public class VoteServiceImpl implements VoteService { + @Autowired + private VoterMapper voterMapper; + @Autowired + private CandidatesMapper candidatesMapper; + @Value("${vote.limit.daily}") + private int dailyVoteLimit; // 每日投票次数限制 + @Autowired + private StringRedisTemplate redisTemplate; + + //投票 + @Override + @Transactional(rollbackFor = Exception.class) + public Integer insertVote(String voterJwcode, String candidateJwcode, String voterName) throws MyException { + // 1. 检查 Redis 中用户今天的投票次数 + String redisKey = "vote_count:" + voterJwcode + ":" + LocalDateTime.now().toLocalDate(); + String currentVoteCount = redisTemplate.opsForValue().get(redisKey); + + int voteCountToday = currentVoteCount == null ? 0 : Integer.parseInt(currentVoteCount); + if (voteCountToday >= dailyVoteLimit) { + throw new MyException("今日投票次数已达上限"); + } + + // 2. 获取候选人信息 + Candidate candidate = candidatesMapper.getByCandidateJwcode(candidateJwcode); + if (candidate == null) { + throw new MyException("候选人不存在!"); + } + + // 3. 检查用户是否已经为该候选人投过票 + List hasVotes = voterMapper.countVotesToday(voterJwcode); + //遍历列表,判断是否有记录 + for (voter vote : hasVotes) { + if (vote.getCandidateJwCode().equals(candidateJwcode)) { + throw new MyException("已投票,可以选择其他人试试哦~"); + } + } + + // 4. 增加候选人票数 + if (!candidatesMapper.addVotes(candidateJwcode)) { + throw new MyException ("候选人票数更新失败,请联系管理员!"); + } + + // 5. 插入投票记录 + voterMapper.insertVote(voterJwcode, candidateJwcode, voterName); + + // 6. 更新 Redis 中的投票次数 + redisTemplate.opsForValue().increment(redisKey, 1); + // 设置 Redis 键的过期时间为当天的23:59:59 + LocalDateTime now = LocalDateTime.now(); + LocalDateTime endOfDay = now.toLocalDate().atTime(23, 59, 59); + long secondsUntilEndOfDay = Duration.between(now, endOfDay).getSeconds(); + redisTemplate.expire(redisKey, secondsUntilEndOfDay, TimeUnit.SECONDS); + //打印剩余长时间过期 + System.out.println("Redis键" + redisKey + "将在" + secondsUntilEndOfDay + "秒后过期。"); + + return 2-voteCountToday; + } + + //获取所有候选人 + @Override + public List getCandidates(String VoterJwcode) { + //插入投票记录,为List插入是否投过票的状态 + List candidateList = candidatesMapper.getCandidates(); + for (Candidate candidate : candidateList) { + List voters = voterMapper.countVotesToday(VoterJwcode); + for (voter voter : voters) { + if (voter.getCandidateJwCode().equals(candidate.getJwCode())) { + candidate.setVoted(true); + break; + } + } + } + return candidateList; + } + + //获取候选人被投票记录 + @Override + public List getVotesByCandidate(String candidateJwcode) { + return voterMapper.getVotesByCandidate(candidateJwcode); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..4008f38 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,32 @@ +# 配置连接池 +spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost:3306/vote_system?serverTimezone=UTC +spring.datasource.username=root +spring.datasource.password=123456 + +# mybatis配置 +# 打印log信息 +mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl +# 开启驼峰命名法,自动将数据库的命名方式,映射成Java中的命名方式 +mybatis.configuration.map-underscore-to-camel-case: true +#下面这些内容是为了让MyBatis映射 +#指定Mybatis的Mapper文件 +mybatis.mapper-locations=classpath:mappers/*xml +#指定Mybatis的实体目录 +mybatis.type-aliases-package=com.lh.mybatis.entity + +# 应用服务 WEB 访问端口 +server.port=8080 + +#Redis 配置 +spring.redis.host=localhost +spring.redis.port=6379 +spring.redis.password=123456 +spring.redis.timeout=2000 +spring.redis.jedis.pool.max-active=10 +spring.redis.jedis.pool.max-idle=5 +spring.redis.jedis.pool.min-idle=1 + +# 每日最大投票次数 +vote.limit.daily=3 + diff --git a/src/main/resources/com/lh/mapper/CandidatesMapper.xml b/src/main/resources/com/lh/mapper/CandidatesMapper.xml new file mode 100644 index 0000000..e1e5b47 --- /dev/null +++ b/src/main/resources/com/lh/mapper/CandidatesMapper.xml @@ -0,0 +1,13 @@ + + + + + update candidates set votes = votes + 1 where jwcode = #{jwcode} + + + + \ No newline at end of file diff --git a/src/main/resources/com/lh/mapper/VoterMapper.xml b/src/main/resources/com/lh/mapper/VoterMapper.xml new file mode 100644 index 0000000..64c12a8 --- /dev/null +++ b/src/main/resources/com/lh/mapper/VoterMapper.xml @@ -0,0 +1,14 @@ + + + + + INSERT INTO voters(jwcode, candidate_jwcode, name) + VALUES(#{jwcode}, #{candidateJwcode}, #{name}) + + + + \ No newline at end of file diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 0000000..e2d94a2 --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,6 @@ + + +

hello word!!!

+

this is a html page

+ + \ No newline at end of file diff --git a/src/test/java/com/lh/VoteSystemApplicationTests.java b/src/test/java/com/lh/VoteSystemApplicationTests.java new file mode 100644 index 0000000..5e19ae6 --- /dev/null +++ b/src/test/java/com/lh/VoteSystemApplicationTests.java @@ -0,0 +1,30 @@ +package com.lh; + +import com.lh.mapper.CandidatesMapper; +import com.lh.mapper.VoterMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class VoteSystemApplicationTests { + @Autowired + private CandidatesMapper candidatesMapper; + @Autowired + private VoterMapper voterMapper; + + @Test + void contextLoads() { + //candidatesMapper.getCandidates().forEach(System.out::println); + System.out.print( voterMapper.countVotesToday("10010")); + } + @Test + void contextLoads1() { + voterMapper.getVotesByCandidate("20010").forEach(System.out::println); + } + @Test + void contextLoads2() { + voterMapper.insertVote("10010","20010","王五"); + } + +}