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-boot | 2.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同级目录下
调整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/