springboot+vue的前后端分离与合并方案
springboot+vue的前后端分离与合并方案
上官胡闹 发表于6个月前
springboot+vue的前后端分离与合并方案
  • 发表于 6个月前
  • 阅读 5530
  • 收藏 356
  • 点赞 6
  • 评论 30

移动开发云端新模式探索实践 >>>   

摘要: springboot+vue的前后端分离与合并

springboot和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的静态资源处理前缀,代码:

@Configuration
public 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;
    }
}

过滤器的注册:

@SpringBootApplication
public 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:本方案只是在特定场景下的选择,如果一切条件允许,强力推荐做完全的前后端分离

版权申明:转载请注明出处,否则后果自负

  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
粉丝 36
博文 66
码字总数 54168
评论 (30)
技术粉
mark
一只小桃子
没必要吧,vue就vue,后台用什么随便
上官胡闹

引用来自“一只小桃子”的评论

没必要吧,vue就vue,后台用什么随便
这只是特定场景的一种选择方案,就像文中描述的一样。
-BlueSky-
前后端分离不都是用spring boot + node.js吗?
上官胡闹

引用来自“lovejar”的评论

前后端分离不都是用spring boot + node.js吗?
加node.js当然更好,用node作为中间层,但也不是必须的,根据实际情况而定
HeyS1
哈哈,我是后端,我就是“在html中直接使用script标签引入vue和一些常用的组件”

那vue必须要使用webpack打包吗
上官胡闹

引用来自“HeyS1”的评论

哈哈,我是后端,我就是“在html中直接使用script标签引入vue和一些常用的组件”

那vue必须要使用webpack打包吗
webpack主要是做前端工程的自动化,当然如果你用到es6的语法的可能导致一些浏览器不能很好的支持,这时候需要使用babel将es6转化成es5就需要用到webpack了
william-wang
不错的方案,目前也在准备进行调整相关的开发,感谢!
开源中国首席颈椎砖家
这需要大量前端开发人员
Flylinran
题主能不能贴个完整的项目结构图
郑勇
不错
上官胡闹

引用来自“Flylinran”的评论

题主能不能贴个完整的项目结构图
后端就是个springboot的项目,前端就按照vue官方说明使用vue-cli脚手夹单独创建个前端工程,前后端就是完全分开来开发的,打包合并后的样子就是文中第二张图那样
砰的一枪
Good idea.
But,why don't you make a new static folder inside static?
and then index.html and other vue static are ok.

Flylinran

引用来自“上官胡闹”的评论

引用来自“Flylinran”的评论

题主能不能贴个完整的项目结构图
后端就是个springboot的项目,前端就按照vue官方说明使用vue-cli脚手夹单独创建个前端工程,前后端就是完全分开来开发的,打包合并后的样子就是文中第二张图那样

是这种:
-- 项目
-- font-end
-- ...
-- src
-- main...
-- test
-- pom.xml

还是:
-- 项目
-- back-end
-- src
-- pom.xml
-- font-end
-- ...
Flylinran

引用来自“Flylinran”的评论

引用来自“上官胡闹”的评论

引用来自“Flylinran”的评论

题主能不能贴个完整的项目结构图
后端就是个springboot的项目,前端就按照vue官方说明使用vue-cli脚手夹单独创建个前端工程,前后端就是完全分开来开发的,打包合并后的样子就是文中第二张图那样

是这种:
-- 项目
-- font-end
-- ...
-- src
-- main...
-- test
-- pom.xml

还是:
-- 项目
-- back-end
-- src
-- pom.xml
-- font-end
-- ...

不好意思,这里评论没法格式化
Flylinran
如果是把前端项目放到springboot项目下,那用idea打开这个项目时,idea会较长时间的扫描node-modules文件夹
上官胡闹

引用来自“Flylinran”的评论

如果是把前端项目放到springboot项目下,那用idea打开这个项目时,idea会较长时间的扫描node-modules文件夹
首先用idea你可以建两个module,一个是后端,一个是前端,两个互不影响,然后可以在idea中配置File tyles来过滤掉node-modules,包括一些你不想看到的文件。
还有一种是你可以让前后端是完全独立项目,提交也是到两个不同的代码仓库
老范的自留地
我们就是这么用的。
老范的自留地

引用来自“lovejar”的评论

前后端分离不都是用spring boot + node.js吗?
node只是运行时环境。。
老范的自留地

引用来自“Flylinran”的评论

如果是把前端项目放到springboot项目下,那用idea打开这个项目时,idea会较长时间的扫描node-modules文件夹
不需要放到一个项目里。只需要把vue工程打包好的静态文件放到 springboot的resources中即可
×
上官胡闹
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: