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">&#xe68e;</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">&#xe716;</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">&#xe66f;</i>用户管理
                	<button class="layui-btn layui-btn-xs layui-btn-normal pull-right mt10" data-type="add"><i class="layui-icon mr5">&#xe654;</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>

4. 项目地址

  spring-boot-layui-demo

posted @ 2020-09-20 19:27  C3Stones  阅读(4504)  评论(0编辑  收藏  举报