将 onemall 老代码,统一到归档目录,后续不断迁移移除

This commit is contained in:
YunaiV
2022-06-16 09:06:44 +08:00
parent 64c478a45b
commit 71930d492e
1095 changed files with 0 additions and 16 deletions

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>common</artifactId>
<groupId>cn.iocoder.mall</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mall-spring-boot-starter-dubbo</artifactId>
<dependencies>
<!-- 通用相关 -->
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>common-framework</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<!-- RPC 相关 -->
<!-- TODO 优化点Spring Cloud Alibaba Dubbo 的示例 -->
<!-- <dependency>-->
<!-- <groupId>com.alibaba.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-dubbo</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!-- 日志相关 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- 工具相关 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,66 @@
package cn.iocoder.mall.dubbo.config;
import cn.iocoder.common.framework.util.OSUtils;
import cn.iocoder.common.framework.util.StringUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.util.CollectionUtils;
import java.util.HashMap;
import java.util.Map;
/**
* Dubbo 配置项的后置处理器,主要目的如下:
*
* 1. 生成 {@link #DUBBO_TAG_PROPERTIES_KEY} 配置项,可用于本地开发环境下的 dubbo.provider.tag 配置项
*/
public class DubboEnvironmentPostProcessor implements EnvironmentPostProcessor {
/**
* 默认配置项的 PropertySource 名字
*/
private static final String PROPERTY_SOURCE_NAME = "mallDubboProperties";
/**
* Dubbo 路由标签属性 KEY
*/
private static final String DUBBO_TAG_PROPERTIES_KEY = "DUBBO_TAG";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 需要修改的配置项
Map<String, Object> modifyProperties = new HashMap<>();
// 生成 DUBBO_TAG_PROPERTIES_KEY使用 hostname
String dubboTag = OSUtils.getHostName();
if (!StringUtils.hasText(dubboTag)) {
dubboTag = StringUtils.uuid(true); // 兜底,强行生成一个
}
modifyProperties.put(DUBBO_TAG_PROPERTIES_KEY, dubboTag);
// 添加到 environment 中,排在最优,最低优先级
addOrReplace(environment.getPropertySources(), modifyProperties);
}
private void addOrReplace(MutablePropertySources propertySources, Map<String, Object> map) {
if (CollectionUtils.isEmpty(map)) {
return;
}
// 情况一,如果存在 defaultProperties 的 PropertySource则进行 key 的修改
if (propertySources.contains(PROPERTY_SOURCE_NAME)) {
PropertySource<?> source = propertySources.get(PROPERTY_SOURCE_NAME);
if (source instanceof MapPropertySource) {
MapPropertySource target = (MapPropertySource) source;
for (String key : map.keySet()) {
target.getSource().put(key, map.get(key));
}
}
return;
}
// 情况二,不存在 defaultProperties 的 PropertySource则直接添加到其中
propertySources.addLast(new MapPropertySource(PROPERTY_SOURCE_NAME, map));
}
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.mall.dubbo.config;
import cn.iocoder.mall.dubbo.core.web.DubboRouterTagWebInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class DubboWebAutoConfiguration implements WebMvcConfigurer {
private Logger logger = LoggerFactory.getLogger(DubboWebAutoConfiguration.class);
// ========== 拦截器相关 ==========
@Override
public void addInterceptors(InterceptorRegistry registry) {
try {
// 设置为 -1000 的原因,保证在比较前面就处理该逻辑。例如说,认证拦截器;
registry.addInterceptor(new DubboRouterTagWebInterceptor()).order(-1000);
logger.info("[addInterceptors][加载 DubboRouterTagWebInterceptor 拦截器完成]");
} catch (NoSuchBeanDefinitionException e) {
logger.warn("[addInterceptors][无法获取 DubboRouterTagWebInterceptor 拦截器,无法使用基于 dubbo-tag 请求头进行 Dubbo 标签路由]");
}
}
}

View File

@@ -0,0 +1,37 @@
package cn.iocoder.mall.dubbo.core.cluster.interceptor;
import cn.iocoder.common.framework.util.StringUtils;
import cn.iocoder.mall.dubbo.core.filter.DubboProviderRouterTagFilter;
import cn.iocoder.mall.dubbo.core.router.DubboRouterTagContextHolder;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.cluster.interceptor.ClusterInterceptor;
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
/**
* Consumer 方,在调用 Provider 时,将 {@link DubboRouterTagContextHolder} 中的 Tag 通过 Dubbo 隐式传参。
*
* 完整逻辑说明,见 {@link DubboProviderRouterTagFilter}
*
* 注意,这里需要设置到 order = 1 的原因,是需要保证排在 ConsumerContextClusterInterceptor 之后
*/
@Activate(group = CommonConstants.CONSUMER, order = 1)
public class DubboConsumerRouterTagClusterInterceptor implements ClusterInterceptor {
@Override
public void before(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation) {
// 设置 Dubbo Tag 到 Dubbo 隐式传参
String dubboTag = DubboRouterTagContextHolder.getTag();
if (StringUtils.hasText(dubboTag)) {
invocation.setAttachment(CommonConstants.TAG_KEY, dubboTag);
}
}
@Override
public void after(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation) {
// 清空 Dubbo Tag 的隐式传参
invocation.setAttachment(CommonConstants.TAG_KEY, null);
}
}

View File

@@ -0,0 +1,114 @@
package cn.iocoder.mall.dubbo.core.filter;
import cn.iocoder.common.framework.exception.GlobalException;
import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.common.framework.util.ExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.service.GenericService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.reflect.Type;
import static cn.iocoder.common.framework.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
import static cn.iocoder.common.framework.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
@Activate(group = CommonConstants.PROVIDER) // TODO 优化点:设置下顺序
public class DubboProviderExceptionFilter implements Filter, Filter.Listener {
private Logger logger = LoggerFactory.getLogger(DubboProviderExceptionFilter.class);
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
return invoker.invoke(invocation);
}
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
try {
// 1. 转换异常
Throwable exception = appResponse.getException();
// 1.1 参数校验异常
if (exception instanceof ConstraintViolationException) {
exception = this.constraintViolationExceptionHandler((ConstraintViolationException) exception);
// 1. ServiceException 业务异常,因为不会有序列化问题,所以无需处理
} else if (exception instanceof ServiceException) {
// 1.3 其它异常,转换成 GlobalException 全局异常,避免可能存在的反序列化问题
} else {
exception = this.defaultExceptionHandler(exception, invocation);
assert exception != null;
}
// 2. 根据不同的方法 schema 返回结果
// 2.1 如果是 ServiceException 异常,并且返回参数类型是 CommonResult 的情况,则将转换成 CommonResult 返回
if (isReturnCommonResult(invocation) && exception instanceof ServiceException) {
appResponse.setException(null); // 一定要清空异常
appResponse.setValue(CommonResult.error((ServiceException) exception));
// 2.2 如果是 GlobalException 全局异常,则直接抛出
} else {
// TODO 优化点:尝试修改成 RpcException
appResponse.setException(exception);
}
} catch (Throwable e) {
logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
}
}
}
@Override
public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
}
private boolean isReturnCommonResult(Invocation invocation) {
if (!(invocation instanceof RpcInvocation)) {
return false;
}
RpcInvocation rpcInvocation = (RpcInvocation) invocation;
Type[] returnTypes = rpcInvocation.getReturnTypes();
if (returnTypes.length == 0) {
return false;
}
Type returnType = returnTypes[0];
if (!(returnType instanceof Class)) {
return false;
}
Class returnClass = (Class) returnType;
return returnClass == CommonResult.class;
}
/**
* 处理 Validator 校验不通过产生的异常
*/
private GlobalException constraintViolationExceptionHandler(ConstraintViolationException ex) {
logger.warn("[constraintViolationExceptionHandler]", ex);
ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
return new GlobalException(BAD_REQUEST.getCode(),
String.format("请求参数不正确:%s", constraintViolation.getMessage()));
}
/**
* 处理系统异常,兜底处理所有的一切
*/
private GlobalException defaultExceptionHandler(Throwable exception, Invocation invocation) {
logger.error("[defaultExceptionHandler][service({}) method({}) params({}) 执行异常]",
invocation.getTargetServiceUniqueName(), invocation.getMethodName(), invocation.getArguments(), exception);
// 如果已经是 GlobalException 全局异常,直接返回即可
if (exception instanceof GlobalException) {
return (GlobalException) exception;
}
return new GlobalException(INTERNAL_SERVER_ERROR)
.setDetailMessage(this.buildDetailMessage(exception, invocation));
}
private String buildDetailMessage(Throwable exception, Invocation invocation) {
return String.format("Service(%s) Method(%s) 发生异常(%s)",
invocation.getTargetServiceUniqueName(), invocation.getMethodName(), ExceptionUtil.getRootCauseMessage(exception));
}
}

View File

@@ -0,0 +1,45 @@
package cn.iocoder.mall.dubbo.core.filter;
import cn.iocoder.common.framework.util.StringUtils;
import cn.iocoder.mall.dubbo.core.cluster.interceptor.DubboConsumerRouterTagClusterInterceptor;
import cn.iocoder.mall.dubbo.core.router.DubboRouterTagContextHolder;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.cluster.router.tag.TagRouter;
/**
* 基于 Dubbo 标签路由规则(http://dubbo.apache.org/zh-cn/docs/user/demos/routing-rule.html),实现如下功能:
* 1. 本地开发调试时,在带有 Dubbo Tag 的情况下,优先调用指定 Tag 的服务提供者。这样,我们可以将本地启动的服务提供者打上相应的 Tag即可优先调用本地
* 并且,前端在调用开发环境上的 Dubbo 服务时,因为不带有 Dubbo Tag所以不会调用到后端开发本地启动的 Dubbo 服务提供者;
* 2. TODO 优化点:蓝绿发布、灰度发布
*
* 实现逻辑为:
* 1. 对于 Consumer 方,在调用 Provider 时,{@link DubboConsumerRouterTagClusterInterceptor} 会将 {@link DubboRouterTagContextHolder} 中的 Tag 通过 Dubbo 隐式传参。
* 同时Dubbo 自带 {@link TagRouter},会根据该参数,会选择符合该 Tag 的 Provider。
* 2. 对于 Provider 方,在通过 Dubbo 隐式传参获得到 Tag 时,会设置到 {@link DubboRouterTagContextHolder} 中。
* 这样,在 Provider 作为 Consumer 角色时,调用其它 Provider 时,可以继续实现标签路由的功能。
*/
@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER}, order = -1000)
public class DubboProviderRouterTagFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 从 Dubbo 隐式传参获得 Dubbo Tag
String dubboTag = invocation.getAttachment(CommonConstants.TAG_KEY);
boolean hasDubboTag = StringUtils.hasText(dubboTag);
if (hasDubboTag) {
invocation.setAttachment(CommonConstants.TAG_KEY, dubboTag);
}
// 继续调用
try {
return invoker.invoke(invocation);
} finally {
// 清理
if (hasDubboTag) {
DubboRouterTagContextHolder.clear();
}
}
}
}

View File

@@ -0,0 +1,27 @@
package cn.iocoder.mall.dubbo.core.router;
import cn.iocoder.mall.dubbo.core.filter.DubboProviderRouterTagFilter;
/**
* Dubbo 路由 Tag 的上下文
*
* @see DubboProviderRouterTagFilter
* @see cn.iocoder.mall.dubbo.core.web.DubboRouterTagWebInterceptor
*/
public class DubboRouterTagContextHolder {
private static ThreadLocal<String> tagContext = new ThreadLocal<>();
public static void setTag(String tag) {
tagContext.set(tag);
}
public static String getTag() {
return tagContext.get();
}
public static void clear() {
tagContext.remove();
}
}

View File

@@ -0,0 +1,45 @@
package cn.iocoder.mall.dubbo.core.web;
import cn.iocoder.common.framework.util.OSUtils;
import cn.iocoder.common.framework.util.StringUtils;
import cn.iocoder.mall.dubbo.core.cluster.interceptor.DubboConsumerRouterTagClusterInterceptor;
import cn.iocoder.mall.dubbo.core.filter.DubboProviderRouterTagFilter;
import cn.iocoder.mall.dubbo.core.router.DubboRouterTagContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Dubbo 路由标签的 Web 拦截器,将请求 Header 中的 {@link #HEADER_DUBBO_TAG} 设置到 {@link DubboRouterTagContextHolder} 中。
*
* @see DubboProviderRouterTagFilter
* @see DubboConsumerRouterTagClusterInterceptor
*/
public class DubboRouterTagWebInterceptor implements HandlerInterceptor {
private static final String HEADER_DUBBO_TAG = "dubbo-tag";
private static final String HOST_NAME_VALUE = "${HOSTNAME}";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String tag = request.getHeader(HEADER_DUBBO_TAG);
if (StringUtils.hasText(tag)) {
// 特殊逻辑,解决 IDEA Rest Client 不支持环境变量的读取,所以就服务器来做
if (HOST_NAME_VALUE.equals(tag)) {
tag = OSUtils.getHostName();
}
// 设置到 DubboRouterTagContextHolder 上下文
DubboRouterTagContextHolder.setTag(tag);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
DubboRouterTagContextHolder.clear();
}
}

View File

@@ -0,0 +1,2 @@
dubboExceptionFilter=cn.iocoder.mall.dubbo.core.filter.DubboProviderExceptionFilter
dubboProviderRouterTagFilter=cn.iocoder.mall.dubbo.core.filter.DubboProviderRouterTagFilter

View File

@@ -0,0 +1 @@
dubboConsumerRouterTagClusterInterceptor=cn.iocoder.mall.dubbo.core.cluster.interceptor.DubboConsumerRouterTagClusterInterceptor

View File

@@ -0,0 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.dubbo.config.DubboWebAutoConfiguration
org.springframework.boot.env.EnvironmentPostProcessor=\
cn.iocoder.mall.dubbo.config.DubboEnvironmentPostProcessor