springcloud+nacos实现灰度发布

灰度发布

gateway网关实现灰度路由

灰度发布实体

package com.scm.boss.common.bean;import lombok.Data;
import lombok.experimental.Accessors;import java.io.Serializable;/*** 灰度发布实体*/
@Data
@Accessors(chain = true)
public class GrayBean implements Serializable {private static final long serialVersionUID = 1L;/*** 版本*/private String preVersion;
}

灰度发布上下文信息

package com.scm.boss.common.utils;import com.scm.boss.common.bean.GrayBean;/*** 灰度信息上下文*/
public class CurrentGrayUtils {private final static InheritableThreadLocal<GrayBean> CURRENT_GRE = new InheritableThreadLocal<>();public static GrayBean getGray() {GrayBean grayBean = CURRENT_GRE.get();return grayBean;}public static void setGray(GrayBean grayBean) {if(grayBean == null){clear();}else {CURRENT_GRE.set(grayBean);}}public static void clear() {CURRENT_GRE.remove();}}

灰度过滤器设置灰度上下文信息

package com.scm.gateway.common.config;import com.scm.boss.common.bean.GrayBean;
import com.scm.boss.common.constants.CommonConstants;
import com.scm.boss.common.utils.CurrentGrayUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;/*** 灰度发布版本标识过滤器*/
@Slf4j
public class GrayFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {HttpHeaders httpHeaders = exchange.getRequest().getHeaders();String grayVersion = httpHeaders.getFirst(CommonConstants.GRAY_VERSION);if (StringUtils.isNotBlank(grayVersion)) {GrayBean grayBean = new GrayBean();grayBean.setPreVersion(grayVersion);CurrentGrayUtils.setGray(grayBean);//请求头添加灰度版本号,用于灰度请求exchange.getRequest().mutate().header(CommonConstants.GRAY_VERSION, grayVersion).build();}return chain.filter(exchange);}@Overridepublic int getOrder() {return Integer.MIN_VALUE;}
}

灰度路由规则

package com.scm.gateway.common.config;import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.google.common.base.Optional;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.scm.boss.common.bean.GrayBean;
import com.scm.boss.common.constants.CommonConstants;
import com.scm.boss.common.exception.ApiException;
import com.scm.boss.common.utils.CurrentGrayUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;@Slf4j
@Component
public class GateWayGrayRouteRule extends ZoneAvoidanceRule {@Overridepublic Server choose(Object key) {Optional<Server> server;try {// 根据灰度路由规则,过滤出符合规则的服务 this.getServers()// 再根据负载均衡策略,过滤掉不可用和性能差的服务,然后在剩下的服务中进行轮询  getPredicate().chooseRoundRobinAfterFiltering()server = getPredicate().chooseRoundRobinAfterFiltering(this.getServers(), key);//获取请求头中的版本号GrayBean grayBean = CurrentGrayUtils.getGray();if (null != grayBean && !StringUtils.isEmpty(grayBean.getPreVersion())) {log.info("灰度路由规则过滤后的服务实例:{}", server.isPresent() ? server.get().getHostPort() : null);}} finally {CurrentGrayUtils.clear();}return server.isPresent() ? server.get() : null;}/*** 灰度路由过滤服务实例** 如果设置了期望版本, 则过滤出所有的期望版本 ,然后再走默认的轮询 如果没有一个期望的版本实例,则不过滤,降级为原有的规则,进行所有的服务轮询。(灰度路由失效) 如果没有设置期望版本* 则不走灰度路由,按原有轮询机制轮询所有*/protected List<Server> getServers() {// 获取spring cloud默认负载均衡器// 获取所有待选的服务List<Server> allServers = getLoadBalancer().getReachableServers();if (CollectionUtils.isEmpty(allServers)) {log.error("没有可用的服务实例");throw new ApiException("没有可用的服务实例");}//获取请求头中的版本号GrayBean grayBean = CurrentGrayUtils.getGray();// 如果没有设置要访问的版本,则不过滤,返回所有,走原有默认的轮询机制if (null == grayBean || StringUtils.isEmpty(grayBean.getPreVersion())) {//这里需要过滤掉灰度服务实例List<Server> list = allServers.stream().filter(f -> {// 获取服务实例在注册中心上的元数据Map<String, String> metadata = ((NacosServer) f).getMetadata();// 如果注册中心上服务的版本标签和期望访问的版本一致,则灰度路由匹配成功if ((null != metadata && StringUtils.isNotBlank(metadata.get(CommonConstants.GRAY_VERSION)))|| CommonConstants.GRAY_VERSION_VALUE.equals(metadata.get(CommonConstants.GRAY_VERSION))) {return false;}return true;}).collect(Collectors.toList());return list;}// 开始灰度规则匹配过滤List<Server> filterServer = new ArrayList<>();for (Server server : allServers) {// 获取服务实例在注册中心上的元数据Map<String, String> metadata = ((NacosServer) server).getMetadata();// 如果注册中心上服务的版本标签和期望访问的版本一致,则灰度路由匹配成功if (null != metadata && grayBean.getPreVersion().equals(metadata.get(CommonConstants.GRAY_VERSION))) {filterServer.add(server);}}// 如果没有匹配到期望的版本实例服务,为了保证服务可用性,让灰度规则失效,走原有的轮询所有可用服务的机制if (CollectionUtils.isEmpty(filterServer)) {log.error("灰度路由规则失效,没有找到期望的版本实例");throw new ApiException("没有匹配的灰度服务实例");}return filterServer;}
}

gateway网关需要引入的pom

 <dependencies><!-- Nacos注册中心 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!-- Nacos配置中心 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!-- gateway --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId>com.scm</groupId><artifactId>scm-common-boss</artifactId><version>${project.version}</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-netflix-ribbon</artifactId></dependency></dependencies>

常量

package com.scm.boss.common.constants;public interface CommonConstants {/*** 灰度请求头参数*/String GRAY_VERSION = "grayVersion";/*** 灰度版本值*/String GRAY_VERSION_VALUE = "V1";}

微服务feign调用灰度

服务路由规则

package com.scm.cloud.config;import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.google.common.base.Optional;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.scm.boss.common.bean.GrayBean;
import com.scm.boss.common.constants.CommonConstants;
import com.scm.boss.common.exception.ApiException;
import com.scm.boss.common.utils.CurrentGrayUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;@Slf4j
public class GrayRouteRule extends ZoneAvoidanceRule {@Overridepublic Server choose(Object key) {// 根据灰度路由规则,过滤出符合规则的服务 this.getServers()// 再根据负载均衡策略,过滤掉不可用和性能差的服务,然后在剩下的服务中进行轮询  getPredicate().chooseRoundRobinAfterFiltering()Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(this.getServers(), key);//获取请求头中的版本号GrayBean grayBean = CurrentGrayUtils.getGray();if (null != grayBean && !StringUtils.isEmpty(grayBean.getPreVersion())) {log.info("灰度路由规则过滤后的服务实例:{}", server.isPresent() ? server.get().getHostPort() : null);}return server.isPresent() ? server.get() : null;}/*** 灰度路由过滤服务实例** 如果设置了期望版本, 则过滤出所有的期望版本 ,然后再走默认的轮询 如果没有一个期望的版本实例,则不过滤,降级为原有的规则,进行所有的服务轮询。(灰度路由失效) 如果没有设置期望版本* 则不走灰度路由,按原有轮询机制轮询所有*/protected List<Server> getServers() {// 获取spring cloud默认负载均衡器// 获取所有待选的服务List<Server> allServers = getLoadBalancer().getReachableServers();if (CollectionUtils.isEmpty(allServers)) {log.error("没有可用的服务实例");throw new ApiException("没有可用的服务实例");}//获取请求头中的版本号GrayBean grayBean = CurrentGrayUtils.getGray();// 如果没有设置要访问的版本,则不过滤,返回所有,走原有默认的轮询机制if (null == grayBean || StringUtils.isEmpty(grayBean.getPreVersion())) {//这里需要过滤掉灰度服务实例List<Server> list = allServers.stream().filter(f -> {// 获取服务实例在注册中心上的元数据Map<String, String> metadata = ((NacosServer) f).getMetadata();// 如果注册中心上服务的版本标签和期望访问的版本一致,则灰度路由匹配成功if ((null != metadata && StringUtils.isNotBlank(metadata.get(CommonConstants.GRAY_VERSION)))|| CommonConstants.GRAY_VERSION_VALUE.equals(metadata.get(CommonConstants.GRAY_VERSION))) {return false;}return true;}).collect(Collectors.toList());return list;}// 开始灰度规则匹配过滤List<Server> filterServer = new ArrayList<>();for (Server server : allServers) {// 获取服务实例在注册中心上的元数据Map<String, String> metadata = ((NacosServer) server).getMetadata();// 如果注册中心上服务的版本标签和期望访问的版本一致,则灰度路由匹配成功if (null != metadata && grayBean.getPreVersion().equals(metadata.get(CommonConstants.GRAY_VERSION))) {filterServer.add(server);}}// 如果没有匹配到期望的版本实例服务,为了保证服务可用性,让灰度规则失效,走原有的轮询所有可用服务的机制if (CollectionUtils.isEmpty(filterServer)) {log.error("灰度路由规则失效,没有找到期望的版本实例,version={}", grayBean.getPreVersion());throw new ApiException("灰度路由规则失效,没有找到期望的版本实例");}return filterServer;}
}

需要传递灰度版本号,所以需要把灰度版本请求参数传递下去,以及解决Hystrix的线程切换导致参数无法传递下的问题

使用TransmittableThreadLocal可以跨线程传递

package com.scm.cloud.config;import com.scm.cloud.security.DefaultSecurityInterceptor;
import com.scm.cloud.security.SecurityInterceptor;
import com.scm.cloud.webmvc.WebMvcCommonConfigurer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.PostConstruct;/*** 配置* @date 2023/7/13 18:12* @author luohao*/
@Configuration
@Slf4j
public class CommonConfiguration {/*** 低优先级*/private final static int LOWER_PRECEDENCE = 10000;/*** 使用TransmittableThreadLocal可以跨线程传递*/@PostConstructpublic void init(){new GlobalHystrixConcurrencyStrategy();}@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcCommonConfigurer();}/*** 优先级* @return*/@Bean@ConditionalOnMissingBean@Order(value = LOWER_PRECEDENCE)public SecurityInterceptor securityInterceptor(){return new DefaultSecurityInterceptor();}}

bean重复则覆盖

package com.scm.cloud.config;import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;/*** @author xiewu* @date 2022/12/29 10:41*/
public class EnvironmentPostProcessorConfig implements EnvironmentPostProcessor {@Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {application.setAllowBeanDefinitionOverriding(true);}
}

feign调用拦截器

package com.scm.cloud.config;import com.scm.boss.common.bean.CurrentUserBean;
import com.scm.boss.common.bean.DealerApiDetailBean;
import com.scm.boss.common.bean.GrayBean;
import com.scm.boss.common.constants.CommonConstants;
import com.scm.boss.common.utils.CurrentGrayUtils;
import com.scm.boss.common.utils.CurrentUserUtils;
import com.scm.boss.common.utils.CurrentDealerApiDetailUtils;
import feign.Feign;
import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@Slf4j
@Configuration
public class FeignConfig {@Beanpublic RequestInterceptor requestInterceptor() {return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate requestTemplate) {GrayBean grayBean = CurrentGrayUtils.getGray();if (null != grayBean) {requestTemplate.header(CommonConstants.GRAY_VERSION, grayBean.getPreVersion());}DealerApiDetailBean dealerApiDetailBean = CurrentDealerApiDetailUtils.getDealerApiConditionNull();if (dealerApiDetailBean != null){requestTemplate.header(CommonConstants.DEALER_ID, dealerApiDetailBean.getDealerId());requestTemplate.header(CommonConstants.DEALER_PROJECT_ID, dealerApiDetailBean.getDealerProjectId());}CurrentUserBean currentUser = CurrentUserUtils.getCurrentUserConditionNull();if (currentUser == null){return;}requestTemplate.header(CommonConstants.SUPPLIER_ID, currentUser.getSupplierId() == null ? null : currentUser.getId().toString());requestTemplate.header(CommonConstants.ACCOUNT_NO, currentUser.getAccountNo());requestTemplate.header(CommonConstants.REQUEST_SOURCE, currentUser.getType());requestTemplate.header(CommonConstants.ID, currentUser.getId() == null ? null : currentUser.getId().toString());}};}/*** Feign 客户端的日志记录,默认级别为NONE* Logger.Level 的具体级别如下:* NONE:不记录任何信息* BASIC:仅记录请求方法、URL以及响应状态码和执行时间* HEADERS:除了记录 BASIC级别的信息外,还会记录请求和响应的头信息* FULL:记录所有请求与响应的明细,包括头信息、请求体、元数据*/@BeanLogger.Level feignLoggerLevel() {return Logger.Level.FULL;}/*** Feign支持文件上传** @param messageConverters* @return*/@Bean@Primary@Scope("prototype")public Encoder multipartFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {return new SpringFormEncoder(new SpringEncoder(messageConverters));}
}

Hystrix并发策略

package com.scm.cloud.config;import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import com.scm.boss.common.bean.CurrentUserBean;
import com.scm.boss.common.bean.DealerApiDetailBean;
import com.scm.boss.common.bean.GrayBean;
import com.scm.boss.common.utils.CurrentGrayUtils;
import com.scm.boss.common.utils.CurrentUserUtils;
import com.scm.boss.common.utils.CurrentDealerApiDetailUtils;
import lombok.extern.slf4j.Slf4j;import java.util.concurrent.Callable;@Slf4j
public class GlobalHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {private HystrixConcurrencyStrategy delegate;public GlobalHystrixConcurrencyStrategy() {this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();if (this.delegate instanceof GlobalHystrixConcurrencyStrategy) {return;}HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy();HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook();HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();HystrixPlugins.reset();HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);// Registers existing plugins except the new MicroMeter Strategy plugin.HystrixPlugins.getInstance().registerConcurrencyStrategy(this);HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);log.info("Construct HystrixConcurrencyStrategy:[{}] for application,",GlobalHystrixConcurrencyStrategy.class.getName());}@Overridepublic <T> Callable<T> wrapCallable(Callable<T> callable) {final CurrentUserBean user = CurrentUserUtils.getCurrentUserConditionNull();final DealerApiDetailBean dealerApiDetailBean = CurrentDealerApiDetailUtils.getDealerApiConditionNull();final GrayBean grayBean = CurrentGrayUtils.getGray();if (callable instanceof HeaderCallable) {return callable;}Callable<T> wrappedCallable = this.delegate != null? this.delegate.wrapCallable(callable) : callable;if (wrappedCallable instanceof HeaderCallable) {return wrappedCallable;}return new HeaderCallable<T>(wrappedCallable,user,dealerApiDetailBean, grayBean);}
}

Hystrix并发参数线程中传递参数

package com.scm.cloud.config;import com.scm.boss.common.bean.CurrentUserBean;
import com.scm.boss.common.bean.DealerApiDetailBean;
import com.scm.boss.common.bean.GrayBean;
import com.scm.boss.common.utils.CurrentGrayUtils;
import com.scm.boss.common.utils.CurrentUserUtils;
import com.scm.boss.common.utils.CurrentDealerApiDetailUtils;
import lombok.extern.slf4j.Slf4j;import java.util.concurrent.Callable;@Slf4j
public class HeaderCallable<V> implements Callable<V> {private final Callable<V> delegate;private final CurrentUserBean currentUserBean;private final DealerApiDetailBean dealerApiDetailBean;private final GrayBean grayBean;public HeaderCallable(Callable<V> delegate, CurrentUserBean currentUserBean, DealerApiDetailBean dealerApiDetailBean, GrayBean grayBean) {this.delegate = delegate;this.currentUserBean = currentUserBean;this.dealerApiDetailBean = dealerApiDetailBean;this.grayBean = grayBean;}@Overridepublic V call() throws Exception {try {CurrentUserUtils.setCurrentUser(currentUserBean);CurrentDealerApiDetailUtils.setDealerApi(dealerApiDetailBean);CurrentGrayUtils.setGray(grayBean);return this.delegate.call();} catch (Exception e) {//这里无法抓取到delegate.call()方法的异常,因为是线程池异步请求的throw e;} finally {CurrentUserUtils.clear();CurrentGrayUtils.clear();CurrentDealerApiDetailUtils.clear();}}
}

LoadBalancerFeignClient

package com.scm.cloud.config;import feign.Client;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory;
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class PersonBeanConfiguration {/*** 创建FeignClient*/@Bean@ConditionalOnMissingBeanpublic Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,SpringClientFactory clientFactory) {return new LoadBalancerFeignClient(new Client.Default(null, null),cachingFactory, clientFactory);}
}

拦截器HandlerInterceptor

package com.scm.cloud.webmvc;import com.alibaba.fastjson.JSONArray;
import com.scm.boss.common.bean.CurrentUserBean;
import com.scm.boss.common.bean.DealerApiDetailBean;
import com.scm.boss.common.bean.GrayBean;
import com.scm.boss.common.bean.RouteAttrPermVO;
import com.scm.boss.common.constants.CommonConstants;
import com.scm.boss.common.constants.PlatformTypeEnum;
import com.scm.boss.common.constants.UserTypeEnum;
import com.scm.boss.common.utils.CurrentDealerApiDetailUtils;
import com.scm.boss.common.utils.CurrentGrayUtils;
import com.scm.boss.common.utils.CurrentUserUtils;
import com.scm.boss.common.utils.FieldListUtils;
import com.scm.redis.template.RedisRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;/*** 拦截器* @date 2023/7/13 18:09* @author luohao*/
@Slf4j
public class GlobalHandlerInterceptor implements HandlerInterceptor {private RedisRepository redisRepository;public GlobalHandlerInterceptor(RedisRepository redisRepository) {this.redisRepository = redisRepository;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{extractedHeadersGre(request);extractedHeaders(request);extractedHeadersApi(request);extractedPermissionFields(request);return HandlerInterceptor.super.preHandle(request, response, handler);}/*** 灰度发布* @param request*/private void extractedHeadersGre(HttpServletRequest request) {String grayVersion = request.getHeader(CommonConstants.GRAY_VERSION);if (StringUtils.isNotBlank(grayVersion)) {GrayBean grayBean = new GrayBean();grayBean.setPreVersion(grayVersion);CurrentGrayUtils.setGray(grayBean);}}/*** 第三方经销商调用* @param request*/private void extractedHeadersApi(HttpServletRequest request) {DealerApiDetailBean dealerApiDetailBean = new DealerApiDetailBean();dealerApiDetailBean.setDealerId(request.getHeader(CommonConstants.DEALER_ID)).setDealerProjectId(request.getHeader(CommonConstants.DEALER_PROJECT_ID));CurrentDealerApiDetailUtils.setDealerApi(dealerApiDetailBean);}private void extractedHeaders(HttpServletRequest request) {CurrentUserBean currentUserBean = new CurrentUserBean();currentUserBean.setAccountNo(request.getHeader(CommonConstants.ACCOUNT_NO));currentUserBean.setType(request.getHeader(CommonConstants.REQUEST_SOURCE));currentUserBean.setStatus(request.getHeader(CommonConstants.STATUS) == null ? null : Integer.valueOf(request.getHeader(CommonConstants.STATUS)));currentUserBean.setId(request.getHeader(CommonConstants.ID) == null ? null : Integer.valueOf(request.getHeader(CommonConstants.ID)));if (UserTypeEnum.SUPPLIER_USER.getCode().equals(currentUserBean.getType())) {currentUserBean.setSupplierId(request.getHeader(CommonConstants.SUPPLIER_ID) == null ? null : Integer.valueOf(request.getHeader(CommonConstants.SUPPLIER_ID)));}CurrentUserUtils.setCurrentUser(currentUserBean);}/*** 获取接口无权限字段* @date 2023/7/13 16:41* @author luohao*/private void extractedPermissionFields(HttpServletRequest request){String requestMapping = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();CurrentUserBean currentUser = CurrentUserUtils.getCurrentUser();if(Objects.isNull(currentUser) || Objects.isNull(currentUser.getAccountNo())){return;}String key;if(currentUser.getType().equals(PlatformTypeEnum.APPLY_CHAIN.getCode().toString())){key = CommonConstants.SUPPLY_CHAIN_ATTR;}else if(currentUser.getType().equals(PlatformTypeEnum.DEALER.getCode().toString())){key = CommonConstants.DEALER_ATTR;}else{return;}String redisKey = new StringBuilder(key).append(currentUser.getAccountNo()).toString();List<RouteAttrPermVO> spuEditDTO = JSONArray.parseArray(redisRepository.get(redisKey), RouteAttrPermVO.class);if(CollectionUtils.isEmpty(spuEditDTO)){return;}List<String> nonPermAttrs = spuEditDTO.stream().filter(i -> i.getUrl().equals(requestMapping)).map(RouteAttrPermVO::getAttrName).collect(Collectors.toList());FieldListUtils.setFieldList(nonPermAttrs);}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {CurrentUserUtils.clear();FieldListUtils.clear();}}

WebMvcConfigurer

package com.scm.cloud.webmvc;import com.scm.redis.template.RedisRepository;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;/*** WebMvc* @date 2023/7/13 18:11* @author luohao*/
public class WebMvcCommonConfigurer implements WebMvcConfigurer {@Resourceprivate RedisRepository redisRepository;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new GlobalHandlerInterceptor(redisRepository)).addPathPatterns("/**").excludePathPatterns("/info","/actuator/**");}
}

特殊数据权限过滤

package com.scm.cloud.webmvc;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.ObjectSerializer;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializeWriter;
import com.scm.boss.common.utils.FieldListUtils;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import java.io.IOException;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;/*** 特殊数据权限过滤* @date 2023/7/12 14:54* @author luohao*/
@Component
@RestControllerAdvice
public class BaseGlobalResponseBodyAdvice implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {return true;}@Overridepublic Object beforeBodyWrite(final Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if(ObjectUtils.isEmpty(body)){return body;}List<String> fieldList = FieldListUtils.getFieldList();if(CollectionUtils.isEmpty(fieldList)){return body;}SerializeConfig config = new SerializeConfig();config.put( Date.class, new DateJsonSerializer());return objectEval(JSONObject.parseObject(JSON.toJSONString(body,config)), fieldList);}/*** 权限数据处理* @param body* @param nonPermAttrs* @return*/public Object objectEval(Object body, List<String> nonPermAttrs) {if (Objects.nonNull(body) && body instanceof Map) {Map<String, Object> map = (Map<String, Object>) body;map.keySet().forEach(key -> {Object o = map.get(key);if (Objects.nonNull(o) && o instanceof Map) {map.put(key, objectEval(o, nonPermAttrs));} else if (Objects.nonNull(o) && o instanceof List){map.put(key, objectEval(o, nonPermAttrs));}else {List<String> collect = nonPermAttrs.stream().filter(i -> i.equals(key)).collect(Collectors.toList());if (CollectionUtils.isNotEmpty(collect)){map.put(key, null);}}});} else if (Objects.nonNull(body) && body instanceof List) {final List<Object> dataList = (List<Object>) body;dataList.forEach(i -> objectEval(i,nonPermAttrs));}return body;}
}class DateJsonSerializer implements ObjectSerializer {@Overridepublic void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {SerializeWriter out = serializer.getWriter();if (object == null) {serializer.getWriter().writeNull();return;}SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");sdf.setTimeZone( TimeZone.getTimeZone("Etc/GMT-8"));out.write("\"" + sdf.format( (Date) object ) + "\"");}
}

微服务的spring.factories配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.scm.cloud.config.FeignConfig,\
com.scm.cloud.config.PersonBeanConfiguration,\
com.scm.cloud.webmvc.BaseGlobalResponseBodyAdvice,\
com.scm.cloud.config.CommonConfiguration,\
com.scm.cloud.config.GrayRouteRule
org.springframework.boot.env.EnvironmentPostProcessor = com.scm.cloud.config.EnvironmentPostProcessorConfig

微服务的pom文件

<dependencies><!-- Nacos注册中心 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!-- Nacos配置中心 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!-- feign --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>com.scm</groupId><artifactId>scm-starter-redis</artifactId><version>${project.version}</version><scope>compile</scope></dependency></dependencies>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/38290.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】—— 进程程序替换

目录 序言 &#xff08;一&#xff09;替换原理 1、进程角度——见见猪跑 1️⃣ 认识 execl 函数 2、程序角度——看图理解 &#xff08;二&#xff09;替换函数 1、命名理解 2、函数理解 1️⃣execlp 2️⃣execv 3️⃣execvp 4️⃣execle 5️⃣execve 6️⃣execve…

机器学习重要内容:特征工程之特征抽取

目录 1、简介 2、⭐为什么需要特征工程 3、特征抽取 3.1、简介 3.2、特征提取主要内容 3.3、字典特征提取 3.4、"one-hot"编码 3.5、文本特征提取 3.5.1、英文文本 3.5.2、结巴分词 3.5.3、中文文本 3.5.4、Tf-idf ⭐所属专栏&#xff1a;人工智能 文中提…

LLaMA长度外推高性价比trick:线性插值法及相关改进源码阅读及相关记录

前言 最近&#xff0c;开源了可商用的llama2&#xff0c;支持长度相比llama1的1024&#xff0c;拓展到了4096长度&#xff0c;然而&#xff0c;相比GPT-4、Claude-2等支持的长度&#xff0c;llama的长度外推显得尤为重要&#xff0c;本文记录了三种网络开源的RoPE改进方式及相…

Vue-打印组件页面

场景: 需要将页面的局部信息打印出来&#xff0c;只在前端实现&#xff0c;不要占用后端的资源。经过百度经验&#xff0c;决定使用 print-js和html2canvas组件。 1. 下载包 npm install print-js --save npm install --save html2canvas 2. 组件内引用 <script>impo…

C语言之数组指针和指针数组

C语言之数组指针和指针数组 一、含义二、定义2.1 指针数组2.2 数组指针 三、使用3.1 指针数组在参数传递时的使用3.1.1 指针数组的排序3.2 数组指针在参数传递时的使用 一、含义 指针数组&#xff1a;顾名思义&#xff0c;其为一个数组&#xff0c;数组里面存放着多个指针&…

C#生成随机验证码

以下是一个简单的C#验证码示例&#xff1a; private void GenerateCaptcha() {// 生成随机字符串string chars "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";Random random new Random();string captchaString new string(Enumerable.Repe…

TPAMI, 2023 | 用压缩隐逆向神经网络进行高精度稀疏雷达成像

CoIR: Compressive Implicit Radar | IEEE TPAMI, 2023 | 用压缩隐逆向神经网络进行高精度稀疏雷达成像 注1:本文系“无线感知论文速递”系列之一,致力于简洁清晰完整地介绍、解读无线感知领域最新的顶会/顶刊论文(包括但不限于Nature/Science及其子刊;MobiCom, Sigcom, MobiSy…

Java【算法 04】HTTP的认证方式之DIGEST认证详细流程说明及举例

HTTP的认证方式之DIGEST 1.是什么2.认值流程2.1 客户端发送请求2.2 服务器返回质询信息2.2.1 质询参数2.2.2 质询举例 2.3 客户端生成响应2.4 服务器验证响应2.5 服务器返回响应 3.算法3.1 SHA-2563.1.1 Response3.1.2 A13.1.3 A2 3.2 MD53.2.1 Request-Digest3.2.2 A13.2.3 A2…

CSS3 中新增了哪些常见的特性?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 圆角&#xff08;Border Radius&#xff09;⭐ 渐变&#xff08;Gradients&#xff09;⭐ 阴影&#xff08;Box Shadow&#xff09;⭐ 文本阴影&#xff08;Text Shadow&#xff09;⭐ 透明度&#xff08;Opacity&#xff09;⭐ 过渡&…

Spring boot与Spring cloud 之间的关系

Spring boot与Spring cloud 之间的关系 Spring boot 是 Spring 的一套快速配置脚手架&#xff0c;可以基于spring boot 快速开发单个微服务&#xff0c;Spring Boot&#xff0c;看名字就知道是Spring的引导&#xff0c;就是用于启动Spring的&#xff0c;使得Spring的学习和使用…

MATLAB中xlsread函数用法

目录 语法 说明 示例 将工作表读取到数值矩阵 读取元胞的范围 读取列 请求数值、文本和原始数据 对工作表执行函数 请求自定义输出 局限性 xlsread函数的功能是读取Microsoft Excel 电子表格文件 语法 num xlsread(filename) num xlsread(filename,sheet) num x…

Nacos和GateWay路由转发NotFoundException: 503 SERVICE_UNAVAILABLE “Unable to find

问题再现&#xff1a; 2023-08-15 16:51:16,151 DEBUG [reactor-http-nio-2][CompositeLog.java:147] - [dc73b32c-1] Encoding [{timestampTue Aug 15 16:51:16 CST 2023, path/content/course/list, status503, errorService Unavai (truncated)...] 2023-08-15 16:51:16,17…

leetcode27—移除元素

思路&#xff1a; 参考26题目双指针的思想&#xff0c;只不过这道题不是快慢指针。 看到示例里面数组是无序的&#xff0c;也就是说后面的元素也是可能跟给定 val值相等的&#xff0c;那么怎么处理呢。就想到了从前往后遍历&#xff0c;如果left对应的元素 val时&#xff0c…

汽车制造业上下游协作时 外发数据如何防泄露?

数据文件是制造业企业的核心竞争力&#xff0c;一旦发生数据外泄&#xff0c;就会给企业造成经济损失&#xff0c;严重的&#xff0c;可能会带来知识产权剽窃损害、名誉伤害等。汽车制造业&#xff0c;会涉及到重要的汽车设计图纸&#xff0c;像小米发送汽车设计图纸外泄事件并…

[足式机器人]Part5 机械设计 Ch00/01 绪论+机器结构组成与连接 ——【课程笔记】

本文仅供学习使用 本文参考&#xff1a; 《机械设计》 王德伦 马雅丽课件与日常作业可登录网址 http://edu.bell-lab.com/manage/#/login&#xff0c;选择观摩登录&#xff0c;查看2023机械设计2。 机械设计-Ch00Ch01——绪论机器结构组成与连接 Ch00-绪论0.1 何为机械设计——…

12.Eclipse导入Javaweb项目

同事复制一份他的项目给我ekp.rar (懒得从SVN上拉取代码了)放在workspace1目录下 新建一个文件夹 workspace2&#xff0c;Eclipse切换到workspace2工作空间 选择Import导入 选择导入的项目(这里是放到workspace1里面) 拷贝一份到workspace2里面 例子 所有不是在自己电脑上开发…

可白嫖的4家免费CDN,并测试其网络加速情况(2023版)

网站加载速度优化过程中&#xff0c;不可避免的会用上CDN来加速资源的请求速度。但是市面上的CDN资源几乎都是要收费的&#xff0c;而且价格还不便宜&#xff0c;对于小公司站长来讲&#xff0c;这将是一笔不小的开销。不过还是有一些良心公司给我们提供了免费的资源&#xff0…

ZooKeeper的基本概念

集群角色 通常在分布式系统中&#xff0c;构成一个集群的每一台机器都有自己的角色&#xff0c;最典型的集群模式就是Master/Slave模式(主备模式)。在这种模式中&#xff0c;我们把能够处理所有写操作的机器称为Master机器&#xff0c;把所有通过异步复制方式获取最新数据&…

Redis_亿级访问量数据处理

11. 亿级访问量数据处理 11.1 场景表述 手机APP用户登录信息&#xff0c;一天用户登录ID或设备ID电商或者美团平台&#xff0c;一个商品对应的评论文章对应的评论APP上有打卡信息网站上访问量统计统计新增用户第二天还留存商品评论的排序月活统计统计独立访客(Unique Vistito…

【BEV】3D视觉 PRELIMINARY

这里的知识来自于论文 Delving into the Devils of Bird’s-eye-view Perception: A Review, Evaluation and Recipe 的 Appendix B.1 部分来自 这篇文章 从透视图转向鸟瞰图。&#xff08;Xw、Yw、Zw&#xff09;、&#xff08;Xc、Yc、Zc&#xff09;表示世界World坐标和相…