SpringBoot + Thymeleaf 实现登录操作(验证码登录、错误页面跳转、log4j2 配置、拦截器配置)

一、项目目录

在这里插入图片描述

二、配置文件

1、pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.14.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.lemon</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <!--指定字符集-->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <!--排除spring boot 默认日志 不然会报 jar 错误-->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency> <!-- 引入log4j2依赖 -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/net.sf.json-lib/json-lib -->
        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <version>2.4</version>
            <classifier>jdk15</classifier>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2、application.properties 或者(application.yml

## 端口
server.port=8080
##
server.servlet.context-path=/

##数据源配置
##连接池类型
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
##数据库驱动
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
##数据库
spring.datasource.url=jdbc:mysql://localhost:3306/db_base_ssm
##用户名
spring.datasource.username=root
##密码
spring.datasource.password=root

##连接池配置-初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
spring.datasource.druid.initial-size=5
##连接池配置-最小连接数
spring.datasource.druid.min.idle=5
##连接池配置-最大连接数
spring.datasource.druid.max-active=20
##连接池配置-等待超时的时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
spring.datasource.druid.max-wait=60000
##连接池配置-是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
spring.datasource.druid.pool-prepared-statements=false
##连接池配置-要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
spring.datasource.druid.max-open-prepared-statements=-1
##连接池配置-申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.druid.test-on-borrow=false
##连接池配置-归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
spring.datasource.druid.test-on-return=false
##连接池配置-建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.druid.test-while-idle=true
##连接池配置-间隔多久才进行一次检测,检测需要关闭的空闲连接,单位毫秒
spring.datasource.druid.time-between-eviction-runs-millis=60000
##连接池配置-配置一个连接在池中最小生存时间,单位毫秒
spring.datasource.druid.min-evictable-idle-time-millis=300000

##MyBatis配置
mybatis.mapper-locations=classpath*:mybatis/*.xml
mybatis.type-aliases-package=cn.lemon.demo.domain

#配置访问静态文件
spring.mvc.static-path-pattern=/static/**

#thymelea模板配置
#默认页面映射路径
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.servlet.content-type=text/html
#热部署文件,页面不产生缓存,及时更新。关闭模板缓存
spring.thymeleaf.cache=false
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**

##指定log4j2配置文件
logging.config=classpath:log4j2-spring.xml
logging.level.cn.lemon.demo=debug

3、log4j2-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--输出日志等级:DEBUG,INFO,WARN,ERROR-->
<Configuration status="DEBUG">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n"/>
        </Console>

        <RollingFile name="RollingFile" filename="log/test.log" filepattern="${logPath}/%d{YYYYMMddHHmmss}-fargo.log">
            <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="10 MB"/>
            </Policies>
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
    </Appenders>

    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="RollingFile"/>
        </Root>
    </Loggers>
</Configuration>

三、实体类、数据访问层、业务逻辑层、控制层

1、实体类 User.java

package cn.lemon.demo.domain.admin;

import lombok.Getter;
import lombok.Setter;
import org.springframework.stereotype.Component;

@Component
@Getter
@Setter
public class User {
    private Integer id;
    private String username;
    private String password;
    private String photo;
    private int sex;//0:未知;1:男;2:女
    private int age;
    private String address;
}

2、数据访问层 IUserDao.java 和 映射文件 IUserDao.xml

package cn.lemon.demo.dao.admin;

import cn.lemon.demo.domain.admin.User;
import org.springframework.stereotype.Repository;

@Repository
public interface IUserDao {
    User findByUsername(String username);
}
<?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="cn.lemon.demo.dao.admin.IUserDao">
    <!--根据用户名查找用户-->
    <select id="findByUsername" resultType="User" parameterType="String">
        select * from user where username = #{username}
    </select>
</mapper>

3、业务逻辑层 IUserService.javaUserServiceImpl.java

package cn.lemon.demo.service.admin;

import cn.lemon.demo.domain.admin.User;
import org.springframework.stereotype.Service;

@Service
public interface IUserService {
    User findByUsername(String username);
}
package cn.lemon.demo.service.admin.impl;

import cn.lemon.demo.dao.admin.IUserDao;
import cn.lemon.demo.domain.admin.User;
import cn.lemon.demo.service.admin.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private IUserDao iUserDao;

    @Override
    public User findByUsername(String username) {
        return iUserDao.findByUsername(username);
    }
}

4、控制层 SystemController.java

package cn.lemon.demo.controller.admin;

import cn.lemon.demo.domain.admin.User;
import cn.lemon.demo.service.admin.impl.UserServiceImpl;
import cn.lemon.demo.utils.CpachaUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/system")
public class SystemController {
    @Autowired
    private UserServiceImpl userService;

    //404 页面
    @GetMapping(value = "/404")
    public String error_404() {
        return "/errors/404";
    }

    //500页面
    @GetMapping(value = "/500")
    public String error_500() {
        return "/errors/500";
    }

    //登录页面
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public ModelAndView login(ModelAndView model) {
        model.setViewName("/system/login");
        return model;
    }

    //登录后的主页
    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public ModelAndView index(ModelAndView model) {
        model.setViewName("/system/index");
        return model;
    }

    //登录表单处理
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ResponseBody
    public Map<String, String> loginAct(User user, String cpacha, HttpServletRequest request) {
        Map<String, String> ret = new HashMap<String, String>();
        if (user == null) {
            ret.put("type", "error");
            ret.put("msg", "请填写用户信息!");
            return ret;
        }
        if (StringUtils.isEmpty(cpacha)) {
            ret.put("type", "error");
            ret.put("msg", "请填写验证码!");
            return ret;
        }
        if (StringUtils.isEmpty(user.getUsername())) {
            ret.put("type", "error");
            ret.put("msg", "请填写用户名!");
            return ret;
        }
        if (StringUtils.isEmpty(user.getPassword())) {
            ret.put("type", "error");
            ret.put("msg", "请填写密码!");
            return ret;
        }
        Object loginCpacha = request.getSession().getAttribute("loginCpacha");
        if (loginCpacha == null) {
            ret.put("type", "error");
            ret.put("msg", "登录超时,请重新登录!");
            return ret;
        }
        if (!cpacha.toUpperCase().equals(loginCpacha.toString().toUpperCase())) {
            ret.put("type", "error");
            ret.put("msg", "验证码错误!");
            return ret;
        }
        User findByUsername = userService.findByUsername(user.getUsername());
        if (findByUsername == null) {
            ret.put("type", "error");
            ret.put("msg", "用户名不存在!");
            return ret;
        }
        if (!user.getPassword().equals(findByUsername.getPassword())) {
            ret.put("type", "error");
            ret.put("msg", "密码错误!");
            return ret;
        }
        // 把登录的用户写进 Session
        request.getSession().setAttribute("admin", findByUsername);

        ret.put("type", "success");
        ret.put("msg", "登录成功!");
        return ret;
    }

    //获取验证码
    @RequestMapping(value = "/get_cpacha", method = RequestMethod.GET)
    public void generateCpacha(
            @RequestParam(name = "vl", required = false, defaultValue = "4") Integer vcodeLen,
            @RequestParam(name = "w", required = false, defaultValue = "110") Integer width,
            @RequestParam(name = "h", required = false, defaultValue = "30") Integer height,
            @RequestParam(name = "type", required = true, defaultValue = "loginCpacha") String cpacheType,
            HttpServletRequest request, HttpServletResponse response) {
        CpachaUtil cpachaUtil = new CpachaUtil(vcodeLen, width, height);
        String generatorVCode = cpachaUtil.generatorVCode();// 生成随机字符串
        request.getSession().setAttribute(cpacheType, generatorVCode);// 写入Session
        BufferedImage generatorRotateVCodeImage = cpachaUtil.generatorRotateVCodeImage(generatorVCode, true);// 创建图片
        try {
            ImageIO.write(generatorRotateVCodeImage, "gif", response.getOutputStream());// 输出流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5、错误页面配置类 ErrorPageController.java

package cn.lemon.demo.config;

import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

@Component
public class ErrorPageConfig implements ErrorPageRegistrar {
    @Override
    public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
        //1、按错误的类型显示错误的网页
        //错误类型为404,找不到网页的,默认显示404.html网页
        ErrorPage e404 = new ErrorPage(HttpStatus.NOT_FOUND, "/system/404");
        //错误类型为500,表示服务器响应错误,默认显示500.html网页
        ErrorPage e500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/system/500");
        errorPageRegistry.addErrorPages(e404, e500);
    }
}

四、拦截器配置

1、登录拦截器 LoginInterceptor.java

package cn.lemon.demo.interceptor.admin;

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;
import java.util.HashMap;
import java.util.Map;
import net.sf.json.JSONObject;

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        Object admin = request.getSession().getAttribute("admin");
        if (admin == null) {
            System.out.println("连接" + requestURI + "被拦截");
            // 判断是否是 Ajax请求
            String header = request.getHeader("X-Requested-With");
            if ("XMLHttpRequest".equals(header)) {
                // 表示是Ajax 请求
                Map<String, String> ret = new HashMap<String, String>();
                ret.put("type", "error");
                ret.put("msg", "登录会话超时或未登录,请重新登录!");
                response.getWriter().write(JSONObject.fromObject(ret).toString());
                return false;
            }
            // 表示普通连接跳转,直接跳转到登录页面
            // 如果 Session 里没有admin这个用户,也就是登录失效或者未登录,直接重定向到登录页面
            response.sendRedirect(request.getServletContext().getContextPath() + "/system/login");
            return false;
        }
        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 {

    }
}

2、注册拦截器 WebConfigurer.java

package cn.lemon.demo.interceptor.admin;

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.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class WebConfigurer implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    // 这个方法是用来配置静态资源的,比如html,js,css,等等
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    }

    // 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加拦截地址,登录地址与静态文件不拦截
        String[] excludes = new String[]{"/system/login","/static/**","/errors/**","/system/get_cpacha"};
        // addPathPatterns("/**") 表示拦截所有的请求,
        // excludePathPatterns("/login", "/register") 表示除了登陆与注册之外,因为登陆注册不需要登陆也可以访问
        registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns(excludes);
        //super.addInterceptors(registry);    //较新Spring Boot的版本中这里可以直接去掉,否则会报错
    }
}

五、验证码工具类

1、 CpachaUtil.java

package cn.lemon.demo.utils;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.Random;

/**
 * 验证码生成器
 *
 * @author llq
 */
public class CpachaUtil {

    /**
     * 验证码来源
     */
    final private char[] code = {
            '2', '3', '4', '5', '6', '7', '8', '9',
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
            'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v',
            'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F',
            'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R',
            'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
    };
    /**
     * 字体
     */
    final private String[] fontNames = new String[]{
            "黑体", "宋体", "Courier", "Arial",
            "Verdana", "Times", "Tahoma", "Georgia"};
    /**
     * 字体样式
     */
    final private int[] fontStyles = new int[]{
            Font.BOLD, Font.ITALIC|Font.BOLD
    };

    /**
     * 验证码长度
     * 默认4个字符
     */
    private int vcodeLen = 4;
    /**
     * 验证码图片字体大小
     * 默认17
     */
    private int fontsize = 21;
    /**
     * 验证码图片宽度
     */
    private int width = (fontsize+1)*vcodeLen+10;
    /**
     * 验证码图片高度
     */
    private int height = fontsize+12;
    /**
     * 干扰线条数
     * 默认3条
     */
    private int disturbline = 3;

    public CpachaUtil(){
    }

    /**
     * 指定验证码长度
     * @param vcodeLen 验证码长度
     */
    public CpachaUtil(int vcodeLen) {
        this.vcodeLen = vcodeLen;
        this.width = (fontsize+1)*vcodeLen+10;
    }

    /**
     * 指定验证码长度、图片宽度、高度
     * @param vcodeLen
     * @param width
     * @param height
     */
    public CpachaUtil(int vcodeLen,int width,int height) {
        this.vcodeLen = vcodeLen;
        this.width = width;
        this.height = height;
    }

    /**
     * 生成验证码图片
     * @param vcode 要画的验证码
     * @param drawline 是否画干扰线
     * @return
     */
    public BufferedImage generatorVCodeImage(String vcode, boolean drawline){
        //创建验证码图片
        BufferedImage vcodeImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g = vcodeImage.getGraphics();
        //填充背景色
        g.setColor(new Color(246, 240, 250));
        g.fillRect(0, 0, width, height);
        if(drawline){
            drawDisturbLine(g);
        }
        //用于生成伪随机数
        Random ran = new Random();
        //在图片上画验证码
        for(int i = 0;i < vcode.length();i++){
            //设置字体
            g.setFont(new Font(fontNames[ran.nextInt(fontNames.length)], fontStyles[ran.nextInt(fontStyles.length)], fontsize));
            //随机生成颜色
            g.setColor(getRandomColor());
            //画验证码
            g.drawString(vcode.charAt(i)+"", i*fontsize+10, fontsize+5);
        }
        //释放此图形的上下文以及它使用的所有系统资源
        g.dispose();

        return vcodeImage;
    }
    /**
     * 获得旋转字体的验证码图片
     * @param vcode
     * @param drawline 是否画干扰线
     * @return
     */
    public BufferedImage generatorRotateVCodeImage(String vcode, boolean drawline){
        //创建验证码图片
        BufferedImage rotateVcodeImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = rotateVcodeImage.createGraphics();
        //填充背景色
        g2d.setColor(new Color(246, 240, 250));
        g2d.fillRect(0, 0, width, height);
        if(drawline){
            drawDisturbLine(g2d);
        }
        //在图片上画验证码
        for(int i = 0;i < vcode.length();i++){
            BufferedImage rotateImage = getRotateImage(vcode.charAt(i));
            g2d.drawImage(rotateImage, null, (int) (this.height * 0.7) * i, 0);
        }
        g2d.dispose();
        return rotateVcodeImage;
    }
    /**
     * 生成验证码
     * @return 验证码
     */
    public String generatorVCode(){
        int len = code.length;
        Random ran = new Random();
        StringBuffer sb = new StringBuffer();
        for(int i = 0;i < vcodeLen;i++){
            int index = ran.nextInt(len);
            sb.append(code[index]);
        }
        return sb.toString();
    }
    /**
     * 为验证码图片画一些干扰线
     * @param g
     */
    private void drawDisturbLine(Graphics g){
        Random ran = new Random();
        for(int i = 0;i < disturbline;i++){
            int x1 = ran.nextInt(width);
            int y1 = ran.nextInt(height);
            int x2 = ran.nextInt(width);
            int y2 = ran.nextInt(height);
            g.setColor(getRandomColor());
            //画干扰线
            g.drawLine(x1, y1, x2, y2);
        }
    }
    /**
     * 获取一张旋转的图片
     * @param c 要画的字符
     * @return
     */
    private BufferedImage getRotateImage(char c){
        BufferedImage rotateImage = new BufferedImage(height, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = rotateImage.createGraphics();
        //设置透明度为0
        g2d.setColor(new Color(255, 255, 255, 0));
        g2d.fillRect(0, 0, height, height);
        Random ran = new Random();
        g2d.setFont(new Font(fontNames[ran.nextInt(fontNames.length)], fontStyles[ran.nextInt(fontStyles.length)], fontsize));
        g2d.setColor(getRandomColor());
        double theta = getTheta();
        //旋转图片
        g2d.rotate(theta, height/2, height/2);
        g2d.drawString(Character.toString(c), (height-fontsize)/2, fontsize+5);
        g2d.dispose();

        return rotateImage;
    }
    /**
     * @return 返回一个随机颜色
     */
    private Color getRandomColor(){
        Random ran = new Random();
        return new Color(ran.nextInt(220), ran.nextInt(220), ran.nextInt(220));
    }
    /**
     * @return 角度
     */
    private double getTheta(){
        return ((int) (Math.random()*1000) % 2 == 0 ? -1 : 1)*Math.random();
    }

    /**
     * @return 验证码字符个数
     */
    public int getVcodeLen() {
        return vcodeLen;
    }
    /**
     * 设置验证码字符个数
     * @param vcodeLen
     */
    public void setVcodeLen(int vcodeLen) {
        this.width = (fontsize+3)*vcodeLen+10;
        this.vcodeLen = vcodeLen;
    }
    /**
     * @return 字体大小
     */
    public int getFontsize() {
        return fontsize;
    }
    /**
     * 设置字体大小
     * @param fontsize
     */
    public void setFontsize(int fontsize) {
        this.width = (fontsize+3)*vcodeLen+10;
        this.height = fontsize+15;
        this.fontsize = fontsize;
    }
    /**
     * @return 图片宽度
     */
    public int getWidth() {
        return width;
    }
    /**
     * 设置图片宽度
     * @param width
     */
    public void setWidth(int width) {
        this.width = width;
    }
    /**
     * @return 图片高度
     */
    public int getHeight() {
        return height;
    }
    /**
     * 设置图片高度
     * @param height
     */
    public void setHeight(int height) {
        this.height = height;
    }
    /**
     * @return 干扰线条数
     */
    public int getDisturbline() {
        return disturbline;
    }
    /**
     * 设置干扰线条数
     * @param disturbline
     */
    public void setDisturbline(int disturbline) {
        this.disturbline = disturbline;
    }
}

六、HTML页面

1、登录 login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>登录</title>
    <meta name="description" content="particles.js is a lightweight JavaScript library for creating particles.">
    <meta name="author" content="Vincent Garreau">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <link rel="stylesheet" media="screen" th:href="@{/static/login/css/style.css}">
    <link rel="stylesheet" type="text/css" th:href="@{/static/login/css/reset.css}">
</head>
<body>

<div id="particles-js">
    <div class="login" style="display: block;">
        <div class="login-top">登录</div>
        <div class="login-center clearfix">
            <div class="login-center-img">
                <img src="/static/login/images/name.png">
            </div>
            <div class="login-center-input">
                <input type="text" name="username" id="username" value=""
                       placeholder="请输入您的用户名" onfocus="this.placeholder=&#39;&#39;"
                       onblur="this.placeholder=&#39;请输入您的用户名&#39;">
                <div class="login-center-input-text">用户名</div>
            </div>
        </div>
        <div class="login-center clearfix">
            <div class="login-center-img">
                <img src="/static/login/images/password.png">
            </div>
            <div class="login-center-input">
                <input type="password" name="password" id="password" value=""
                       placeholder="请输入您的密码" onfocus="this.placeholder=&#39;&#39;"
                       onblur="this.placeholder=&#39;请输入您的密码&#39;">
                <div class="login-center-input-text">密码</div>
            </div>
        </div>
        <div class="login-center clearfix">
            <div class="login-center-img">
                <img src="/static/login/images/cpacha.png">
            </div>
            <div class="login-center-input">
                <input type="text" style="width: 50%;" name="cpacha" id="cpacha"
                       value="" placeholder="请输入验证码"
                       onfocus="this.placeholder=&#39;&#39;"
                       onblur="this.placeholder=&#39;请输入验证码&#39;">
                <div class="login-center-input-text">验证码</div>
                <img id="cpacha-img" style="cursor: pointer;" title="点击切换验证码"
                     src="get_cpacha?vl=4&w=100&h=30&type=loginCpacha" width="110px"
                     height="30px" onclick="changeCpacha()">
            </div>
        </div>
        <div class="login-button">登录</div>
    </div>
    <div class="sk-rotating-plane"></div>
    <canvas class="particles-js-canvas-el" width="1147" height="952"
            style="width: 100%; height: 100%;"></canvas>
</div>

<!-- scripts -->
<script th:src="@{/static/login/js/particles.min.js}"></script>
<script th:src="@{/static/login/js/jquery-1.8.0.min.js}"></script>
<script th:src="@{/static/login/js/app.js}"></script>
<script type="text/javascript">
    function hasClass(elem, cls) {
        cls = cls || '';
        if (cls.replace(/\s/g, '').length == 0) return false; //当cls没有参数时,返回false
        return new RegExp(' ' + cls + ' ').test(' ' + elem.className + ' ');
    }

    function addClass(ele, cls) {
        if (!hasClass(ele, cls)) {
            ele.className = ele.className == '' ? cls : ele.className + ' ' + cls;
        }
    }

    function removeClass(ele, cls) {
        if (hasClass(ele, cls)) {
            var newClass = ' ' + ele.className.replace(/[\t\r\n]/g, '') + ' ';
            while (newClass.indexOf(' ' + cls + ' ') >= 0) {
                newClass = newClass.replace(' ' + cls + ' ', ' ');
            }
            ele.className = newClass.replace(/^\s+|\s+$/g, '');
        }
    }

    function changeCpacha() {
        $("#cpacha-img").attr("src", 'get_cpacha?vl=4&w=100&h=30&type=loginCpacha&t=' + new Date().getTime());
    }

    document.querySelector(".login-button").onclick = function () {
        var username = $("#username").val();
        var password = $("#password").val();
        var cpacha = $("#cpacha").val();
        if (username == '' || username == 'undefined') {
            alert("请填写用户名!");
            return;
        }
        if (password == '' || password == 'undefined') {
            alert("请填写密码!");
            return;
        }
        if (cpacha == '' || cpacha == 'undefined') {
            alert("请填写验证码!");
            return;
        }
        addClass(document.querySelector(".login"), "active")
        addClass(document.querySelector(".sk-rotating-plane"), "active")
        document.querySelector(".login").style.display = "none"
        /* setTimeout(function(){
            addClass(document.querySelector(".sk-rotating-plane"), "active")
            document.querySelector(".login").style.display = "none"
        },800) */
        $.ajax({
            url: "login",
            data: {username: username, password: password, cpacha: cpacha},
            type: 'post',
            dataType: 'json',
            success: function (data) {
                if (data.type == 'success') {
                    window.location = 'index';
                } else {
                    removeClass(document.querySelector(".login"), "active")
                    removeClass(document.querySelector(".sk-rotating-plane"), "active")
                    document.querySelector(".login").style.display = "block"
                    alert(data.msg);
                    changeCpacha();
                }
            }
        });
    }
</script>
</body>
</html>

2、 404.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>页面找不到</title>
    <link href="/static/h-ui/css/H-ui.min.css" rel="stylesheet" type="text/css" />
    <link href="/static/h-ui/lib/Hui-iconfont/1.0.1/iconfont.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="page-404 text-c">
    <p class="error-title"><i class="Hui-iconfont">&#xe688;</i>404</p>
    <p class="error-description">不好意思,您访问的页面不存在~</p>
</div>
</body>
</html>

3、 500.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>服务端错误</title>
    <link href="/static/h-ui/css/H-ui.min.css" rel="stylesheet" type="text/css" />
    <link href="/static/h-ui/lib/Hui-iconfont/1.0.1/iconfont.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="page-404 text-c">
    <p class="error-title"><i class="Hui-iconfont">&#xe688;</i>500</p>
    <p class="error-description">不好意思,服务端错误啦~</p>
</div>
</body>
</html>

七、登录显示

在这里插入图片描述

八、代码下载

点击下载

  • 3
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值