最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot+Vue靜態(tài)資源刷新后無法訪問的問題解決方案

 更新時(shí)間:2024年05月15日 10:25:52   作者:左邊的天堂  
這篇文章主要介紹了SpringBoot+Vue靜態(tài)資源刷新后無法訪問的問題解決方案,文中通過代碼示例和圖文講解的非常詳細(xì),對(duì)大家解決問題有一定的幫助,需要的朋友可以參考下

一、背景

原項(xiàng)目是有前后端分離設(shè)計(jì),測試環(huán)境是centos系統(tǒng),采用nginx代理和轉(zhuǎn)發(fā),項(xiàng)目正常運(yùn)行。
項(xiàng)目近期上線到正式環(huán)境,結(jié)果更換了系統(tǒng)環(huán)境,需要放到一臺(tái)windows系統(tǒng)中,前后端打成一個(gè)jar包,然后做成系統(tǒng)服務(wù)。這臺(tái)服務(wù)器中已經(jīng)有很多其他服務(wù),都是采用一樣的部署方式,所以沒辦法只能對(duì)這個(gè)項(xiàng)目進(jìn)行修改。

二、修改過程

2.1 首先看項(xiàng)目結(jié)構(gòu)

在這里插入圖片描述

admin是后端代碼,使用的是springboot,使用 spring security 權(quán)限控制;UI是前端,使用的是vue3+vite

admin的結(jié)構(gòu)

在這里插入圖片描述

ui的結(jié)構(gòu)

在這里插入圖片描述

2.2 打包靜態(tài)資源

修改前端打包配置vite.config.js

import { defineConfig, loadEnv } from 'vite'
import path from 'path'
import createVitePlugins from './vite/plugins'

// https://vitejs.dev/config/
export default defineConfig(({ mode, command }) => {
  const env = loadEnv(mode, process.cwd())
  const { VITE_APP_ENV } = env
  return {
    // 部署生產(chǎn)環(huán)境和開發(fā)環(huán)境下的URL。
    // 默認(rèn)情況下,vite 會(huì)假設(shè)你的應(yīng)用是被部署在一個(gè)域名的根路徑上
    base: VITE_APP_ENV === 'production' ? '/' : '/',
    build: {
      outDir: '../admin/src/main/resources/static'
    },
    plugins: createVitePlugins(env, command === 'build'),
    resolve: {
      // https://cn.vitejs.dev/config/#resolve-alias
      alias: {
        // 設(shè)置路徑
        '~': path.resolve(__dirname, './'),
        // 設(shè)置別名
        '@': path.resolve(__dirname, './src')
      },
      // https://cn.vitejs.dev/config/#resolve-extensions
      extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
    },
    // vite 相關(guān)配置
    server: {
      port: 888,
      host: true,
      open: true,
      proxy: {
        // https://cn.vitejs.dev/config/#server-proxy
        '/': {
          target: 'http://localhost:8080',
          changeOrigin: true,
          // rewrite: (p) => p.replace(/^\/api/, '')
        }
      }
    },
    //fix:error:stdin>:7356:1: warning: "@charset" must be the first rule in the file
    css: {
      postcss: {
        plugins: [
          {
            postcssPlugin: 'internal:charset-removal',
            AtRule: {
              charset: (atRule) => {
                if (atRule.name === 'charset') {
                  atRule.remove();
                }
              }
            }
          }
        ]
      }
    }
  }
})

增加下面代碼

build: {
  outDir: '../admin/src/main/resources/static'
},

指定編譯后的靜態(tài)文件存放目錄,默認(rèn)的是在ui/dist目錄,然后執(zhí)行生產(chǎn)環(huán)境打包命令 npm run prod / npm run build,成功后會(huì)在admin/resource目錄下生成一個(gè)static文件夾

在這里插入圖片描述

在這里插入圖片描述

同時(shí),把前端路徑與后端路徑?jīng)_突的修改一下。

2.3 修改后端權(quán)限控制

修改SecurityConfig,增加靜態(tài)資源的訪問權(quán)限

/**
 * spring security配置
 *
 * @author admin
 */
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 自定義用戶認(rèn)證邏輯
     */
    @Resource
    private UserDetailsService userDetailsService;

    /**
     * 認(rèn)證失敗處理類
     */
    @Resource
    private AuthenticationEntryPointImpl unauthorizedHandler;

    /**
     * 退出處理類
     */
    @Resource
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**
     * token認(rèn)證過濾器
     */
    @Resource
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    /**
     * 跨域過濾器
     */
    @Resource
    private CorsFilter corsFilter;

    /**
     * 允許匿名訪問的地址
     */
    @Resource
    private PermitAllUrlProperties permitAllUrl;
    
    /**
     * 鑒權(quán)
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        // 注解標(biāo)記允許匿名訪問的url
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
        permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());

        httpSecurity
                // CSRF禁用,因?yàn)椴皇褂胹ession
                .csrf().disable()
                // 禁用HTTP響應(yīng)標(biāo)頭
                .headers().cacheControl().disable().and()
                // 認(rèn)證失敗處理類
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 過濾請求
                .authorizeRequests()
                // 對(duì)于登錄login 驗(yàn)證碼captchaImage 允許匿名訪問
                .antMatchers("/login", "/sendSmsCode/*", "/captchaImage").permitAll()
                // 靜態(tài)資源,可匿名訪問
                .antMatchers(HttpMethod.GET, "/", "/*.html", "/*.html.gz", "/assets/**", "/favicon.ico", "/profile/**").permitAll()
                .antMatchers("/webjars/**", "/druid/**").permitAll()
                // 除上面外的所有請求全部需要鑒權(quán)認(rèn)證
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();
        // 添加Logout filter
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
        // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加CORS filter
        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
    }
}

靜態(tài)文件處理類ResourcesConfig

@Configuration
public class ResourcesConfig implements WebMvcConfigurer {
    @Resource
    private RepeatSubmitInterceptor repeatSubmitInterceptor;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        /** 本地文件上傳路徑,F(xiàn)ileConfig.getPath() 為本地磁盤目錄 */
        registry.addResourceHandler("/profile/**")
                .addResourceLocations("file:" + FileConfig.getPath() + "/");
        /** 靜態(tài)資源 */
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
    }

    /**
     * 自定義攔截規(guī)則
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
    }

    /**
     * 跨域配置
     */
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        // 設(shè)置訪問源地址
        config.addAllowedOriginPattern("*");
        // 設(shè)置訪問源請求頭
        config.addAllowedHeader("*");
        // 設(shè)置訪問源請求方法
        config.addAllowedMethod("*");
        // 有效期 1800秒
        config.setMaxAge(1800L);
        // 添加映射路徑,攔截一切請求
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        // 返回新的CorsFilter
        return new CorsFilter(source);
    }
}

認(rèn)證失敗處理類AuthenticationEntryPointImpl,解決退出成功后無法跳轉(zhuǎn)到登錄頁的問題

/**
 * 認(rèn)證失敗處理類 返回未授權(quán)
 *
 * @author admin
 */
@Slf4j
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
    private static final long serialVersionUID = -8970718410437077606L;

    /**
     * 向調(diào)用者提供有關(guān)哪些HTTP端口與系統(tǒng)上的哪些HTTPS端口相關(guān)聯(lián)的信息
     */
    private PortMapper portMapper = new PortMapperImpl();
    /**
     * 端口解析器,基于請求解析出端口
     */
    private PortResolver portResolver = new PortResolverImpl();
    /**
     * 登陸頁面URL
     */
    private String loginFormUrl;
    /**
     * 默認(rèn)為false,即不強(qiáng)制Https轉(zhuǎn)發(fā)或重定向
     */
    private boolean forceHttps = false;
    /**
     * 默認(rèn)為false,即不是轉(zhuǎn)發(fā)到登陸頁面,而是進(jìn)行重定向
     */
    private boolean useForward = false;
    /**
     * 重定向策略
     */
    private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    public String getLoginFormUrl() {
        return loginFormUrl;
    }

    public void setLoginFormUrl(String loginFormUrl) {
        this.loginFormUrl = loginFormUrl;
    }


    /**
     * 允許子類修改成適用于給定請求的登錄表單URL
     */
    protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) {
        return getLoginFormUrl();
    }

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        String redirectUrl = null;
        if (useForward) {
            if (forceHttps && HttpScheme.HTTP.name().equals(request.getScheme())) {
                redirectUrl = buildHttpsRedirectUrlForRequest(request);
            }
            if (redirectUrl == null) {
                String loginForm = determineUrlToUseForThisRequest(request, response, authException);
                RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
                dispatcher.forward(request, response);
                return;
            }
        } else {
            redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
        }
        redirectStrategy.sendRedirect(request, response, redirectUrl);
    }

    /**
     * 構(gòu)建重定向URL
     *
     * @param request
     * @param response
     * @param authException
     * @return
     */
    protected String buildRedirectUrlToLoginPage(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
        // 通過determineUrlToUseForThisRequest方法獲取URL
        String loginForm = determineUrlToUseForThisRequest(request, response, authException);
        // 如果是絕對(duì)URL,直接返回
        if (UrlUtils.isAbsoluteUrl(loginForm)) {
            return loginForm;
        }
        // 如果是相對(duì)URL
        // 構(gòu)造重定向URL
        int serverPort = portResolver.getServerPort(request);
        String scheme = request.getScheme();

        RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder();

        urlBuilder.setScheme(scheme);
        urlBuilder.setServerName(request.getServerName());
        urlBuilder.setPort(serverPort);
        urlBuilder.setContextPath(request.getContextPath());
        urlBuilder.setPathInfo(loginForm);

        if (forceHttps && HttpScheme.HTTP.name().equals(scheme)) {
            Integer httpsPort = portMapper.lookupHttpsPort(serverPort);

            if (httpsPort != null) {
                // 覆蓋重定向URL中的scheme和port
                urlBuilder.setScheme("https");
                urlBuilder.setPort(httpsPort);
            } else {
                log.warn("Unable to redirect to HTTPS as no port mapping found for HTTP port " + serverPort);
            }
        }

        return urlBuilder.getUrl();
    }

    /**
     * 構(gòu)建一個(gè)URL以將提供的請求重定向到HTTPS
     * 用于在轉(zhuǎn)發(fā)到登錄頁面之前將當(dāng)前請求重定向到HTTPS
     */
    protected String buildHttpsRedirectUrlForRequest(HttpServletRequest request) throws IOException, ServletException {

        int serverPort = portResolver.getServerPort(request);
        Integer httpsPort = portMapper.lookupHttpsPort(serverPort);

        if (httpsPort != null) {
            RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder();
            urlBuilder.setScheme("https");
            urlBuilder.setServerName(request.getServerName());
            urlBuilder.setPort(httpsPort);
            urlBuilder.setContextPath(request.getContextPath());
            urlBuilder.setServletPath(request.getServletPath());
            urlBuilder.setPathInfo(request.getPathInfo());
            urlBuilder.setQuery(request.getQueryString());

            return urlBuilder.getUrl();
        }

        // 通過警告消息進(jìn)入服務(wù)器端轉(zhuǎn)發(fā)
        log.warn("Unable to redirect to HTTPS as no port mapping found for HTTP port " + serverPort);

        return null;
    }

    /**
     * 設(shè)置為true以強(qiáng)制通過https訪問登錄表單
     * 如果此值為true(默認(rèn)為false),并且觸發(fā)攔截器的請求還不是https
     * 則客戶端將首先重定向到https URL,即使serverSideRedirect(服務(wù)器端轉(zhuǎn)發(fā))設(shè)置為true
     */
    public void setForceHttps(boolean forceHttps) {
        this.forceHttps = forceHttps;
    }


    /**
     * 是否要使用RequestDispatcher轉(zhuǎn)發(fā)到loginFormUrl,而不是302重定向
     */
    public void setUseForward(boolean useForward) {
        this.useForward = useForward;
    }
}

增加一個(gè)刷新跳轉(zhuǎn)處理類 ServletConfig,很關(guān)鍵。

@Configuration
public class ServletConfig {
    @Bean
    public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer() {
        return factory -> {
        	// 404時(shí)跳轉(zhuǎn)到首頁
            ErrorPage errorPage = new ErrorPage(HttpStatus.NOT_FOUND, "/index.html");
            factory.addErrorPages(errorPage);
        };
    }
}

修改一下TokenService類,增加從前端頁面請求中獲取Cookie,并且獲取token

/**
 * 獲取用戶身份信息
 *
 * @return 用戶信息
 */
public LoginUser getLoginUser(HttpServletRequest request) {
    // 獲取請求攜帶的令牌
    String token = getToken(request);
    if (StringUtils.isBlank(token)) {
	    // 增加從前端頁面請求中獲取Cookie,并且獲取token
        Cookie cookie = Arrays.stream(request.getCookies()).filter(item -> "Admin-Token".equals(item.getName())).findFirst().orElse(null);
        if (cookie != null) {
            token = cookie.getValue();
        }
    }
    if (StringUtils.isNotEmpty(token)) {
        try {
            Claims claims = parseToken(token);
            // 解析對(duì)應(yīng)的權(quán)限以及用戶信息,Constants.LOGIN_USER_KEY為自定義redis key
            String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
            String userKey = getTokenKey(uuid);
            return redisCache.getCacheObject(userKey);
        } catch (Exception e) {
        }
    }
    return null;
}

大概的修改步驟就是這樣,啟動(dòng)后順利運(yùn)行,跟前后端分離沒有什么區(qū)別。

以上就是SpringBoot+Vue靜態(tài)資源刷新后無法訪問的問題解決方案的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Vue靜態(tài)資源無法訪問的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java中的Semaphore(信號(hào)量)全面解析

    Java中的Semaphore(信號(hào)量)全面解析

    信號(hào)量(Semaphore)是Java多線程兵法中的一種JDK內(nèi)置同步器,通過它可以實(shí)現(xiàn)多線程對(duì)公共資源的并發(fā)訪問控制,這篇文章主要介紹了Java中Semaphore(信號(hào)量)的相關(guān)資料,需要的朋友可以參考下
    2025-08-08
  • SpringMvc MultipartFile實(shí)現(xiàn)圖片文件上傳示例

    SpringMvc MultipartFile實(shí)現(xiàn)圖片文件上傳示例

    本篇文章主要介紹了SpringMvc MultipartFile實(shí)現(xiàn)圖片文件上傳示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-02-02
  • JAVA異常體系結(jié)構(gòu)詳解

    JAVA異常體系結(jié)構(gòu)詳解

    Java把異常當(dāng)作對(duì)象來處理,并定義一個(gè)基類java.lang.Throwable作為所有異常的超類,下面通過本文給大家分享JAVA異常體系結(jié)構(gòu),感興趣的朋友一起看看吧
    2017-11-11
  • jdk17+springboot使用webservice的踩坑實(shí)戰(zhàn)記錄

    jdk17+springboot使用webservice的踩坑實(shí)戰(zhàn)記錄

    這篇文章主要給大家介紹了關(guān)于jdk17+springboot使用webservice踩坑的相關(guān)資料,網(wǎng)上很多教程是基于jdk8的,所以很多在17上面跑不起來,折騰兩天,直接給答案,需要的朋友可以參考下
    2024-01-01
  • SpringBoot集成ClickHouse數(shù)據(jù)庫操作過程舉例

    SpringBoot集成ClickHouse數(shù)據(jù)庫操作過程舉例

    本文講解如何在SpringBoot中集成ClickHouse列式數(shù)據(jù)庫,涵蓋依賴配置、實(shí)體映射、CRUD操作及復(fù)雜查詢實(shí)現(xiàn),結(jié)合實(shí)踐項(xiàng)目提升數(shù)據(jù)處理效率,本教程還將說明ClickHouse的數(shù)據(jù)類型、并行處理能力、聚合函數(shù)等特性,并提供一個(gè)實(shí)踐項(xiàng)目以加深理解,需要的朋友一起看看吧
    2025-08-08
  • SpringCloud之@FeignClient()注解的使用詳解

    SpringCloud之@FeignClient()注解的使用詳解

    @FeignClient是SpringCloud中用于聲明一個(gè)Feign客戶端的注解,用于解決模塊方法互相調(diào)用的問題,Feign是一個(gè)聲明式的WebService客戶端,通過Feign,只需要?jiǎng)?chuàng)建一個(gè)接口,并使用注解來描述請求,就可以直接執(zhí)行HTTP請求了
    2024-11-11
  • 使用jenkins+maven+git發(fā)布jar包過程詳解

    使用jenkins+maven+git發(fā)布jar包過程詳解

    這篇文章主要介紹了使用jenkins+maven+git發(fā)布jar包過程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Java的ArrayList擴(kuò)容源碼解析

    Java的ArrayList擴(kuò)容源碼解析

    這篇文章主要介紹了Java的ArrayList擴(kuò)容源碼解析,通過動(dòng)態(tài)擴(kuò)容,ArrayList能夠在添加元素時(shí)保持高效的性能,擴(kuò)容操作是有一定開銷的,但由于擴(kuò)容的時(shí)間復(fù)雜度為O(n),其中n是當(dāng)前元素個(gè)數(shù),所以平均情況下,每次添加元素的時(shí)間復(fù)雜度仍然是O(1),需要的朋友可以參考下
    2024-01-01
  • Mybatis-Plus將字段設(shè)置為null解決方法

    Mybatis-Plus將字段設(shè)置為null解決方法

    MyBatis-Plus是一個(gè)MyBatis的增強(qiáng)工具,在MyBatis的基礎(chǔ)上只做增 強(qiáng)不做改變,為簡化開發(fā)、提高效率而生,下面這篇文章主要給大家介紹了關(guān)于Mybatis-Plus將字段設(shè)置為null的解決方法的相關(guān)資料,需要的朋友可以參考下
    2023-04-04
  • Java實(shí)現(xiàn)“年-月-日 上午/下午時(shí):分:秒”的簡單代碼

    Java實(shí)現(xiàn)“年-月-日 上午/下午時(shí):分:秒”的簡單代碼

    當(dāng)前的日期輸出的方法有很多,本文為大家介紹下在java中是如何實(shí)現(xiàn)“年-月-日 上午/下午時(shí):分:秒”,感興趣的朋友不妨參考下
    2015-08-08

最新評(píng)論

平武县| 达日县| 广宗县| 宾川县| 佛冈县| 尉犁县| 四平市| 北票市| 邯郸市| 班玛县| 正安县| 奉节县| 会泽县| 永川市| 孟村| 启东市| 闻喜县| 孟连| 图们市| 滦南县| 大关县| 宁陵县| 萨迦县| 雷山县| 绥芬河市| 神农架林区| 电白县| 彩票| 沂水县| 大新县| 黄冈市| 绥芬河市| 白河县| 黑水县| 河曲县| 通城县| 宣化县| 汤阴县| 福州市| 蕉岭县| 灵寿县|