pringboot和vue结合的方案网络上的主要有如下两种:html
1. 【不推荐】在html中直接使用script标签引入vue和一些经常使用的组件,这种方式和之前传统的开发是同样的,只是能够很爽的使用vue的双向数据绑定,这种方式只适合于普通的全栈开发。前端
2.【推荐】使用vue官方的脚手架建立单独的前端工程项目,作到和后端彻底独立开发和部署,后端单独部署一个纯restful的服务,而前端直接采用nginx来部署,这种称为彻底的先后端分离架构开发模式,可是在分离中有不少api权限的问题须要解决,包括部署后的vue router路由须要在nginx中配置rewrite规则。这种先后端彻底分离的架构也是目前互联网公司所采用的,后端服务器再也不须要处理静态资源,也能减小后端服务器一些压力。vue
在传统行业中不少是以项目思想来主导的,而不是产品,一个项目会卖给不少的客户,而且部署到客户本地的机房里。在一些传统行业里面,部署实施人员的技术没法和互联网公司的运维团队相比,因为各类不定的环境也没法作到自动构建,容器化部署等。所以在这种状况下尽可能减小部署时的服务软件需求,打出的包数量也尽可能少。针对这种状况这里采用的在开发中作到先后端独立开发,整个开发方式和上面提到的第二种方式是相同的,可是在后端springboot打包发布时将前端的构建输出一块儿打入,最后只需部署springboot的项目便可,无需再安装nginx服务器。webpack
实际上本文中这种先后端分离的开发,前端开发好后将build构建好的dist下static中的文件拷贝到springboot的resource的static下,index.html则直接拷贝到springboot的resource的static下。下面是示例图:nginx
vue前端项目web
springboot项目:正则表达式
上面这是最简单的合并方式,可是若是做为工程级的项目开发,并不推荐使用手工合并,也不推荐将前端代码构建后提交到springboot的resouce下,好的方式应该是保持先后端彻底独立开发代码,项目代码互不影响,借助jenkins这样的构建工具在构建springboot时触发前端构建并编写自动化脚本将前端webpack构建好的资源拷贝到springboot下再进行jar的打包,最后就获得了一个彻底包含先后端的springboot项目了。spring
经过上面的整合后会出现两个比较大的问题:后端
1. 没法正常访问静态资源 。api
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:本方案只是在特定场景下的选择,若是一切条件容许,强力推荐作彻底的先后端分离