SpringBoot + Layui +Mybatis-plus实现简单后台管理系统(内置安全过滤器)
1. 简介
layui(谐音:类UI)是一款采用自身模块规范编写的前端UI框架,遵循原生HTML/CSS/JS的书写与组织形式,门槛极低,拿来即用。其外在极简,却又不失饱满的内在,体积轻盈,组件丰盈,从核心代码到API的每一处细节都经过精心雕琢,非常适合界面的快速开发。
(1)为服务端程序员量身定做的低门槛开箱即用的前端UI解决方案;
(2)兼容IE6/7除外的全部浏览器;
(3)采用经典模块化,避免工具的复杂配置,回归简单;
(4)更多请浏览Layui官网:https://www.layui.com/
2. 初始化数据库
创建数据库layuidemo
,并初始化表结构:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_sys_user
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_user`;
CREATE TABLE `t_sys_user` (
`id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户名称',
`nickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户昵称',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户密码',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统用户' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of t_sys_user
-- ----------------------------
INSERT INTO `t_sys_user` VALUES (1, 'user', 'C3Stones', '$2a$10$WXEPqxjMwY6d6A0hkeBtGu.acRRWUOJmX7oLUuYMHF1VWWUm4EqOC');
INSERT INTO `t_sys_user` VALUES (2, 'system', '管理员', '$2a$10$dmO7Uk9/lo1D5d1SvCGgWuB050a0E2uuBDNITEpWFiIfCg.3UbA8y');
SET FOREIGN_KEY_CHECKS = 1;
3. 示例代码
建议下载示例工程,参考搭建自己的示例工程。
- 创建项目
- 修改pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.c3stones</groupId>
<artifactId>spring-boot-layui-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-layui-demo</name>
<description>SpringBoot+Mybatis-Plus+Layui Demo</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.8.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.11.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 创建配置文件application.yml
server:
port: 8080
servlet:
session:
timeout: 1800s
spring:
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/layuidemo?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
username: root
password: 123456
thymeleaf:
prefix: classpath:/view/
suffix: .html
encoding: UTF-8
servlet:
content-type: text/html
# 生产环境设置true
cache: false
# Mybatis-plus配置
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
global-config:
db-config:
id-type: AUTO
configuration:
# 打印sql
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 信息安全
security:
web:
excludes:
- /login
- /logout
- /images/**
- /jquery/**
- /layui/**
xss:
enable: true
excludes:
- /login
- /logout
- /images/*
- /jquery/*
- /layui/*
sql:
enable: true
excludes:
- /images/*
- /jquery/*
- /layui/*
csrf:
enable: true
excludes:
- 创建Mybatis-Plus配置类(配置分页)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
/**
* Mybatis-Plus配置类
*
* @author CL
*
*/
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class MybatisPlusConfig {
/**
* 注入分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
- 创建响应实体
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* 响应实体
*
* @author CL
*
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Response<T> {
/**
* 响应码
*/
private int code;
/**
* 响应消息体
*/
private String msg;
/**
* 响应数据
*/
private T data;
/**
* 失败响应
*
* @param msg 响应消息体
* @return
*/
public static <T> Response<T> error(String msg) {
return new Response<T>(500, msg, null);
}
/**
* 成功响应
*
* @param data 响应数据
* @return
*/
public static <T> Response<T> success(T data) {
return new Response<T>(200, null, data);
}
/**
* 成功响应
*
* @param msg 响应消息体
* @return
*/
public static <T> Response<T> success(String msg) {
return new Response<T>(200, msg, null);
}
/**
* 成功响应
*
* @param msg 响应消息体
* @param data 响应数据
* @return
*/
public static <T> Response<T> success(String msg, T data) {
return new Response<T>(200, msg, data);
}
}
- 创建全局异常处理类
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 全局异常处理
*
* @author CL
*
*/
@Controller
public class WebExceptionAdvice implements ErrorController {
/**
* 获得异常路径
*/
@Override
public String getErrorPath() {
return "error";
}
/**
* 异常处理,跳转到响应的页面
*
* @param request
* @param model
* @return
*/
@RequestMapping(value = "error")
public String handleError(HttpServletRequest request, Model model) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
Throwable throwable = (Throwable) request.getAttribute("javax.servlet.error.exception");
model.addAttribute("message", throwable != null ? throwable.getMessage() : null);
switch (statusCode) {
case 400:
return "error/400";
case 403:
return "error/403";
case 404:
return "error/404";
default:
return "error/500";
}
}
}
- 创建实体
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* 系统用户信息
*
* @author CL
*
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "t_sys_user")
@EqualsAndHashCode(callSuper = false)
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 用户名称
*/
private String username;
/**
* 用户昵称
*/
private String nickname;
/**
* 用户密码
*/
@JsonIgnore
private String password;
}
- 创建Mapper
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.c3stones.entity.User;
/**
* 系统用户Mapper
*
* @author CL
*
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
- 创建Service
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.c3stones.entity.User;
/**
* 系统用户Service
*
* @author CL
*
*/
public interface UserService extends IService<User> {
/**
* 查询列表数据
*
* @param user 系统用户
* @param current 当前页
* @param size 每页显示条数
* @return
*/
public Page<User> listData(User user, long current, long size);
/**
* 检验用户名称是否唯一
*
* @param userName 用户名称
* @return
*/
public Boolean checkUserName(String userName);
}
- 创建Service实现
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.c3stones.entity.User;
import com.c3stones.mapper.UserMapper;
import com.c3stones.service.UserService;
import cn.hutool.core.util.StrUtil;
/**
* 系统用户Service实现
*
* @author CL
*
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
/**
* 查询列表数据
*
* @param user 系统用户
* @param current 当前页
* @param size 每页显示条数
* @return
*/
@Override
public Page<User> listData(User user, long current, long size) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (null != user.getId()) {
queryWrapper.eq("id", user.getId());
}
if (StrUtil.isNotBlank(user.getUsername())) {
queryWrapper.like("username", user.getUsername());
}
if (StrUtil.isNotBlank(user.getNickname())) {
queryWrapper.like("nickname", user.getNickname());
}
return baseMapper.selectPage(new Page<>(current, size), queryWrapper);
}
/**
* 检验用户名称是否唯一
*
* @param userName 用户名称
* @return
*/
@Override
public Boolean checkUserName(String userName) {
if (StrUtil.isNotBlank(userName)) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("username", userName);
Integer count = baseMapper.selectCount(queryWrapper);
return (count != null && count > 0) ? false : true;
}
return false;
}
}
- 创建登录Controller
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.c3stones.common.Response;
import com.c3stones.entity.User;
import com.c3stones.service.UserService;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.BCrypt;
/**
* 系统登录Controller
*
* @author CL
*
*/
@Controller
public class LoginController {
@Autowired
private UserService userService;
/**
* 登录页
*
* @return
*/
@GetMapping(value = { "login", "" })
public String login() {
return "login";
}
/***
* 登录验证
*
* @param user 系统用户
* @return
*/
@PostMapping(value = "login")
@ResponseBody
public Response<User> login(User user, HttpSession session) {
if (StrUtil.isBlank(user.getUsername()) || StrUtil.isBlank(user.getPassword())) {
return Response.error("用户名或密码不能为空");
}
User queryUser = new User();
queryUser.setUsername(user.getUsername());
queryUser = userService.getOne(new QueryWrapper<>(queryUser));
if (queryUser == null || !StrUtil.equals(queryUser.getUsername(), user.getUsername())
|| !BCrypt.checkpw(user.getPassword(), queryUser.getPassword())) {
return Response.error("用户名或密码错误");
}
session.setAttribute("user", queryUser);
return Response.success("登录成功", queryUser);
}
/**
* 登出
*
* @param httpSession
* @return
*/
@GetMapping(value = "logout")
public String logout(HttpSession httpSession) {
httpSession.invalidate();
return "redirect:/login";
}
}
- 创建登录拦截器
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 登录拦截器
*
* @author CL
*
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
/**
* 拦截处理
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
Object user = request.getSession().getAttribute("user");
if (null == user) {
response.sendRedirect("/login");
}
return true;
}
}
- 配置登录拦截器
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import lombok.Setter;
/**
* Web配置类
*
* @author CL
*
*/
@Configuration
@ConfigurationProperties(prefix = "security.web")
public class WebConfigurer implements WebMvcConfigurer {
/**
* 忽略的URL
*/
@Setter
private List<String> excludes;
/**
* 配置拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns(excludes);
}
}
- 创建首页Contrller
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
/**
* 系统首页Controller
*
* @author CL
*
*/
@Controller
public class IndexController {
/**
* 首页
*
* @return
*/
@GetMapping(value = "index")
public String index(Model model, HttpSession httpSession) {
model.addAttribute("user", httpSession.getAttribute("user"));
return "index";
}
/**
* 控制台
*
* @return
*/
@GetMapping(value = "view")
public String view() {
return "pages/view";
}
}
- 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动类
*
* @author CL
*
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- 拷贝静态资源
将示例工程的resource目录下的static文件夹及其子文件拷贝到本工程对应文件夹下。 - 创建前端页面文件夹
在resource目录下创建view文件夹,工程的所有页面都会写在此文件夹下(和配置文件中的spring.thymeleaf.prefix对应)。 - 创建登录页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>C3Stones</title>
<link th:href="@{/images/favicon.ico}" rel="icon">
<link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
<link th:href="@{/layui/css/login.css}" rel="stylesheet" />
<link th:href="@{/layui/css/view.css}" rel="stylesheet" />
<script th:src="@{/layui/layui.all.js}"></script>
<script th:src="@{/jquery/jquery-2.1.4.min.js}"></script>
</head>
<body class="login-wrap">
<div class="login-container">
<form class="login-form">
<div class="input-group text-center text-gray">
<h2>欢迎登录</h2>
</div>
<div class="input-group">
<input type="text" id="username" class="input-field">
<label for="username" class="input-label">
<span class="label-title">用户名</span>
</label>
</div>
<div class="input-group">
<input type="password" id="password" class="input-field">
<label for="password" class="input-label">
<span class="label-title">密码</span>
</label>
</div>
<button type="button" class="login-button">登录<i class="ai ai-enter"></i></button>
</form>
</div>
</body>
</html>
<script>
layui.define(['element'],function(exports){
var $ = layui.$;
$('.input-field').on('change',function(){
var $this = $(this),
value = $.trim($this.val()),
$parent = $this.parent();
if(!isEmpty(value)){
$parent.addClass('field-focus');
}else{
$parent.removeClass('field-focus');
}
})
exports('login');
});
// 登录
var layer = layui.layer;
$(".login-button").click(function() {
var username = $("#username").val();
var password = $("#password").val();
if (isEmpty(username) || isEmpty(password)) {
layer.msg("用户名或密码不能为空", {icon: 2});
return ;
}
var loading = layer.load(1, {shade: [0.3, '#fff']});
$.ajax({
url : "[[@{/}]]login",
data : {username : username, password : password},
type : "post",
dataType : "json",
error : function(data) {
},
success : function(data) {
layer.close(loading);
if (data.code == 200) {
location.href = "[[@{/}]]index";
} else {
layer.msg(data.msg, {icon: 2});
}
}
});
});
function isEmpty(n) {
if (n == null || n == '' || typeof(n) == 'undefined') {
return true;
}
return false;
}
</script>
- 创建系统框架页面,并初始化菜单
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>C3Stones</title>
<link th:href="@{/images/favicon.ico}" rel="icon">
<link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
<link th:href="@{/layui/css/admin.css}" rel="stylesheet" />
<script th:src="@{/layui/layui.js}"></script>
<script th:src="@{/layui/js/index.js}" data-main="home"></script>
</head>
<body class="layui-layout-body">
<div class="layui-layout layui-layout-admin">
<div class="layui-header custom-header">
<ul class="layui-nav layui-layout-left">
<li class="layui-nav-item slide-sidebar" lay-unselect>
<a href="javascript:;" class="icon-font"><i class="ai ai-menufold"></i></a>
</li>
</ul>
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item">
<a href="javascript:;">[[${user?.nickname}]]</a>
<dl class="layui-nav-child">
<dd><a th:href="@{/logout}">退出</a></dd>
</dl>
</li>
</ul>
</div>
<div class="layui-side custom-admin">
<div class="layui-side-scroll">
<div class="custom-logo">
<img alt="" th:src="@{/images/logo.jpg}">
<h1>C3Stones</h1>
</div>
<ul id="Nav" class="layui-nav layui-nav-tree">
<li class="layui-nav-item">
<a href="javascript:;">
<i class="layui-icon"></i>
<em>主页</em>
</a>
<dl class="layui-nav-child">
<dd><a th:href="@{/view}">控制台</a></dd>
</dl>
</li>
<li class="layui-nav-item">
<a href="javascript:;">
<i class="layui-icon"></i>
<em>系统管理</em>
</a>
<dl class="layui-nav-child">
<dd><a th:href="@{/user/list}">用户管理</a></dd>
</dl>
</li>
</ul>
</div>
</div>
<div class="layui-body">
<div class="layui-tab app-container" lay-allowClose="true" lay-filter="tabs">
<ul id="appTabs" class="layui-tab-title custom-tab"></ul>
<div id="appTabPage" class="layui-tab-content"></div>
</div>
</div>
<div class="layui-footer">
<p>© 2020 - C3Stones Blog : <a href="https://www.cnblogs.com/cao-lei/" target="_blank">https://www.cnblogs.com/cao-lei/</a></p>
</div>
<div class="mobile-mask"></div>
</div>
</body>
</html>
- 创建控制台页面
在view文件夹下创建pages文件夹。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
<link th:href="@{/layui/css/view.css}" rel="stylesheet" />
<script th:src="@{/layui/layui.all.js}"></script>
<script th:src="@{/jquery/jquery-2.1.4.min.js}"></script>
<title></title>
</head>
<body class="layui-view-body">
<div class="layui-row" style="text-align: center;">
<div class="layui-col-md12" style="padding: 18% 0px 20px 0px;">
<font class="layui-text"><h1>欢迎使用</h1></font>
</div>
</div>
</body>
</html>
- 登录测试
浏览器访问:http://127.0.0.1:8080/,输入用户名密码:user/123456,或者system/123456,进行测试正常用户登录,并测试用户不存在、密码错误等异常。 - 创建用户Controller
import javax.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.c3stones.common.Response;
import com.c3stones.entity.User;
import com.c3stones.service.UserService;
import cn.hutool.crypto.digest.BCrypt;
/**
* 系统用户Controller
*
* @author CL
*
*/
@Controller
@RequestMapping(value = "user")
public class UserController {
@Autowired
private UserService userService;
/**
* 查询列表
*
* @return
*/
@RequestMapping(value = "list")
public String list() {
return "pages/userList";
}
/**
* 查询列表数据
*
* @param user 系统用户
* @param current 当前页
* @param size 每页显示条数
* @return
*/
@RequestMapping(value = "listData")
@ResponseBody
public Response<Page<User>> listData(User user, @RequestParam(name = "page") long current,
@RequestParam(name = "limit") long size) {
Page<User> page = userService.listData(user, current, size);
return Response.success(page);
}
/**
* 删除
*
* @param user 系统用户
* @return
*/
@RequestMapping(value = "delete")
@ResponseBody
public Response<Boolean> delete(User user) {
Assert.notNull(user.getId(), "ID不能为空");
boolean result = userService.removeById(user.getId());
return Response.success(result);
}
/**
* 修改
*
* @param user 系统用户
* @param model
* @return
*/
@RequestMapping(value = "edit")
public String edit(User user, Model model) {
Assert.notNull(user.getId(), "ID不能为空");
model.addAttribute("user", userService.getById(user.getId()));
return "pages/userEdit";
}
/**
* 检验用户名称是否唯一
*
* @param userName 用户名称
* @return
*/
@RequestMapping(value = "check")
@ResponseBody
public Response<Boolean> checkUserName(@NotNull String username) {
Boolean checkResult = userService.checkUserName(username);
return Response.success(checkResult);
}
/**
* 更新
*
* @param user 系统用户
* @return
*/
@RequestMapping(value = "update")
@ResponseBody
public Response<Boolean> update(User user) {
Assert.notNull(user.getId(), "ID不能为空");
boolean result = userService.updateById(user);
return Response.success(result);
}
/**
* 新增
*
* @return
*/
@RequestMapping(value = "add")
public String add() {
return "pages/userAdd";
}
/**
* 保存
*
* @param user 系统用户
* @return
*/
@RequestMapping(value = "save")
@ResponseBody
public Response<Boolean> save(User user) {
user.setPassword(BCrypt.hashpw(user.getPassword()));
boolean result = userService.save(user);
return Response.success(result);
}
}
- 创建用户列表及新增、编辑页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
<link th:href="@{/layui/css/view.css}" rel="stylesheet" />
<script th:src="@{/layui/layui.all.js}"></script>
<script th:src="@{/jquery/jquery-2.1.4.min.js}"></script>
<title></title>
</head>
<body class="layui-view-body">
<div class="layui-content">
<div class="layui-row">
<div class="layui-card">
<div class="layui-card-header">
<i class="layui-icon mr5"></i>用户管理
<button class="layui-btn layui-btn-xs layui-btn-normal pull-right mt10" data-type="add"><i class="layui-icon mr5"></i>新增</button>
</div>
<div class="layui-card-body">
<div class="searchTable">
用户ID:
<div class="layui-inline mr5">
<input class="layui-input" name="id" autocomplete="off">
</div>
用户名称:
<div class="layui-inline mr5">
<input class="layui-input" name="username" autocomplete="off">
</div>
用户昵称:
<div class="layui-inline mr10">
<input class="layui-input" name="nickname" autocomplete="off">
</div>
<button class="layui-btn" data-type="reload">查询</button>
<button class="layui-btn layui-btn-primary" data-type="reset">重置</button>
</div>
<table class="layui-hide" id="userDataTable" lay-filter="config"></table>
<script type="text/html" id="operation">
<a class="layui-btn layui-btn-xs" lay-event="edit">修改</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
</div>
</div>
</div>
</div>
</body>
<script>
var element = layui.element;
var table = layui.table;
var layer = layui.layer;
table.render({
id: 'userTable'
,elem: '#userDataTable'
,url: '[[@{/user/listData}]]'
,page: {
layout: ['prev', 'page', 'next', 'count', 'skip', 'limit']
,groups: 5
,first: false
,last: false
}
,cols: [
[
{field:'id', width: 50, title: 'ID'}
,{field:'username', title: '用户名称', align: 'center'}
,{field:'nickname', title: '用户昵称', align: 'center'}
,{title:'操作', align: 'center', toolbar: '#operation', width:150}
]
]
,response: {
statusCode: 200
}
,parseData: function(res){
return {
"code": res.code
,"msg": res.msg
,"count": res.data.total
,"data": res.data.records
};
}
});
active = {
add: function() {
layer.open({
type: 2,
area: ['80%', '80%'],
title: '新增',
content: '[[@{/}]]user/add'
});
},
reload: function() {
table.reload('userTable', {
page: {
curr: 1
}
,where: {
id : $("input[name='id']").val()
,username : $("input[name='username']").val()
,nickname : $("input[name='nickname']").val()
}
}, 'data');
},
reset: function() {
$(".layui-input").val("");
}
};
// 按钮事件
$('.layui-btn').on('click', function(){
var type = $(this).data('type');
active[type] ? active[type].call(this) : '';
});
//监听行工具事件
table.on('tool(config)', function(obj){
var row = obj.data;
if(obj.event === 'del') {
layer.confirm("确认删除吗?", {icon: 3, title:'提示'}, function(index) {
layer.close(index);
$.ajax({
url : "[[@{/}]]user/delete",
data : {'id': row.id},
type : "post",
dataType : "json",
error : function(data) {
errorHandle(data);
},
success : function(data) {
$(".searchTable .layui-btn").eq(0).click();
}
});
});
} else if (obj.event === 'edit') {
layer.open({
type: 2,
area: ['80%', '80%'],
title: '修改',
content: '[[@{/}]]user/edit?id=' + row.id
});
}
});
//错误处理
function errorHandle(data) {
if (data.status == 404) {
layer.msg("请求资源不存在", {icon: 2});
} else {
layer.msg("服务端异常", {icon: 2});
}
return;
}
</script>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
<link th:href="@{/layui/css/view.css}" rel="stylesheet" />
<script th:src="@{/layui/layui.all.js}"></script>
<script th:src="@{/jquery/jquery-2.1.4.min.js}"></script>
<script th:src="@{/jquery/jquery-form.js}"></script>
<title></title>
</head>
<body class="layui-view-body">
<div class="layui-row">
<div class="layui-card">
<form class="layui-form layui-card-body layui-form-pane" method="post" th:action="@{/user/save}">
<div class="layui-form-item">
<label class="layui-form-label"><i>*</i>用户名称</label>
<div class="layui-input-block">
<input type="text" name="username" lay-verify="username" placeholder="6-8位英文字母" maxlength="8" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><i>*</i>用户昵称</label>
<div class="layui-input-block">
<input type="text" name="nickname" lay-verify="required" maxlength="15" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><i>*</i>用户密码</label>
<div class="layui-input-inline">
<input type="password" name="password" required lay-verify="password" maxlength="8" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">请输入6-8位密码,且只能包含字母或数字</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><i>*</i>确认密码</label>
<div class="layui-input-inline">
<input type="password" name="confirmPwd" lay-verify="confirm" maxlength="8" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<button type="submit" class="layui-btn" lay-submit lay-filter="*">提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</form>
</div>
</div>
</body>
<script>
var form = layui.form;
var layer = layui.layer;
// 自定义检验
form.verify({
username: function(val) {
if (isEmpty(val)) {
return '必填项不能为空';
}
debugger;
var reg = /^[A-Za-z]{6,8}$/;
if (!reg.test(val)) {
return '用户名称不合法';
}
if (!checkUsername(val)) {
return '用户名称已存在';
}
},
password: [
/^[A-Za-z0-9]{6,8}$/
,'请输入6-8位密码,且只能包含字母或数字'
],
confirm: function(val) {
if (isEmpty(val)) {
return '必填项不能为空';
}
if (val != $("input[name='password']").val()) {
return '确认密码与用户密码不一致';
}
}
});
// 检测用户名称是否唯一
function checkUsername(username) {
var checkResult = true;
$.ajax({
url : "[[@{/}]]user/check",
data : {'username': username},
type : "post",
dataType : "json",
async: false,
error : function(data) {
errorHandle(data);
},
success : function(data) {
checkResult = data.data;
}
});
return checkResult;
}
// 提交表单
form.on('submit(*)', function(data){
$(".layui-form").ajaxForm({
error: function(data){
errorHandle(data);
},
success: function(data) {
parent.location.reload();
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
}
});
});
//是否为空
function isEmpty(n) {
if (n == null || n == '' || typeof(n) == 'undefined') {
return true;
}
return false;
}
// 错误处理
function errorHandle(data) {
if (data.status == 404) {
layer.msg("请求资源不存在", {icon: 2});
} else {
layer.msg("服务端异常", {icon: 2});
}
return;
}
</script>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
<link th:href="@{/layui/css/view.css}" rel="stylesheet" />
<script th:src="@{/layui/layui.all.js}"></script>
<script th:src="@{/jquery/jquery-2.1.4.min.js}"></script>
<script th:src="@{/jquery/jquery-form.js}"></script>
<title></title>
</head>
<body class="layui-view-body">
<div class="layui-row">
<div class="layui-card">
<form class="layui-form layui-card-body layui-form-pane" method="post" th:action="@{/user/update}">
<div class="layui-form-item">
<label class="layui-form-label">用户ID</label>
<div class="layui-input-block">
<input type="text" name="id" th:value="${user?.id}" readonly="readonly" class="layui-input readonly">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><i>*</i>用户名称</label>
<div class="layui-input-block">
<input type="text" name="username" th:value="${user?.username}" lay-verify="username" placeholder="6-8位英文字母" maxlength="8" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><i>*</i>用户昵称</label>
<div class="layui-input-block">
<input type="text" name="nickname" th:value="${user?.nickname}" lay-verify="required" maxlength="15" lay-reqtext="用户昵称是必填项" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<button type="submit" class="layui-btn" lay-submit lay-filter="*">提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</form>
</div>
</div>
</body>
<script>
var form = layui.form;
var layer = layui.layer;
// 自定义检验
form.verify({
username: function(val) {
if (isEmpty(val)) {
return '必填项不能为空';
}
if (val != '[[${user?.username}]]') {
var reg = /^[A-Za-z]{6,8}$/;
if (!reg.test(val)) {
return '用户名称不合法';
}
if (!checkUsername(val)) {
return '用户名称已存在';
}
}
}
});
// 检测用户名称是否唯一
function checkUsername(username) {
var checkResult = true;
$.ajax({
url : "[[@{/}]]user/check",
data : {'username': username},
type : "post",
dataType : "json",
async: false,
error : function(data) {
errorHandle(data);
},
success : function(data) {
checkResult = data.data;
}
});
return checkResult;
}
// 提交表单
form.on('submit(*)', function(data){
$(".layui-form").ajaxForm({
error: function(data){
errorHandle(data);
},
success: function(data) {
parent.location.reload();
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
}
});
});
// 是否为空
function isEmpty(n) {
if (n == null || n == '' || typeof(n) == 'undefined') {
return true;
}
return false;
}
// 错误处理
function errorHandle(data) {
if (data.status == 404) {
layer.msg("请求资源不存在", {icon: 2});
} else {
layer.msg("服务端异常", {icon: 2});
}
return;
}
</script>
</html>
- 创建全局异常页面(404、500等)
在view文件夹下创建error文件夹,并创建404.html、500.html等。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>C3Stones</title>
<link th:href="@{/images/favicon.ico}" rel="icon">
<style>
body {
height: 300px;
background: url("/images/404.png") center no-repeat;
}
</style>
</head>
<body class="layui-layout-body">
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>C3Stones</title>
<link th:href="@{/images/favicon.ico}" rel="icon">
<style>
body {
height: 300px;
background: url("/images/500.png") center no-repeat;
}
</style>
</head>
<body class="layui-layout-body">
</body>
</html>
- 配置安全过滤器
示例工程已经添加了部分相关过滤器,详情请浏览:
跨站点脚本编制 - SpringBoot配置XSS过滤器(基于Jsoup)
SQL盲注、SQL注入 - SpringBoot配置SQL注入过滤器
跨站点请求伪造 - SpringBoot配置CSRF过滤器 - 测试用户管理
点击菜单:系统管理>用户管理,测试模糊查询、新增、修改,并通过新增用户登录。