07、vue前后端整合
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等)使用。