Browse Source

优化

lh_vote_java
lenghui 5 months ago
parent
commit
2a2afc8472
  1. 15
      src/main/java/com/lh/bean/VoteMessage.java
  2. 2
      src/main/java/com/lh/bean/Voter.java
  3. 80
      src/main/java/com/lh/config/CandidateCacheRepository.java
  4. 52
      src/main/java/com/lh/config/RedisConfig.java
  5. 9
      src/main/java/com/lh/controller/VoteController.java
  6. 6
      src/main/java/com/lh/mapper/VoterMapper.java
  7. 85
      src/main/java/com/lh/service/VoteConsumer.java
  8. 26
      src/main/java/com/lh/service/VoteProducer.java
  9. 4
      src/main/java/com/lh/service/VoteService.java
  10. 93
      src/main/java/com/lh/service/VoteServiceImpl.java
  11. 2
      src/main/resources/application.properties
  12. 4
      src/main/resources/com/lh/mapper/VoterMapper.xml

15
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; // 投票人IDJWCode
private String candidateJwcode; // 候选人IDJWCode
private String voterName; // 投票人姓名
private String voteTime; // 投票时间
}

2
src/main/java/com/lh/bean/voter.java → 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;

80
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<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");
}
}

52
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<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;
}
}

9
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 + "次");
}

6
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<voter> countVotesToday(@Param("jwcode") String jwcode);
List<Voter> countVotesToday(@Param("jwcode") String jwcode);
//插入投票记录
void insertVote(@Param("jwcode") String jwcode, @Param("candidateJwcode") String candidateJwcode,@Param("name") String namne);
//根据候选人的jwcode查询投票记录
List<voter> getVotesByCandidate(@Param("candidateJwcode") String candidateJwcode);
List<Voter> getVotesByCandidate(@Param("candidateJwcode") String candidateJwcode);
}

85
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<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;
}
}

26
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<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
}
}

4
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<Candidate> getCandidates(String VoterJwcode);
//获取某个候选人的被投票记录
List<voter> getVotesByCandidate(String candidateJwcode);
List<Voter> getVotesByCandidate(String candidateJwcode);
}

93
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<String, Object> redisTemplate;
@Autowired
private CandidateCacheRepository candidateCacheRepository;
// 初始化时加载所有候选人数据并缓存到 Redis
@PostConstruct
public void initCandidatesCache() {
List<Candidate> 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<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);
//将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<Candidate> getCandidates(String VoterJwcode) {
public List<Candidate> getCandidates(String voterJwcode) {
// Redis 中获取按投票数排序的候选人 jwCode 列表
Set<Object> jwCodes = candidateCacheRepository.getCandidateJwCodesByVotes();
//将jwCodes转为字符串类型
List<String> jwCodeList = new ArrayList<>();
for (Object jwCode : jwCodes) {
jwCodeList.add(jwCode.toString());
}
List<Candidate> candidateList = new ArrayList<>();
for (String jwCode : jwCodeList) {
Candidate candidate = candidateCacheRepository.getCandidate(jwCode);
candidateList.add(candidate);
}
// 插入投票记录 List 插入是否投过票的状态
List<Candidate> candidateList = candidatesMapper.getCandidates();
for (Candidate candidate : candidateList) {
List<voter> voters = voterMapper.countVotesToday(VoterJwcode);
for (voter voter : voters) {
List<Voter> voters = voterMapper.countVotesToday(voterJwcode);
for (Voter voter : voters) {
if (voter.getCandidateJwCode().equals(candidate.getJwCode())) {
candidate.setVoted(true);
break;
}
}
}
return candidateList;
}
//获取候选人被投票记录
@Override
public List<voter> getVotesByCandidate(String candidateJwcode) {
public List<Voter> getVotesByCandidate(String candidateJwcode) {
return voterMapper.getVotesByCandidate(candidateJwcode);
}
}

2
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

4
src/main/resources/com/lh/mapper/VoterMapper.xml

@ -5,10 +5,10 @@
INSERT INTO voters(jwcode, candidate_jwcode, name)
VALUES(#{jwcode}, #{candidateJwcode}, #{name})
</insert>
<select id="countVotesToday" resultType="com.lh.bean.voter">
<select id="countVotesToday" resultType="com.lh.bean.Voter">
SELECT * FROM voters WHERE jwcode = #{jwcode} AND DATE(vote_time) = CURDATE()
</select>
<select id="getVotesByCandidate" resultType="com.lh.bean.voter">
<select id="getVotesByCandidate" resultType="com.lh.bean.Voter">
SELECT * FROM voters WHERE candidate_jwcode = #{candidateJwcode}
</select>
</mapper>
Loading…
Cancel
Save