文章目录
前言
接下来,我们主要还剩下前端页面的编写工作,对于vue
不了解的同学到这基本上后端的东西说完了,下面的可以选学,学一学vue
再来看,其实也没什么,希望大家一鼓作气学习完。
一、修改
1、修改错误信息
我们之前写的登录的代码,现在可能大家会发现登录进来还有错误信息的提示,我们先暂时把那个提示去掉。
打开views/login/index.vue
文件,修改handleLogin()
方法,去掉以下代码。
this.$notify.error({
title: '登录失败',
message: '请输入正确的用户名或密码!'
})
完整代码:
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
this.$store.dispatch('Login', this.loginForm).then(() => {
this.loading = false
this.$router.push({ path: this.redirect || '/' })
}).catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
}
2、主页页面修改
我们修改完,然后运行项目打开页面,会看到如下页面。
我们可以看一下vue-element-admin
框架的页面,首页为控制台,我们可以放一些大的标题、图表等信息,美化一下我们的后台网站。
打开src/router/index.js
文件,然后我们配置一下路由,将以下的代码注释掉。
{
path: '/',
component: Layout,
redirect: '/dashboard',
name: 'Dashboard',
hidden: true,
children: [{
path: 'dashboard',
component: () => import('@/views/dashboard/index')
}]
},
然后我们自己添加dashboard
路由,其中icon的图表可以从https://www.iconfont.cn/进行下载,或者我是下载的vue-element-admin的src/icons/svg
中找的。
{
path: '/',
component: Layout,
redirect: '/dashboard',
name: 'Dashboard',
children: [{
path: 'dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: 'Dashboard', icon: 'dashboard' }
}]
},
然后我们再打开项目,就会看到我们一进来就是项目的控制台首页了。
这时我们会注意到左侧的导航栏都是英文,我们如何改成自己的导航的文字呢,也是在src/router/index.js
这个文件中,我们会看到meta属性,然后修改title的名称即可。
二、导航栏改造
现在我们开始改造一下左侧的导航栏,我们将原来的导航栏都删除掉,只留下dashboard控制台。
如何删除呢?我们先把src/router/index.js
里面的删除掉,然后再去views里面找到对应的文件删除掉,如果你不会,可以去https://gitee.com/xyhwh/personal_vue下载。
搭建一个新的页面导航要做以下操作。
1、添加路由
首先我们还是打开src/router/index.js
文件,然后我们新添加一个导航的路由。其中icon的图标可以在https://www.iconfont.cn/下载,格式为svg。下载完放到src/icons/svg
文件中。
{
path: '/operation',
component: Layout,
redirect: '/operation/list',
name: 'Operation',
meta: { title: '日志中心', icon: 'operationlog' },
children: [
{
path: 'list',
name: 'OperationList',
component: () => import('@/views/operation/index'),
meta: { title: '操作日志', icon: '' }
},
{
path: 'loginlog',
name: 'LoginLogList',
component: () => import('@/views/operation/loginlog'),
meta: { title: '登录日志', icon: '' }
}
]
},
这里我要说一下,路由和侧边栏是组织起一个后台应用的关键骨架。这个框架项目的侧边栏和路由是绑定在一起的,所以你只有在 @/router/index.js
下面配置对应的路由,侧边栏就能动态的生成了。大大减轻了手动重复编辑侧边栏的工作量。当然这样就需要在配置路由的时候遵循一些约定的规则。
例如:
{
path: '/permission',
component: Layout,
redirect: '/permission/index', //重定向地址,在面包屑中点击会重定向去的地址
hidden: true, // 不在侧边栏显示
alwaysShow: true, //一直显示根路由
meta: { roles: ['admin','editor'] }, //你可以在根路由设置权限,这样它下面所有的子路由都继承了这个权限
children: [{
path: 'index',
component: ()=>import('permission/index'),
name: 'permission',
meta: {
title: 'permission',
icon: 'lock', //图标
roles: ['admin','editor'], //或者你可以给每一个子路由设置自己的权限
noCache: true // 不会被 <keep-alive> 缓存
}
}]
}
前端这个东西要多自己动手看效果,不行就修改,不要记忆,大脑是用来思考的,不是用来记忆的。
2、添加页面
在rcs/views
文件下,我们新建一个operation
文件夹,然后再里面新建index.vue
和loginlog.vue
两个文件,我们先创建一些模板。
index.vue文件:
<template>
<div class="operationlog">
<q>这是操作日志页面</q>
</div>
</template>
<script>
export default {
name: 'Operationlog',
computed: {
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
</style>
loginlog.vue文件:
<template>
<div class="operationlog">
<q>这是登录日志页面</q>
</div>
</template>
<script>
export default {
name: 'Operationlog',
computed: {
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
</style>
然后我们再运行项目,打开浏览器看一下。然后我们可以在右侧开发我们的功能了。
看下我们的后台系统,是不是看着也挺好看的,还是开始的目标,我们知道开发的流程和具体的操作即可,有些安全性的东西和页面美化就不太多的描述,作为毕设系统和初学的项目完全足够了。
三、前后端分离会话问题解决
我再测试的时候发现一个后台的问题,就是我们登录的时候拿到的Cookie
没有进行后端的会话存储,导致我们页面请求出现302
的问题,前后端的分离问题要考虑会话存储的问题,所以我们现在先解决前后端分离项目中使用shiro的会话
问题。要不然页面请求的接口会出现问题。我当时也没考虑页面的问题,只用了postman测了一下。
我这里选择的是redis来存储会话信息 。
1、导入shiro整合redis的jar包
在我们项目的pom.xml
中添加如下:
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.4.2.1-RELEASE</version>
</dependency>
2、自定义SessionManager类
我们在项目的shiro包中新建一个MySessionManager.java
类,然后这个类继承DefaultWebSessionManager类
。
package com.blog.personalblog.shiro;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
* @author: SuperMan
* @create: 2022-04-14
**/
public class MySessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public MySessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
//如果请求头中有 Authorization 则其值为sessionId
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
这里大家可能会问,这个类是干嘛的,什么是SessionManager
?
**SessionManager:**会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的,负责管理shiro自己封装的session的生命周期。
为什么shiro要自己封装session?
1.可以为任意应用提供session支持,不依赖于底层容器
2.简单扩容session管理容器,可以实现任何数据源(redis,ehcache)来管理session,而不必担心jvm内存溢出。
还有想学习更多的可以看下我找的这一篇文章:shiro详解
3、ShiroConfig配置文件注入SessionManager
写到这的时候,我们就需要下载一下redis
的软件了,有很多不知道redis是干嘛的,接下来先说一下redis,这也是以后企业经常用到的,大家尽可能的多去了解和使用。
3.1、什么是redis?
Redis(Remote Dictionary Server )
,即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value
数据库,并提供多种语言的API。
Redis支持主从同步、是一个高性能的key-value数据库。
redis官网地址:https://redis.io/
学习教程:https://www.runoob.com/redis/redis-tutorial.html
3.2、下载(文件版)
3.3、下载(安装版)
下载地址:https://github.com/tporadowski/redis/releases
下载下来之后,直接安装即可。如果下载不下来就去我公众号里下载,上边有说下载方式。
这里有几个常用的redis配置,大家学习一下。
其他的东西大家可以去菜鸟教程上边去学习,网上资料还是很多的,这个不是我们学习的重点。
3.4、注入SessionManager
(1)首先去配置文件配置redis
打开·application.yml·,然后添加如下代码,注意是spring配置下的,redis的密码我的为空,你可以看你安装时是不是设置了密码,如果是解压的基本上都是空。
redis:
host: 127.0.0.1
port: 6379
timeout: 300
password:
data:
redis:
repositories:
enabled: false
然后我们在就在ShiroConfig类中引入这些配置。这里用到了一个注解@Value
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis..port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.password}")
private String password;
(2)自定义sessionManger
/**
* 自定义sessionManager,用户的唯一标识,即Token或Authorization的认证
* @return
*/
@Bean
public SessionManager sessionManager() {
//创建一个上面自定的SessionManager
MySessionManager mySessionManager = new MySessionManager();
mySessionManager.setSessionDAO(redisSessionDAO());
return mySessionManager;
}
(3)配置shiro redisManager
在这个方法里面,就使用了我们配置的redis的信息,以后需要改动的时候直接改配置文件即可,也是规范化开发。
/**
* 配置shiro redisManager,使用的是shiro-redis开源插件
*
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
// 配置缓存过期时间
redisManager.setExpire(1800);
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
return redisManager;
}
(4)cacheManager 缓存
/**
* cacheManager 缓存 redis实现
* @return
*/
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
(5)自定义session管理
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* @return
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
// 自定义session管理 使用redis
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
3.5、SessionManager注入安全管理器
这个方法我们之前已经写过了,只是没有配置redis这个,现在再加入这个。
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//一定要通过上面的定义来加载自定义realm,否则在自定义realm中无法注入service层
securityManager.setRealm(myShiroRealm());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
return securityManager;
}
一切都配置完成之后,我们再修改一下我们之前的配置过滤规则shirFilter
这个方法。这里只需要去掉info接口即可。将这个注释掉即可
filterChainDefinitionMap.put("/user/info", "anon");
然后我们再去登录的接口,打开UserController.java
,找到info接口,我们这里可以获取到登录的用户了,我们再修改一下。
/**
* 登录info信息
* @return
*/
@GetMapping("/info")
public JsonResult<Object> info(){
String username = (String) SecurityUtils.getSubject().getPrincipal();
User user = userService.getUserByUserName(username);
Map<String, Object> ret = new HashMap<>(3);
ret.put("roles", "[admin]");
ret.put("name", user.getUserName());
ret.put("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
return JsonResult.success(ret);
}
好啦,到现在我们的改造完了,下面去打开前端项目,运行,进行登录一下。看到登录进来name为admin这个是真实的用户,你可以再换一个用户,看看是否会变。
到这里,后端的问题已经解决,下面我们去写页面。
四、操作日志页面开发
在开发的过程中,这个前端框架使用了eslint校验
代码,虽然是限制我们规范化写代码,但是特别的烦,动不动就报红色,所以我们先不用这个了,我设置一下就不让它检验src目录下的文件了。打开项目的这个文件,然后加入/src
保存即可。
现在开始写页面,我们先加一下操作日志的接口,也就是我们后端写的Controller的api接口,首先打开src/api
,然后创建一个operation.js
文件,然后写入我们的api
接口,和原来的login登录一样,可以参考登录的api。
import request from '@/utils/request'
export function fetchLogList(query) {
return request({
url: '/log/operationLog/list',
method: 'post',
data: query
})
}
1、接口请求
首先打开views/operation/index.vue
文件进行编写页面。
我们先写接口,我们首先在script里引入接口
import { fetchLogList } from '@/api/operation'
然后写获取接口里面的数据,在methods方法
里面写。还记得我们封装的返回类,返回的参数是什么嘛,不记得可以去看后端的JsonResult类
,我们将数据都封装到了result
里面了,我们前端只要去result里面去拿数据即可。数据的请求为JSON格式
。
getList() {
this.listLoading = true
var body = this.listQuery;
fetchLogList({body}).then(response => {
this.list = response.data.result
this.count = response.data.totalSize
this.listLoading = false
})
},
由于我们这里使用的是JSON格式
请求,我们的后端也是接收的JSON
,所以我们在传递参数的时候,以现在后端的写法是接收不到参数的(你可以自己实验一下,后端的代码可以先不改动,看看参数是否可以接收到),我们现在先去后端改一下Controller
接口的接收参数。(注意其余的接口只要是前端传递参数都要按照以下写)。
我们先写一个公共的类,把接口的接收的参数外边再包一层,和controller包同级下新建一个common包
,然后里面添加一个PageRequestApi.java
类,用于封装请求的参数,也就是参数外边再包一层大括号。
package com.blog.personalblog.common;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import javax.validation.Valid;
/**
* @author: SuperMan
* @create: 2022-04-26
**/
@JsonIgnoreProperties(
ignoreUnknown = true
)
public class PageRequestApi<T> {
@Valid
private T body = null;
public PageRequestApi() {
}
public PageRequestApi(T body) {
this.body = body;
}
public static <T> PageRequestApi<T> instance(T body) {
return new PageRequestApi(body);
}
public T getBody() {
return this.body;
}
public void setBody(final T body) {
this.body = body;
}
}
写完之后,然后打开OperationLogController.java
类,然后将操作日志的接口进行修改,以下代码就是将PageRequest
包起来,当做一个泛型。
public JsonResult<Object> OperationLogListPage(@RequestBody @Valid PageRequestApi<PageRequest> pageRequest) {
}
我们包起来之后,如何拿到前端传来的参数呢,别着急,我们只需要将PageRequestApi
对象下的body获取到就可以拿到传来的参数。
@ApiOperation(value = "操作日志列表")
@PostMapping("/operationLog/list")
public JsonResult<Object> OperationLogListPage(@RequestBody @Valid PageRequestApi<PageRequest> pageRequest) {
List<OperationLog> operationLogPage = operationLogService.getOperationLogPage(pageRequest.getBody());
PageInfo pageInfo = new PageInfo(operationLogPage);
PageResult pageResult = PageUtil.getPageResult(pageRequest.getBody(), pageInfo);
return JsonResult.success(pageResult);
}
上边代码看到了吧,只要pageRequest.getBody()
就可以获取到了,就比之前深了一层。好了,需要改的就那么多,下面还是进行页面编写。
2、页面编写
页面的编写没什么难度,主要是将我们拿到的数据进行遍历然后展示,我们基本上都是拿表格进行展示数据,这个都是element封装好的,我们直接拿来使用即可。
<template>
<el-card class="main-card">
<!-- 设置标题操作日志 -->
<div class="title">操作日志</div>
<div class="operation-container"></div>
<el-table v-loading="listLoading" :data="list" fit highlight-current-row style="width: 90%; margin-top:30px;">
<el-table-column align="center" label="ID" >
<template slot-scope="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="操作IP">
<template slot-scope="scope">
<span>{{ scope.row.operationIp}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="操作IP地址">
<template slot-scope="scope">
<span>{{ scope.row.operaLocation}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="方法名">
<template slot-scope="scope">
<span>{{ scope.row.methods}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="请求参数">
<template slot-scope="scope">
<span>{{ scope.row.args}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="操作人员">
<template slot-scope="scope">
<span>{{ scope.row.operationName}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="请求方式">
<template slot-scope="scope">
<el-tag :type="tagType(scope.row.operationType)">
{{ scope.row.operationType }}
</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="返回结果">
<template slot-scope="scope">
<span>{{ scope.row.returnValue}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="操作日期">
<template slot-scope="scope">
<span>{{ scope.row.createTime}}</span>
</template>
</el-table-column>
</el-table>
</el-card>
</template>
这里我在请求方式上面优化了一下页面效果,需要我们在export default
中添加个方法。也就是个switch判断,根据后端返回来的数据,来判断是加什么颜色。
computed: {
tagType() {
return function(type) {
switch (type) {
case "SYSTEM":
return "";
case "LOGIN":
return "success";
case "INSERT":
return "warning";
case "SELECT":
return "warning";
case "UPDATE":
return "warning";
case "DELETE":
return "danger";
}
};
}
}
3、分页
然后就是分页。分页作为数据列表的很重要的一部分,总不能把所有的数据全部放到页面上展示出来,显然不是我们想要的,我们上边修改的传参的请求主要是为了分页进行修改的。
我们在el-card
标签中添加分页效果。
<!-- 分页 -->
<el-pagination
class="pagination-container"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="this.listQuery.pageNum"
:page-size="this.listQuery.pageSize"
:total="count"
:page-sizes="[10, 20, 30]"
layout="total, sizes, prev, pager, next, jumper"
/>
添加返回的数据
data() {
return {
list: null,
listLoading: true,
count: 0,
listQuery: {
pageNum: 1,
pageSize: 10
}
}
},
根据官方文档,我们还要写两个方法用来传递页码数。
handleSizeChange(pageSize) {
this.listQuery.pageSize = pageSize
this.getList()
},
handleCurrentChange(pageNum) {
this.listQuery.pageNum = pageNum
this.getList()
}
基本上到这里页面全部写完了,也没啥好说的,多看几遍就可以了,做过html的都差不多还可以,我将这个全部的代码贴出来。看看自己还遗漏了哪些,自行补充。
<template>
<el-card class="main-card">
<!-- 设置标题操作日志 -->
<div class="title">操作日志</div>
<div class="operation-container"></div>
<el-table v-loading="listLoading" :data="list" fit highlight-current-row style="width: 90%; margin-top:30px;">
<el-table-column align="center" label="ID" >
<template slot-scope="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="操作IP">
<template slot-scope="scope">
<span>{{ scope.row.operationIp}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="操作IP地址">
<template slot-scope="scope">
<span>{{ scope.row.operaLocation}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="方法名">
<template slot-scope="scope">
<span>{{ scope.row.methods}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="请求参数">
<template slot-scope="scope">
<span>{{ scope.row.args}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="操作人员">
<template slot-scope="scope">
<span>{{ scope.row.operationName}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="请求方式">
<template slot-scope="scope">
<el-tag :type="tagType(scope.row.operationType)">
{{ scope.row.operationType }}
</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="返回结果">
<template slot-scope="scope">
<span>{{ scope.row.returnValue}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="操作日期">
<template slot-scope="scope">
<span>{{ scope.row.createTime}}</span>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
class="pagination-container"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="this.listQuery.pageNum"
:page-size="this.listQuery.pageSize"
:total="count"
:page-sizes="[10, 20, 30]"
layout="total, sizes, prev, pager, next, jumper"
/>
</el-card>
</template>
<script>
import { fetchLogList } from '@/api/operation'
export default {
name: 'operationLogList',
created() {
this.getList()
},
data() {
return {
list: null,
listLoading: true,
count: 0,
listQuery: {
pageNum: 1,
pageSize: 10
}
}
},
methods: {
getList() {
this.listLoading = true
var body = this.listQuery;
fetchLogList({body}).then(response => {
this.list = response.data.result
this.count = response.data.totalSize
this.listLoading = false
})
},
handleSizeChange(pageSize) {
this.listQuery.pageSize = pageSize
this.getList()
},
handleCurrentChange(pageNum) {
this.listQuery.pageNum = pageNum
this.getList()
}
},
computed: {
tagType() {
return function(type) {
switch (type) {
case "SYSTEM":
return "";
case "LOGIN":
return "success";
case "INSERT":
return "warning";
case "SELECT":
return "warning";
case "UPDATE":
return "warning";
case "DELETE":
return "danger";
}
};
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.pagination-container {
float: right;
margin-top: 1.25rem;
margin-bottom: 1.25rem;
}
.title {
position: absolute;
left: 0;
font-size: 16px;
font-weight: 700;
color: #202a34;
}
</style>
好啦,到这里,我们基本上把操作日志的页面搞完了,可能页面有点丑,后期在优化吧,我们启动后端项目,别忘了启动redis,然后再将前端项目启动起来,登录进入,打开日志中心下的操作日志,看一下是否有数据展示,然后还有分页的效果。
4、中文化
我们看到页面的分页,页数,共多少条之类的都是英文,我们要将这些进行汉化,所以我们打开前端项目,找到src/main.js
,然后注释掉以下这一句:
//import locale from 'element-ui/lib/locale/lang/en' // lang i18n
然后添加以下这一句即可。
import locale from 'element-ui/lib/locale/lang/zh-CN'
修改完,保存看一下页面是不是出现了汉字,如果是的,说明已经汉化了。
五、总结
好啦,这一篇我们先写到这里吧,我们的操作日志的页面完成了,有想学习java或者技术交流的可以加入我的群:626974338,大家一起学习。还有一个下面的登录日志的页面,我就在这一篇不写了,大家自己先仿照着操作日志的写,下一篇我们再说。
上一篇:Spring Boot + vue-element 开发个人博客项目实战教程(十八、操作日志功能实现)
下一篇:Spring Boot + vue-element 开发个人博客项目实战教程(二十、待定)