一、登录与主界面
1.新建前端项目
2.配置组件路由
npm i vue-router@3.5.3
src下创建 router文件夹 ,并在router下创建index.js 文件
同时在src下创建两个组件 Login.vue和Main.vue
在index.js中配置 上面两个组件
/* 导入路由 */
import Vue from 'vue';
import router from 'vue-router';
/* 导入自己的组件 */
import Login from "../Login.vue";
import Main from "../Main.vue"; // ../上一级目录中找
Vue.use(router) //vue支持使用router组件
/* 定义组件路由 */
var rout = new router({ //新建router对象
routes: [ //组件映射
{
path: '/login', //为组件定义地址
name: 'name', //组件起名 可不写
component: Login //对应那个组件
},
{
path: '/main',
component: Main
}
]
});
//导出路由对象
export default rout;
main.js中配置
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
import router from './router/index.js';
Vue.use(router);
new Vue({
render: h => h(App),
router,
}).$mount('#app')
app.vue中<-router-view>标签
可以将不需要的内容删去
<template>
<div id="app">
<!-- 在此标签上,用来切换不同的组件 -->
<router-view>< /router-view>
</div>
</template>
<script>
</script>
<style>
</style>
3.导入elementUI
main.js中配置elementUI
将此段代码加入,与导入组件路由相同
//导入 element mui
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
使用elementUI中的代码美化 登录和主界面
登录
<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: 20px;">
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="账号">
<el-input v-model="form.account"></el-input>
</el-form-item>
<el-form-item label="密码">
<el-input v-model="form.password" type="password"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="login()">登录</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script>
export default { //导出组件
data() {
return {
form: {
account: "admin",
password: "111"
}
}
},
methods: {
login() {
// 后端进行一次交互的位置
//路由跳转
this.$router.push("/main");
}
}
}
//将json对象序列化为键=值&键=值
function jsonToString(jsonobj) {
console.log(jsonobj)
var str = "";
for (var s in jsonobj) {
str += s + "=" + jsonobj[s] + "&";
}
return str.substring(0, str.length - 1);
}
</script>
<style>
.login_container {
height: 100vh;
margin: 0px;
padding: 0px;
background-image: url(assets/R-C.jpg);
}
.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>
main
<template>
<el-container>
<!-- 顶部-->
<el-header>
<div style="width: 500px; display: inline-block; font-size: 26px;font-weight: 400; color: #fff;">
管理系统后台
</div>
<el-dropdown style="float: right;">
<i class="el-icon-s-custom" style="margin-right: 15px">
<span>{{account}}</span>
</i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>修改密码</el-dropdown-item>
<el-dropdown-item><span @click="">安全退出</span></el-dropdown-item>
<el-dropdown-item><span @click="">测试</span></el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-header>
<el-container>
<!-- 左边-->
<el-aside width="200px">
<el-menu :default-openeds="['1', '3']" router>
<el-submenu index="1">
<template slot="title"><i class="el-icon-message"></i>操作菜单</template>
<el-menu-item>宿舍管理</el-menu-item>
<el-menu-item>宿管员管理</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<!-- 工作区-->
<el-main>
</el-main>
</el-container>
</el-container>
</template>
<script>
</script>
<style>
.el-header {
background-color: #55aaff;
color: #333;
line-height: 60px;
}
.el-aside {
background-color: #D3DCE6;
color: #333;
text-align: center;
height: 90vh;
}
.el-main {
background-color: #E9EEF3;
color: #333;
/* text-align: center; */
height: 90vh;
}
</style>
此时两个主要界面设置完成
二、登录操作
1.前端发送请求到后端
1.1安装axios
与导入elementUi方法相同
npm install axios
端口要与后端的不同
//导入网络请求库
import axios from 'axios';
axios.defaults.baseURL="http://127.0.0.1:8088/dormms/";
Vue.prototype.$http=axios;
1.2像后端发送请求
在Login中加入函数 并接收后端返回的账号 与 token
login() {
// 后端进行一次交互的位置
//console.log(this.form)
this.$http.post("login", jsonToString(this.form)).then((resp) => {
if (resp.data.code == 200) {
//提示
this.$message({
message: resp.data.message,
type: 'success'
});
//浏览器中存储用户账号信息
sessionStorage.setItem("account", resp.data.data.account);
sessionStorage.setItem("adminToken", resp.data.data.token);
//路由跳转
this.$router.push("/main");
}
if (resp.data.code == 201) {
this.$message({
message: resp.data.message,
type: 'warning'
});
}
if (resp.data.code == 500) {
this.$message.error(resp.data.message);
}
})
//login为后端servlet的地址
}
在main中加入函数 拿到响应回来的账号并显示
export default {
data() {
return {
account: ""
}
},
methods: {
},
mounted() {
this.account = sessionStorage.getItem("account");
}
}
2.后端处理
此时后端javaEE以搭建完成,所用到的jar包以全部导入
以及创建了一个类LoginServlet来接收请求,并在web.xml文件中配置LoginServlet
2.1后端接收请求
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter printWriter= resp.getWriter();
CommonResult commonResult=null;
String account=req.getParameter("account");
String password=req.getParameter("password");
LoginDao loginDao=new LoginDao();
try {
Admin admin=loginDao.login(account,password);
if(admin!=null){
String token= JWTUtil.getToken(admin);
admin.setToken(token);//token赋给admin
commonResult=new CommonResult(200,admin,"登陆成功");
}else {
commonResult=new CommonResult(201,"账号或密码错误");
}
} catch (Exception e) {
e.printStackTrace();
commonResult =new CommonResult(500,"系统忙");
}
ObjectMapper objectMapper=new ObjectMapper();
String json=objectMapper.writeValueAsString(commonResult);
printWriter.print(json);
}
}
2.2连接数据库
CREATE DATABASE dorm_db CHARSET utf8
CREATE TABLE admin(
id INT PRIMARY KEY AUTO_INCREMENT,
account VARCHAR(20),
PASSWORD VARCHAR(20)
)
public class LoginDao {
public Admin login(String account,String password) throws ClassNotFoundException, SQLException {
Admin admin=null;
Connection connection=null;
PreparedStatement ps=null;
try{
Class.forName("com.mysql.cj.jdbc.Driver");
//动态 加载Driver类
String url="jdbc:mysql://127.0.0.1:3306/dorm_db?serverTimezone=Asia/Shanghai";
String uname="root";
String pwd="root";
connection= DriverManager.getConnection(url,uname,pwd);
ps= connection.prepareStatement("SELECT * FROM admin WHERE account=? AND PASSWORD=?");
ps.setObject(1,account);
ps.setObject(2,password);
ResultSet res=ps.executeQuery();
while (res.next()){
admin=new Admin();
admin.setId(res.getInt("id"));
admin.setAccount(res.getString("account"));
}
}finally {
if(connection!=null){
connection.close();
}
if(ps!=null){
ps.close();
}
}
return admin;
}
}
2.3数据封装
创建CommonResult类 code data message
LoginServlet 接收到dao传回的数据, (token)
将状态码,admin对象,返回的信息通过 commonResult封装,已json格式响应到前端
public class CommonResult {
private int code;
private Object data;
private String message;
public CommonResult(int code, Object data, String message) {
this.code = code;
this.data = data;
this.message = message;
}
public CommonResult(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
public class Admin {
private int id;
private String account;
private String password;
private String token;
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;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
3.验证登录
为了避免直接通过网址来进入系统界面,
加入路由导航守卫,不允许直接通过网址进入main
index.js
//路由导航守卫,每次发生路由跳转时,就会自动执行此段逻辑
rout.beforeEach((to, from, next) => {
if (to.path == '/login') {//如果用户访问的登录页, 直接放行
return next();//放行,
} else {
var token = sessionStorage.getItem("account");//从浏览器中取出用户信息
if (token == null) {
return next("/login");
} else {//已经登录过
next();
}
}
})
4.会话跟踪
会话:从一个客户打开浏览器并连接到服务器开始,到客户关闭浏览器离开这
个服务器结束,被称为一个会话。
http请求是无状态的,只有用户发出请求时服务器才会响应,客户端与服务端之间的联系是离散的、非连续的;如果用户想在同一个网站的多个页面之间转换时,无法确定是否是同一个用户;对会话进行跟踪就是为了解决这样的问题。
向后端发送账号密码,与数据库连接验证成功后,在后端生成一个token(令牌 唯一的),把token响应给前端
4.1生成token
/**
* JWT工具类
*/
public class JWTUtil {
/**
* 根据用户id,账号生成token
* @param
* @return
*/
public static String getToken(Admin admin) {
String token = "";
try {
//过期时间 为1970.1.1 0:0:0 至 过期时间 当前的毫秒值 + 有效时间
Date expireDate = new Date(new Date().getTime() + 30*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)
.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);
}
}
此时通过loginservelt
if(admin!=null){
String token= JWTUtil.getToken(admin);
admin.setToken(token);//token赋给admin
4.2前端存储token
sessionStorage.setItem("adminToken",resp.data.data.token);
4.3将token携带向后端发送
之后的每次请求,都将token携带着向后端发送
main.js中 将token发送给后端
//axios 请求拦截 每发送一次http请求都会支持此拦截器
axios.interceptors.request.use(config => {
//为请求头对象,添加 Token 验证的 token 字段
config.headers.adminToken = window.sessionStorage.getItem('adminToken');
return config;
})
4.4后端token进行解析验证
后端的java对请求中的token进行解析验证
添加token过滤器
JWTUtil.verify(adminToken); verify验证token的方法
验证成功–>过滤器向后执行
验证失败–>将错误信息封装到工具类中
public class AdminTokenFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//System.out.println("token过滤验证");
//接收请求头中的 adminToken
HttpServletRequest request=(HttpServletRequest)servletRequest;
String adminToken=request.getHeader("adminToken");
boolean verify= JWTUtil.verify(adminToken);
//System.out.println(verify+" 过滤器验证token");
if(verify){
//验证成功 继续向后执行
filterChain.doFilter(servletRequest, servletResponse);
}else {
//验证失败
servletResponse.setContentType("text/html;charset-utf-8");
PrintWriter printWriter= servletResponse.getWriter();
CommonResult commonResult=new CommonResult(401,"Token验证失败,请重新登录");//后端封装数据的公共类
ObjectMapper objectMapper=new ObjectMapper();
String json=objectMapper.writeValueAsString(commonResult);
printWriter.print(json);
}
}
}
配置token过滤器
<filter>
<filter-name>AdminTokenFilter</filter-name>
<filter-class>com.ffyc.dorm.filter.AdminTokenFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AdminTokenFilter</filter-name>
<url-pattern>/back/*</url-pattern>
</filter-mapping>
5.响应拦截
由于后端对前端的响应,401与500较为常见,我们不必对每次响应回的401 500进行if处理
如:
if(resp.data.code==201){
this.$message({message: resp.data.message,type: 'warning' });
}
main.js
// 添加响应拦截器
axios.interceptors.response.use((resp) => { //正常响应拦截
if (resp.data.code == 500) {
ElementUI.Message({
message: resp.data.message,
type: "error"
})
}
if (resp.data.code == 401) {
ElementUI.Message({
message: resp.data.message,
type: "token验证失败"
})
router.replace("/login");
}
return resp;
});
6.过滤器
解决异步请求过滤器
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
//允许携带Cookie时不能设置为* 否则前端报错
httpResponse.setHeader("Access-Control-Allow-Origin", httpRequest.getHeader("origin"));//允许所有请求跨域
httpResponse.setHeader("Access-Control-Allow-Methods", "*");//允许跨域的请求方法GET, POST, HEAD 等
httpResponse.setHeader("Access-Control-Allow-Headers", "*");//允许跨域的请求头
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");//是否携带cookie
filterChain.doFilter(servletRequest, servletResponse);
}
}
设置字符过滤器
public class EncodFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("utf-8");
servletResponse.setCharacterEncoding("utf-8");
filterChain.doFilter(servletRequest,servletResponse);
}
}
xml配置
在开始登录时提交表单此时scessionstorage里面还没有信息
并且在登录时也不需要验证token,所以当前端请求得sevlet地址为login时,不需要通过token过滤器。
所以在配置其他访问地址时(除过登录)前面可以加上/back/servlet地址,表示登录之后的访问
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>login11</servlet-name>
<servlet-class>com.ffyc.dorm.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login11</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<filter>
<filter-name>filterlogin</filter-name>
<filter-class>com.ffyc.dorm.filter.EncodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>filterlogin</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>filterco</filter-name>
<filter-class>com.ffyc.dorm.filter.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>filterco</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>AdminTokenFilter</filter-name>
<filter-class>com.ffyc.dorm.filter.AdminTokenFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AdminTokenFilter</filter-name>
<url-pattern>/back/*</url-pattern>
</filter-mapping>
</web-app>
7.简单封装jdbc代码
新建工具类,处理重复代码
public class JdbcUtil {
static String url="jdbc:mysql://127.0.0.1:3306/dorm_db?serverTimezone=Asia/Shanghai";
static String uname="root";
static String pwd="root";
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取数据库连接对象
public static Connection getConnection() throws SQLException {
Connection connection= DriverManager.getConnection(url,uname,pwd);
return connection;
}
//
public static void close(Connection connection, PreparedStatement preparedStatement) throws SQLException {
if(connection!=null){
connection.close();
}
if(preparedStatement!=null){
preparedStatement.close();
}
}
}
此时logindao
public class LoginDao {
public Admin login(String account,String password) throws ClassNotFoundException, SQLException {
Admin admin=null;
Connection connection=null;
PreparedStatement ps=null;
try{
connection = JdbcUtil.getConnection();
ps= connection.prepareStatement("SELECT * FROM admin WHERE account=? AND PASSWORD=?");
ps.setObject(1,account);
ps.setObject(2,password);
ResultSet res=ps.executeQuery();
while (res.next()){
admin=new Admin();
admin.setId(res.getInt("id"));
admin.setAccount(res.getString("account"));
}
}finally {
JdbcUtil.close(connection,ps);
}
return admin;
}
}