redis 配置缓存

消息处理机制调整
This commit is contained in:
shikong 2024-12-08 20:17:24 +08:00
parent e8a7435a42
commit 52c019627e
Signed by: Shikong
GPG Key ID: BD85FF18B373C341
14 changed files with 3156 additions and 43 deletions

View File

@ -0,0 +1,44 @@
package cn.skcks.docking.wx.services.mp.common.redis;
import cn.skcks.docking.wx.common.redis.RedisUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.redis.BaseWxRedisOps;
import me.chanjar.weixin.common.util.locks.RedisTemplateSimpleDistributedLock;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
@Slf4j
@RequiredArgsConstructor
@Component
public class WxRedisOps extends BaseWxRedisOps {
private final StringRedisTemplate redisTemplate;
@Override
public Lock getLock(String key) {
return new RedisTemplateSimpleDistributedLock(redisTemplate, key, 60 * 1000);
}
@Override
public void expire(String key, int expire, TimeUnit timeUnit) {
RedisUtil.KeyOps.expire(key, expire, timeUnit);
}
@Override
public Long getExpire(String key) {
return RedisUtil.KeyOps.getExpire(key);
}
@Override
public void setValue(String key, String value, int expire, TimeUnit timeUnit) {
RedisUtil.StringOps.setEx(key, value, expire, timeUnit);
}
@Override
public String getValue(String key) {
return RedisUtil.StringOps.get(key);
}
}

View File

@ -4,9 +4,8 @@ import cn.skcks.docking.wx.annotation.web.methods.GetJson;
import cn.skcks.docking.wx.common.json.JsonResponse;
import cn.skcks.docking.wx.common.page.PageWrapper;
import cn.skcks.docking.wx.orm.entity.MpApp;
import cn.skcks.docking.wx.services.mp.service.MpAppService;
import cn.skcks.docking.wx.services.mp.service.app.MpAppService;
import cn.skcks.docking.wx.services.mp.vo.verify.SignatureVo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
@ -17,7 +16,6 @@ import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
@Tag(name = "微信公众号")

View File

@ -1,9 +1,11 @@
package cn.skcks.docking.wx.services.mp.service;
package cn.skcks.docking.wx.services.mp.service.app;
import cn.hutool.core.date.DateUtil;
import cn.skcks.docking.wx.common.page.PageWrapper;
import cn.skcks.docking.wx.orm.entity.MpApp;
import cn.skcks.docking.wx.orm.mapper.MpAppMapper;
import cn.skcks.docking.wx.services.mp.common.redis.WxRedisOps;
import cn.skcks.docking.wx.services.mp.service.kefu.MpKeFuService;
import cn.skcks.docking.wx.services.mp.service.message.handler.MessageHandlerService;
import cn.skcks.docking.wx.services.mp.vo.verify.SignatureVo;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@ -11,13 +13,14 @@ import io.micrometer.common.util.StringUtils;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Service;
@ -32,7 +35,11 @@ import java.util.concurrent.ForkJoinPool;
@RequiredArgsConstructor
public class MpAppService {
private final MpAppMapper mpAppMapper;
private final WxRedisOps wxRedisOps;
private final MpKeFuService mpKeFuService;
private final MessageHandlerService messageHandlerService;
@Getter
private final Map<String, WxMpService> serviceMap = new ConcurrentHashMap<>();
@PostConstruct
@ -43,7 +50,7 @@ public class MpAppService {
new ForkJoinPool(Runtime.getRuntime().availableProcessors()).execute(() -> {
mpApps.parallelStream().forEach(mpApp -> {
WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
WxMpRedisConfigImpl config = new WxMpRedisConfigImpl(wxRedisOps, "wx_mp_");
config.setAppId(mpApp.getAppId());
config.setSecret(mpApp.getAppSecret());
config.setToken(mpApp.getToken());
@ -80,47 +87,64 @@ public class MpAppService {
return;
}
String encryptType = StringUtils.isBlank(request.getParameter("encrypt_type")) ?
"raw" :
request.getParameter("encrypt_type");
if ("raw".equals(encryptType)) {
// 明文传输的消息
WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(request.getInputStream());
// WxMpXmlOutMessage outMessage = wxMpMessageRouter.route(inMessage);
// if(outMessage == null) {
// //为null说明路由配置有问题需要注意
// response.getWriter().write("");
// }
// response.getWriter().write(outMessage.toXml());
WxMpXmlMessage inMessage = messageHandlerService.handleMessage(request, wxMpService, signatureVo);
if(inMessage == null) {
response.getWriter().println("不可识别的加密类型");
} else {
log.debug("\n[↓] 接收消息: \n{}", inMessage);
response.getWriter().write("");
WxMpXmlOutMessage outMessage = WxMpXmlOutMessage.TEXT()
.toUser(inMessage.getFromUser())
.fromUser(inMessage.getToUser())
.content(DateUtil.now())
.build();
log.debug("\n[↑] 响应消息: \n{}", outMessage);
response.getWriter().write(outMessage.toXml());
return;
mpKeFuService.reply(response, wxMpService, inMessage);
}
// String encryptType = StringUtils.isBlank(request.getParameter("encrypt_type")) ?
// "raw" :
// request.getParameter("encrypt_type");
//
// if ("raw".equals(encryptType)) {
// // 明文传输的消息
// // WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(request.getInputStream());
// // WxMpXmlOutMessage outMessage = wxMpMessageRouter.route(inMessage);
// // if(outMessage == null) {
// // //为null说明路由配置有问题需要注意
// // response.getWriter().write("");
// // }
// // response.getWriter().write(outMessage.toXml());
//
// log.debug("\n[↓] 接收消息: \n{}", inMessage);
//
// // WxMpXmlOutMessage outMessage = WxMpXmlOutMessage.TEXT()
// // .toUser(inMessage.getFromUser())
// // .fromUser(inMessage.getToUser())
// // .content(DateUtil.now())
// // .build();
// //
// // log.debug("\n[↑] 响应消息: \n{}", outMessage);
//
// // response.getWriter().write(outMessage.toXml());
// response.getWriter().write("");
//
// mpKeFuService.reply(response, wxMpService, inMessage);
// return;
// }
//
//
// if ("aes".equals(encryptType)) {
// WxMpConfigStorage wxMpConfigStorage = wxMpService.getWxMpConfigStorage();
//
// // 是aes加密的消息
// String msgSignature = request.getParameter("msg_signature");
// WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(request.getInputStream(), wxMpConfigStorage, timestamp, nonce, msgSignature);
// WxMpXmlOutMessage outMessage = wxMpMessageRouter.route(inMessage);
// if(outMessage == null) {
// //为null说明路由配置有问题需要注意
// response.getWriter().write("");
// }
// response.getWriter().write(outMessage.toEncryptedXml(wxMpConfigStorage));
// WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(request.getInputStream(), wxMpConfigStorage, signatureVo.getTimestamp(), signatureVo.getNonce(), msgSignature);
// // WxMpXmlOutMessage outMessage = wxMpMessageRouter.route(inMessage);
// // if(outMessage == null) {
// // //为null说明路由配置有问题需要注意
// // response.getWriter().write("");
// // }
// // response.getWriter().write(outMessage.toEncryptedXml(wxMpConfigStorage));
// return;
// }
response.getWriter().println("不可识别的加密类型");
// response.getWriter().println("不可识别的加密类型");
// return;
}
@ -142,10 +166,6 @@ public class MpAppService {
* @since 2024-12-07
*/
public PageWrapper<MpApp> getMpAppPageList(Integer pageNum, Integer pageSize){
// try(Page<Object> ignored = PageHelper.startPage(pageNum, pageSize)){
// return new PageInfo<>(mpAppMapper.selectList(null));
// }
Page<MpApp> mpAppPage = mpAppMapper.selectPage(new Page<>(pageNum, pageSize), null);
return PageWrapper.of(mpAppPage);
}

View File

@ -0,0 +1,31 @@
package cn.skcks.docking.wx.services.mp.service.kefu;
import cn.hutool.core.date.DateUtil;
import cn.skcks.docking.wx.services.mp.service.app.MpAppService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import org.springframework.stereotype.Service;
@Slf4j
@RequiredArgsConstructor
@Service
public class MpKeFuService {
@SneakyThrows
public void reply(HttpServletResponse response, WxMpService wxMpService, WxMpXmlMessage inMessage) {
WxMpKefuMessage outMessage = WxMpKefuMessage.TEXT()
.toUser(inMessage.getFromUser())
.content(DateUtil.now())
.build();
wxMpService.getKefuService().sendKefuMessage(outMessage);
log.debug("\n[↑] 响应消息: \n{}", outMessage);
}
}

View File

@ -0,0 +1,15 @@
package cn.skcks.docking.wx.services.mp.service.message.base;
import cn.skcks.docking.wx.services.mp.service.app.MpAppService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@RequiredArgsConstructor
@Service
public class MpBaseMessageService {
private final MpAppService mpAppService;
}

View File

@ -0,0 +1,24 @@
package cn.skcks.docking.wx.services.mp.service.message.handler;
import cn.skcks.docking.wx.services.mp.vo.verify.SignatureVo;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import java.io.IOException;
public interface MessageHandler {
default int order(){
return 0;
}
boolean canHandle(HttpServletRequest request);
WxMpXmlMessage receive(HttpServletRequest request, WxMpService wxMpService, SignatureVo signatureVo) throws IOException;
default String getEnctypeType(HttpServletRequest request){
return StringUtils.isBlank(request.getParameter("encrypt_type")) ?
"raw" :
request.getParameter("encrypt_type");
}
}

View File

@ -0,0 +1,49 @@
package cn.skcks.docking.wx.services.mp.service.message.handler;
import cn.skcks.docking.wx.services.mp.vo.verify.SignatureVo;
import jakarta.annotation.Nullable;
import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedList;
@Service
public class MessageHandlerService implements InitializingBean, ApplicationContextAware {
private ApplicationContext applicationContext;
private final Collection<MessageHandler> messageHandlers = new LinkedList<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
applicationContext.getBeansOfType(MessageHandler.class)
.values()
.stream()
.sorted(Comparator.comparingInt(MessageHandler::order))
.forEach(messageHandlers::add);
}
@Nullable
@SneakyThrows
public WxMpXmlMessage handleMessage(HttpServletRequest request, WxMpService wxMpService, SignatureVo signatureVo) {
for (MessageHandler messageHandler : messageHandlers) {
if(messageHandler.canHandle(request)){
return messageHandler.receive(request, wxMpService, signatureVo);
}
}
return null;
}
}

View File

@ -0,0 +1,34 @@
package cn.skcks.docking.wx.services.mp.service.message.handler.impl;
import cn.skcks.docking.wx.services.mp.service.message.handler.MessageHandler;
import cn.skcks.docking.wx.services.mp.vo.verify.SignatureVo;
import jakarta.servlet.http.HttpServletRequest;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class AESMessageHandler implements MessageHandler {
@Override
public boolean canHandle(HttpServletRequest request) {
return StringUtils.equalsIgnoreCase(getEnctypeType(request), "aes");
}
@Override
public WxMpXmlMessage receive(HttpServletRequest request, WxMpService wxMpService, SignatureVo signatureVo) throws IOException {
WxMpConfigStorage wxMpConfigStorage = wxMpService.getWxMpConfigStorage();
// 是aes加密的消息
String msgSignature = request.getParameter("msg_signature");
return WxMpXmlMessage.fromEncryptedXml(request.getInputStream(),
wxMpConfigStorage,
signatureVo.getTimestamp(),
signatureVo.getNonce(),
msgSignature);
}
}

View File

@ -0,0 +1,24 @@
package cn.skcks.docking.wx.services.mp.service.message.handler.impl;
import cn.skcks.docking.wx.services.mp.service.message.handler.MessageHandler;
import cn.skcks.docking.wx.services.mp.vo.verify.SignatureVo;
import jakarta.servlet.http.HttpServletRequest;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class RawMessageHandler implements MessageHandler {
@Override
public boolean canHandle(HttpServletRequest request) {
return StringUtils.equalsIgnoreCase(getEnctypeType(request), "raw");
}
@Override
public WxMpXmlMessage receive(HttpServletRequest request, WxMpService ignored, SignatureVo ignored2) throws IOException {
return WxMpXmlMessage.fromXml(request.getInputStream());
}
}

View File

@ -0,0 +1,34 @@
package cn.skcks.docking.wx.services.mp.service.message.handler.impl;
import cn.skcks.docking.wx.services.mp.service.message.handler.MessageHandler;
import cn.skcks.docking.wx.services.mp.vo.verify.SignatureVo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Slf4j
@Component
public class UnknownMessageHandler implements MessageHandler {
@Override
public int order() {
return Integer.MAX_VALUE;
}
@Override
public boolean canHandle(HttpServletRequest request) {
return Boolean.TRUE;
}
@Override
public WxMpXmlMessage receive(HttpServletRequest request, WxMpService wxMpService, SignatureVo signatureVo) throws IOException {
// 是aes加密的消息
String msgSignature = request.getParameter("msg_signature");
log.error("[x] 无法处理的消息加密类型 {}", msgSignature);
return null;
}
}

View File

@ -1,6 +1,19 @@
spring:
application:
name: wx-mp-service
data:
redis:
database: 1
host: 10.10.10.200
port: 16379
password: 12341234
jedis:
pool:
min-idle: 0
max-active: 8
max-idle: 8
max-wait: -1ms
connect-timeout: 30000ms
server:
port: 18881

View File

@ -72,5 +72,24 @@
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<exclusions>
<exclusion>
<artifactId>lettuce-core</artifactId>
<groupId>io.lettuce</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,24 @@
package cn.skcks.docking.wx.common.redis;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}