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.pswp.cn/news/38290.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/38290.shtml
英文地址,請注明出處:http://en.pswp.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坐標和相…