12 changed files with 294 additions and 86 deletions
-
15src/main/java/com/lh/bean/VoteMessage.java
-
2src/main/java/com/lh/bean/Voter.java
-
80src/main/java/com/lh/config/CandidateCacheRepository.java
-
52src/main/java/com/lh/config/RedisConfig.java
-
9src/main/java/com/lh/controller/VoteController.java
-
6src/main/java/com/lh/mapper/VoterMapper.java
-
85src/main/java/com/lh/service/VoteConsumer.java
-
26src/main/java/com/lh/service/VoteProducer.java
-
4src/main/java/com/lh/service/VoteService.java
-
95src/main/java/com/lh/service/VoteServiceImpl.java
-
2src/main/resources/application.properties
-
4src/main/resources/com/lh/mapper/VoterMapper.xml
@ -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; // 投票时间 |
||||
|
} |
@ -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<String, Object> 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<Object> getCandidateJwCodesByVotes() { |
||||
|
return redisTemplate.opsForZSet().reverseRange("candidate:votes", 0, -1); |
||||
|
} |
||||
|
|
||||
|
// 删除 Redis 中所有候选人数据 |
||||
|
public void deleteAllCandidatesFromCache() { |
||||
|
Set<Object> jwCodes = redisTemplate.opsForZSet().range("candidate:votes", 0, -1); |
||||
|
if (jwCodes != null) { |
||||
|
for (Object jwCode : jwCodes) { |
||||
|
redisTemplate.delete("candidate:" + jwCode); |
||||
|
} |
||||
|
} |
||||
|
redisTemplate.delete("candidate:votes"); |
||||
|
} |
||||
|
} |
@ -1,51 +1,37 @@ |
|||||
package com.lh.config; |
package com.lh.config; |
||||
|
|
||||
import org.springframework.cache.CacheManager; |
|
||||
import org.springframework.cache.annotation.EnableCaching; |
import org.springframework.cache.annotation.EnableCaching; |
||||
import org.springframework.context.annotation.Bean; |
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
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.connection.RedisConnectionFactory; |
||||
|
import org.springframework.data.redis.core.RedisTemplate; |
||||
import org.springframework.data.redis.core.StringRedisTemplate; |
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 org.springframework.data.redis.serializer.StringRedisSerializer; |
||||
|
|
||||
import java.time.Duration; |
|
||||
|
|
||||
@Configuration |
@Configuration |
||||
@EnableCaching |
@EnableCaching |
||||
public class RedisConfig { |
public class RedisConfig { |
||||
@Bean |
@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 |
@Bean |
||||
public StringRedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { |
|
||||
return new StringRedisTemplate(connectionFactory); |
|
||||
|
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { |
||||
|
RedisTemplate<String, Object> 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; |
||||
} |
} |
||||
|
|
||||
} |
} |
@ -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<String, String> 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<Voter> 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; |
||||
|
} |
||||
|
|
||||
|
} |
@ -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<String, String> 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 |
||||
|
} |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue