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:8082response.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 */@RestControllerpublic 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 */@RestControllerpublic 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等)使用。
作者:spilledyear