07、vue前后端整合

作者: spilledyear | 来源:发表于2018-04-21 19:31 被阅读314次

    vue-monitor作为前端应用,vue-admin作为后台提供标准接口,这是标准的前后端分离解决方案。本文主要讲解两个应用整合的过程。按理来说,也不存在什么整合的过程,这本就是两个独立的应用,只需要将两个应用分别启动就可以正常访问了,事实也确实如此,但再次之前需要先解决一个问题:跨域问题。

    启动vue-monitor

    进入vue-monitor跟目录,执行以下命令:

    npm run dev
    

    这样前端应用就启动了,这时候前端时完全依赖于node的。如果想不依赖于node也可以,可以执行

     npm run build 
    

    这样就可以将前端应用打包成静态资源,然后将这些静态资源部署到nginx服务器,同样可以正常访问,这里用的时第一种方法。

    启动vue-admin

    其实就是启动一个spring-boot应用,启动方法很多种,这里是在IDEA中直接 运行的

    image.png

    至此,两个应用已经完全启动了。

    前端调用后端接口

    在我们的前端应用中,有一个登录界面,效果如下

    注意,随着应用的开发,可能之后这些代码会被删除或者覆盖,这里只是 为了说明前端调用后端应用的一个示例。

    image.png

    当点击的登录按钮的时候,调用后台的登录接口,后台对应 的接口信息如下

    image.png

    我们期望的结果是:点击登录的时候,可以访问到后台这个接口。但是当我们点击登录按钮的时候,发现界面没有任何响应,后台也没有任何日志输出,这已经可以说明接口没有调用成功了。打开浏览器调试工具,发现有如下错误:

    image.png

    具体内容如下:

    Failed to load http://localhost:8081/api/system/login: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8082' is therefore not allowed access. The response had HTTP status code 403. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
    

    这是一个跨域问题,我们前端应用的端口是 8002,后台应用的端口是8001,存在跨域问题。怎么解决?有很多方法:

    • nginx方向代理
    • CORS

    这里用了CORS 解决跨域问题。第二种方法没有用过,之后补上吧。

    CORS解决跨域

    使用cors解决跨域问题,需要在后台将 前端域名 设置成可以访问。
    先在后台添加一个 过滤器:

    package com.hand.sxy.filter;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.Configuration;
    
    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * 不加 @Configuration 注解不生效
     *
     * @author spilledyear
     * @date 2018/4/21 18:42
     */
    @Configuration
    @WebFilter(urlPatterns = "/*")
    public class CorsFilter implements Filter {
        private Logger logger = LoggerFactory.getLogger(CorsFilter.class);
    
        @Override
        public void init(FilterConfig arg0) throws ServletException {
        }
    
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            logger.debug("跨域拦截");
    
            HttpServletResponse response = (HttpServletResponse) res;
    
            // 指定允许其他域名访问
            response.setHeader("Access-Control-Allow-Origin", "*");
            // 响应类型
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE");
            // 响应头设置
            response.setHeader("Access-Control-Allow-Headers", "token,Content-Type,Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Max-Age,authorization");
            response.setHeader("Access-Control-Max-Age", "3600");
    
            chain.doFilter(request, response);
        }
    
        @Override
        public void destroy() {
    
        }
    }
    

    这里有一个需要注意的地方,需要添加 @Configuration注解,要不然这个filter 是不生效了。添加过滤器后,重新启动后台应用,再次点击登录按钮,发现还是报错了,报错信息如下:

    image.png
    Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'http://localhost:8082' is therefore not allowed access.
    

    大概意思是说, 后台中的响应头不能设置 Access-Control-Allow-Origin 的值为 通配符 *。这时候对后台应用该稍作修改

    // 指定允许其他域名访问,因为前端应用域名是 http://localhost:8082
    response.setHeader("Access-Control-Allow-Origin", "http://localhost:8082");
    

    再次重新启动该后台应用,在前端再次访问,发现还是失败了,报错信息如下:

    Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. Origin 'http://localhost:8082' is therefore not allowed access
    
    提示当 request's credentials mode 的值是  'include' 的时候, Access-Control-Allow-Credentials 在响应头中必输设置成 true。
    

    这让我想到一个问题,那就是在前端应用中 request 请求头设置问题,内容如下:

    let request = {
      credentials: 'include',
      method: type,
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      mode: "cors",
      cache: "force-cache"
    }
    

    果然设置了 credentials: 'include'。
    可是这个参数我并不太清楚是干嘛的,解决方法有两个:
    1、在后台添加一行代码,设置 Access-Control-Allow-Credentials 的值为 true

    response.setHeader("Access-Control-Allow-Credentials", "true");
    

    2、在前端的请求头中,去除 credentials: 'include' 这个设置

    let request = {
      method: type,
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      mode: "cors",
      cache: "force-cache"
    }
    

    至此,跨域问题得到解决。再次点击登录按钮,查看后台日志:

    image.png

    从上图中的日志中可以看出,成功的调用了 登录接口。

    接收参数

    跨域问题是成功解决了,但是参数还没有传过来。为了将用户名和密码传到后台测试,对后台代码稍作修改。

    //对查询方法稍作修改
    <select id="query" resultMap="BaseResultMap" parameterType="com.hand.sxy.account.dto.User">
        SELECT * FROM USER WHERE USERNAME = #{username} AND PASSWORD = #{password}
    </select>
    

    然后其它地方也稍作修改,具体的请看源码,controller 中的代码如下

    package com.hand.sxy.system.controller;
    
    import com.hand.sxy.account.dto.User;
    import com.hand.sxy.system.dto.Result;
    import com.hand.sxy.system.service.ILoginService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.List;
    
    /**
     * @author spilledyear
     * @date 2018/4/21 12:58
     */
    @RestController
    public class LoginController {
        private Logger logger = LoggerFactory.getLogger(LoginController.class);
    
        @Autowired
        private ILoginService loginService;
    
        @RequestMapping(value = "/api/system/login", method = RequestMethod.POST)
        public Result login(HttpServletRequest request, User user) {
    
            List<User> userList = loginService.login(user);
            Result result = new Result(userList);
    
            if (userList == null || userList.isEmpty()) {
                logger.info("登录失败,用户名或密码错误");
                result.setSuccess(false);
                result.setMessage("用户名或密码错误");
            }
            logger.info("登录成功");
            return result;
        }
    
    }
    

    修改之后,重启后台系统,在前端界面输入用户名和密码,点击登录按钮,用浏览器调试工具发现前端调用了请求,参数也传了,但是在后台打断点发现参数没有穿过来。
    解决方法:方法加上 @RequestBody 注解

    package com.hand.sxy.system.controller;
    
    import com.hand.sxy.account.dto.User;
    import com.hand.sxy.system.dto.Result;
    import com.hand.sxy.system.service.ILoginService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.List;
    
    /**
     * @author spilledyear
     * @date 2018/4/21 12:58
     */
    @RestController
    public class LoginController {
        private Logger logger = LoggerFactory.getLogger(LoginController.class);
    
        @Autowired
        private ILoginService loginService;
    
        @RequestMapping(value = "/api/system/login", method = RequestMethod.POST)
        public Result login(HttpServletRequest request, @RequestBody User user) {
    
            List<User> userList = loginService.login(user);
            Result result = new Result(userList);
    
            if (userList == null || userList.isEmpty()) {
                logger.info("登录失败,用户名或密码错误");
                result.setSuccess(false);
                result.setMessage("用户名或密码错误");
            }
            logger.info("登录成功");
            return result;
        }
    
    }
    

    @RequestBody用于读取Request请求的body部分数据,使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对象上,然后再把HttpMessageConverter返回的对象数据绑定到 controller中方法的参数上。

    @RequestBody注解是否必须要,根据request header Content-Type的值来判断
    GET、POST方式提时

    • application/x-www-form-urlencoded, 可选(即非必须,因为这种情况的数据@RequestParam, @ModelAttribute也可以处理,当然@RequestBody也能处理)
    • multipart/form-data, 不能处理(即使用@RequestBody不能处理这种格式的数据)
    • 其他格式, 必须(其他格式包括application/json, application/xml等。这些格式的数据,必须使用@RequestBody来处理)

    PUT方式提交时

    • application/x-www-form-urlencoded, 必须
    • multipart/form-data, 不能处理
    • 其他格式, 必须

    说明:request的body部分的数据编码格式由header部分的Content-Type指定;

    @ResponseBody用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。 在Controller中返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用。

    相关文章

      网友评论

      • PGOne爱吃饺子:你好 ,可以问你一个问题么,请问vue如何导入你自己写的一个模块,我导入的时候为什么报错呢,谢谢
      • IT人故事会:谢谢分享,相互学习!

      本文标题:07、vue前后端整合

      本文链接:https://www.haomeiwen.com/subject/izfflftx.html