JavaEE项目总结(1)

一、在vue项目中安装axios

由于需要使用axios框架进行异步请求,所以需要在vue项目中安装axios框架。在官方下载速度较慢,所以选择更换镜像源(我使用的是华为云镜像)

在项目终端中输入npm config set registry http://mirrors.cloud.tencent.com/npm/

更换后通过在终端输入npm config get registry检查当前镜像源

二、前端向后端发送请求

用户在浏览器进行操作,而这些操作最开始到达的是前端。这时候需要前端向后端发出响应。

我们以一个vue项目的登录功能为例,当用户输入了账户密码并点击登录是,前端需要拿到账户密码的数据并向后端发送

html 复制代码
<!-- 一个.vue文件是一个组件,可以理解为一个页面,但是和页面不同 
  内容都写在一个template标签中,
  template标签必须有一个根标签
-->
<template>
	 <div class="login_container">
	     <!-- 登录盒子-->
	     <div class="login_box">
	          <!-- 头像盒子-->
	          <div class="img_box">
	                <img src="./assets/logo.png" />
	          </div>
			  
			<!-- 表单 -->
			<div style="margin-top: 100px; padding-right: 50px;">
				
				<el-form ref="form" label-width="80px">
					<!-- 第一栏 -->
					<el-form-item label="账号">
						 <el-input v-model="account"></el-input>
					</el-form-item>
					<!-- 第二栏 -->
					<el-form-item label="密码" show-password>
						 <el-input v-model="password"></el-input>
					</el-form-item>
					<!-- 按钮 -->
					 <el-form-item>
						<el-button type="primary"  @click="save()">登录</el-button>
						<el-button>取消</el-button>
					  </el-form-item>
					  
				</el-form>  
			</div>
				
		
	     </div>
	  </div>
</template>

<script>
/* 导出组件,并为组件定义数据,函数,生命周期函数 */
 export default{
	 data(){
		 return{
			 account:'admin',
			 password:'111'
		 }
	 },
	 methods:{
		 save(){
			 if(this.account.length==0){
				 this.$message({
				           message: '账号不得为空',
				           type: 'warning'
				         });
						 return;
			 }
			 if(this.password.length==0){
				 this.$message({
				           message: '密码不得为空',
				           type: 'warning'
				         });
						 return;		
			 }
			 // 向后端交互"/login","account="+this.account+"&password="+this.password
			 this.$http.post("login","account="+this.account+"&password="+this.password).then((resp)=>{
			 				/* 后端响应的结果 */
			 				
			 })
			 	
		 }
	 }
 }
</script>

<style>
  .login_container{
    height: 100vh;
    margin: 0px;
    padding: 0px;
	background-image: url(assets/bg.png);
  }

    .login_box{
      width: 450px;
      height: 350px;
      background-color: #fff;
      border-radius: 10px;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%,-50%);
	  opacity: 0.95;
    }

    .img_box{
       width: 130px;
       height: 130px;
       position: absolute;
       left: 50%;
       transform: translate(-50%,-50%);
       background-color: #fff;
       border-radius: 50%;
       padding: 5px;
       border: 1px solid #eee;
    }
    
    .img_box img{
         width: 100%;
         height: 100%;
         border-radius: 50%;
         background-color: #eee;
     }
</style>

三、后端请求的处理

后端处理主要分成三个步骤,简单来说就是"接化发"。

接是接收来自前端的请求,化是在后端进行处理,发是对前端进行响应。这三个步骤也是后端的基本作用

登录servlet

java 复制代码
package com.wbc.dorm.web;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wbc.dorm.dao.LoginDao;
import com.wbc.dorm.model.Admin;
import com.wbc.dorm.model.Result;
import com.wbc.dorm.util.JWTUtil;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/*登录处理servlet*/
@WebServlet(urlPatterns = "/login",name = "login", loadOnStartup = 1)
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*登录请求的账号密码*/
        String account = req.getParameter("account");
        String password = req.getParameter("password");
        //System.out.println(account);
        //System.out.println(password);
        Result result = null;
        PrintWriter printWriter = resp.getWriter();
        /*调用dao层查询账号密码是否正确*/

        try {
            LoginDao loginDao = new LoginDao();
            Admin admin = loginDao.login(account, password);
            //System.out.println(admin.toString());
            if (admin!=null){
                //将admin放入标准化响应模型
                result = new Result(200, "登陆成功", admin);
            }
            else {
                result = new Result(201, "账号或密码错误", null);

            }
        } catch (Exception e) {
            e.printStackTrace();
            result = new Result(500, "系统忙", null);
        }
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonString = objectMapper.writeValueAsString(result);
        printWriter.print(jsonString);
    }
}

管理员模型

java 复制代码
package com.wbc.dorm.model;

public class Admin {
    private int id;
    private String account;
    private String password;
   
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "Admin{" +
                "id=" + id +
                ", account='" + account + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

与数据库交互的dao

java 复制代码
package com.wbc.dorm.dao;

import com.wbc.dorm.model.Admin;
import com.wbc.dorm.model.Admin;

import java.sql.*;

public class LoginDao {
    public Admin login(String username, String password) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        String url ="jdbc:mysql://127.0.0.1:3306/dormdb?serverTimezone=Asia/Shanghai";//定义连接sql所需的url
        String users ="root";//用户名
        String passwords ="Wbc11280";//密码
        //建立连接
        Connection connection = DriverManager.getConnection(url,users,passwords);//建立连接
        //预编译
        PreparedStatement preparedStatement =connection.prepareStatement("select id,account from admin where account = ? and password = ?");
        try{
            //传入数据
            preparedStatement.setObject(1, username);
            preparedStatement.setObject(2, password);
            //查询操作
            ResultSet resultSet = preparedStatement.executeQuery();//将查询结构封装到ResultSet类型的对象中 需要将数据封装到指定类型的对象中
            Admin admin = null;
            while (resultSet.next()) {
                admin = new Admin();
                admin.setId(resultSet.getInt("id"));
                admin.setAccount(resultSet.getString("account"));
//                System.out.println(admin.toString());
            }
            return admin;
            //return null;
        }
        finally {
            if(preparedStatement != null){
                preparedStatement.close();
            }
            if(connection != null){
                connection.close();
            }
        }
    }
}

四、前端接收响应并做处理

当后端程序向前端响应后,前端需要接收响应并作出响应的处理

javascript 复制代码
// 向后端交互"/login","account="+this.account+"&password="+this.password
			 this.$http.post("login","account="+this.account+"&password="+this.password).then((resp)=>{
			 				/* 后端响应的结果 */
			 				if(resp.data.code==200){
								sessionStorage.setItem("account",resp.data.data.account);//存储到绘画空间,浏览器内部存储
					
			 					this.$router.push("/main");
			 				}
			 				else if(resp.data.code==201){
			 					this.$message({message:resp.data.message,type:'warning'})
			 				}
			 				else if(resp.data.code==500){
			 					this.$message({message:resp.data.message,type:'warning'})
			 				}
			 })

根据后端传来的标准化响应模型对象中的状态码,来做出对用户的响应,如登陆成功跳转到主页面,登陆失败根据状态码给出相应提示

说明:

sessionStorage.setItem(key, value)可以将浏览器中的数据存储到浏览器中的会话空间,只要窗口打开,数据就可以一直使用,直到界面关闭或者通过sessionStorage.clear()方法手动清楚。会话空间的数据可以通过sessionStorage.getItem(key)方法取出,从而在整个项目中共享数据。

五、路由导航守卫

当我们有了登录验证功能之后,肯定不希望用户通过路由导航(直接输入网址)的方式越过登录直接进入到主页面。如果在每次跳转之后都进行判断则相当的麻烦

html 复制代码
<template>
	<div>
		<el-container>
		  <el-header style="text-align: right; font-size: 12px">
			  <div class="header-title">后台管理系统</div>
						<el-dropdown>
						  <i class="el-icon-setting" style="margin-right: 15px"></i>
						  <el-dropdown-menu slot="dropdown" >
							<el-dropdown-item>主页</el-dropdown-item>
							<el-dropdown-item>修改密码</el-dropdown-item>
							<el-dropdown-item><span @click="logout()">安全退出</span></el-dropdown-item>
						  </el-dropdown-menu>
						</el-dropdown>
						<span>{{account}}</span>
		    </el-header>
			
		  <el-container>
		     <el-aside width="200px" style="background-color: rgb(238, 241, 246)">
		        <el-menu :default-openeds="['1', '3']" router>
		          <el-submenu index="1">
					  <template slot="title"><i class="el-icon-message"></i>操作菜单</template>
		            <el-menu-item-group>
		              <el-menu-item index="/majorlist">专业管理</el-menu-item>
		              <el-menu-item index="/studentlist">学生管理</el-menu-item>
					  <el-menu-item index="1-3">学院管理</el-menu-item>
		            </el-menu-item-group>
		          </el-submenu>
		        </el-menu>
		      </el-aside>
		    <el-main>
				<router-view></router-view>
			</el-main>
		  </el-container>
		  
		</el-container>
	</div>
</template>

<script>
	export default{
		
		data(){
			return{
				account:"",
				
			}
		},
		methods:{
			logout(){
				this.$confirm('您确定要退出么?', '提示', {
				          confirmButtonText: '确定',
				          cancelButtonText: '取消',
				          type: 'warning'
				        }).then(() => {
				          this.$message({
				            type: 'success',
				            message: '退出成功成功!'
				          });
						  sessionStorage.clear();
						  this.$router.push("/login")
				        })
			}
		},
		mounted(){
			this.account=window.sessionStorage.getItem("account");//将account存入路由器内部
			console.log(this.account);
			if(account==null){
				 this.$router.push("/login")
			}
		}
	}
</script>

<style>
  .el-header {
    background-color: #00a7fa;
    color: #333;
    line-height: 60px;
  }

  .header-title{
     width: 300px;
     float: left;
     text-align: left;
     font-size: 20px;
     color: white;
  }
  
  .el-main{
	  background-color: aliceblue;
	  height: 100vh;
  }
</style>

需要在每一次跳转中都进行一次判断。

而我们所使用的axios框架为我们提出了更加方便的解决方法------路由导航守卫。我们可以通过在路由中配置路由导航守卫,在每一次跳转时自动执行代码,进行判断是否已经登录

在/router/index.js文件中定义路由组件代码下方加入如下代码

javascript 复制代码
//配置路由导航守卫
//每当进行一次组件路由时,就会触发导航守卫
rout.beforeEach((to,from,next)=>{
	if(to.path=='/login'){//如果用户访问的登录页,直接放行
		return next();//继续访问
	}	
	else{
	var account = window.sessionStorage.getItem("account");//获取路由器中存储的管理员信息
	if(account==null){//如果为空说明没有登录
		return next("/login");//跳转到登陆页面
	}
	else{//已经登陆
		next();//继续访问
	}
 }
})

如此我们就快要省去每次跳转判断的代码

html 复制代码
<template>
	<div>
		<el-container>
		  <el-header style="text-align: right; font-size: 12px">
			  <div class="header-title">后台管理系统</div>
						<el-dropdown>
						  <i class="el-icon-setting" style="margin-right: 15px"></i>
						  <el-dropdown-menu slot="dropdown" >
							<el-dropdown-item>主页</el-dropdown-item>
							<el-dropdown-item>修改密码</el-dropdown-item>
							<el-dropdown-item><span @click="logout()">安全退出</span></el-dropdown-item>
						  </el-dropdown-menu>
						</el-dropdown>
						<span>{{account}}</span>
		    </el-header>
			
		  <el-container>
		     <el-aside width="200px" style="background-color: rgb(238, 241, 246)">
		        <el-menu :default-openeds="['1', '3']" router>
		          <el-submenu index="1">
					  <template slot="title"><i class="el-icon-message"></i>操作菜单</template>
		            <el-menu-item-group>
		              <el-menu-item index="/majorlist">专业管理</el-menu-item>
		              <el-menu-item index="/studentlist">学生管理</el-menu-item>
					  <el-menu-item index="1-3">学院管理</el-menu-item>
		            </el-menu-item-group>
		          </el-submenu>
		        </el-menu>
		      </el-aside>
		    <el-main>
				<router-view></router-view>
			</el-main>
		  </el-container>
		  
		</el-container>
	</div>
</template>

<script>
	export default{
		
		data(){
			return{
				account:"",
				
			}
		},
		methods:{
			logout(){
				this.$confirm('您确定要退出么?', '提示', {
				          confirmButtonText: '确定',
				          cancelButtonText: '取消',
				          type: 'warning'
				        }).then(() => {
				          this.$message({
				            type: 'success',
				            message: '退出成功成功!'
				          });
						  sessionStorage.clear();
						  this.$router.push("/login")
				        })
			}
		},
		mounted(){
			this.account=window.sessionStorage.getItem("account");//将account存入路由器内部
			console.log(this.account);
			/* if(account==null){
				 this.$router.push("/login")
			} */
		}
	}
</script>

<style>
  .el-header {
    background-color: #00a7fa;
    color: #333;
    line-height: 60px;
  }

  .header-title{
     width: 300px;
     float: left;
     text-align: left;
     font-size: 20px;
     color: white;
  }
  
  .el-main{
	  background-color: aliceblue;
	  height: 100vh;
  }
</style>

六、web前后端之间的会话跟踪

当我们在登陆成功后需要进行其他操作时,前端会像后端发送http请求。但http请求是无状态的,因此后端不知道是谁在进行操作。对会话进行跟踪 就是为了解决这样的问题。

会话跟踪是Web程序中常用的技术,用来跟踪用户的整个会话过程。 给客户端们颁发一个通行证,每人一个,无论谁访问都必须携带自己通行证。 这样服务器就能从通行证上确认客户身份了。

我们大致可以将会话跟踪描述为下图的过程

当我们在

具体实现

(1)在登陆成功后,在后端为用户生成一个token字符串

token令牌,可以理解为身份证号,是该用户唯一标识字符串,通过这个字符串进行前后端验证。

我们可以通过jwt组件为管理员生成token令牌

说明:

token令牌分为三部分:声明、载荷、签证

声明包含着生成类型和加密算法的基本信息,载荷包含着用户信息。二者通过base64转码生成,此过程可逆,不包含加密,因此不建议在载荷中加入用户关键信息。

签证结合前两部分以及密钥,加密生成,故密钥十分重要

我们通过maven添加依赖

XML 复制代码
<dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.8.2</version>
        </dependency>

并在服务器中注册(web.xml)

XML 复制代码
 <!--验证token过滤器-->
    <filter>
        <filter-name>tokenfilter</filter-name>
        <filter-class>com.wbc.dorm.filter.AdminTokenFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>tokenfilter</filter-name>
        <!--请求地址中有api地址的Servlet进入过滤器-->
        <url-pattern>/api/*</url-pattern>
    </filter-mapping>

由于登录界面不需要生成token,所以通过在其他配置信息的地址前添加/api来区分

将jwt的util稍作修改直接拿来用

java 复制代码
package com.wbc.dorm.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.wbc.dorm.model.Admin;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT工具类
 */
public class JWTUtil {

    /**
     * 根据用户id,账号生成token
     * @param admin
     * @return
     */
    public static String getToken(Admin admin) {
        String token = "";
        try {
            //过期时间 为1970.1.1 0:0:0 至 过期时间  当前的毫秒值 + 有效时间
            Date expireDate = new Date(new Date().getTime() + 10000*1000);
            //秘钥及加密算法
            Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
            //设置头部信息
            Map<String,Object> header = new HashMap<>();
            header.put("typ","JWT");
            header.put("alg","HS256");
            //携带id,账号信息,生成签名
            token = JWT.create()
                    .withHeader(header)//设置头部
                    .withClaim("id",admin.getId())//设置载荷
                    .withClaim("account",admin.getAccount())//设置载荷
                    .withExpiresAt(expireDate)//设置token有效时间
                    .sign(algorithm);//设置密钥
        }catch (Exception e){
            e.printStackTrace();
            return  null;
        }
        return token;
    }

    /**
     * 验证token是否有效
     * @param token
     * @return
     */
    public static boolean verify(String token){
        try {
            //验签
            Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception e) {//当传过来的token如果有问题,抛出异常
            return false;
        }
    }

    /**
     * 获得token 中playload部分数据,按需使用
     * @param token
     * @return
     */
    public static DecodedJWT getTokenInfo(String token){
        return JWT.require(Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE")).build().verify(token);
    }

}

添加Admin内的成员变量adminToken,并增加相应的get、set方法。

java 复制代码
package com.wbc.dorm.model;

public class Admin {
    private int id;
    private String account;
    private String password;
    private String adminToken;

    public String getAdminToken() {
        return adminToken;
    }

    public void setAdminToken(String adminToken) {
        this.adminToken = adminToken;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "Admin{" +
                "id=" + id +
                ", account='" + account + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

并在登录servlet中添加相应代码以生成token

java 复制代码
package com.wbc.dorm.web;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wbc.dorm.dao.LoginDao;
import com.wbc.dorm.model.Admin;
import com.wbc.dorm.model.Result;
import com.wbc.dorm.util.JWTUtil;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/*登录处理servlet*/
@WebServlet(urlPatterns = "/login",name = "login", loadOnStartup = 1)
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*登录请求的账号密码*/
        String account = req.getParameter("account");
        String password = req.getParameter("password");
        //System.out.println(account);
        //System.out.println(password);
        Result result = null;
        PrintWriter printWriter = resp.getWriter();
        /*调用dao层查询账号密码是否正确*/

        try {
            LoginDao loginDao = new LoginDao();
            Admin admin = loginDao.login(account, password);
            //System.out.println(admin.toString());
            if (admin!=null){

                //登陆成功后,为admin生成一个token字符串
                //使用jwt组件为管理员生成公私密钥
                String adminToken = JWTUtil.getToken(admin);
                //将adminToken放入admin
                admin.setAdminToken(adminToken);
                //将admin放入标准化响应模型
                result = new Result(200, "登陆成功", admin);
            }
            else {
                result = new Result(201, "账号或密码错误", null);

            }
        } catch (Exception e) {
            e.printStackTrace();
            result = new Result(500, "系统忙", null);
        }
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonString = objectMapper.writeValueAsString(result);
        printWriter.print(jsonString);
    }
}

(2)在浏览器中存储token,每次发送请求时将身份码一同发出

在登录时,接收后端相应处,添加接收身份码的代码,并将其添加到会话空间

javascript 复制代码
sessionStorage.setItem("adminToken",resp.data.data.adminToken);

在我们操作发送请求时,如果在发送请求时,通过字符串拼接将身份码一同发于后端,实在过于麻烦,幸好axios为我们提供了请求拦截的功能

在main.js中添加如下代码,可以将身份码添加到请求头中,直接发送

javascript 复制代码
//axios 请求拦截
axios.interceptors.request.use(config =>{
	 //为请求头对象,添加Token验证的token字段
	config.headers.adminToken = window.sessionStorage.getItem('adminToken');
	 return config;
 });

(3)在后端创建一个过滤器,用于检测token的正确性,正确则退出过滤器继续响应,错误则直接向前端响应并给出状态码和提示信息

java 复制代码
package com.wbc.dorm.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wbc.dorm.model.Result;
import com.wbc.dorm.util.JWTUtil;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;

public class AdminTokenFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //父类向子类转换(强制类型转换)
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String adminToken = request.getHeader("adminToken");
        //验证token
        Boolean res= JWTUtil.verify(adminToken);
        System.out.println(res);
        //处理验证结果
        if(res){
            filterChain.doFilter(request, servletResponse);//离开过滤器继续向下执行
        }
        else{
            //向前端进行响应
            Result result = new Result(401,"token验证失败,请重新登录",null);
            PrintWriter printWriter = new PrintWriter(servletResponse.getWriter());
            printWriter.write(new ObjectMapper().writeValueAsString(result));
        }


    }
}

在前端中main.js中添加响应拦截器(类似于javaEE中的过滤器),用于拦截后端的身份码错误响应

javascript 复制代码
 // 添加响应拦截器
axios.interceptors.response.use((resp) =>{
	//正常响应拦截
	if(resp.data.code==500){
		ElementUI.Message({message:resp.data.message,type:"error"});
		return;
	 }
	 if(resp.data.code==401){
		 ElementUI.Message({message:resp.data.message,type:"error"});
		 sessionStorage.clear();
		router.replace("/login");
	 }
	 return resp;
 });
相关推荐
m0_571957581 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
并不会1 小时前
常见 CSS 选择器用法
前端·css·学习·html·前端开发·css选择器
悦涵仙子1 小时前
CSS中的变量应用——:root,Sass变量,JavaScript中使用Sass变量
javascript·css·sass
衣乌安、1 小时前
【CSS】居中样式
前端·css·css3
兔老大的胡萝卜1 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
低代码布道师1 小时前
CSS的三个重点
前端·css
一点媛艺2 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风2 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
耶啵奶膘2 小时前
uniapp-是否删除
linux·前端·uni-app
奋斗的小花生3 小时前
c++ 多态性
开发语言·c++