SpirngBoot与VUE开发分离,部署不分离

680 阅读3分钟

SpirngBoot与VUE开发分离,部署不分离

背景

目前web项目开发普遍流行SpringBoot + Vue 框架前后端分离的方式开发。上线时,后端以Jar包、前端以Nginx的形式来进行部署。笔者在经历一些特表小的项目的时候,发现这样的部署方式还是比较繁琐,既然后端Jar包内嵌了Tomcat或者其他的服务引擎,那么前端以静态的方式嵌入其中,这样只需要Jar和前端静态资源就可以完成上线部署。按照这个想法,笔者尝试后端Jar包和前端静态资源放于同级目录下进行部署,这样减少了Nginx的部署,前后端的发布也互不影响。

此文记录了笔者搭建的过程,对过程中出现了问题以及解决的思路。

版本

前端版本

前端框架:vben

"dependencies": {
    "@ant-design/icons-vue": "^6.1.0",
    "@iconify/iconify": "^3.1.1",
    "@logicflow/core": "^1.2.9",
    "@logicflow/extension": "^1.2.9",
    "@vben/hooks": "workspace:*",
    "@vue/shared": "^3.3.4",
    "@vueuse/core": "^10.2.1",
    "@vueuse/shared": "^10.2.1",
    "@zxcvbn-ts/core": "^3.0.2",
    "ant-design-vue": "^4.0.6",
    "axios": "^1.4.0",
    "codemirror": "^5.65.12",
    "cropperjs": "^1.5.13",
    "crypto-js": "^4.1.1",
    "dayjs": "^1.11.9",
    "driver.js": "^1.3.0",
    "echarts": "^5.4.2",
    "exceljs": "^4.3.0",
    "lodash-es": "^4.17.21",
    "mockjs": "^1.1.0",
    "nprogress": "^0.2.0",
    "path-to-regexp": "^6.2.1",
    "pinia": "2.1.4",
    "pinia-plugin-persistedstate": "^3.2.0",
    "print-js": "^1.6.0",
    "qrcode": "^1.5.3",
    "qs": "^6.11.2",
    "resize-observer-polyfill": "^1.5.1",
    "showdown": "^2.1.0",
    "sortablejs": "^1.15.0",
    "tinymce": "^5.10.7",
    "unocss": "0.53.4",
    "vditor": "^3.9.4",
    "vue": "^3.3.4",
    "vue-i18n": "^9.6.4",
    "vue-json-pretty": "^2.2.4",
    "vue-router": "^4.2.3",
    "vue-types": "^5.1.0",
    "vuedraggable": "^4.1.0",
    "vxe-table": "^4.4.5",
    "vxe-table-plugin-export-xlsx": "^3.0.4",
    "xe-utils": "^3.5.11",
    "xlsx": "^0.18.5"
  },
  "devDependencies": {
    "@commitlint/cli": "^17.6.6",
    "@commitlint/config-conventional": "^17.6.6",
    "@iconify/json": "^2.2.87",
    "@purge-icons/generated": "^0.9.0",
    "@types/codemirror": "^5.60.8",
    "@types/crypto-js": "^4.1.1",
    "@types/lodash-es": "^4.17.7",
    "@types/mockjs": "^1.0.7",
    "@types/nprogress": "^0.2.0",
    "@types/qrcode": "^1.5.1",
    "@types/qs": "^6.9.7",
    "@types/showdown": "^2.0.1",
    "@types/sortablejs": "^1.15.1",
    "@vben/eslint-config": "workspace:*",
    "@vben/stylelint-config": "workspace:*",
    "@vben/ts-config": "workspace:*",
    "@vben/types": "workspace:*",
    "@vben/vite-config": "workspace:*",
    "@vue/compiler-sfc": "^3.3.4",
    "@vue/test-utils": "^2.4.0",
    "cross-env": "^7.0.3",
    "cz-git": "^1.6.1",
    "czg": "^1.6.1",
    "husky": "^8.0.3",
    "lint-staged": "13.2.3",
    "prettier": "^2.8.8",
    "prettier-plugin-packagejson": "^2.4.6",
    "rimraf": "^5.0.1",
    "turbo": "^1.10.7",
    "typescript": "^5.2.2",
    "unbuild": "^1.2.1",
    "vite": "^4.4.0",
    "vite-plugin-mock": "^2.9.6",
    "vue-tsc": "^1.8.4"
  },
  "packageManager": "pnpm@8.1.0",
  "engines": {
    "node": ">=16.15.1",
    "pnpm": ">=8.1.0"
  },
  "volta": {
    "node": "20.16.0"
  }

后端版本

开源框架版本
Spring-boot2.7.12

部署方案

前端资源包放入后端resource

查看org.springframework.boot.autoconfigure.web.WebProperties可以看出staticLocations默认的配置为:

private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
                "classpath:/resources/", "classpath:/static/", "classpath:/public/" };

根据默认配置,将打包的静态资源dist目录下的所有文件放在/resources/public下面。

打开地址: http://127.0.0.1:8081/index.html报错403

/index.html加入到放权队列中。再次打开首页,发现assets/*favicon.ico/_app.config.js都被权限拦截了,将这些路由都添加到放权队列中。

再次打开首页,发现页面正常展示。

下面是后端 application.properties的重要配置:

# 静态资源的访问路径
spring.mvc.static-path-pattern=/**
​
# 权限放行队列
# 根据次配置内置实现Spring Security 中的放行
modern.security.permit-urls[0]=/index.html
modern.security.permit-urls[1]=/assets/**
modern.security.permit-urls[2]=/favicon.ico
modern.security.permit-urls[3]=/_app.config.js

Spring Security 放行配置关键代码如下(包含了一些笔者封装的类可以忽略):

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableConfigurationProperties(SpringSecurityProperties.class)
public class SpringSecurityConfiguration {
    /**
     * 认证资源过滤链配置
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http,
                                                   UserDetailsService userDetailsService, AuthenticationTokenFilter authenticationTokenFilter) throws Exception {
        //支持跨域
        http.cors().and()
                //不使用session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                //csrf关闭
                .and().csrf().disable()
                .authorizeRequests(rep -> rep
                        .antMatchers(noAuthConfiguration.getPermitAllUrls().toArray(new String[0])).permitAll()
                        // 剩下的全部页面需要执行认证
                        .anyRequest().authenticated())
​
                .exceptionHandling()
                //授权异常处理
                .accessDeniedHandler(new AccessDeniedHandler())
                //自定义认证
                .and()
                //token 验证
                .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
                .userDetailsService(userDetailsService);
        return http.build();
    }
​
}
​
@Data
public class SpringSecurityProperties {
    /**
     * 放行的地址
     */
    private String[] permitUrls = new String[0];
}
​
@Component
public class NoAuthConfiguration implements InitializingBean, ApplicationContextAware {
    private final List<String> permitAllUrls = new ArrayList<>();
​
    @Override
    public void afterPropertiesSet() throws Exception {
        // 其他逻辑
    }
​
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        SpringSecurityProperties properties = this.applicationContext.getBean(SpringSecurityProperties.class);
        if(ArrayUtils.isNotEmpty(properties.getPermitUrls())) {
            this.permitAllUrls.addAll(Arrays.asList(properties.getPermitUrls()));
        }
    }
​
    public List<String> getPermitAllUrls() {
        return permitAllUrls;
    }
}

Vue 中关键配置

.env.production中将 VITE_GLOB_API_URL设置为 后端的访问地址

前端资源包放在后端Jar同级目录下

image-20241027133838638.png

调整application-{env}.properties即可,如下:

#security
modern.security.storage-policy=useRedis
modern.security.permit-urls[0]=/index.html
modern.security.permit-urls[1]=/assets/**
modern.security.permit-urls[2]=/favicon.ico
modern.security.permit-urls[3]=/_app.config.js
​
# 静态资源的访问路径
spring.mvc.static-path-pattern=/**
# 静态资源的目录
spring.web.resources.static-locations[0]=file:dist/