com.mxixm.fastboot.weixin.mvc.annotation.WxMappingHandlerMapping Maven / Gradle / Ivy
The newest version!
package com.mxixm.fastboot.weixin.mvc.annotation;
import com.mxixm.fastboot.weixin.annotation.WxButton;
import com.mxixm.fastboot.weixin.annotation.WxController;
import com.mxixm.fastboot.weixin.annotation.WxEventMapping;
import com.mxixm.fastboot.weixin.annotation.WxMessageMapping;
import com.mxixm.fastboot.weixin.controller.WxBuildinVerify;
import com.mxixm.fastboot.weixin.module.Wx;
import com.mxixm.fastboot.weixin.module.event.WxEvent;
import com.mxixm.fastboot.weixin.module.web.WxRequest;
import com.mxixm.fastboot.weixin.module.menu.WxButtonItem;
import com.mxixm.fastboot.weixin.module.menu.WxMenuManager;
import com.mxixm.fastboot.weixin.module.message.WxMessage;
import com.mxixm.fastboot.weixin.module.web.session.WxSessionManager;
import com.mxixm.fastboot.weixin.mvc.method.WxMappingHandlerMethodNamingStrategy;
import com.mxixm.fastboot.weixin.mvc.method.WxMappingInfo;
import com.mxixm.fastboot.weixin.util.WildcardUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.MediaType;
import org.springframework.util.*;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
public class WxMappingHandlerMapping extends AbstractHandlerMethodMapping implements InitializingBean {
/**
* http://blog.csdn.net/Mr_SeaTurtle_/article/details/52992207
*/
private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
private static final ParamsRequestCondition WX_VERIFY_PARAMS_CONDITION = new ParamsRequestCondition("echostr", "nonce", "signature", "timestamp");
private static final ParamsRequestCondition WX_POST_PARAMS_CONDITION = new ParamsRequestCondition("openid", "nonce", "signature", "timestamp");
private static final ConsumesRequestCondition WX_POST_CONSUMES_CONDITION = new ConsumesRequestCondition(MediaType.TEXT_XML_VALUE);
private static final Method WX_VERIFY_METHOD = ClassUtils.getMethod(WxBuildinVerify.class, "verify", null);
private final HandlerMethod wxVerifyMethodHandler;
// mappingRegistry同父类完全不同,故自己创建一个
// 也因为此,要把父类所有使用mappingRegistry的地方覆盖父类方法
private final MappingRegistry mappingRegistry = new MappingRegistry();
// 可以加一个开关功能:已经加了
private WxMenuManager wxMenuManager;
private WxSessionManager wxSessionManager;
public WxMappingHandlerMapping(WxBuildinVerify wxBuildinVerify, WxMenuManager wxMenuManager, WxSessionManager wxSessionManager) {
super();
this.wxVerifyMethodHandler = new HandlerMethod(wxBuildinVerify, WX_VERIFY_METHOD);
this.wxMenuManager = wxMenuManager;
this.wxSessionManager = wxSessionManager;
this.setHandlerMethodMappingNamingStrategy(new WxMappingHandlerMethodNamingStrategy());
}
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
// 只接受根目录的请求
if (!"/".equals(lookupPath)) {
return null;
}
if (isWxVerifyRequest(request)) {
return wxVerifyMethodHandler;
}
if (isWxPostRequest(request)) {
return lookupHandlerMethod(lookupPath, request);
}
return null;
}
// 判断是否是微信verify请求
private boolean isWxVerifyRequest(HttpServletRequest request) {
return "GET".equals(request.getMethod())
&& WX_VERIFY_PARAMS_CONDITION.getMatchingCondition(request) != null;
}
// 判断是否是微信POST请求
private boolean isWxPostRequest(HttpServletRequest request) {
return "POST".equals(request.getMethod())
&& WX_POST_PARAMS_CONDITION.getMatchingCondition(request) != null
&& WX_POST_CONSUMES_CONDITION.getMatchingCondition(request) != null;
}
public Map getHandlerMethods() {
this.mappingRegistry.acquireReadLock();
try {
return Collections.unmodifiableMap(this.mappingRegistry.getMappings());
} finally {
this.mappingRegistry.releaseReadLock();
}
}
@Override
public List getHandlerMethodsForMappingName(String mappingName) {
return this.mappingRegistry.getHandlerMethodsByMappingName(mappingName);
}
MappingRegistry getMappingRegistry() {
return this.mappingRegistry;
}
@Override
public void registerMapping(WxMappingInfo mapping, Object handler, Method method) {
this.mappingRegistry.register(mapping, handler, method);
}
@Override
public void unregisterMapping(WxMappingInfo mapping) {
this.mappingRegistry.unregister(mapping);
}
/**
* 父类中只有getHandlerInternal方法有使用
*/
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = null;
WxRequest.Body wxRequestBody = new WxRequest(request, wxSessionManager).getBody();
// switch不被推荐缺少default
switch (wxRequestBody.getCategory()) {
case BUTTON:
handlerMethod = lookupButtonHandlerMethod(wxRequestBody);
break;
case EVENT:
handlerMethod = lookupEventHandlerMethod(wxRequestBody);
break;
case MESSAGE:
handlerMethod = lookupMessageHandlerMethod(wxRequestBody);
break;
default:break;
}
handleMatch(handlerMethod, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
} finally {
this.mappingRegistry.releaseReadLock();
}
}
protected void handleMatch(HandlerMethod handlerMethod, HttpServletRequest request) {
// 返回XML
if (handlerMethod != null) {
request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_XML));
}
}
private HandlerMethod lookupButtonHandlerMethod(WxRequest.Body wxRequestBody) {
HandlerMethod handlerMethod = mappingRegistry.getHandlerButtonByEventKey(wxRequestBody.getEventKey());
if (handlerMethod == null) {
handlerMethod = mappingRegistry.getHandlerButtonByButtonType(wxRequestBody.getButtonType());
}
return handlerMethod;
}
private HandlerMethod lookupEventHandlerMethod(WxRequest.Body wxRequestBody) {
return mappingRegistry.getHandlerEventByEventType(wxRequestBody.getEventType());
}
private HandlerMethod lookupMessageHandlerMethod(WxRequest.Body wxRequestBody) {
if (wxRequestBody.getMessageType() == WxMessage.Type.TEXT) {
List handlerMethods = mappingRegistry.getHandlersByContent(wxRequestBody.getContent());
if (!handlerMethods.isEmpty()) {
return handlerMethods.get(0);
}
}
return mappingRegistry.getHandlerMessageByMessageType(wxRequestBody.getMessageType());
}
protected boolean isHandler(Class> beanType) {
return AnnotatedElementUtils.hasAnnotation(beanType, WxController.class);
}
protected void registerHandlerMethod(Object handler, Method method, WxMappingInfo mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
@Override
protected Set getMappingPathPatterns(WxMappingInfo info) {
return Collections.singleton("/");
}
@Override
protected WxMappingInfo getMatchingMapping(WxMappingInfo mapping, HttpServletRequest request) {
return mapping.getMatchingCondition(request);
}
@Override
protected Comparator getMappingComparator(HttpServletRequest request) {
return (info1, info2) -> info1.compareTo(info2, request);
}
@Override
protected WxMappingInfo getMappingForMethod(Method method, Class> handlerType) {
WxMappingInfo info = createWxMappingInfo(method);
if (info != null) {
WxMappingInfo typeInfo = createWxMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
}
return info;
}
private WxMappingInfo createWxMappingInfo(AnnotatedElement element) {
WxButton wxButton = AnnotatedElementUtils.findMergedAnnotation(element, WxButton.class);
// 由于这个机制,所以无法为同一个方法绑定多个WxButton、WxEventMapping、WxMessageMapping
if (wxButton != null) {
return createWxButtonMappingInfo(wxButton);
}
WxMessageMapping wxMessageMapping = AnnotatedElementUtils.findMergedAnnotation(element, WxMessageMapping.class);
if (wxMessageMapping != null) {
return createWxMessageMappingInfo(wxMessageMapping);
}
WxEventMapping wxEventMapping = AnnotatedElementUtils.findMergedAnnotation(element, WxEventMapping.class);
if (wxEventMapping != null) {
return createWxEventMappingInfo(wxEventMapping);
}
return null;
}
private WxMappingInfo createWxButtonMappingInfo(WxButton wxButton) {
// 在这里加上菜单管理是否启用的判断
WxButtonItem wxButtonItem = wxMenuManager.add(wxButton);
return WxMappingInfo
.category(Wx.Category.BUTTON)
// eventKey是url,如果类型是VIEW的话
.eventKey(wxButtonItem.getKey())
.mappingName(wxButton.name())
.buttonTypes(wxButton.type())
.build();
}
private WxMappingInfo createWxMessageMappingInfo(WxMessageMapping wxMessageMapping) {
return WxMappingInfo
.category(Wx.Category.MESSAGE)
.messageTypes(wxMessageMapping.type())
.mappingName(wxMessageMapping.name())
.wildcards(wxMessageMapping.wildcard())
.build();
}
private WxMappingInfo createWxEventMappingInfo(WxEventMapping wxEventMapping) {
return WxMappingInfo
.category(Wx.Category.EVENT)
.eventTypes(wxEventMapping.type())
.mappingName(wxEventMapping.name())
.build();
}
class MappingRegistry {
private final Map> registry = new HashMap<>();
private final Map mappingLookup = new LinkedHashMap<>();
private final Map eventKeyLookup = new LinkedHashMap<>();
private final Map buttonTypeLookup = new LinkedHashMap<>();
private final Map eventTypeLookup = new LinkedHashMap<>();
private final Map messageTypeLookup = new LinkedHashMap<>();
private final MultiValueMap categoryLookup = new LinkedMultiValueMap<>();
private final Map> nameLookup = new ConcurrentHashMap<>();
private final MultiValueMap wildcardLookup = new LinkedMultiValueMap<>();
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public Map getMappings() {
return this.mappingLookup;
}
public HandlerMethod getHandlerButtonByEventKey(String eventKey) {
return this.eventKeyLookup.get(eventKey);
}
public HandlerMethod getHandlerButtonByButtonType(WxButton.Type buttonType) {
return this.buttonTypeLookup.get(buttonType);
}
public HandlerMethod getHandlerEventByEventType(WxEvent.Type eventType) {
return this.eventTypeLookup.get(eventType);
}
public HandlerMethod getHandlerMessageByMessageType(WxMessage.Type messageType) {
return this.messageTypeLookup.get(messageType);
}
public List getHandlerMethodsByMappingName(String mappingName) {
return this.nameLookup.get(mappingName);
}
public List getHandlersByContent(String content) {
List matchs = this.wildcardLookup.keySet().stream().filter(w -> WildcardUtils.wildcardMatch(content, w))
.sorted(Comparator.comparing(String::length).reversed()).collect(Collectors.toList());
if (matchs.isEmpty()) {
return Collections.emptyList();
}
if (matchs.size() > 1) {
if (matchs.get(0).length() == matchs.get(1).length()) {
logger.error("有两个重复的通配符!以后加入通配符权重!!");
}
}
String selectedMatch = matchs.get(0);
final List handlerMethods = this.wildcardLookup.get(selectedMatch).stream().map(w -> mappingLookup.get(w)).collect(Collectors.toList());
if (handlerMethods.size() > 1) {
logger.error("有一个通配符有两个匹配的方法!");
}
return handlerMethods;
}
public void acquireReadLock() {
this.readWriteLock.readLock().lock();
}
public void releaseReadLock() {
this.readWriteLock.readLock().unlock();
}
public void register(WxMappingInfo mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
assertUniqueMethodMapping(handlerMethod, mapping);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
}
this.mappingLookup.put(mapping, handlerMethod);
this.categoryLookup.add(mapping.getCategory(), mapping);
if (!StringUtils.isEmpty(mapping.getEventKey())) {
eventKeyLookup.put(mapping.getEventKey(), handlerMethod);
}
// 对于button类型,暂时只支持key查找
if (!mapping.getWxEventTypeCondition().isEmpty()) {
mapping.getWxEventTypeCondition().getEnums().forEach(
e -> eventTypeLookup.put(e, handlerMethod)
);
}
if (!mapping.getWxMessageTypeCondition().isEmpty()) {
mapping.getWxMessageTypeCondition().getEnums().forEach(
e -> messageTypeLookup.put(e, handlerMethod)
);
}
if (!mapping.getWxButtonTypeCondition().isEmpty()) {
mapping.getWxButtonTypeCondition().getEnums().forEach(
e -> buttonTypeLookup.put(e, handlerMethod)
);
}
if (!mapping.getWxMessageWildcardCondition().isEmpty()) {
mapping.getWxMessageWildcardCondition().getWildcards().forEach(
w -> wildcardLookup.add(w, mapping)
);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, name));
} finally {
this.readWriteLock.writeLock().unlock();
}
}
private void assertUniqueMethodMapping(HandlerMethod newHandlerMethod, WxMappingInfo mapping) {
HandlerMethod handlerMethod = this.mappingLookup.get(mapping);
if (handlerMethod != null && !handlerMethod.equals(newHandlerMethod)) {
throw new IllegalStateException(
"Ambiguous mapping. Cannot map '" + newHandlerMethod.getBean() + "' method \n" +
newHandlerMethod + "\nto " + mapping + ": There is already '" +
handlerMethod.getBean() + "' bean method\n" + handlerMethod + " mapped.");
}
}
private void addMappingName(String name, HandlerMethod handlerMethod) {
List oldList = this.nameLookup.get(name);
if (oldList == null) {
oldList = Collections.emptyList();
}
for (HandlerMethod current : oldList) {
if (handlerMethod.equals(current)) {
return;
}
}
if (logger.isTraceEnabled()) {
logger.trace("Mapping value '" + name + "'");
}
List newList = new ArrayList(oldList.size() + 1);
newList.addAll(oldList);
newList.add(handlerMethod);
this.nameLookup.put(name, newList);
if (newList.size() > 1) {
if (logger.isTraceEnabled()) {
logger.trace("Mapping value clash for handlerMethods " + newList +
". Consider assigning explicit names.");
}
}
}
public void unregister(WxMappingInfo mapping) {
this.readWriteLock.writeLock().lock();
try {
MappingRegistration definition = this.registry.remove(mapping);
if (definition == null) {
return;
}
this.mappingLookup.remove(definition.getMapping());
if (mapping.getEventKey() != null) {
eventKeyLookup.remove(mapping.getEventKey());
}
removeMappingName(definition);
} finally {
this.readWriteLock.writeLock().unlock();
}
}
private void removeMappingName(MappingRegistration definition) {
String name = definition.getMappingName();
if (name == null) {
return;
}
HandlerMethod handlerMethod = definition.getHandlerMethod();
List oldList = this.nameLookup.get(name);
if (oldList == null) {
return;
}
if (oldList.size() <= 1) {
this.nameLookup.remove(name);
return;
}
List newList = new ArrayList(oldList.size() - 1);
for (HandlerMethod current : oldList) {
if (!current.equals(handlerMethod)) {
newList.add(current);
}
}
this.nameLookup.put(name, newList);
}
}
private static class MappingRegistration {
private final T mapping;
private final HandlerMethod handlerMethod;
private final String mappingName;
public MappingRegistration(T mapping, HandlerMethod handlerMethod, String mappingName) {
Assert.notNull(mapping, "Mapping must not be null");
Assert.notNull(handlerMethod, "HandlerMethod must not be null");
this.mapping = mapping;
this.handlerMethod = handlerMethod;
this.mappingName = mappingName;
}
public T getMapping() {
return this.mapping;
}
public HandlerMethod getHandlerMethod() {
return this.handlerMethod;
}
public String getMappingName() {
return this.mappingName;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy