• 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',  50, title: 'ID'}
    	      ,{field:'username', title: '用户名称', align: 'center'}
    	      ,{field:'nickname', title: '用户昵称', align: 'center'}
    	      ,{title:'操作', align: 'center', toolbar: '#operation', 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

  • 相关阅读:
    Sqoop
    Mediawiki
    TextMate 通用快捷键
    Wind7外接显示器选择拓展模式后,鼠标只能往右移动才能切换到外接显示器上,不能修改切换方向
    用Nginx+Lua(OpenResty)开发高性能Web应用
    netty4.0.x源码分析—bootstrap
    mysql分组合并GROUP_CONCAT
    Only POT texture can be compressed to PVRTC format
    手机屏幕左下角显示Fastboot mode是什么情况?
    判断UNITY版本号
  • 原文地址:https://www.cnblogs.com/cao-lei/p/13706852.html
Copyright © 2020-2023  润新知