### 1. 技术选型 ---- * 注册中心:nacos,替代方案eureka、consul、zookeeper * 配置中心: nacos ,替代方案sc config、consul config * 服务调用:feign,替代方案:resttempate * 熔断:sentinel、,替代方案:Resilience4j * 熔断监控:sentinel dashboard * 负载均衡:sc loadbalancer * 网关:spring cloud gateway * 链路:spring cloud sleuth+zipkin,替代方案:skywalking等。 * 分时任务调度框架 xxl-job * admin 监控 ----- ### 2.工程搭建
#### 2.1 搭建父工程
* mystyle-cloud-parent ```xml 4.0.0 com.zhangmeng mystyle-cloud-parent 1.0-SNAPSHOT 1.8 2.4.4 2020.0.2 2020.0.RC1 org.springframework.boot spring-boot-dependencies ${spring-boot.version} pom import org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring-cloud-alibaba.version} pom import ``` #### 2.2 搭建用户中心(废除) * mystyle-cloud-user > 包结构 : com.zhangmeng.user [mystyle-cloud-user](http://localhost:8848/nacos/) ```xml org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery ``` #### 2.3 搭建文件中心 * mystyle-cloud-file > 包结构 :com.zhangmeng.file [file调用user](http://localhost:8763/file/name?name=转身的背影在心底里沉沦) ![file调用user](images/snipaste_20211030_152133.png) ![文件列表](images/snipaste_20211117_153517.png) ------- ```xml org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-openfeign org.springframework.cloud spring-cloud-starter-loadbalancer ``` #### 2.4 搭建网关 * mystyle-cloud-gateway > 包结构 :com.zhangmeng.gateway [测试](http://localhost:5000/mystyle-file/file/name?name=转身的背影在心底里沉沦) ![](images/snipaste_20211030_155720.png) ------- ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-gateway org.springframework.boot spring-boot-starter-webflux org.springframework.cloud spring-cloud-starter-loadbalancer com.alibaba.cloud spring-cloud-starter-alibaba-sentinel com.alibaba.cloud spring-cloud-alibaba-sentinel-gateway ``` * 创建一个网关分组和网关的限流规则 ```java @Configuration public class GatewayConfiguration { private final List viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public GatewayConfiguration(ObjectProvider> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @Bean @Order(-1) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } @PostConstruct public void doInit() { initCustomizedApis(); initGatewayRules(); } private void initCustomizedApis() { Set definitions = new HashSet<>(); ApiDefinition api1 = new ApiDefinition("consumer") .setPredicateItems(new HashSet() ); ApiDefinition api2 = new ApiDefinition("provider") .setPredicateItems(new HashSet() ); definitions.add(api1); definitions.add(api2); GatewayApiDefinitionManager.loadApiDefinitions(definitions); } private void initGatewayRules() { Set rules = new HashSet<>(); rules.add(new GatewayFlowRule("consumer") .setCount(10) .setIntervalSec(1) ); rules.add(new GatewayFlowRule("consumer") .setCount(2) .setIntervalSec(2) .setBurst(2) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) ) ); rules.add(new GatewayFlowRule("provider") .setCount(10) .setIntervalSec(1) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) .setMaxQueueingTimeoutMs(600) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) .setFieldName("X-Sentinel-Flag") ) ); rules.add(new GatewayFlowRule("provider") .setCount(1) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName("pa") ) ); rules.add(new GatewayFlowRule("provider") .setCount(2) .setIntervalSec(30) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName("type") .setPattern("warn") .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS) ) ); rules.add(new GatewayFlowRule("provider") .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME) .setCount(5) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName("pn") ) ); GatewayRuleManager.loadRules(rules); } } ``` #### 2.5 sentinel dashboard [sentinel dashboard](http://localhost:8748) > java -Dserver.port=8748 -Dcsp.sentinel.dashboard.server=localhost:8748 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar 添加流控规则 : ![sentinel添加流控规则](images/snipaste_20211030_164359.png)
#### 2.6 搭建注册中心
[nacos](http://localhost:8848/nacos/#/login) ![nacos](images/snipaste_20211030_164909.png) #### 2.7 搭建 zipkin server [zipkin server](http://localhost:9411/zipkin/) ![](images/snipaste_20211030_175939.png)
![](images/snipaste_20211030_180038.png) 批处理 启动 ```bat @echo off start /d "E:\nacos-server-2.0.2\nacos\bin\" startup.cmd start cmd /c "title sentinel-dashboard && java -jar E:\nacos-server-2.0.2\sentinel-dashboard-1.8.2.jar --server.port=8748 &" start cmd /c "title zipkin-server-2.23.4-exec && java -jar E:\nacos-server-2.0.2\zipkin-server-2.23.4-exec.jar --server.port=9411 &" pause ``` #### 2.8 搭建 model * mystyle-cloud-model > 包结构 :com.zhangmeng.model 集成 jpa 及 通用 mapper ![](images/snipaste_20211101_112449.png) ```java @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @ComponentScan(basePackages = {"com.zhangmeng.model","com.zhangmeng.file"}) public class FileApplication { public static void main(String[] args) { SpringApplication.run(FileApplication.class,args); } } ``` 测试 ![](images/snipaste_20211101_112721.png) #### 2.8 搭建 管理后台 * mystyle-cloud-admin-manager > 包结构 :com.zhangmeng.admin.manager ![snipaste_20211101_145937](images/snipaste_20211101_145937.png) #### 2.9 api 搭建 api * mystyle-cloud-api > 包结构 :com.zhangmeng.api > ![snipaste_20211101_145754](images/snipaste_20211101_145754.png) ```java package com.zhangmeng.api.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.*; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.ArrayList; import java.util.List; /** * @author zhangmeng * @version 1.0 * @date 2021年7月8日09:12:01 */ @EnableSwagger2 @Configuration public class SwaggerConfig { @Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2).groupName("swagger接口文档") .apiInfo(new ApiInfoBuilder().title("swagger接口文档") .contact(new Contact("转身的背影在心底里沉沦", "", "1334717033.com")).version("1.0").build()) .securitySchemes(securitySchemes()) .securityContexts(securityContexts()) .select() .apis(RequestHandlerSelectors.basePackage("com.zhangmeng")) .paths(PathSelectors.any()).build(); } private List securitySchemes() { List apiKeyList= new ArrayList<>(); apiKeyList.add(new ApiKey("token", "token", "header")); return apiKeyList; } private List securityContexts() { List securityContexts=new ArrayList<>(); securityContexts.add( SecurityContext.builder() .securityReferences(defaultAuth()) .forPaths(PathSelectors.regex("^(?!auth).*$")) .build()); return securityContexts; } private List defaultAuth() { AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; authorizationScopes[0] = authorizationScope; List securityReferences=new ArrayList<>(); securityReferences.add(new SecurityReference("token", authorizationScopes)); return securityReferences; } } ``` [admin_manager](http://localhost:31003/swagger-ui.html) ![](images/snipaste_20211101_145433.png) #### 2.10 搭建 canal * mystyle-cloud-canal > 包结构 :com.zhangmeng.canal ![](images/snipaste_20211101_172449.png) (2) 创建账号 用于测试使用, 使用root账号创建用户并授予权限 ```properties create user canal@'%' IDENTIFIED by 'canal'; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%'; FLUSH PRIVILEGES; ``` ```java @SpringBootApplication(exclude={DataSourceAutoConfiguration.class}) @EnableEurekaClient @EnableCanalClient public class CanalApplication { public static void main(String[] args) { SpringApplication.run(CanalApplication.class,args); } } ``` ![canal](images/snipaste_20211102_151056.png) #### 2.11 搭建 消息中间件 rabbit mq * mystyle-cloud-mq > 包结构 :com.zhangmeng.mq #### 2.12 搭建 授权中心 * mystyle-cloud-oauth > 包结构 :com.zhangmeng.oauth * Encoded password does not look like BCrypt ![Encoded password does not look like BCrypt](images/snipaste_20211104_104922.png) ![](images/snipaste_20211104_105055.png) #### 2.13 搭建博客 * mystyle-cloud-blog > 包结构 :com.zhangmeng.blog #### 2.14 搭建定时任务 思路 : >* quartz + rabbit_mq > >* xxl-job 分布式调度框架 * mystyle-cloud-quartz > com.zhangmeng.quartz ![snipaste_20211112_143604](images/snipaste_20211112_143604.png) 后续调整为: * mystyle-cloud-xxl-job > com.zhangmeng.job 引入依赖 ```yaml com.xuxueli xxl-job-core 2.2.0 ``` #### 2.15 搭建邮件 * mystyle-cloud-mail > 包结构 :com.zhangmeng.mail ```java org.springframework.boot spring-boot-starter-mail ``` #### 2.15 搭建小说 * mystyle-cloud-fiction > 包结构 :com.zhangmeng.fiction #### 2.16 搭建admin 监控 * mystyle-cloud-admin-monitor > 包结构 :com.zhangmeng.monitor ```yaml management: endpoints: web: exposure: include: '*' endpoint: health: show-details: ALWAYS enabled: true ``` ```xml de.codecentric spring-boot-admin-starter-client ``` ```java - /actuator/** - /instances/** ``` ![springboot-admin-snipaste_20211117_153059](images/snipaste_20211117_153059.png) ![日志文件](images/snipaste_20211117_153239.png) ### 3.接口说明 #### 3.1 登录接口 ##### 3.1.1 验证码 [验证码](http://localhost:9000/mystyle-cloud-admin-manager/verificationCode/generate) ![20211106_144846](images/snipaste_20211106_144846.png) ##### 3.1.2 查询当前用户 [地址](http://localhost:9000/mystyle-cloud-admin-manager/user/current) ##### 3.1.3 登录 * 问题 跨域 ![](images/snipaste_20211106_154833.png) [地址](http://localhost:9000/mystyle-cloud-oauth/user/login) * 解决办法 gate_way yml 配置 ```yaml server: port: 9000 spring: application: name: mystyle-cloud-gateway zipkin: sender: type: web base-url: http://localhost:9411/ service: name: mystyle-cloud-gateway sleuth: sampler: probability: 1 cloud: sentinel: transport: port: 15000 dashboard: localhost:8748 nacos: discovery: server-addr: 127.0.0.1:8848 gateway: globalcors: corsConfigurations: '[/**]': # 匹配所有请求 allowedOrigins: "*" #跨域处理 允许所有的域 allowedMethods: # 支持的方法 - GET - POST - PUT - DELETE allowedOriginPatterns: "*" discovery: locator: enabled: false lowerCaseServiceId: true routes: - id: mystyle-cloud-oauth uri: lb://mystyle-cloud-oauth predicates: - Path=/mystyle-cloud-oauth/** filters: - StripPrefix=1 - id: mystyle-cloud-admin-manager uri: lb://mystyle-cloud-admin-manager predicates: - Path=/mystyle-cloud-admin-manager/** filters: - StripPrefix=1 ``` 或者 ```java package com.zhangmeng.gateway.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsWebFilter; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; /** * @author zhangmeng * @date 2021年11月8日15:52:27 * @version 1.0 */ @Configuration public class CorsConfig { @Bean public CorsWebFilter corsWebFilter(){ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration corsConfiguration = new CorsConfiguration(); //1、配置跨域 //允许哪些头进行跨域 corsConfiguration.addAllowedHeader("*"); //允许哪些请求方式进行跨域 corsConfiguration.addAllowedMethod("*"); //允许哪些请求来源进行跨域 corsConfiguration.addAllowedOriginPattern("*"); //是否允许携带cookie进行跨域,否则跨域请求会丢失cookie信息 corsConfiguration.setAllowCredentials(true); source.registerCorsConfiguration("/**",corsConfiguration); return new CorsWebFilter(source); } } ``` > 提示 : 调用的具体服务去掉跨域配置 [首页访问](http://localhost:31007/login.html) ![login](images/snipaste_20211108_160302.png) #### 3.2 首页 ------- >**获取鉴权信息**
* 源码
oauth2.0 获取鉴权 ```java /** * Copyright 2006-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package org.springframework.security.oauth2.provider.authentication; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.util.Assert; /** * A pre-authentication filter for OAuth2 protected resources. Extracts an OAuth2 token from the incoming request and * uses it to populate the Spring Security context with an {@link OAuth2Authentication} (if used in conjunction with an * {@link OAuth2AuthenticationManager}). * * @author Dave Syer * */ public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean { private final static Log logger = LogFactory.getLog(OAuth2AuthenticationProcessingFilter.class); private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint(); private AuthenticationManager authenticationManager; private AuthenticationDetailsSource authenticationDetailsSource = new OAuth2AuthenticationDetailsSource(); private TokenExtractor tokenExtractor = new BearerTokenExtractor(); private AuthenticationEventPublisher eventPublisher = new NullEventPublisher(); private boolean stateless = true; /** * Flag to say that this filter guards stateless resources (default true). Set this to true if the only way the * resource can be accessed is with a token. If false then an incoming cookie can populate the security context and * allow access to a caller that isn't an OAuth2 client. * * @param stateless the flag to set (default true) */ public void setStateless(boolean stateless) { this.stateless = stateless; } /** * @param authenticationEntryPoint the authentication entry point to set */ public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { this.authenticationEntryPoint = authenticationEntryPoint; } /** * @param authenticationManager the authentication manager to set (mandatory with no default) */ public void setAuthenticationManager(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } /** * @param tokenExtractor the tokenExtractor to set */ public void setTokenExtractor(TokenExtractor tokenExtractor) { this.tokenExtractor = tokenExtractor; } /** * @param eventPublisher the event publisher to set */ public void setAuthenticationEventPublisher(AuthenticationEventPublisher eventPublisher) { this.eventPublisher = eventPublisher; } /** * @param authenticationDetailsSource The AuthenticationDetailsSource to use */ public void setAuthenticationDetailsSource( AuthenticationDetailsSource authenticationDetailsSource) { Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required"); this.authenticationDetailsSource = authenticationDetailsSource; } public void afterPropertiesSet() { Assert.state(authenticationManager != null, "AuthenticationManager is required"); } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { final boolean debug = logger.isDebugEnabled(); final HttpServletRequest request = (HttpServletRequest) req; final HttpServletResponse response = (HttpServletResponse) res; try { Authentication authentication = tokenExtractor.extract(request); if (authentication == null) { if (stateless && isAuthenticated()) { if (debug) { logger.debug("Clearing security context."); } SecurityContextHolder.clearContext(); } if (debug) { logger.debug("No token in request, will continue chain."); } } else { request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal()); if (authentication instanceof AbstractAuthenticationToken) { AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication; needsDetails.setDetails(authenticationDetailsSource.buildDetails(request)); } Authentication authResult = authenticationManager.authenticate(authentication); if (debug) { logger.debug("Authentication success: " + authResult); } eventPublisher.publishAuthenticationSuccess(authResult); SecurityContextHolder.getContext().setAuthentication(authResult); } } catch (OAuth2Exception failed) { SecurityContextHolder.clearContext(); if (debug) { logger.debug("Authentication request failed: " + failed); } eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed), new PreAuthenticatedAuthenticationToken("access-token", "N/A")); authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(failed.getMessage(), failed)); return; } chain.doFilter(request, response); } private boolean isAuthenticated() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || authentication instanceof AnonymousAuthenticationToken) { return false; } return true; } public void init(FilterConfig filterConfig) throws ServletException { } public void destroy() { } private static final class NullEventPublisher implements AuthenticationEventPublisher { public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) { } public void publishAuthenticationSuccess(Authentication authentication) { } } } ``` ![snipaste_20211108_163411](images/snipaste_20211108_163411.png) ##### 3.2.1 先从请求头中获取 ```java protected String extractToken(HttpServletRequest request) { // first check the header... -------------------------------------------------------------- String token = extractHeaderToken(request); -------------------------------------------------------------- // bearer type allows a request parameter as well if (token == null) { logger.debug("Token not found in headers. Trying request parameters."); token = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN); if (token == null) { logger.debug("Token not found in request parameters. Not an OAuth2 request."); } else { request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, OAuth2AccessToken.BEARER_TYPE); } } return token; } ``` ![](images/snipaste_20211108_163640.png) ```java /** * Extract the OAuth bearer token from a header. * * @param request The request. * @return The token, or null if no OAuth authorization header was supplied. */ protected String extractHeaderToken(HttpServletRequest request) { --------------------------------------------------------------------------- Enumeration headers = request.getHeaders("Authorization"); ---------------------------------------------------------------------------- while (headers.hasMoreElements()) { // typically there is only one (most servers enforce that) String value = headers.nextElement(); if ((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) { String authHeaderValue = value.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim(); // Add this here for the auth details later. Would be better to change the signature of this method. request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, value.substring(0, OAuth2AccessToken.BEARER_TYPE.length()).trim()); int commaIndex = authHeaderValue.indexOf(','); if (commaIndex > 0) { authHeaderValue = authHeaderValue.substring(0, commaIndex); } return authHeaderValue; } } return null; } ``` ![](images/snipaste_20211108_163901.png) ##### 3.2.2 从请求参数中获取 ```java protected String extractToken(HttpServletRequest request) { // first check the header... String token = extractHeaderToken(request); // bearer type allows a request parameter as well if (token == null) { logger.debug("Token not found in headers. Trying request parameters."); ------------------------------------------------------------------- token = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN); ------------------------------------------------------------------- if (token == null) { logger.debug("Token not found in request parameters. Not an OAuth2 request."); } else { request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, OAuth2AccessToken.BEARER_TYPE); } } return token; } ``` ![snipaste_20211108_165356](images/snipaste_20211108_165356.png) ------------------------------------------- ##### 3.2.3 问题 >首页不展示 ![](images/snipaste_20211109_150805.png) >控制台报错 Refused to display 'http://localhost:9000/' in a frame because it set 'X-Frame-Options' to 'deny'. > 解决办法 ```java http.headers().frameOptions().disable(); ``` ![snipaste_20211109_151003](images/snipaste_20211109_151003.png) -------------------------- ![首页](images/snipaste_20211109_150329.png) ------------------------------- > token 过期展示 ![token过期](images/snipaste_20211109_162244.png) #### 3.3 文件上传 ![snipaste_20211110_141127](images/snipaste_20211110_141127.png) > 解决办法 ```yaml spring: servlet: multipart: max-file-size: 4GB max-request-size: 4GB ``` ![](images/snipaste_20211213_161208.png) #### 3.4 feign 远程调用 > 报错 :nested exception is feign.RetryableException: too many bytes written executing >* 解决办法 > ```java // 跳过 content-length if (element.equals("content-length")){ continue; } ``` ```java @Configuration public class FeignOauth2RequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { // 获取的全部请求信息 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes != null){ HttpServletRequest request = attributes.getRequest(); // 获取所有的请求头信息 Enumeration headerNames = request.getHeaderNames(); if (headerNames != null){ while (headerNames.hasMoreElements()){ // 获取请求头的key String element = headerNames.nextElement(); // 获取请求头的value String value = request.getHeader(element); ---------------------------------------------------------------------------------------------------------------- // 跳过 content-length if (element.equals("content-length")){ continue; } ---------------------------------------------------------------------------------------------------------------- // 将请求头信息放入到请求头 requestTemplate.header(element,value); } } } } } ``` ### 4. 博客展示 ![](images/snipaste_20211213_160516.png) #### 4.1 详情 ![](images/snipaste_20211213_160628.png) #### 4.2 关于我 ![](images/snipaste_20211213_160806.png) ### 5.小说展示 ![](images/snipaste_20211213_170811.png) #### 5.2 小说展示 ![](images/snipaste_20211213_170643.png)