diff --git a/member.impl/src/main/java/com/apobates/forum/member/impl/RedisMemberEventConfig.java b/member.impl/src/main/java/com/apobates/forum/member/impl/RedisMemberEventConfig.java index ab1dde7ac3c3a1eea3d292d8b8f4afe700e9011f..6dfe7c565bdaf2d35f30e5d71684eb8ca6339dce 100644 --- a/member.impl/src/main/java/com/apobates/forum/member/impl/RedisMemberEventConfig.java +++ b/member.impl/src/main/java/com/apobates/forum/member/impl/RedisMemberEventConfig.java @@ -46,6 +46,12 @@ public class RedisMemberEventConfig { mq.setQueueName("member:VipExchangeEvent"); return mq; } + @Bean("changeEventQueue") + public RedisMessageQueue changeEventQueue(){ + RedisMessageQueue mq = new com.github.davidmarquis.redisq.RedisMessageQueue(); + mq.setQueueName("member:ChangeEvent"); + return mq; + } // loop Producer @Bean("signUpEventProducer") public MessageProducer signUpEventProducer(@Qualifier("signUpEventQueue") RedisMessageQueue signUpEventQueue){ @@ -77,6 +83,12 @@ public class RedisMemberEventConfig { dmq.setQueue(vipExchangeEventQueue); return dmq; } + @Bean("changeEventProducer") + public MessageProducer changeEventProducer(@Qualifier("changeEventQueue") RedisMessageQueue changeEventQueue){ + DefaultMessageProducer dmq = new com.github.davidmarquis.redisq.producer.DefaultMessageProducer<>(); + dmq.setQueue(changeEventQueue); + return dmq; + } //loop Consumer @Bean public MessageConsumer promoteRoleConsumer( diff --git a/member.impl/src/main/java/com/apobates/forum/member/impl/dao/MemberDaoImpl.java b/member.impl/src/main/java/com/apobates/forum/member/impl/dao/MemberDaoImpl.java index 12aff1457b48a0fb2d3cd47f54582df5e5177e4e..5a1e70856286bc5f7338adfd092073754f1f614c 100644 --- a/member.impl/src/main/java/com/apobates/forum/member/impl/dao/MemberDaoImpl.java +++ b/member.impl/src/main/java/com/apobates/forum/member/impl/dao/MemberDaoImpl.java @@ -5,6 +5,7 @@ import com.apobates.forum.member.entity.Member; import com.apobates.forum.member.entity.MemberGroupEnum; import com.apobates.forum.member.entity.MemberRoleEnum; import com.apobates.forum.member.entity.MemberStatusEnum; +import com.apobates.forum.member.impl.event.MemberChangeEvent; import com.apobates.forum.utils.Commons; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -13,8 +14,11 @@ import java.util.Map.Entry; import java.util.stream.Stream; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import com.github.davidmarquis.redisq.producer.MessageProducer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; @@ -32,6 +36,8 @@ import org.springframework.transaction.annotation.Transactional; public class MemberDaoImpl implements MemberDao { @PersistenceContext private EntityManager entityManager; + @Autowired @Qualifier("changeEventProducer") + private MessageProducer changeEventProducer; private final static Logger logger = LoggerFactory.getLogger(MemberDaoImpl.class); @Cacheable(key="#memberId", unless="#result==null") @@ -50,6 +56,9 @@ public class MemberDaoImpl implements MemberDao { public boolean editMemberRole(long memberId, MemberRoleEnum role) { int affect = entityManager.createQuery("UPDATE Member m SET m.mrole = ?1 WHERE m.id = ?2 AND m.mrole != ?3").setParameter(1, role).setParameter(2, memberId).setParameter(3, role).executeUpdate(); boolean complete = (affect == 1); + if(complete) { + changeEventProducer.create(new MemberChangeEvent(memberId, role)).submit(); + } return complete; } @@ -59,6 +68,9 @@ public class MemberDaoImpl implements MemberDao { public boolean editMemberGroup(long memberId, MemberGroupEnum group) { int affect = entityManager.createQuery("UPDATE Member m SET m.mgroup = ?1 WHERE m.id = ?2 AND m.mgroup != ?3").setParameter(1, group).setParameter(2, memberId).setParameter(3, group).executeUpdate(); boolean complete = (affect == 1); + if(complete) { + changeEventProducer.create(new MemberChangeEvent(memberId, group)).submit(); + } return complete; } @@ -68,6 +80,9 @@ public class MemberDaoImpl implements MemberDao { public boolean editMemberStatus(long memberId, MemberStatusEnum status) { int affect = entityManager.createQuery("UPDATE Member m SET m.status = ?1 WHERE m.id = ?2 AND m.status != ?3").setParameter(1, status).setParameter(2, memberId).setParameter(3, status).executeUpdate(); boolean complete = (affect == 1); + if(complete) { + changeEventProducer.create(new MemberChangeEvent(memberId, status)).submit(); + } return complete; } diff --git a/member.impl/src/main/java/com/apobates/forum/member/impl/event/MemberChangeEvent.java b/member.impl/src/main/java/com/apobates/forum/member/impl/event/MemberChangeEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..f1420da551474d90aeeeb5d2cbf62063a63e6fb1 --- /dev/null +++ b/member.impl/src/main/java/com/apobates/forum/member/impl/event/MemberChangeEvent.java @@ -0,0 +1,103 @@ +package com.apobates.forum.member.impl.event; + +import com.apobates.forum.member.entity.MemberGroupEnum; +import com.apobates.forum.member.entity.MemberRoleEnum; +import com.apobates.forum.member.entity.MemberStatusEnum; +import com.apobates.forum.utils.lang.EnumArchitecture; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * 会员变化事件 + * + * @author xiaofanku + * @since 20200927 + */ +public class MemberChangeEvent { + private final long memberId; + private final String key; + private final int value; + private final static Map MAP_STRUCT = Map.ofEntries(Map.entry("MemberStatusEnum", "status"), Map.entry("MemberGroupEnum", "group"), Map.entry("MemberRoleEnum", "role")); + + public MemberChangeEvent(long memberId, MemberStatusEnum status) { + this(memberId, status, MemberStatusEnum.class); + } + + public MemberChangeEvent(long memberId, MemberRoleEnum role) { + this(memberId, role, MemberRoleEnum.class); + } + + public MemberChangeEvent(long memberId, MemberGroupEnum group) { + this(memberId, group, MemberGroupEnum.class); + } + + private MemberChangeEvent(long memberId, EnumArchitecture archite, Class cls){ + Optional k = getMapKey(cls); + if(!k.isPresent()){ + throw new IllegalStateException("不支持的Key"); + } + if(cls.isAssignableFrom(EnumArchitecture.class)){ + throw new IllegalStateException("参数不是合法值"); + } + this.memberId = memberId; + this.key = k.get(); + this.value = archite.getSymbol(); + } + + public long getMemberId() { + return memberId; + } + + public String getKey() { + return key; + } + + public int getValue() { + return value; + } + + /** + * 变化事件允许的映射Key + * + * @return + */ + public static Set allowKey(){ + return Set.copyOf(MAP_STRUCT.values()); + } + + /** + * 根据类查询映射Key + * + * @param EnumArchitecture的实现枚举类型 + * @param cls EnumArchitecture的实现枚举.class + * @return + */ + public static Optional getMapKey(Class cls){ + String cCN = cls.getSimpleName(); + if(MAP_STRUCT.containsKey(cCN)){ + return Optional.ofNullable(MAP_STRUCT.get(cCN)); + } + return Optional.empty(); + } + + /** + * + * @param EnumArchitecture的实现枚举类型 + * @param cls EnumArchitecture的实现枚举.class + * @param mapping 映射的值集合 + * @return + */ + public static Optional get(Class cls, Map mapping){ + Optional mapKey = getMapKey(cls); + if(mapKey.isPresent() && mapping.containsKey(mapKey.get())){ + return EnumArchitecture.getInstance(mapping.get(mapKey.get()), cls); + } + return Optional.empty(); + } + + @Override + public String toString() { + return "MemberChangeEvent{" + "memberId=" + memberId + ", key=" + key + ", value=" + value + '}'; + } +} diff --git a/thrones/src/main/java/com/apobates/forum/thrones/ThronesRedisEventConfig.java b/thrones/src/main/java/com/apobates/forum/thrones/ThronesRedisEventConfig.java index 9c45457268c1e68e227cf8cbaf6f38ced635ce89..760e3bc125c706346eba51ed9da96ae4f591a4a5 100644 --- a/thrones/src/main/java/com/apobates/forum/thrones/ThronesRedisEventConfig.java +++ b/thrones/src/main/java/com/apobates/forum/thrones/ThronesRedisEventConfig.java @@ -1,10 +1,7 @@ package com.apobates.forum.thrones; import com.apobates.forum.core.impl.event.*; -import com.apobates.forum.member.impl.event.MemberPenalizeEvent; -import com.apobates.forum.member.impl.event.MemberSignInEvent; -import com.apobates.forum.member.impl.event.MemberSignUpEvent; -import com.apobates.forum.member.impl.event.MemberVipExchangeEvent; +import com.apobates.forum.member.impl.event.*; import com.apobates.forum.thrones.event.*; import com.github.davidmarquis.redisq.RedisMessageQueue; import com.github.davidmarquis.redisq.consumer.MessageConsumer; @@ -129,4 +126,15 @@ public class ThronesRedisEventConfig { messageConsumer.setMessageListener(bornNotice); return messageConsumer; } + @Bean + public MessageConsumer changeEventConsumer( + @Qualifier("changeEventQueue") RedisMessageQueue changeEventQueue, + @Qualifier("asyncInfoCache") MemberChangeEventListener asyncInfoCache){ + MessageConsumer messageConsumer = new com.github.davidmarquis.redisq.consumer.MessageConsumer<>(); + messageConsumer.setQueue(changeEventQueue); + messageConsumer.setConsumerId("MemberChangeEvent:asyncInfo"); + messageConsumer.setMessageListener(asyncInfoCache); + //messageConsumer.setThreadingStrategy(new com.github.davidmarquis.redisq.consumer.MultiThreadingStrategy(4)); + return messageConsumer; + } } diff --git a/thrones/src/main/java/com/apobates/forum/thrones/controller/helper/CommonControllerAdvice.java b/thrones/src/main/java/com/apobates/forum/thrones/controller/helper/CommonControllerAdvice.java index c7e8985a013eed79ed129f233075ed0937b82fd9..7c4ab229e006f0f8ca94ff91f9b826d786ca7d2a 100644 --- a/thrones/src/main/java/com/apobates/forum/thrones/controller/helper/CommonControllerAdvice.java +++ b/thrones/src/main/java/com/apobates/forum/thrones/controller/helper/CommonControllerAdvice.java @@ -2,11 +2,14 @@ package com.apobates.forum.thrones.controller.helper; import com.apobates.forum.core.security.exception.StrategyException; import com.apobates.forum.core.security.exception.VerificaFailException; -import com.apobates.forum.member.entity.Member; -import com.apobates.forum.member.service.MemberService; +import com.apobates.forum.member.entity.MemberGroupEnum; +import com.apobates.forum.member.entity.MemberRoleEnum; +import com.apobates.forum.member.entity.MemberStatusEnum; +import com.apobates.forum.member.impl.event.MemberChangeEvent; import com.apobates.forum.member.storage.OnlineMemberStorage; import com.apobates.forum.member.storage.core.MemberSessionBean; import com.apobates.forum.member.storage.core.MemberSessionBeanBuilder; +import com.apobates.forum.thrones.event.MemberChangeEventListener; import com.apobates.forum.thrones.exception.BorbidMemberRegisterException; import com.apobates.forum.thrones.exception.ForumValidateException; import com.apobates.forum.thrones.exception.MemberFreezeException; @@ -17,7 +20,10 @@ import com.apobates.forum.utils.TipMessage; import com.apobates.forum.utils.TipMessageLevelEnum; import com.apobates.forum.utils.lang.ReplicableException; import java.io.IOException; -import java.io.PrintWriter;import java.util.Optional; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; @@ -25,6 +31,8 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.TypeMismatchException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; @@ -50,7 +58,7 @@ public class CommonControllerAdvice { @Autowired private OnlineMemberStorage onlineMemberStorage; @Autowired - private MemberService memberService; + private RedisTemplate template; @Value("${site.domain}") private String siteDomain; private final static Logger logger = LoggerFactory.getLogger(CommonControllerAdvice.class); @@ -63,18 +71,39 @@ public class CommonControllerAdvice { // Cookie MemberSessionBean mbean = onlineMemberStorage.getInstance(request, "CommonControllerAdvice").orElse(MemberSessionBeanBuilder.empty().build(Commons.getRequestIp(request), "CommonControllerAdvice")); if (mbean.getMid()>0) { - //总要查一下看一看有没变化 - Optional data = memberService.get(mbean.getMid()); - if (data.isPresent()) { - Member obj = data.get(); - if(obj.getMrole()!=mbean.getRole() || obj.getMgroup()!=mbean.getGroup() || obj.getStatus()!=mbean.getStatus()){ - mbean = mbean.refact(obj.getMgroup(), obj.getMrole(), obj.getStatus()); //为什么要更新一下角色,组,状态?这三者随时可能发生变化@20200502 - onlineMemberStorage.refresh(request, response, obj.getStatus(), obj.getMgroup(), obj.getMrole()); - } + Optional data = refactMemberSessionBean(mbean); + data.ifPresent(newMSB -> { + onlineMemberStorage.refresh(request, response, newMSB.getStatus(), newMSB.getGroup(), newMSB.getRole()); + }); + if(data.isPresent()){ + mbean = data.get(); } } return mbean; } + private Optional refactMemberSessionBean(MemberSessionBean oldBean) { + Map cachedata = getEventPushDate(oldBean.getMid()); + if (!cachedata.isEmpty()) { + Optional newGroup = MemberChangeEvent.get(MemberGroupEnum.class, cachedata); + Optional newStatus = MemberChangeEvent.get(MemberStatusEnum.class, cachedata); + Optional newRole = MemberChangeEvent.get(MemberRoleEnum.class, cachedata); + // + MemberSessionBean newBean = oldBean.refact(newGroup.orElse(oldBean.getGroup()), newRole.orElse(oldBean.getRole()), newStatus.orElse(oldBean.getStatus())); + return Optional.of(newBean); + } + return Optional.empty(); + } + + private Map getEventPushDate(long memberId){ + String redisKey = MemberChangeEventListener.getEventRedisKey(memberId); + HashOperations operation = template.opsForHash(); + Map cachedata = operation.entries(redisKey); + if (null != cachedata && !cachedata.isEmpty()) { //总有一个变了 + template.delete(redisKey); + return cachedata; + } + return Collections.emptyMap(); + } //https://stackoverflow.com/questions/24498662/howto-handle-404-exceptions-globally-using-spring-mvc-configured-using-java-base //https://stackoverflow.com/questions/2066946/trigger-404-in-spring-mvc-controller @ExceptionHandler(value={ResourceNotFoundException.class, NoHandlerFoundException.class, ReplicableException.class}) diff --git a/thrones/src/main/java/com/apobates/forum/thrones/event/MemberChangeEventListener.java b/thrones/src/main/java/com/apobates/forum/thrones/event/MemberChangeEventListener.java new file mode 100644 index 0000000000000000000000000000000000000000..c12f413441f214a331829bfc053d06f1a1581b2e --- /dev/null +++ b/thrones/src/main/java/com/apobates/forum/thrones/event/MemberChangeEventListener.java @@ -0,0 +1,32 @@ +package com.apobates.forum.thrones.event; + +import com.apobates.forum.member.impl.event.MemberChangeEvent; +import com.github.davidmarquis.redisq.Message; +import com.github.davidmarquis.redisq.consumer.MessageListener; +import com.github.davidmarquis.redisq.consumer.retry.RetryableMessageException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import java.util.Map; + +@Component("asyncInfoCache") +public class MemberChangeEventListener implements MessageListener { + @Autowired + private RedisTemplate template; + private final static Logger logger = LoggerFactory.getLogger(MemberChangeEventListener.class); + + @Override + public void onMessage(Message message) throws RetryableMessageException { + logger.info("[Member][ChangeEvent][1]会员变更侦听器处理开始"); + MemberChangeEvent event = message.getPayload(); + Map gm = Map.ofEntries(Map.entry(event.getKey(), event.getValue())); + template.opsForHash().putAll(getEventRedisKey(event.getMemberId()), gm); + logger.info("[Member][ChangeEvent][1]会员变更侦听器结束处理"); + } + + public static String getEventRedisKey(long memberId){ + return "memberEvent:"+memberId; + } +}