在包“com.example.demo”中新建一个“LoginInterceptor”类,代码如下:
package com.example.demo;
import java.io.PrintWriter;
/**
* 登录拦截器
*/
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import com.example.demo.vo.Json;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component
public class LoginInterceptor implements HandlerInterceptor {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private ObjectMapper mapper;//Jackson使用ObjectMapper类将POJO对象序列化成JSON字符串,也能将JSON字符串反序列化成POJO对象。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //请求进入这个拦截器
logger.info("进入拦截器...");
HttpSession session = request.getSession();
if(session.getAttribute("user") == null){//判断session中有没有user信息
logger.info("session中没有user信息");
//ajax异步请求
if("XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"))){
logger.info("ajax异步请求");
Json j = new Json();
j.setSuccess(false);
j.setMsg("session会话超时,请重新登录!");
response.setContentType("application/json;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write(mapper.writeValueAsString(j));//将对象转成字符串
logger.info(mapper.writeValueAsString(j));
writer.close();
response.flushBuffer();
return false;
}else {
logger.info("页面请求");
response.sendRedirect("/login");//没有user信息的话进行路由重定向到登录页面
return false;
}
}
logger.info("登录拦截验证通过。");
return true;//有的话就继续操作
}
}
每一个配置为拦截的http请求都会执行preHandle方法,可以在这里检查用户的session是否为空来进行登录验证,因为请求有可能是异步的,所以对于异步的请求不能直接跳转页面,而是返回json数据。
修改在包“com.example.demo”中新建一个“WebConfig”类,代码如下:
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;//登录拦截器
//图片存放根路径,从application.yml中读取upload
@Value("${upload}")
private String UPLOAD_PATH;
/**
* 文件上传
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//外部静态资源映射路径,用来上传文件
String filePath = "file:" + UPLOAD_PATH;
registry.addResourceHandler("/upload/**").addResourceLocations(filePath);
}
//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//登录拦截的管理器
InterceptorRegistration registration = registry.addInterceptor(loginInterceptor);//拦截的对象会进入这个类中进行判断
registration.addPathPatterns("/**");//所有路径都被拦截
registration.excludePathPatterns("/","/login","/UserController/login","/kaptcha","/static/**","/UserController/logout","/upload/**");//添加不拦截路径
}
}
在WebConfig中增加了拦截器的设置。
修改在包“com.example.demo.controller”中新建一个“HomeController”类,代码如下:
package com.example.demo.controller;
/**
* 后台管理主页,登录页面,验证码生成
*/
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.example.demo.model.User;
import com.example.demo.service.PermissionService;
import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.Producer;
@Controller
public class HomeController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private Producer captchaProducer;//验证码
@Autowired
private PermissionService permissionService;// 注入业务层的service
// 未加入@ResponseBody用来返回数据给页面
@RequestMapping("/index")
public String index(HttpServletRequest request,Model model) {
HttpSession session = request.getSession();
User user = (User)session.getAttribute("user");
logger.info(user.getUsername());
logger.info(user.getId());
model.addAttribute("username", user.getUsername());
model.addAttribute("userid", user.getId());
model.addAttribute("menulist", permissionService.getUserMenu(user.getId()));
return "admin/index";
}
// 未加入@ResponseBody用来返回数据给页面
@RequestMapping("/home")
public String home(Model model) {
return "admin/home";
}
// 未加入@ResponseBody用来返回数据给页面
@RequestMapping("/")
public String login(Model model) {
return "admin/login";
}
// 未加入@ResponseBody用来返回数据给页面
@RequestMapping("/login")
public String login2(Model model) {
return "admin/login";
}
@RequestMapping("/kaptcha")
public ModelAndView getKaptchaImage(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpSession session = request.getSession();
String code = (String)session.getAttribute(Constants.KAPTCHA_SESSION_KEY);
logger.info("******************验证码是: " + code + "******************");
response.setDateHeader("Expires", 0);
// Set standard HTTP/1.1 no-cache headers.
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
// Set IE extended HTTP/1.1 no-cache headers (use addHeader).
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
// Set standard HTTP/1.0 no-cache header.
response.setHeader("Pragma", "no-cache");
// return a jpeg
response.setContentType("image/jpeg");
// create the text for the image
String capText = captchaProducer.createText();
// store the text in the session
session.setAttribute(Constants.KAPTCHA_SESSION_KEY, capText);
// create the image with the text
BufferedImage bi = captchaProducer.createImage(capText);
ServletOutputStream out = response.getOutputStream();
// write the data out
ImageIO.write(bi, "jpg", out);
try {
out.flush();
} finally {
out.close();
}
return null;
}
}
增加了登录页面路径映射以及验证码生成接口。
修改在包“com.example.demo”中新建一个“MisApplication”类,代码如下:
package com.example.demo;
import java.util.Properties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
@SpringBootApplication
public class MisApplication {
public static void main(String[] args) {
SpringApplication.run(MisApplication.class, args);
}
/**
* 验证码
* @return
*/
@Bean
public DefaultKaptcha captchaProducer() {
DefaultKaptcha captchaProducer = new DefaultKaptcha();
Properties properties = new Properties();
// properties.setProperty(Constants.KAPTCHA_BORDER, "yes");
// properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "red");
// properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "50");
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "宋体,楷体,微软雅黑");
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789");
properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "120");
properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "50");
properties.setProperty(Constants.KAPTCHA_SESSION_CONFIG_KEY, "code");
Config config = new Config(properties);
captchaProducer.setConfig(config);
return captchaProducer;
}
}
对验证码的生成进行配置。
修改在包“com.example.demo.controller”中新建一个“UserController”类,代码如下:
package com.example.demo.controller;
import java.io.File;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import com.example.demo.util.SnowflakeIdWorker;
import com.example.demo.vo.DataTableResult;
import com.example.demo.vo.Json;
import com.example.demo.vo.UserVO;
import com.google.code.kaptcha.Constants;
@Controller
@RequestMapping("/UserController")
public class UserController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private String prefix = "admin/user";// 页面的路径,注意admin前面不要有/
// 图片存放根路径,从application.yml中读取upload
@Value("${upload}")
private String UPLOAD_PATH;
@Autowired
private UserService userService;// 注入业务层的service
// 未加入@ResponseBody用来返回数据给页面
@RequestMapping("view")
public String view(Model model) {
// String str="个人";
// model用来回传数据给前端页面
// setTitle(model, new TitleVo("列表", str+"管理", true,"欢迎进入"+str+"页面", true, false));
return prefix + "/view";
}
// @ResponseBody,直接通过js异步返回数据给页面
@RequestMapping("list")
@ResponseBody
public DataTableResult list(HttpServletRequest request, UserVO userVO) {
// DataTableResult返回给datatables控件的数据格式
DataTableResult result = new DataTableResult();
// 获取分页参数
int start = Integer.parseInt(request.getParameter("start"));
int length = Integer.parseInt(request.getParameter("length"));
// 获取排序字段
String orderIdx = request.getParameter("order[0][column]");
// 获取排序字段名
String orderField = request.getParameter("columns[" + orderIdx + "][name]");
// 获取排序方式,降序desc或者升序asc
String orderDir = request.getParameter("order[0][dir]");
// 调用分页查询方法
result = userService.selectUserListPage(userVO, start, length, orderField, orderDir);
// result.setDraw(userVO.getDraw());
return result;
}
// @ResponseBody,直接通过js异步返回数据给页面
@RequestMapping("insert")
@ResponseBody
public Json insert(User user) {
Json j = new Json();
if (userService.insert(user) > 0) {
j.setSuccess(true);
j.setMsg("添加成功!");
} else {
j.setSuccess(false);
j.setMsg("添加失败!");
}
return j;
}
// @ResponseBody,直接通过js异步返回数据给页面
@RequestMapping("update")
@ResponseBody
public Json updateById(User user) {
Json j = new Json();
if (userService.updateById(user) > 0) {
j.setSuccess(true);
j.setMsg("修改成功!");
} else {
j.setSuccess(false);
j.setMsg("修改失败!");
}
return j;
}
// @ResponseBody,直接通过js异步返回数据给页面
@RequestMapping("select")
@ResponseBody
public Json selectById(User user) {
Json j = new Json();
j.setSuccess(true);
j.setObj(userService.selectById(user.getId()));
return j;
}
// @ResponseBody,直接通过js异步返回数据给页面
@RequestMapping("delete")
@ResponseBody
public Json delete(HttpServletRequest request) {
Json j = new Json();
String ids = request.getParameter("ids");
if (!StringUtils.isEmpty(ids)) {
j.setSuccess(true);
j.setObj("成功删除" + userService.delete(ids) + "条记录");
} else {
j.setSuccess(false);
j.setMsg("没有需要删除的记录!");
}
return j;
}
// @ResponseBody,直接通过js异步返回数据给页面
// @RequestParam("file[]") MultipartFile[] file 多个文件
@RequestMapping("upload")
@ResponseBody
public Json upload(HttpServletRequest request, @RequestParam("file") MultipartFile file) {
Json j = new Json();
if (!file.isEmpty()) {
try {
String originalFilename = file.getOriginalFilename();
// 随机文件名
String newFileName = SnowflakeIdWorker.getUUID()
+ originalFilename.substring(originalFilename.lastIndexOf("."));
// 上传文件路径
File upload = new File(UPLOAD_PATH, "images/");
if (!upload.exists())
upload.mkdirs();
String uploadPath = upload + "\\";
logger.info("uploadPath = " + uploadPath);
File uploadfile = new File(uploadPath + newFileName);
// 将上传文件保存到一个目标文件当中
file.transferTo(uploadfile);
j.setSuccess(true);
j.setObj("/upload/images/" + newFileName);
} catch (IllegalStateException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
j.setSuccess(false);
j.setObj("上传异常");
}
} else {
j.setSuccess(false);
j.setObj("上传失败");
}
return j;
}
/**
* 登录验证
* @param request
* @param user
* @return
*/
// @ResponseBody,直接通过js异步返回数据给页面
@RequestMapping("login")
@ResponseBody
public Json login(HttpServletRequest request, User user) {
Json j = new Json();
HttpSession session = request.getSession();
String code = (String) session.getAttribute(Constants.KAPTCHA_SESSION_KEY);
String kaptcha = request.getParameter("kaptcha");
if (kaptcha.equals(code)) {
// 验证码正确
User loginUser = userService.selectByUser(user);
if (loginUser != null) {
j.setSuccess(true);
loginUser.setPassword("");// 密码不回传
j.setObj(loginUser);
j.setMsg("登录成功!");
session.setAttribute("user", loginUser);// 保存session会话
} else {
j.setSuccess(false);
j.setMsg("登录失败!");
}
} else {
j.setSuccess(false);
j.setMsg("验证码错误!");
}
return j;
}
/**
* 注销
* @param request
* @param model
* @return
*/
// 未加入@ResponseBody用来返回数据给页面
@RequestMapping("logout")
public String logout(HttpServletRequest request,Model model) {
HttpSession session = request.getSession();
session.removeAttribute("user");
return "admin/login";
}
}
增加了用户登录与注销接口。
添加登录界面文件,在“admin”文件夹-> 右键->new->file,输入“login.html”,代码如下:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<title> - 登录</title>
<meta name="keywords" content="">
<meta name="description" content="">
<link th:href="@{/static/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/static/css/font-awesome.css?v=4.4.0}" rel="stylesheet">
<link th:href="@{/static/css/animate.css}" rel="stylesheet">
<link th:href="@{/static/css/style.css}" rel="stylesheet">
<link th:href="@{/static/css/login.css}" rel="stylesheet">
<!-- toastr -->
<link th:href="@{/static/css/plugins/toastr/toastr.min.css}" rel="stylesheet">
<!--[if lt IE 9]>
<meta http-equiv="refresh" content="0;ie.html" />
<![endif]-->
<script>
if (window.top !== window.self) {
window.top.location = window.location;
}
</script>
</head>
<body class="signin">
<div class="signinpanel">
<div class="row">
<div class="col-sm-12">
<form>
<h4 class="no-margins">登录:</h4>
<p class="m-t-md">登录XXX后台管理系统</p>
<input type="text" name="username" id="username" class="form-control uname" placeholder="用户名" />
<input type="password" name="password" id="password" class="form-control pword m-b" placeholder="密码" />
<div class="input-group" style="margin-top:15px;color:#333;">
<input type="text" name="kaptcha" id="kaptcha" class="form-control" style="margin-top:0px;" placeholder="验证码" >
<span style="padding:1px;" class="input-group-addon "><img src="/kaptcha" id="kaptchaImage" style="height:30px;vertical-align:middle;" alt="点击刷新" title="点击刷新" onclick='$("#kaptchaImage").attr("src","/kaptcha?r="+Math.random())'></span>
</div>
<a href="">忘记密码了?</a>
<button type="button" id="btn_login" class="btn btn-success btn-block">登录</button>
</form>
</div>
</div>
<div class="signup-footer">
<div class="pull-left">
© 福建江夏学院电子信息科学学院信息管理系,2019年
</div>
</div>
</div>
<!-- 全局js -->
<script th:src="@{/static/js/jquery.min.js?v=2.1.4}"></script>
<!-- Toastr script -->
<script th:src="@{/static/js/plugins/toastr/toastr.min.js}"></script>
<!-- Page-Level Scripts -->
<script>
$(document).ready(function () {
//toastr选项
toastr.options = {
"positionClass": "toast-bottom-center",
}
$("#btn_login").click(function(){
var username=$("#username").val();
var password=$("#password").val();
var kaptcha=$("#kaptcha").val();
if(username.length==0 || password.length==0 || kaptcha.length==0){
toastr.error("用户名、密码或者验证码为空!", '错误!');
}else{
//异步添加数据
$.ajax({
type: "post",
data: {
username:$("#username").val(),
password:$("#password").val(),
kaptcha:$("#kaptcha").val(),
},
url: "/UserController/login",//后台处理地址
success: function (data) {
if(data.success){
toastr.success(data.msg, '登录成功!');
location.href="/index";
}else{
toastr.error(data.msg, '登录失败!');
}
}
}); // end ajax
}
});//end btn_login
});//end ready
</script>
</body>
</html>
修改主界面文件,在“admin”文件夹中的“index.html”,代码如下:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="renderer" content="webkit">
<title> hAdmin- 主页</title>
<meta name="keywords" content="">
<meta name="description" content="">
<!--[if lt IE 9]>
<meta http-equiv="refresh" content="0;ie.html" />
<![endif]-->
<link rel="shortcut icon" href="favicon.ico">
<link th:href="@{/static/css/bootstrap.min.css?v=3.3.6}" rel="stylesheet">
<link th:href="@{/static/css/font-awesome.min.css?v=4.4.0}" rel="stylesheet">
<link th:href="@{/static/css/animate.css}" rel="stylesheet">
<link th:href="@{/static/css/style.css?v=4.1.0}" rel="stylesheet">
</head>
<body class="fixed-sidebar full-height-layout gray-bg" style="overflow:hidden">
<div id="wrapper">
<!--左侧导航开始-->
<nav class="navbar-default navbar-static-side" role="navigation">
<div class="nav-close"><i class="fa fa-times-circle"></i>
</div>
<div class="sidebar-collapse">
<ul class="nav" id="side-menu">
<li class="nav-header">
<div class="dropdown profile-element">
<a data-toggle="dropdown" class="dropdown-toggle" href="#">
<span class="clear">
<span class="block m-t-xs" style="font-size:20px;">
<i class="fa fa-bank"></i>
<strong class="font-bold">管理系统</strong>
</span>
</span>
</a>
</div>
<div class="logo-element">管理系统
</div>
</li>
<li>
<a class="J_menuItem" href="/home">
<i class="fa fa-home"></i>
<span class="nav-label">主页</span>
</a>
</li>
<li th:each="menu,menuStat:${menulist}">
<a href="#">
<i th:class="${menu.menu.menuIcon}"></i>
<span class="nav-label" th:text="${menu.menu.menuText}"></span>
<span class="fa arrow"></span>
</a>
<ul class="nav nav-second-level">
<li th:each="submenu,submenuStat:${menu.submenus}">
<a class="J_menuItem" th:href="@{${submenu.menuUrl}}" th:text="${submenu.menuText}"></a>
</li>
</ul>
</li>
</ul>
</div>
</nav>
<!--左侧导航结束-->
<!--右侧部分开始-->
<div id="page-wrapper" class="gray-bg dashbard-1">
<div class="row border-bottom">
<nav class="navbar navbar-static-top" role="navigation" style="margin-bottom: 0">
<div class="navbar-header"><a class="navbar-minimalize minimalize-styl-2 btn btn-info " href="#"><i class="fa fa-bars"></i> </a>
<form role="search" class="navbar-form-custom" method="post" action="search_results.html">
<div class="form-group">
<input type="text" placeholder="请输入您需要查找的内容 …" class="form-control" name="top-search" id="top-search">
</div>
</form>
</div>
<ul class="nav navbar-top-links navbar-right">
<li>
<span th:text="${username}"></span><span><a href="/UserController/logout">注销</a></span>
</li>
<li class="dropdown">
<a class="dropdown-toggle count-info" data-toggle="dropdown" href="#">
<i class="fa fa-envelope"></i> <span class="label label-warning">16</span>
</a>
<ul class="dropdown-menu dropdown-messages">
<li class="m-t-xs">
<div class="dropdown-messages-box">
<a href="profile.html" class="pull-left">
<img alt="image" class="img-circle" th:src="@{/static/img/a7.jpg}">
</a>
<div class="media-body">
<small class="pull-right">46小时前</small>
<strong>小四</strong> 是不是只有我死了,你们才不骂爵迹
<br>
<small class="text-muted">3天前 2014.11.8</small>
</div>
</div>
</li>
<li class="divider"></li>
<li>
<div class="dropdown-messages-box">
<a href="profile.html" class="pull-left">
<img alt="image" class="img-circle" th:src="@{/static/img/a4.jpg}">
</a>
<div class="media-body ">
<small class="pull-right text-navy">25小时前</small>
<strong>二愣子</strong> 呵呵
<br>
<small class="text-muted">昨天</small>
</div>
</div>
</li>
<li class="divider"></li>
<li>
<div class="text-center link-block">
<a class="J_menuItem" href="mailbox.html">
<i class="fa fa-envelope"></i> <strong> 查看所有消息</strong>
</a>
</div>
</li>
</ul>
</li>
<li class="dropdown">
<a class="dropdown-toggle count-info" data-toggle="dropdown" href="#">
<i class="fa fa-bell"></i> <span class="label label-primary">8</span>
</a>
<ul class="dropdown-menu dropdown-alerts">
<li>
<a href="mailbox.html">
<div>
<i class="fa fa-envelope fa-fw"></i> 您有16条未读消息
<span class="pull-right text-muted small">4分钟前</span>
</div>
</a>
</li>
<li class="divider"></li>
<li>
<a href="profile.html">
<div>
<i class="fa fa-qq fa-fw"></i> 3条新回复
<span class="pull-right text-muted small">12分钟钱</span>
</div>
</a>
</li>
<li class="divider"></li>
<li>
<div class="text-center link-block">
<a class="J_menuItem" href="notifications.html">
<strong>查看所有 </strong>
<i class="fa fa-angle-right"></i>
</a>
</div>
</li>
</ul>
</li>
</ul>
</nav>
</div>
<div class="row J_mainContent" id="content-main">
<iframe id="J_iframe" width="100%" height="100%" src="/home" frameborder="0" data-id="/home" seamless></iframe>
</div>
</div>
<!--右侧部分结束-->
</div>
<!-- 全局js -->
<script th:src="@{/static/js/jquery.min.js?v=2.1.4}"></script>
<script th:src="@{/static/js/bootstrap.min.js?v=3.3.6}"></script>
<script th:src="@{/static/js/plugins/metisMenu/jquery.metisMenu.js}"></script>
<script th:src="@{/static/js/plugins/slimscroll/jquery.slimscroll.min.js}"></script>
<script th:src="@{/static/js/plugins/layer/layer.min.js}"></script>
<!-- 自定义js -->
<script th:src="@{/static/js/hAdmin.js?v=4.1.0}"></script>
<script type="text/javascript" th:src="@{/static/js/index.js}"></script>
<!-- 第三方插件 -->
<script th:src="@{/static/js/plugins/pace/pace.min.js}"></script>
</body>
</html>
利用“th:each”对后台返回的"${menulist}"数据进行二重遍历,动态构造出用户的菜单。
打开浏览器,先输入地址http://127.0.0.1:8080/index,会发现跳转到登录页面,效果如下:
以不同身份登录时菜单不同,以用户名和密码都为“e”登录:
点击右上角的注销链接后,以用户名和密码都为“a”登录:
还可以通过后台动态修改角色的权限和用户的角色,注意首先是对角色分配权限,然后再把角色分配给用户。
完整的项目源码已经分享在GitHub网站上(https://github.com/gjq246/springboot2author)。