前言
前端页面基于 AdminLTE3 模板进行开发的。
下载地址:https://github.com/ColorlibHQ/AdminLTE/releases
声明:该项目是GitHub上的开源项目,本人已购买作者相关课程,仅供个人学习使用。
课程链接:https://www.lanqiao.cn/courses/1367
用到的技术
- springboot
- thymeleaf
- lombok
- ajax
- kaptcha(验证码功能)
AdminLTE3 模板整合
新建一个springboot项目
添加web启动器和thymeleaf启动器及lombok
- 后续需要其他功能了再添加对应的依赖
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
整合AdminLTE3 模板
整合过程其实是我们把 AdminLTE3 代码压缩包中我们需要的样式文件、js 文件、图片等静态资源放入我们 Spring Boot 项目的静态资源目录下,比如 static 目录或者其他我们设置的静态资源目录,几个重要的文件我们都在下图中用红线进行标注了,目录如下:
后台管理模块
登录功能模块
数据库和表设计
注意点
- 插入数据的时候就使用md5加密,密码字段要设计的大点
-- 新建一个数据库
CREATE DATABASE `rm_blog`;
USE `rm_blog`;
-- 新建一个adminUser表
DROP TABLE IF EXISTS `admin_user`;
CREATE TABLE `admin_user`(
`id` INT(8) NOT NULL AUTO_INCREMENT COMMENT'用户id',
`user_name` VARCHAR(20) NOT NULL COMMENT'用户姓名',
`password` VARCHAR(50) NOT NULL COMMENT'用户密码', -- 使用md5加密,该字段要设计的长点
`nick_name` VARCHAR(20) COMMENT'显示在浏览器界面的用户名',
`locked` TINYINT(4) DEFAULT '0' COMMENT'该用户是否锁定,0未锁',
PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `admin_user`VALUES(1,'rm',MD5('123456'),'初夏那片海',0);
实体类
根据数据库创建对应的实体类
- 实际项目可以看情况是否使用lombok插件
- 使用基本类型包装类创建属性
import lombok.Data;
@Data
public class AdminUser {
//使用基本类型包装类
private Integer id;
private String userName;
private String password;
private String nickName;
private Byte locked;
}
application.yaml
- 开启驼峰命名
mybatis:
configuration:
map-underscore-to-camel-case: true #开启驼峰命名
dao层
导入mybatis-springboot依赖
- 声明mapper,配置mybatis
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
AdminUserMapper
@Mapper //表明是一个Mapper,也可以通过扫描包的方式实现
@Repository //注入spring容器
public interface AdminUserMapper {
//根据用户名和密码进行登录 @Param后面的值是从前端页面接收的值
AdminUser login(@Param("userName") String userName,@Param("password") String password);
}
AdminUserMapper.xml
- 密码使用md5加密处理
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.rm.dao.AdminUserMapper">
<select id="login" resultType="adminUser">
select * from admin_user where user_name=#{userName} and password=md5(#{password});
</select>
</mapper>
application.yaml
- 开启别名
- 开启mapper路径映射
mybatis:
type-aliases-package: com.rm.pojo #别名扫描包
mapper-locations: /mapper/*.xml
sercice层
调用dao层
AdminUserService
@Service
public interface AdminUserService {
AdminUser login(@Param("userName") String userName, @Param("password") String password);
}
AdminUserServiceImpl
@Service
public class AdminUserServiceImpl implements AdminUserService {
@Autowired
private AdminUserMapper adminUserMapper;
@Override
public AdminUser login(String userName, String password) {
return adminUserMapper.login(userName,password);
}
}
验证码功能
验证码配置
配置验证码颜色,字体,长度等属性
KaptchaConfig
package com.rm.config;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
//验证码配置类
@Configuration
public class KaptchaConfig {
@Bean
public DefaultKaptcha getDefaultKaptcha(){
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 图片边框
properties.put("kaptcha.border", "no");
// 字体颜色
properties.put("kaptcha.textproducer.font.color", "blue");
// 图片宽
properties.put("kaptcha.image.width", "160");
// 图片高
properties.put("kaptcha.image.height", "40");
// 字体大小
properties.put("kaptcha.textproducer.font.size", "30");
// 验证码长度
properties.put("kaptcha.textproducer.char.length", "4");
// 字体
properties.setProperty("kaptcha.textproducer.font.names", "宋体,微软雅黑");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
验证码生成
生成验证码并保存到session中
package com.rm.controller.common;
import com.google.code.kaptcha.impl.DefaultKaptcha;
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.RequestMapping;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
//验证码Controller
//生成验证码并保存到session中
@Controller
@RequestMapping("/common")
public class KaptchaController {
@Autowired
private DefaultKaptcha captchaProducer;
@GetMapping("/kaptcha")
public void defaultKaptcha(
HttpServletRequest request,
HttpServletResponse response) throws Exception {
byte[] captchaOutputStream;
ByteArrayOutputStream imgOutputStream = new ByteArrayOutputStream();
try {
//生产验证码字符串并保存到session中
String verifyCode = captchaProducer.createText();
request.getSession().setAttribute("verifyCode", verifyCode);
BufferedImage challenge = captchaProducer.createImage(verifyCode);
ImageIO.write(challenge, "jpg", imgOutputStream);
} catch (IllegalArgumentException e) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
captchaOutputStream = imgOutputStream.toByteArray();
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
ServletOutputStream responseOutputStream = response.getOutputStream();
responseOutputStream.write(captchaOutputStream);
responseOutputStream.flush();
responseOutputStream.close();
}
}
controller层
AdminUserController
import com.rm.pojo.AdminUser;
import com.rm.service.AdminUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpSession;
@Controller
@RequestMapping("/admin")
public class AdminController {
//注入AdminUserService
@Autowired
private AdminUserService adminUserService;
//请求方式是get时,解析到login页面
@GetMapping({"/login"})
public String login() {
return "admin/login";
}
//登录成功,重定向到首页
@GetMapping({"/index","/index.html"})
public String index() {
return "admin/index";
}
//请求方式是post时,验证验证码是否输入正确
@PostMapping("/login")
public String login(@RequestParam("userName") String userName,
@RequestParam("password") String password,
@RequestParam("verifyCode") String verifyCode,
HttpSession session) {
if (StringUtils.isEmpty(verifyCode)) {
session.setAttribute("errorMsg", "验证码不能为空");
return "admin/login";
}
if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(password)) {
session.setAttribute("errorMsg", "用户名或密码不能为空");
return "admin/login";
}
String kaptchaCode = session.getAttribute("verifyCode") + "";
if (StringUtils.isEmpty(kaptchaCode) || !verifyCode.equals(kaptchaCode)) {
session.setAttribute("errorMsg", "验证码错误");
return "admin/login";
}
AdminUser adminUser = adminUserService.login(userName, password);
System.out.println("adminUser========="+adminUser);
if (adminUser != null) {
//将登录的用户信息保存到session中
session.setAttribute("loginUser", adminUser);
session.setAttribute("loginUserId", adminUser.getId());
//session过期时间设置为7200秒 即两小时
session.setMaxInactiveInterval(60 * 60 * 2);
return "redirect:/admin/index";
} else {
session.setAttribute("errorMsg", "用户名或密码错误");
return "admin/login";
}
}
}
前端
login.html
<!DOCTYPE html>
<!--导入thymeleaf约束-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Rm Blog登录页</title>
<!--告诉浏览器响应屏幕的宽度-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--导入样式文件-->
<!--该页面的favicon(static目录能自动识别)-->
<link rel="shortcut icon" th:href="@{/admin/dist/img/favicon.ico}" />
<!-- 字体样式 -->
<link rel="stylesheet" th:href="@{/admin/dist/css/font-awesome.min.css}" />
<!-- 图标样式 -->
<link rel="stylesheet" th:href="@{/admin/dist/css/ionicons.min.css}" />
<!-- 主题样式 -->
<link rel="stylesheet" th:href="@{/admin/dist/css/adminlte.min.css}" />
</head>
<body class="hold-transition login-page">
<!--登录框-->
<div class="login-box">
<!--rm blog-->
<div class="login-logo" style="color: #007bff;">
<h1>rm blog</h1>
</div>
<!-- /.login-logo -->
<div class="card">
<div class="card-body login-card-body">
<p class="login-box-msg">请登录</p>
<!--表单提交到后台/login请求-->
<form th:action="@{/admin/login}" method="post">
<div
th:if="${not #strings.isEmpty(session.errorMsg)}"
class="form-group"
>
<div
class="alert alert-danger"
th:text="${session.errorMsg}"
></div>
</div>
<div class="form-group has-feedback">
<span class="fa fa-user form-control-feedback"></span>
<input
type="text"
id="userName"
name="userName"
class="form-control"
placeholder="请输入账号"
required="true"
/>
</div>
<div class="form-group has-feedback">
<span class="fa fa-lock form-control-feedback"></span>
<input
type="password"
id="password"
name="password"
class="form-control"
placeholder="请输入密码"
required="true"
/>
</div>
<div class="row">
<div class="col-6">
<input
type="text"
class="form-control"
name="verifyCode"
placeholder="请输入验证码"
required="true"
/>
</div>
<div class="col-6">
<img
alt="单击图片刷新!"
class="pointer"
th:src="@{/common/kaptcha}"
onclick="this.src='/common/kaptcha?d='+new Date()*1"
/>
</div>
</div>
<div class="form-group has-feedback"></div>
<div class="row">
<div class="col-8"></div>
<div class="col-4">
<button
type="submit"
class="btn btn-primary btn-block btn-flat"
>登录
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<!-- jQuery -->
<script src="@{/admin/plugins/jquery/jquery.min.js}"></script>
<!-- Bootstrap 4 -->
<script src="@{/admin/plugins/bootstrap/js/bootstrap.bundle.min.js}"></script>
<script th:src="@{/admin/dist/js/plugins/particles.js}"></script>
<script th:src="@{/admin/dist/js/plugins/login-bg-particles.js}"></script>
</body>
</html>
测试登录
登录成功跳转到主页
至此,简单的登录模块就完成了。接下来就需要配置登录拦截器,不登录的用户无法访问到主页。
登录拦截器
前面简单的实现了登陆功能,本节将会对功能进行完善,对后台资源请求进行身份认证,即请求拦截验证。
定义拦截器
package com.rm.interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component //添加到spring容器
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
if (uri.startsWith("/admin") && null == request.getSession().getAttribute("loginUser")) {
request.getSession().setAttribute("errorMsg", "请重新登陆");
response.sendRedirect(request.getContextPath() + "/admin/login");
return false;
} else {
request.getSession().removeAttribute("errorMsg");
return true;
}
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
配置拦截器
MvcConfig
package com.rm.config;
import com.rm.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//SpringMvc配置类
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
public void addInterceptors(InterceptorRegistry registry) {
// 添加一个拦截器,拦截以/admin为前缀的url路径
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/admin/**") //拦截以/admin为前缀的url路径
.excludePathPatterns("/admin/login") //放行登录页
.excludePathPatterns("/admin/dist/**") //放行静态资源
.excludePathPatterns("/admin/plugins/**"); //放行静态资源
}
}
未登录状态下/admin以下的所有请求都会被拦截,除了登录页