springboot+vue的前后端分离与合并方案
pringboot和vue结合的方案网络上的主要有以下两种:
1. 【不推荐】在html中直接使用script标签引入vue和一些常用的组件,这种方式和以前传统的开发是一样的,只是可以很爽的使用vue的双向数据绑定,这种方式只适合于普通的全栈开发。
2.【推荐】使用vue官方的脚手架创建单独的前端工程项目,做到和后端完全独立开发和部署,后端单独部署一个纯restful的服务,而前端直接采用nginx来部署,这种称为完全的前后端分离架构开发模式,但是在分离中有很多api权限的问题需要解决,包括部署后的vue router路由需要在nginx中配置rewrite规则。这种前后端完全分离的架构也是目前互联网公司所采用的,后端服务器不再需要处理静态资源,也能减少后端服务器一些压力。
一、为什么做前后端分离开发合并
在传统行业中很多是以项目思想来主导的,而不是产品,一个项目会卖给很多的客户,并且部署到客户本地的机房里。在一些传统行业里面,部署实施人员的技术无法和互联网公司的运维团队相比,由于各种不定的环境也无法做到自动构建,容器化部署等。因此在这种情况下尽量减少部署时的服务软件需求,打出的包数量也尽量少。针对这种情况这里采用的在开发中做到前后端独立开发,整个开发方式和上面提到的第二种方式是相同的,但是在后端springboot打包发布时将前端的构建输出一起打入,最后只需部署springboot的项目即可,无需再安装nginx服务器。
二、springboot和vue整合的关键操作
实际上本文中这种前后端分离的开发,前端开发好后将build构建好的dist下static中的文件拷贝到springboot的resource的static下,index.html则直接拷贝到springboot的resource的static下。下面是示例图:
vue前端项目
springboot项目:
上面这是最简单的合并方式,但是如果作为工程级的项目开发,并不推荐使用手工合并,也不推荐将前端代码构建后提交到springboot的resouce下,好的方式应该是保持前后端完全独立开发代码,项目代码互不影响,借助jenkins这样的构建工具在构建springboot时触发前端构建并编写自动化脚本将前端webpack构建好的资源拷贝到springboot下再进行jar的打包,最后就得到了一个完全包含前后端的springboot项目了。
三、整合的核心问题处理
通过上面的整合后会出现两个比较大的问题:
1. 无法正常访问静态资源 。
2. vue router路由的路径无法正常解析 。
解决第一个问题,我们必须重新指定springboot的静态资源处理前缀,代码:
@Configurationpublic class SpringWebMvcConfig extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); super.addResourceHandlers(registry); }}
解决第二个问题的方式是对vue的路由的路径做rewrite,交给router来处理,而不是springboot自己处理,rewrite时可以考虑路由的路径统一增加后最,然后在springboot中编写过滤拦截特定后缀来做请求转发交给vue的路由处理。如:
const router = new VueRouter({ mode: 'history', base: __dirname, routes: [ { path: '/ui/first.vhtml', component: First }, { path: '/ui/second.vhtml', component: secondcomponent } ]})
后端拦截到带有vhtml的都交给router来处理,这种方式在后端写过滤器拦截后打包是完全可行的,但是前端开发的直接访问带后缀的路径会有问题。
另外一种方式是给前端的路由path统一加个前缀比如/ui,这时后端写过滤器匹配该前缀,也不会影响前端单独开发是的路由解析问题。过滤器参考如下:
/*** be used to rewrite vue router** @author yu on 2017-11-22 19:47:23.*/
public class RewriteFilter implements Filter { /** * 需要rewrite到的目的地址 */
public static final String REWRITE_TO = "rewriteUrl"; /** * 拦截的url,url通配符之前用英文分号隔开 */
public static final String REWRITE_PATTERNS = "urlPatterns"; private Set<String> urlPatterns = null;//配置url通配符 private String rewriteTo = null;
@Override public void init(FilterConfig cfg) throws ServletException { //初始化拦截配置 rewriteTo = cfg.getInitParameter(REWRITE_TO);
String exceptUrlString = cfg.getInitParameter(REWRITE_PATTERNS);
if (StringUtil.isNotEmpty(exceptUrlString)) { urlPatterns = Collections.unmodifiableSet( new HashSet<>(Arrays.asList(exceptUrlString.split(";", 0))));
} else { urlPatterns = Collections.emptySet(); } }
@Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req;
String servletPath = request.getServletPath();
String context = request.getContextPath(); //匹配的路径重写 if (isMatches(urlPatterns, servletPath)) { req.getRequestDispatcher(context+"/"+rewriteTo).forward(req, resp);
}else{ chain.doFilter(req, resp); } }
@Override public void destroy() { } /** * 匹配返回true,不匹配返回false * @param patterns 正则表达式或通配符 * @param url 请求的url * @return */
private boolean isMatches(Set<String> patterns, String url) { if(null == patterns){ return false; }
for (String str : patterns) {
if (str.endsWith("/*")) { String name = str.substring(0, str.length() - 2);
if (url.contains(name)) { return true; } } else { Pattern pattern = Pattern.compile(str); if (pattern.matcher(url).matches()) { return true; } } } return false; }}
过滤器的注册:
@SpringBootApplicationpublic class SpringBootMainApplication { public static void main(String[] args) { SpringApplication.run(SpringBootMainApplication.class, args); } @Bean public EmbeddedServletContainerCustomizer containerCustomizer() { return (container -> { ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/errors/401.html");
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/errors/404.html"); ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/errors/500.html");
container.addErrorPages(error401Page, error404Page, error500Page); }); }
@Bean public FilterRegistrationBean testFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new RewriteFilter());//注册rewrite过滤器 registration.addUrlPatterns("/*"); registration.addInitParameter(RewriteFilter.REWRITE_TO,"/index.html"); registration.addInitParameter(RewriteFilter.REWRITE_PATTERNS, "/ui/*"); registration.setName("rewriteFilter"); registration.setOrder(1); return registration; }}
这时springboot就可以将前端的路由资源交给路由来处理了。至此整个完整前后端分离开发合并方案就完成了。这种方式在后期有条件情况下也可以很容易做到前后端的完全分离开发部署。
ps:本方案只是在特定场景下的选择,如果一切条件允许,强力推荐做完全的前后端分离
springboot+vue的前后端分离与合并方案的更多相关文章
- 一套基于SpringBoot+Vue+Shiro 前后端分离 开发的代码生成器
一.前言 最近花了一个月时间完成了一套基于Spring Boot+Vue+Shiro前后端分离的代码生成器,目前项目代码已基本完成 止步传统CRUD,进阶代码优化: 该项目可根据数据库字段动态生成 c ...
- SpringBoot+vue+Iview前后端分离权限内容管理CMS系统
hrcms基于springBoot框架的内容管理系统,采用最新最主流的技术,后端采用spring boot,mybatis-plus,freemaker,shiro,redis,mysql,等,主要功 ...
- SpringBoot+Jpa+SpringSecurity+Redis+Vue的前后端分离开源系统
项目简介: eladmin基于 Spring Boot 2.1.0 . Jpa. Spring Security.redis.Vue的前后端分离的后台管理系统,项目采用分模块开发方式, 权限控制采用 ...
- Flask & Vue 构建前后端分离的应用
Flask & Vue 构建前后端分离的应用 最近在使用 Flask 制作基于 HTML5 的桌面应用,前面写过<用 Python 构建 web 应用>,借助于完善的 Flask ...
- gin+vue的前后端分离开源项目
该项目是gin+vue的前后端分离项目,使用gorm访问MySQL,其中vue前端是使用vue-element-admin框架简单实现的: go后台使用jwt,对API接口进行权限控制.此外,Web页 ...
- dotnetcore vue+elementUI 前后端分离架二(后端篇)
前言 最近几年前后端分离架构大行其道,而且各种框架也是层出不穷.本文通过dotnetcore +vue 来介绍 前后端分离架构实战. 涉及的技术栈 服务端技术 mysql 本项目使用mysql 作为持 ...
- Vue .Net 前后端分离框架搭建
[参考]IntellIJ IDEA 配置 Vue 支持 打开Vue项目 一.前端开发环境搭建 1.零基础 Vue 开发环境搭建 打开运行Vue项目 2.nodejs http-proxy-middle ...
- 《论vue在前后端分离项目中的实践之年终总结》
我是2014年的时候开始了解知道的vue,当时vue还不太成熟,想用但是又怕自己hold不住,况且那时候vue还没有成熟的(路由.验证.ui组件)插件,社区也是不温不火的,再说也没有合适的机遇让我去项 ...
- 【转】python+django+vue搭建前后端分离项目
https://www.cnblogs.com/zhixi/p/9996832.html 以前一直是做基于PHP或JAVA的前后端分离开发,最近跟着python风搭建了一个基于django的前后端分享 ...
随机推荐
- 后缀自动机求多串LCS——spojlcs2
/* 每个状态存最长匹配长度,然后多个串匹配过程中取最小的最长匹配长度 和LCS1不同的地方:LCS只要维护住当前匹配长度和最长匹配长度即可,但是多串匹配需要维护的是每个状态结点(即后缀树上)的信息 ...
- Win7如何部署定制的Quicklaunch图标
在严格的网络管理环境中,最终用户的权限被限制得比较严格,被禁止随意改变系统的行为.在我们的网络环境中,学生是被禁止添加/删除QuickLaunch上的图标的,不仅如此,无线网络,打印机等等,都受到严格 ...
- APIO 2007 风铃
题目描述 你准备给弟弟 Ike 买一件礼物,但是,Ike 挑选礼物的方式很特别:他只喜欢那些能被他排成有序形状的东西. 你准备给 Ike 买一个风铃.风铃是一种多层的装饰品,一般挂在天花板上. 每个风 ...
- NPAPI插件开发新手容易遇到的问题
在网上找了一个npdemo的例子,编译了一下在FireFox运行正常,在Chrome下就是不行,也没任何提示. 折腾了好久,最后发现是rc文件 支持语言编码问题 NPAPI插件开发详细记录:用VS20 ...
- 可搭建SS服务上网的不限流量VPS推荐
https://itldc.com/en,7个机房,推荐指数:★★★★ 1995年运作至今,有多个机房,包括:新加坡.洛杉矶.新泽西.立陶宛.乌克兰.保加利亚.荷兰.VPS特征: KVM虚拟(支持BB ...
- 使用nginx访问本地电脑的目录文件
cat /usr/local/opt/nginx/ //nginx路径 cd /usr/local/opt/nginx/html //localhost的指向 ln -s ~/Documents do ...
- LA 3263 /// 欧拉定理 oj21860
题目大意: n个端点的一笔画 第n个和第1个重合 即一笔画必定是闭合曲线 输出平面被分成的区域数 欧拉定理 V+F-E=2 即 点数+面数-边数=2 (这里的面数包括了外部) #include < ...
- python接口自动化(接口基础)
一.什么是接口? 前端负责展示和收集数据 后端负责处理数据,返回对应的结果 接口是前端与后端之间的桥梁,传递数据的通道 二.
- css3的选择器
先来做一下准备工作 页面的效果: 看到上面的框框了吗?我们就是通过给这些框框添加背景色的方式,来让大家,了解css3的选择器的效果,下面正式开始: 关联选择器 E1~E2 选择 E1 后面的兄弟 E2 ...
- C++之控制内存分配
一.内存分配方式 在C++中,内存分成5个区,他们分别是堆.栈.自由存储区.全局/静态存储区和常量存储区.栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释 ...