当前位置: 首页 > news >正文

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>

相关文章:

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 Serializ…...

【C++笔记】C++之类与对象(上)

【C笔记】C之类与对象&#xff08;上&#xff09; 1、类是结构体的升级2、类中可以定义的东西3、类访问限定符4、类的声明5、类的实例化(定义)6、类的大小的计算7、this指针 1、类是结构体的升级 C的一个显著特征就是兼容C语言&#xff0c;所以C把结构体“升级”成了“类”&am…...

ebay灯串UL报告 UL588检测标准

季节性和装饰性照明用品即灯串以及配件都是便携式插头连接的临时性商品&#xff0c;最大额定输入电压为 120 伏。 由 ILAC ISO 17025 认证的实验室出具的检测报告&#xff0c;确认每件商品均已经过检测&#xff0c;符合下列要求&#xff1a; 季节性和装饰性照明用品(灯串&…...

TCP/IP协议追层分析物理层(第三十九课)

TCP/IP协议追层分析物理层(第三十九课) 1 物理层:建立、维护、断开物理连接,定义了接口及介质,实现了比特流的传输。 1、传输介质分类 有线介质:网线(双绞线)、光纤 无线介质:无线电 微波 激光 红外线 2、双绞线分类: 五类cat5: 适用于100Mbps 超五类cat5e:适用于…...

Kotlin优点及为什么使用Kotlin

文章目录 一 Hello Kotlin二 Kotlin优点三 团队为什么采用 Kotlin 一 Hello Kotlin Kotlin和Andriod 二 Kotlin优点 三 团队为什么采用 Kotlin...

ES 概念

es 概念 Elasticsearch是分布式实时搜索、实时分析、实时存储引擎&#xff0c;简称&#xff08;ES&#xff09;成立于2012年&#xff0c;是一家来自荷兰的、开源的大数据搜索、分析服务提供商&#xff0c;为企业提供实时搜索、数据分析服务&#xff0c;支持PB级的大数据。 -- …...

Fairy下载和使用

写在最前&#xff1a;本系列中将会涉及到 Unity&#xff0c;C#&#xff0c;Lua和FairyGUI&#xff08;FGUI&#xff09;。 FairyGUI介绍 官网&#xff1a; FairyGUI 编辑器下载&#xff1a; FairyGUI 截至文档记录最新版&#xff1a; https://res.fairygui.com/FairyGUI-Ed…...

隧道HTTP优化程序示例

作为专业爬虫程序员&#xff0c;我们经常需要使用代理服务器处理大量的请求。但是&#xff0c;单一服务器往往无法承担高并发请求和HTTPS加密的压力&#xff0c;这时候我们可以利用CDN来优化性能&#xff0c;并实现反向代理和HTTPS加速。下面&#xff0c;让我们一步步来了解。 …...

Flink源码之State创建流程

StreamOperatorStateHandler 在StreamTask启动初始化时通过StreamTaskStateInitializerImpl::streamOperatorStateContext会为每个StreamOperator 创建keyedStatedBackend和operatorStateBackend&#xff0c;在AbstractStreamOperator中有个StreamOperatorStateHandler成员变量…...

selenium常见等待机制及其特点和使用方法

目录 1、强制等待 2、隐式等待 3、显示等待 1、强制等待 强制等待是在程序中直接调用Thread.sleep(timeout) ,来完成的&#xff0c;该用法的优点是使用起来方便&#xff0c;语法也比较简单&#xff0c;缺点就是需要强制等待固定的时间&#xff0c;可能会造成测试的时间过…...

C++物件数组的常用方法介绍

以下代码建立了一个物件数组Student&#xff0c;并展示了如何计算物件数组的长度&#xff0c;如何从物件数组中找到特定的对象&#xff0c;如何根据数组的不同参数进行排序&#xff0c;以及如何找到最大和最小值。 #include <iostream> #include <algorithm>using…...

云计算:新一代的技术革命

云计算&#xff0c;作为21世纪的一项重要技术革命&#xff0c;已在全球范围内引发了深远的影响。它改变了我们存储和处理数据的方式&#xff0c;使得企业无需再建设和维护昂贵的本地服务器和数据中心。本文将深入探讨云计算的基本概念&#xff0c;类型&#xff0c;主要优点&…...

数据结构—图的应用

6.4图的应用 概念回顾—生成树 生成树&#xff1a;所有顶点均由边连接在一起&#xff0c;但不存在回路的图。 一个图可以有许多棵不同的生成树、含有n个顶点 n-1 条边的图不一定是生成树所有生成树具有以下共同特点 生成树的顶点个数与图的顶点个数相同&#xff1b;生成树是图的…...

Unity 鼠标控制 UI 放大、缩小、拖拽

文章目录 1. 代码2. 测试场景 1. 代码 using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems;public class UIDragZoom : MonoBehaviour, IDragHandler, IScrollHandler {private Vector2 originalSize;private Vector2 originalPosition;private RectTr…...

tensorflow 模型计算中,预测错误;权重参数加载

tensorflow 模型计算中&#xff0c;预测错误&#xff1b;权重参数加载 tensorflow 模型计算主要代码&#xff08;正确代码&#xff09; linear1_kernel_initializer tf.constant_initializer(numpy.transpose(data["linear1.weight"])) linear1_bias_initializer …...

Jay17 2023.8.14日报 即 留校集训阶段性总结

8.14 打了moeCTF&#xff0c;还剩一题ak Web。 Jay17-集训结束阶段性总结&#xff1a; 集训产出&#xff1a; 自集训开始以来一个半月&#xff0c;最主要做的事情有三。 一是跟课程&#xff0c;复习学过的知识&#xff0c;学习新的知识&#xff1b;目前课程已大体听完&…...

【C语言】小游戏-扫雷(清屏+递归展开+标记)

大家好&#xff0c;我是深鱼~ 目录 一、游戏介绍 二、文件分装 三、代码实现步骤 1.制作简易游戏菜单 2. 初始化棋盘(11*11) 3.打印棋盘(9*9) 4.布置雷 5.计算(x,y)周围8个坐标的和 6.排查雷 <1>清屏后打印棋盘 <2>递归展开 <3>标记雷 四、完整代…...

云服务 Ubuntu 20.04 版本 使用 Nginx 部署静态网页

所需操作&#xff1a; 1.安装Nginx 2.修改配置文件 3.测试、重启 Nginx 4.内部修改防火墙 5.配置解析 6.测试是否部署成功 1.安装Nginx // 未使用 root 账号 apt-get update // 更新apt-get install nginx // 安装 nginx 1.1.测试是否安装没问题 在网页上输入云服务的公网…...

无后效性

动态规划的概念 在上例的多阶段决策问题中&#xff0c;各个阶段采取的决策&#xff0c;一般来说是与时间有关的&#xff0c;决策依赖于当前状态&#xff0c;又随即引起状态的转移&#xff0c;一个决策序列就是在变化的状态中产生出来的&#xff0c;故有“动态”的含义&#xf…...

Kubernetes系列-删除deployment和pod

通过deployment创建的pod直接执行delete是不会正常被删除的&#xff0c;因为deployment中设置了pod的数量&#xff0c;deployment会动态维护pod的数量&#xff0c;倘若pod数量少于约定数量&#xff0c;deployment会创建pod&#xff0c;直到pod数量达到约定数量才会停止。 如若…...

简约商务通用宣传年终总结12套PPT模版分享

IOS风格企业宣传PPT模版&#xff0c;年终工作总结PPT模版&#xff0c;简约精致扁平化商务通用动画PPT模版&#xff0c;素雅商务PPT模版 简约商务通用宣传年终总结12套PPT模版分享:商务通用年终总结类PPT模版https://pan.quark.cn/s/ece1e252d7df...

Spring事务传播机制有哪些?

导语&#xff1a; Spring事务传播机制是后端面试中的必考知识点&#xff0c;特别容易出现在“项目细节挖掘”阶段。面试官通过它来判断你是否真正理解事务控制的本质与异常传播机制。本文将从实战与源码角度出发&#xff0c;全面剖析Spring事务传播机制&#xff0c;帮助你答得有…...

LTR-381RGB-01RGB+环境光检测应用场景及客户类型主要有哪些?

RGB环境光检测 功能&#xff0c;在应用场景及客户类型&#xff1a; 1. 可应用的儿童玩具类型 (1) 智能互动玩具 功能&#xff1a;通过检测环境光或物体颜色触发互动&#xff08;如颜色识别积木、光感音乐盒&#xff09;。 客户参考&#xff1a; LEGO&#xff08;乐高&#x…...

迁移科技3D视觉系统:重塑纸箱拆垛场景的智能革命

一、传统拆垛场景的困局与破局之道 在汽车零部件仓库中&#xff0c;每天有超过2万只异形纸箱需要拆垛分拣。传统人工拆垛面临三大挑战&#xff1a; 效率瓶颈&#xff1a;工人每小时仅能处理200-300件&#xff0c;且存在间歇性疲劳安全隐患&#xff1a;20kg以上重箱搬运导致年…...

FTPS、HTTPS、SMTPS以及WebSockets over TLS的概念及其应用场景

一、什么是FTPS&#xff1f; FTPS&#xff0c;英文全称File Transfer Protocol with support for Transport Layer Security (SSL/TLS)&#xff0c;安全文件传输协议&#xff0c;是一种对常用的文件传输协议(FTP)添加传输层安全(TLS)和安全套接层(SSL)加密协议支持的扩展协议。…...

LeetCode 2894.分类求和并作差

目录 题目&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; 思路&#xff1a; 思路一详解&#xff08;遍历 判断&#xff09;&#xff1a; 思路二详解&#xff08;数学规律/公式&#xff09;&#xff1a; 代码&#xff1a; Java思路一&#xff08;遍历 判断&a…...

Android Settings 数据库生成、监听与默认值配置

一、Settings 数据库生成机制​ ​传统数据库生成&#xff08;Android 6.0 前&#xff09;​​ ​路径​&#xff1a;/data/data/com.android.providers.settings/databases/settings.db​创建流程​&#xff1a; ​SQL 脚本初始化​&#xff1a;通过 sqlite 工具创建数据库文件…...

Python网页自动化测试,DrissonPage库入门说明文档

&#x1f6f0;️ 基本逻辑​ 操作浏览器的基本逻辑如下&#xff1a; 创建浏览器对象&#xff0c;用于启动或接管浏览器获取一个 Tab 对象使用 Tab 对象访问网址使用 Tab 对象获取标签页内需要的元素对象使用元素对象进行交互 除此以外&#xff0c;还能执行更为复杂的操作&am…...

慢慢欣赏linux 之 last = switch_to(prev, next)分析

last switch_to(prev, next); 为什么需要定义last作为调用switch_to之前的prev的引用 原因如下&#xff1a; struct task_struct * switch_to(struct task_struct *prev,struct task_struct *next) {... ...return cpu_switch_to(prev, next);> .global cpu_switch_tocpu_…...

易语言是什么?易语言能做什么?

易语言&#xff08;EPL&#xff09;是什么&#xff1f;​​ ​​易语言​​&#xff08;Easy Programming Language&#xff0c;简称EPL&#xff09;是一款​​面向中文用户的编程语言​​&#xff0c;由中国人吴涛于2000年开发&#xff0c;专为降低编程门槛设计。其核心特点是…...