前后端分离开发
在前后端开发中会遇到很多问题,在这我就笼统的称用户界面部分为前端,处理以及数据库交互部分称为后盾,页面则仅仅为单一的一个页面例如:
- 前端如何向后端发起请求?
- 前端需要传输的数据如何向后端发送,通过怎样的方式请求?
- 请求成功后该如何向前端传输成功后的信息?
- 前端又怎样获得后端传来的数据?
- 获得数据之后前端又该如何保存获得的数据?
- 前端的页面跳转该如何(是通过后端直接跳转)还是通过前端跳转?
- 以及在后端开发时所用的request,response,session,context等等在前端同样适用吗?
使用sessionid传递参数
问题1:
用户看到的都是前端页面,那么一切的开始也是通过前端的第一的页面(一般为登录页面),或者首页。那么就引出了第一个问题,当用户要登录或验证登录信息时,如何向后端发送请求?
-
分析:
- 首先我们要明白前后端分离开发不同的资源的分布在不同的服务器上的。
明白了上面的前提,我们就会想到如何访问服务器,直接将两台服务器关联就行了,在前端用户发起请求时让前端的服务器去访问后端服务器的某个处理器,OK这种想法是对的,那么我们一般上网在地址栏会看到http://等开头,其实这就是在访问某个服务器的开头。那么我们需要在前端怎样设置能达到我们预期的效果那?
-
实践
在.js文件中
// 全局变量 请求服务器的格式------->请求://ip地址:端口号
var serverAddress = “http://localhost:9999”;
页面请求(ajax)
注意调用js文件
ajax异步请求,传参数,在前端页面处理返回的结果并进行页面之间的跳转
$.post(serverAddress+"/login/tosuccess",
data.field,function(res){
console.log(res);
if(res.code == 0){
layer.msg('登录成功',{icon: 1,time:900},function(){
sessionStorage.setItem("id",res.user.id);
sessionStorage.setItem("account",res.user.account);
location.href="success.html";
});
}else if(res.code == 1){
layer.msg('账号或密码错误',{icon: 2,time:900});
}else{
layer.msg('服务器在忙,请稍后重试',{icon: 3,time:1000});
}
},"json");")
后端Controller
在其处理类的头部要添加以下注解标签
@RestController
@RequestMapping(path="/login")
@CrossOrigin(origins=“http://127.0.0.1:8848”)
@RequestMapping(path="/tosuccess")
public Map<String, Object> toSuccess(HttpServletRequest request){
Map<String, Object> map = null;
try {
map = new HashMap<String, Object>();
String account = request.getParameter("account");
String password = request.getParameter("password");
User user = userService.findUserByAcPs(account,password);
if (user != null) {
map.put("code", 0);
map.put("user", user);
}else{
map.put("code", 1);
}
} catch (Exception e) {
e.printStackTrace();
map.put("code", 2);
}
return map;
}
通过上面的案例,我们大概能够进行前后端开发的流程,前端通过向后端服务器发送异步请求,带参数,后端通过Crontroller处理后通过注解标签返回map。
success.html页面bodu内容
<span id="msgId"></span>登录成功
<script type="text/jscript">
//从浏览器中取出账号信息
var id = sessionStorage.getItem("id");
var account = sessionStorage.getItem("account");
document.getElementById("msgId").innerHTML=account+":::"+id;
</script>
问题2:
通过上面简单的搭建分离开发的基本问题,我们已经向后端发送请求,并获得返回值,但是我们如何保存从后端返回来的值,在跳转后的页面继续使用,其实细心的人已经发现在上面异步请求之后我们使用
sessionStorage.setItem(“id”,res.user.id);
sessionStorage.setItem(“account”,res.user.account);
以上代码之间存储在window中可以在浏览器中打开开发人员模式在application中看到存储的键值对。
那么可以通过session传值吗?可以
1.当登录成功时,将sessionid返回给前端,并保存在window中。
2.每次将sessionid传回到后端";jsessionid="+
3.但是当长时间不操作时,session会失效,所以必须验证
实践;
.js文件
// 全局变量
var serverAddress = "http://localhost:9999";
var id = sessionStorage.getItem("id");
var account = sessionStorage.getItem("account");
var sessionid = sessionStorage.getItem("sessionid");
// 定义方法,即调即用,不调不用
function isLogin(){
$.ajax({
url:serverAddress+"/login/toSession;jsessionid="+sessionid,
type:"post",
async:false,
success:function(res){
if(res == 1){
location.replace("index.html");
}else if(res ==2){
location.href = "500.html";
}
}
})
}
前端页面代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>success</title>
<script type="text/javascript" src="../js/jquery-3.5.1.min.js"></script>
<script src="../js/common.js"></script>
</head>
<body>
<span id="msgId"></span>登录成功
<input type="button" value="保存" id="saveId" />
<script type="text/jscript">
// 直接从js文件中获取账号信息
document.getElementById("msgId").innerHTML=account+":::"+id;
isLogin();// 直接验证页面
$("#saveId").click(function(){
isLogin();// 在操作保存按钮时验证session是否失效,失效返回登录页面
$.post(serverAddress+"/login/tosave;jsessionid="+sessionid);
})
</script>
</body>
</html>
后端controller处理
返回的jsessionid主要没有失效而且和登录时session一致,那么就能够给session赋属性值以及获得属性值
/**
* 登录成功,session是否失效
* @param request
* @param session
* @return
*/
@RequestMapping(path="/toSession")
public int toSession(HttpSession session){
try {
User user = (User)session.getAttribute("user");
if(user != null){
return 0;
}else{
return 1;
}
} catch (Exception e) {
e.printStackTrace();
return 2;
}
}
@RequestMapping(path="/tosave")
public void toSave(HttpSession session){
System.out.println(session.getId());
}
问题3:
上面我们已经能够进行访问,以及数据之间的存储和获得,那么页面之间的跳转该如何解决?
首先我们要知道一切页面都得跳转都在前端完成,后端只是处理并且返回处理的结果,那么这就要求你熟悉html,css,js等前端知识。
但是在前端当我们要动态的获取一些多个值时,像下拉框,复选框是该如何请求,在哪个页面发起请求,获得返回值后该如何处理才能达到下拉框,复选框的效果。
// jsp中可以直接通过<c:forEach></c:forEach>标签获得
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
// 获得部门列表
<c:forEach items="${deptList}" var="dept">
<option value="${dept.id}">${dept.name}</option>
</c:forEach>
// 获得菜单列表
<c:forEach items="${menuList}" var="menu" varStatus="i">
<input type="checkbox" title="${menu.name}" name="menus" value="${menu.id }"/>
</c:forEach>
// 通过异步请求获得部门以及菜单
$(function(){
$.post(serverAddress+"/user/addUser;jsessionid="+sessionid,function(res){
console.log(res);
var deptStr = "";
for(var i = 0; i < res.deptList.length;i++){
deptStr += "<option value="+res.deptList[i].id+">"+res.deptList[i].name+"</option>";
}
$("#did").html(deptStr);
form.render('select');
var menuStr = "";
for(var i = 0; i < res.menuList.length;i++){
menuStr += "<input type='checkbox' title="+res.menuList[i].name+" name='menus' value="+res.menuList[i].id +" />";
}
$("#mid").html(menuStr);
form.render('checkbox');
})
});
问题4:
在前端我们会用到日期等一些需要转换特定格式输出的数据
-- 2019-07-01 12:00:00
function time(time = +new Date()) {
console.log(time)
var date = new Date(time + 8 * 3600 * 1000); // 增加8小时
return date.toJSON().substr(0, 19).replace('T', ' ');
}
console.log(time(+new Date("2014-10-01 12:00:00")))//里面穿的格式只要符合要求就行例如: 2014/02/12 2014-12-21 时间不传默认00:00:00
-- 2019-12-12
function date (date) {
var nowdate = new Date(date).toLocaleDateString().replace(/\//g, '-')
return nowdate
}
-- 2019/12/12
function date (date) {
var nowdate = new Date(date).toLocaleDateString()
return nowdate
}
-- 2019年12月12日
function getdate() {
var now = new Date(),
y = now.getFullYear(),
m = ("0" + (now.getMonth() + 1)).slice(-2),
d = ("0" + now.getDate()).slice(-2);
return y + "-" + m + "-" + d + " " + now.toTimeString().substr(0, 8);
}
-- 正则时间格式化
var date = "2018-10-08 15:22:45";
//格式化掉 时分秒
var newDate=/\d{4}-\d{1,2}-\d{1,2}/g.exec(date)
//格式化掉秒
var newDate=/\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}/g.exec(date)
问题5:
针对问题3我们已经完美的解决了,将后端传来的数据进行下拉框,复选框等赋值的操作,那么等我们需要修改时,也就是说我们需要将已经选中的结果向用户展示时,前端页面该如何处理?
// 在jsp页面直接通过后端传来的数据进行循环
// 为下拉框部门赋值
<select name="deptId">
<c:forEach items="${deptList}" var="dept">
<option value="${dept.id }"
${userInfo.chDeptId==dept.id?'selected':''}>${dept.name}
</option>
</c:forEach>
</select>
// 为复选框菜单赋值
<c:forEach items="${menuList}" var="menu">
<input type="checkbox" title="${menu.name}" name="menus" value="${menu.id }"
<%-- 该forTokens循环在forEach循环中,而且在input标签中,注意选中为checked --%>
<c:forTokens items="${userInfo.menuIds}" delims="," var="mid">
${menu.id==mid?'checked':''}
</c:forTokens>
/>
</c:forEach>
// 前后端分离开发
// 通过异步请求获得部门以及菜单
$(function(){
$.post(serverAddress+"/user/toUpdateById;jsessionid="+sessionid,{id:id},function(res){
//给表单赋值
form.val("editForm", { //formTest 即 class="layui-form" 所在元素属性 lay-filter="" 对应的值
"id": res.user.id // "name": "value"
,"account": res.user.account
,"sex": res.user.sex
,"address": res.user.address
,"phone": res.user.phone
,"birthday": new Date(res.user.birthday).toLocaleDateString().replace(/\//g, '-')
});
// 向部门下拉框赋值
var deptStr = "";
for(var i = 0; i < res.deptList.length;i++){
deptStr += "<option value='"+res.deptList[i].id+"'";
if(res.deptList[i].id == res.user.dept_id){
deptStr += " selected ";
}
deptStr += ">"+res.deptList[i].name+"</option>";
}
$("#did").html(deptStr);
form.render('select');
// 向菜单复选框赋值
var menuStr = "";
var arr = res.user.menuIds.split(",");
for(var i = 0; i < res.menuList.length;i++){
menuStr += "<input type='checkbox' title="+res.menuList[i].name+" name='menus' value='"+res.menuList[i].id +"'";
for(var j = 0;j < arr.length;j++){
if(res.menuList[i].id == arr[j]){
menuStr += " checked ";
}
}
menuStr += " />";
}
$("#mid").html(menuStr);
form.render('checkbox');
})
});
总结
其实读者有没有发现,其实以上在向后端发起请求时,都要添加第一次缓存的sessionid也就是说每次请求的末尾都需要添加;jsessionid="+sessionid(sessionid是第一次登陆成功后存储在sessionStorag),这是很麻烦的,而且当多个后端服务器时,会更加麻烦,那么有没有不用返回sessionid的方式?是有的,继续阅读,学习更多……
使用jwt
这种方式就不会使用到session,也就是说在每次向服务器发起请求时,不用在添加(;jsessionid="+sessionid),这也是微信小程序的开发
base64转码(文件上传,下载)
- 报错Caused by: java.lang.ClassNotFoundException: org.apache.commons.codec.binary.Base64
导jar包
<!-- jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.2</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.6</version>
</dependency>
创建ToKen
package com.wenhua.spring.project.util;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
public class JwtToken {
// 私钥
private static final String ALGORITHM="ZCEQIUBFKSJBFJH2020BQWE";
/**
* jwt生成token
* @param id
* @param account
* @return
*/
public static String token (Integer id, String account){
String token = "";
try {
//过期时间 为1970.1.1 0:0:0 至 过期时间 当前的毫秒值 + 有效时间
Date expireDate = new Date(new Date().getTime() + 10*1000);
//秘钥及加密算法
Algorithm algorithm = Algorithm.HMAC256(ALGORITHM);
//设置头部信息
Map<String,Object> header = new HashMap<>();
header.put("typ","JWT");
header.put("alg","HS256");
//携带id,账号信息,生成签名
token = JWT.create()
.withHeader(header)
.withClaim("id",id)
.withClaim("account",account)
.withExpiresAt(expireDate)
.sign(algorithm);
}catch (Exception e){
e.printStackTrace();
return null;
}
return token;
}
// 验证token是否有效
public static boolean verify(String token){
try {
//验签
Algorithm algorithm = Algorithm.HMAC256(ALGORITHM);
JWTVerifier verifier = JWT.require(algorithm).build();
@SuppressWarnings("unused")
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception e) {
return false;
}
}
// 获得token 中playload部分数据
public static DecodedJWT getTokenInfo(String token){
return JWT.require(Algorithm.HMAC256(ALGORITHM)).build().verify(token);
}
// main方法测试
public static void main(String[] args) {
// 生成token
String token = JwtToken.token(1,"jem");
System.out.println(token);
// 验证token是否失效
boolean res = JwtToken.verify(token);
System.out.println(res);
if(res){
DecodedJWT jwt1 = JwtToken.getTokenInfo(token);
System.out.println(jwt1.getClaim("id").asInt());
System.out.println(jwt1.getClaim("account").asString());
}
}
}
创建拦截器
- 重写preHandle方法,并且向浏览器发送token状态
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 获得session并取得user对象
String token = request.getParameter("token");
boolean res = JwtToken.verify(token);
if(res){
return true;
}
// 通过map向浏览器传送token错误的信息 jackson转json
Map<String, Object> map = new HashMap<String, Object>();
map.put("code",1);
ObjectMapper objectMapper = new ObjectMapper();
response.getWriter().write(objectMapper.writeValueAsString(map));
return false;
}
注册拦截器
package com.wenhua.spring.project.config;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.wenhua.spring.project.util.isLoginInterceptor;
/**
* 注册拦截器
*
* @author ASUS
*
*/
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加拦截器
InterceptorRegistration inter = registry.addInterceptor(new isLoginInterceptor());
inter.addPathPatterns("/**"); // 需要拦截过滤地址
inter.excludePathPatterns("/login/tohello");// 放行地址
inter.excludePathPatterns("/login/tosuccess");// 放行地址
}
}
后端处理first请求
/**
* 验证登录信息
* @param request
* @param session
* @return
*/
@RequestMapping(path="/tosuccess")
public Map<String, Object> toSuccess(HttpServletRequest request){
Map<String, Object> map = null;
try {
map = new HashMap<String, Object>();
String account = request.getParameter("account");
String password = request.getParameter("password");
User user = loginService.findUserByAcPs(account,password);
if (user != null) {
// 登录成功后,创建token并且向浏览器发送
String token = JwtToken.token(user.getId(),user.getAccount());
map.put("code", 0);
map.put("account", account);
map.put("token",token);
}else{
map.put("code", 1);
}
} catch (Exception e) {
e.printStackTrace();
map.put("code", 2);
}
return map;
}
前端save返回参数
- 主要是存储获得的token以及常用的用户信息
sessionStorage.setItem("account",res.account);
sessionStorage.setItem("token",res.token);