1. 程式人生 > 實用技巧 >SpringBoot + Layui +Mybatis-plus實現簡單後臺管理系統(內建安全過濾器)

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等)
<!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