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
-
93src/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; |
|||
|
|||
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<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