From 2a2afc8472092f9c5a3b685a9dfe0a43616cfaa1 Mon Sep 17 00:00:00 2001 From: lenghui Date: Tue, 17 Dec 2024 16:39:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/lh/bean/VoteMessage.java | 15 ++++ .../java/com/lh/bean/{voter.java => Voter.java} | 2 +- .../com/lh/config/CandidateCacheRepository.java | 80 ++++++++++++++++++ src/main/java/com/lh/config/RedisConfig.java | 52 +++++------- .../java/com/lh/controller/VoteController.java | 9 +- src/main/java/com/lh/mapper/VoterMapper.java | 6 +- src/main/java/com/lh/service/VoteConsumer.java | 85 +++++++++++++++++++ src/main/java/com/lh/service/VoteProducer.java | 26 ++++++ src/main/java/com/lh/service/VoteService.java | 4 +- src/main/java/com/lh/service/VoteServiceImpl.java | 95 ++++++++++++---------- src/main/resources/application.properties | 2 +- src/main/resources/com/lh/mapper/VoterMapper.xml | 4 +- 12 files changed, 294 insertions(+), 86 deletions(-) create mode 100644 src/main/java/com/lh/bean/VoteMessage.java rename src/main/java/com/lh/bean/{voter.java => Voter.java} (94%) create mode 100644 src/main/java/com/lh/config/CandidateCacheRepository.java create mode 100644 src/main/java/com/lh/service/VoteConsumer.java create mode 100644 src/main/java/com/lh/service/VoteProducer.java diff --git a/src/main/java/com/lh/bean/VoteMessage.java b/src/main/java/com/lh/bean/VoteMessage.java new file mode 100644 index 0000000..5433a6d --- /dev/null +++ b/src/main/java/com/lh/bean/VoteMessage.java @@ -0,0 +1,15 @@ +package com.lh.bean; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class VoteMessage { + private String voterJwcode; // 投票人ID(JWCode) + private String candidateJwcode; // 候选人ID(JWCode) + private String voterName; // 投票人姓名 + private String voteTime; // 投票时间 +} diff --git a/src/main/java/com/lh/bean/voter.java b/src/main/java/com/lh/bean/Voter.java similarity index 94% rename from src/main/java/com/lh/bean/voter.java rename to src/main/java/com/lh/bean/Voter.java index ff9fcab..9598d91 100644 --- a/src/main/java/com/lh/bean/voter.java +++ b/src/main/java/com/lh/bean/Voter.java @@ -9,7 +9,7 @@ import java.time.LocalDateTime; @Data @NoArgsConstructor @AllArgsConstructor -public class voter { +public class Voter { private Long id; private String jwCode; private String name; diff --git a/src/main/java/com/lh/config/CandidateCacheRepository.java b/src/main/java/com/lh/config/CandidateCacheRepository.java new file mode 100644 index 0000000..3c296a8 --- /dev/null +++ b/src/main/java/com/lh/config/CandidateCacheRepository.java @@ -0,0 +1,80 @@ +package com.lh.config; + +import com.lh.bean.Candidate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.stereotype.Repository; + +import javax.annotation.PostConstruct; +import java.util.Set; + +@Repository +public class CandidateCacheRepository { + + @Autowired + private RedisTemplate redisTemplate; + + @PostConstruct + public void init() { + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); + redisTemplate.afterPropertiesSet(); + } + + // 保存候选人信息到 Redis + + // 保存候选人信息到 Redis + public void saveCandidate(Candidate candidate) { + String key = "candidate:" + candidate.getJwCode(); + + // 确保所有值都是字符串类型 + redisTemplate.opsForHash().put(key, "id", String.valueOf(candidate.getId())); + redisTemplate.opsForHash().put(key, "jwCode", candidate.getJwCode()); + redisTemplate.opsForHash().put(key, "name", candidate.getName()); + redisTemplate.opsForHash().put(key, "avatar", candidate.getAvatar()); + redisTemplate.opsForHash().put(key, "votes", String.valueOf(candidate.getVotes())); + + // 更新 ZSET + redisTemplate.opsForZSet().add("candidate:votes", candidate.getJwCode(), candidate.getVotes()); + } + + // 获取候选人详细信息 + public Candidate getCandidate(String jwCode) { + String key = "candidate:" + jwCode; + Candidate candidate = new Candidate(); + + Object idObj = redisTemplate.opsForHash().get(key, "id"); + if (idObj != null) { + candidate.setId(Long.parseLong(idObj.toString())); + } + + candidate.setJwCode((String) redisTemplate.opsForHash().get(key, "jwCode")); + candidate.setName((String) redisTemplate.opsForHash().get(key, "name")); + candidate.setAvatar((String) redisTemplate.opsForHash().get(key, "avatar")); + + Object votesObj = redisTemplate.opsForHash().get(key, "votes"); + if (votesObj != null) { + candidate.setVotes(Integer.parseInt(votesObj.toString())); + } + + return candidate; + } + + // 获取所有候选人的 jwCode 按投票数排序 + public Set getCandidateJwCodesByVotes() { + return redisTemplate.opsForZSet().reverseRange("candidate:votes", 0, -1); + } + + // 删除 Redis 中所有候选人数据 + public void deleteAllCandidatesFromCache() { + Set jwCodes = redisTemplate.opsForZSet().range("candidate:votes", 0, -1); + if (jwCodes != null) { + for (Object jwCode : jwCodes) { + redisTemplate.delete("candidate:" + jwCode); + } + } + redisTemplate.delete("candidate:votes"); + } +} diff --git a/src/main/java/com/lh/config/RedisConfig.java b/src/main/java/com/lh/config/RedisConfig.java index 115232e..df347a6 100644 --- a/src/main/java/com/lh/config/RedisConfig.java +++ b/src/main/java/com/lh/config/RedisConfig.java @@ -1,51 +1,37 @@ 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.RedisTemplate; 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; + public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) { + return new StringRedisTemplate(connectionFactory); } @Bean - public StringRedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { - return new StringRedisTemplate(connectionFactory); + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory); + + // 设置 key 的序列化方式 + template.setKeySerializer(new StringRedisSerializer()); + + // 设置 hash key 和 hash value 的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(new StringRedisSerializer()); + + // 设置 value 的序列化方式 + template.setValueSerializer(new StringRedisSerializer()); + + return template; } + } \ 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 index 1058101..6bac3e4 100644 --- a/src/main/java/com/lh/controller/VoteController.java +++ b/src/main/java/com/lh/controller/VoteController.java @@ -1,6 +1,8 @@ package com.lh.controller; import com.lh.bean.RespBean; +import com.lh.bean.Voter; +import com.lh.service.VoteConsumer; import com.lh.service.VoteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -11,9 +13,14 @@ import org.springframework.web.bind.annotation.*; public class VoteController { @Autowired private VoteService voteService; + @Autowired + private VoteConsumer voteConsumer; //投票 @PostMapping("/vote") - public RespBean vote(String voterJwcode, String candidateJwcode, String voterName) throws Exception { + public RespBean vote(@RequestBody Voter voter) throws Exception { + String voterJwcode = voter.getJwCode(); + String candidateJwcode = voter.getCandidateJwCode(); + String voterName = voter.getName(); Integer result =voteService.insertVote(voterJwcode, candidateJwcode, voterName); return RespBean.ok("投票成功!今日还可以投" + result + "次"); } diff --git a/src/main/java/com/lh/mapper/VoterMapper.java b/src/main/java/com/lh/mapper/VoterMapper.java index df8a33d..923106d 100644 --- a/src/main/java/com/lh/mapper/VoterMapper.java +++ b/src/main/java/com/lh/mapper/VoterMapper.java @@ -1,6 +1,6 @@ package com.lh.mapper; -import com.lh.bean.voter; +import com.lh.bean.Voter; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -9,9 +9,9 @@ import java.util.List; @Mapper public interface VoterMapper { // 查询用户今日投票信息 - List countVotesToday(@Param("jwcode") String jwcode); + 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); + List getVotesByCandidate(@Param("candidateJwcode") String candidateJwcode); } diff --git a/src/main/java/com/lh/service/VoteConsumer.java b/src/main/java/com/lh/service/VoteConsumer.java new file mode 100644 index 0000000..ce93e6e --- /dev/null +++ b/src/main/java/com/lh/service/VoteConsumer.java @@ -0,0 +1,85 @@ +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.apache.kafka.clients.consumer.ConsumerRecord; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.kafka.annotation.EnableKafka; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Service +@EnableKafka +public class VoteConsumer { + @Autowired + private VoterMapper voterMapper; + @Autowired + private CandidatesMapper candidatesMapper; + @Autowired + private StringRedisTemplate redisTemplate; + + @KafkaListener(topics = "vote-topic", groupId = "vote-group") + public void handleVoteMessage(ConsumerRecord record) throws MyException { + String message = record.value(); + + // 将消息分割为投票信息 + String[] parts = message.split(","); + String voterJwcode = parts[0]; + String candidateJwcode = parts[1]; + String voterName = parts[2]; + String voteTime = parts[3]; + + // 处理投票 + processVote(voterJwcode, candidateJwcode, voterName, voteTime); + } + + private boolean processVote(String voterJwcode, String candidateJwcode, String voterName, String voteTime) throws MyException { + // 获取候选人信息 + Candidate candidate = candidatesMapper.getByCandidateJwcode(candidateJwcode); + + // 2. 获取候选人信息 + 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 中的投票次数 + String redisKey = "vote_count:" + voterJwcode + ":" + LocalDateTime.now().toLocalDate(); + 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 + "秒后过期。"); + System.out.println("投票成功!用户:" + voterJwcode + " 投给了 " + candidateJwcode); + return true; + } + +} diff --git a/src/main/java/com/lh/service/VoteProducer.java b/src/main/java/com/lh/service/VoteProducer.java new file mode 100644 index 0000000..a410d83 --- /dev/null +++ b/src/main/java/com/lh/service/VoteProducer.java @@ -0,0 +1,26 @@ +package com.lh.service; + +import com.lh.bean.VoteMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; + +@Service +public class VoteProducer { + + private static final String TOPIC = "vote-topic"; // Kafka topic + + @Autowired + private KafkaTemplate kafkaTemplate; + + // 发送投票消息 + public void sendVoteMessage(VoteMessage voteMessage) { + String message = String.format("%s,%s,%s,%s", + voteMessage.getVoterJwcode(), + voteMessage.getCandidateJwcode(), + voteMessage.getVoterName(), + voteMessage.getVoteTime()); + + kafkaTemplate.send(TOPIC, message); // 将投票信息发送到Kafka + } +} \ No newline at end of file diff --git a/src/main/java/com/lh/service/VoteService.java b/src/main/java/com/lh/service/VoteService.java index 4dd7ca8..d4a19a6 100644 --- a/src/main/java/com/lh/service/VoteService.java +++ b/src/main/java/com/lh/service/VoteService.java @@ -1,7 +1,7 @@ package com.lh.service; import com.lh.bean.Candidate; -import com.lh.bean.voter; +import com.lh.bean.Voter; import com.lh.exception.MyException; import java.util.List; @@ -12,5 +12,5 @@ public interface VoteService { //获取所有候选人 List getCandidates(String VoterJwcode); //获取某个候选人的被投票记录 - List getVotesByCandidate(String candidateJwcode); + List getVotesByCandidate(String candidateJwcode); } diff --git a/src/main/java/com/lh/service/VoteServiceImpl.java b/src/main/java/com/lh/service/VoteServiceImpl.java index 28ebe95..4e8d5ac 100644 --- a/src/main/java/com/lh/service/VoteServiceImpl.java +++ b/src/main/java/com/lh/service/VoteServiceImpl.java @@ -1,20 +1,25 @@ package com.lh.service; import com.lh.bean.Candidate; -import com.lh.bean.voter; +import com.lh.bean.VoteMessage; +import com.lh.bean.Voter; +import com.lh.config.CandidateCacheRepository; 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.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.Duration; +import javax.annotation.PostConstruct; +import java.sql.Timestamp; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.TimeUnit; +import java.util.Set; @Service public class VoteServiceImpl implements VoteService { @@ -25,7 +30,22 @@ public class VoteServiceImpl implements VoteService { @Value("${vote.limit.daily}") private int dailyVoteLimit; // 每日投票次数限制 @Autowired - private StringRedisTemplate redisTemplate; + private StringRedisTemplate stringRedisTemplate; + @Autowired + private VoteProducer voteProducer; + @Autowired + private RedisTemplate redisTemplate; + @Autowired + private CandidateCacheRepository candidateCacheRepository; + + // 初始化时加载所有候选人数据并缓存到 Redis + @PostConstruct + public void initCandidatesCache() { + List candidates = candidatesMapper.getCandidates(); // 从数据库加载所有教师信息 + for (Candidate candidate : candidates) { + candidateCacheRepository.saveCandidate(candidate); // 将数据保存到 Redis + } + } //投票 @Override @@ -33,70 +53,59 @@ public class VoteServiceImpl implements VoteService { 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); + String currentVoteCount = stringRedisTemplate.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); + //将redis中的candidates中的投票次数+1 - // 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 + "秒后过期。"); + //将投票请求发送到 Kafka 消息队列 + voteProducer.sendVoteMessage(new VoteMessage(voterJwcode, + candidateJwcode, voterName, + Timestamp.valueOf(LocalDateTime.now()).toString())); return 2-voteCountToday; } //获取所有候选人 + //先查redis中有没有数据,没有就从数据库中查 @Override - public List getCandidates(String VoterJwcode) { - //插入投票记录,为List插入是否投过票的状态 - List candidateList = candidatesMapper.getCandidates(); + public List getCandidates(String voterJwcode) { + // 从 Redis 中获取按投票数排序的候选人 jwCode 列表 + Set jwCodes = candidateCacheRepository.getCandidateJwCodesByVotes(); + //将jwCodes转为字符串类型 + List jwCodeList = new ArrayList<>(); + for (Object jwCode : jwCodes) { + jwCodeList.add(jwCode.toString()); + } + + List candidateList = new ArrayList<>(); + for (String jwCode : jwCodeList) { + Candidate candidate = candidateCacheRepository.getCandidate(jwCode); + candidateList.add(candidate); + } + + + // 插入投票记录,为 List 插入是否投过票的状态 for (Candidate candidate : candidateList) { - List voters = voterMapper.countVotesToday(VoterJwcode); - for (voter voter : voters) { + 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) { - + public List getVotesByCandidate(String candidateJwcode) { return voterMapper.getVotesByCandidate(candidateJwcode); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4008f38..91fe0c7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -16,7 +16,7 @@ mybatis.mapper-locations=classpath:mappers/*xml mybatis.type-aliases-package=com.lh.mybatis.entity # 应用服务 WEB 访问端口 -server.port=8080 +server.port=8091 #Redis 配置 spring.redis.host=localhost diff --git a/src/main/resources/com/lh/mapper/VoterMapper.xml b/src/main/resources/com/lh/mapper/VoterMapper.xml index 64c12a8..a2a999a 100644 --- a/src/main/resources/com/lh/mapper/VoterMapper.xml +++ b/src/main/resources/com/lh/mapper/VoterMapper.xml @@ -5,10 +5,10 @@ INSERT INTO voters(jwcode, candidate_jwcode, name) VALUES(#{jwcode}, #{candidateJwcode}, #{name}) - SELECT * FROM voters WHERE jwcode = #{jwcode} AND DATE(vote_time) = CURDATE() - SELECT * FROM voters WHERE candidate_jwcode = #{candidateJwcode} \ No newline at end of file