【SpringBoot】SpringBoot使用mail实现登录邮箱验证

427ef4152dbf4b6c92618a198935cb6c.png

  📝个人主页:哈__

期待您的关注 

1b7335aca73b41609b7f05d1d366f476.gif

目录

一、前期准备

1 开启邮箱服务

2 SpringBoot导入依赖

3 创建application.yml配置文件 

4 创建数据库文件

 5 配置redis服务

二、验证邮件发送功能 

三、注册功能实现邮箱验证

1 创建User实体类

2 创建UserParam

3 创建CaptchaService

4 创建EmailTemplateEnum

5 创建CaptchaServiceImpl

 6 创建CaptchaController

7 创建LoginController 

四、创建登陆页面 

1 login.html 

2 register.html


 

在实际的开发当中,不少的场景中需要我们使用更加安全的认证方式,同时也为了防止一些用户恶意注册,我们可能会需要用户使用一些可以证明个人身份的注册方式,如短信验证、邮箱验证等。

一、前期准备

为了实现邮箱认证服务,我们需要提供出来一个邮箱作为验证码的发送者,这里我使用的是QQ邮箱。

1 开启邮箱服务

首先打开QQ邮箱,然后找到设置,点击账号。

a5b0567d19f143a7acbdfa6c60ff09f0.png

然后找到下方的邮件协议服务,打开。因为这里我已经打开了,而且服务开启也较为简单,需要我们发送一个短信到指定的号码,可能会收取一定的费用,不过不是很多,好像是几毛钱。

31d8524967e54c389103fe92393c8b86.png 开启之后会给你一个授权码,一定要记住这个授权码,发邮件的时候需要这个。

2 SpringBoot导入依赖

核心的就是mail依赖,因为我这个项目东西不少,为了方便我就全拷贝过来了,可能有的用不到。

 <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.7.6</spring-boot.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <mybatisplus.version>3.4.3.1</mybatisplus.version>
        <mybatisplus.generator>3.3.2</mybatisplus.generator>
        <mybatisplus.velocity>1.7</mybatisplus.velocity>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatisplus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>${mybatisplus.generator}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
            <version>${mybatisplus.velocity}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.56</version>
        </dependency>


    </dependencies>

3 创建application.yml配置文件 

spring:
  #邮件服务配置
  mail:
    host: smtp.qq.com #邮件服务器地址
    protocol: smtp #协议
    username:  #发送邮件的邮箱也就是你开通服务的邮箱
    password:  #开通服务后得到的授权码
    default-encoding: utf-8 #邮件内容的编码
  redis:
    host: 127.0.0.1
    port: 6379
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mail
    username: root
    password:  # 数据库的密码

4 创建数据库文件

在上边的配置文件中你也看到了,我们用到了mysql还有redis。

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `account` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'admin', '123456');
INSERT INTO `user` VALUES (2, '123456', '123456');

SET FOREIGN_KEY_CHECKS = 1;

 5 配置redis服务

在之前的文章当中我有说到过安装redis的事情,大家可以看看我这篇文章。【Spring】SpringBoot整合Redis,用Redis实现限流(附Redis解压包)_springboot 限流 redis-CSDN博客

二、验证邮件发送功能 

大家可以先看一下我的项目结构。其中的一些代码是我学习另一位大佬的文章,这篇文章也是对该大佬文章的一个总结。蒾酒-CSDN博客

d18a1b016999488794cb4594e46ab934.png

我们最重要的邮件发送工具 就是util包下的EmailApi。将以下代码导入后,创建一个测试方法。

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.mail.internet.MimeMessage;
import java.io.File;
import java.util.Objects;

/**
 * @author mijiupro
 */
@Component
@Slf4j
public class EmailApi {
    @Resource
    private JavaMailSender mailSender;

    @Value("${spring.mail.username}")
    private String from ;// 发件人

    /**
     * 发送纯文本的邮件
     * @param to 收件人
     * @param subject 主题
     * @param content 内容
     * @return 是否成功
     */
    @SneakyThrows(Exception.class)
    public boolean sendGeneralEmail(String subject, String content, String... to){
        // 创建邮件消息
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(from);
        // 设置收件人
        message.setTo(to);
        // 设置邮件主题
        message.setSubject(subject);
        // 设置邮件内容
        message.setText(content);

        // 发送邮件
        mailSender.send(message);

        return true;
    }
    /**
     * 发送html的邮件
     * @param to 收件人
     * @param subject 主题
     * @param content 内容
     * @return 是否成功
     */
    @SneakyThrows(Exception.class)
    public boolean sendHtmlEmail(String subject, String content, String... to){
        // 创建邮件消息
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        helper.setFrom(from);
        // 设置收件人
        helper.setTo(to);
        // 设置邮件主题
        helper.setSubject(subject);
        // 设置邮件内容
        helper.setText(content, true);

        // 发送邮件
        mailSender.send(mimeMessage);

        log.info("发送邮件成功");
        return true;

    }
    /**
     * 发送带附件的邮件
     * @param to 收件人
     * @param subject 主题
     * @param content 内容
     * @param filePaths 附件路径
     * @return 是否成功
     */
    @SneakyThrows(Exception.class)
    public boolean sendAttachmentsEmail(String subject, String content, String[] to, String[] filePaths) {
        // 创建邮件消息
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        helper.setFrom(from);
        // 设置收件人
        helper.setTo(to);
        // 设置邮件主题
        helper.setSubject(subject);
        // 设置邮件内容
        helper.setText(content,true);

        // 添加附件
        if (filePaths != null) {
            for (String filePath : filePaths) {
                FileSystemResource file = new FileSystemResource(new File(filePath));
                helper.addAttachment(Objects.requireNonNull(file.getFilename()), file);

            }
        }
        // 发送邮件
        mailSender.send(mimeMessage);
        return true;
    }

    /**
     * 发送带静态资源的邮件
     * @param to 收件人
     * @param subject 主题
     * @param content 内容
     * @param rscPath 静态资源路径
     * @param rscId 静态资源id
     * @return 是否成功
     */
    @SneakyThrows(Exception.class)
    public boolean sendInlineResourceEmail(String subject, String content, String to, String rscPath, String rscId) {
        // 创建邮件消息
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        // 设置发件人
        helper.setFrom(from);
        // 设置收件人
        helper.setTo(to);
        // 设置邮件主题
        helper.setSubject(subject);

        //html内容图片
        String contentHtml = "<html><body>这是邮件的内容,包含一个图片:<img src=\'cid:" + rscId + "\'>"+content+"</body></html>";

        helper.setText(contentHtml, true);
        //指定讲资源地址
        FileSystemResource res = new FileSystemResource(new File(rscPath));
        helper.addInline(rscId, res);

        mailSender.send(mimeMessage);
        return true;
    }

}

 进行邮件发送的测试。

cce23808aa9c476eb51c0896c3ef38fa.png

 看看结果,成功的发过来了。

e582910586124e76b8b1baf429574f72.png

 接下来就要进行登录注册功能的开发了。

三、注册功能实现邮箱验证

1 创建User实体类

@Data
@EqualsAndHashCode(callSuper = false)
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    private String account;

    private String password;


}

2 创建UserParam

@Data
@EqualsAndHashCode(callSuper = false)
public class UserParam implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    private String account;

    private String password;

    private String emailCode;

    private String email;


}

3 创建CaptchaService

public interface CaptchaService {
    boolean sendCaptcha(String email);
}

4 创建EmailTemplateEnum

这一步我没有选择创建CaptchaServiceImpl,因为这个类中涉及到了一些核心的代码,而我们一些类还没有创建完,我们先创建这样的一个枚举类,这个枚举类的作用就是定义我们发送邮件的一个模板,我们在发送邮件的时候,直接向模板内插入内容就可以了。

ublic enum EmailTemplateEnum {
    // 验证码邮件
    VERIFICATION_CODE_EMAIL_HTML("<html><body>用户你好,你的验证码是:<h1>%s</h1>请在五分钟内完成注册</body></html>","登录验证"),

    // 用户被封禁邮件通知
    USER_BANNED_EMAIL("用户你好,你已经被管理员封禁,封禁原因:%s", "封禁通知");

    private final String template;
    private final String subject;

    EmailTemplateEnum(String template, String subject) {
        this.template = template;
        this.subject = subject;
    }

    public String getTemplate(){
        return this.template;
    }

    public String set(String captcha) {
        return String.format(this.template, captcha);
    }


    public String getSubject() {
        return this.subject;
    }
}

5 创建CaptchaServiceImpl

首先把我们前端传过来的邮箱加一个前缀,用于redis中的存储,因为我们不仅可以有邮箱认证还可以有手机认证。

 @Resource
    StringRedisTemplate stringRedisTemplate;
    @Resource
    EmailApi emailApi;
    @Override
    public boolean sendCaptcha(String email) {
        sendMailCaptcha("login:email:captcha:"+email);
        return true;
    }

 在redis当中,验证码的存储使用的是Hash结构,Hash存储了验证码,验证次数,还有上一次的发送时间,因为我们要限制一分钟发送的次数。一分钟内我们只能发一条短信,验证码在redis中的过期时间为五分钟,在验证码未过期之前发送的认证,都会让这个发送次数加一,倘若发送的次数达到了5次还要发送,那么就封禁一天不让发送短信。

例如,在3:30:30的时候发送了一次短信,一分钟后,3:31:30的时候又发送了短信,直到3:35:30的时候又发了一次,此时的发送次数已经达到了5,这时候就会封一天,因为每次发送验证码的时候,redis都存储着上一次还没过期的验证码,所以发送次数会增加。

下边讲一下代码。

先从redis中找到这个hash结构,如果hash结构的值不为空并且达到了发送次数上限,就封禁一天,否则的话看一下上一次的发送时间是否存在,如果存在的话,判断一下当前时间和上一次的发送时间间隔是否大于60秒,如果小于60秒那么不让发送。如果正常发送短信,那么就把发送的次数加1,然后用随机数生成一个六位数的验证码,发送验证码,并且向redis中保存刚才的hash结构。

  private boolean sendMailCaptcha(String key){
        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(key);
        // 初始检查
        String lastSendTimestamp = hashOps.get("lastSendTimestamp");
        String sendCount = hashOps.get("sendCount");

        if(StringUtils.isNotBlank(sendCount)&&Integer.parseInt(sendCount)>=5){
            hashOps.expire(24, TimeUnit.HOURS);
            throw new  RuntimeException("验证码发送过于频繁");
        }
        if(StringUtils.isNotBlank(lastSendTimestamp)){
            long lastSendTime = Long.parseLong(lastSendTimestamp);
            long currentTime = System.currentTimeMillis();
            long elapsedTime = currentTime - lastSendTime;
            if(elapsedTime < 60 * 1000){
                throw new  RuntimeException("验证码发送过于频繁");
            }
        }
        int newSendCount = StringUtils.isNotBlank(sendCount) ? Integer.parseInt(sendCount) + 1 : 1;
        String captcha = RandomStringUtils.randomNumeric(6);

        try {
            sendCaptcha(key,captcha);
        } catch (Exception e) {
            return false;
        }
        hashOps.put("captcha", captcha);
        hashOps.put("lastSendTimestamp", String.valueOf(System.currentTimeMillis()));
        hashOps.put("sendCount", String.valueOf(newSendCount));
        hashOps.expire(5, TimeUnit.MINUTES); // 设置过期时间为5分钟

        return true;
    }

发送验证码调用的是下边的函数。

 private void sendCaptcha(String hashKey, String captcha) thorw Exception{
        // 根据hashKey判断是发送邮件还是短信,然后调用相应的发送方法
        if("email".equals(hashKey.split(":")[1])){
            if (!emailApi.sendHtmlEmail(EmailTemplateEnum.VERIFICATION_CODE_EMAIL_HTML.getSubject(),
                    EmailTemplateEnum.VERIFICATION_CODE_EMAIL_HTML.set(captcha),hashKey.split(":")[3])) {
                throw new RuntimeException("发送邮件失败");
            }
        }
    }

 6 创建CaptchaController

@RestController
@RequestMapping("/captcha")
public class CaptchaController {

    @Resource
    CaptchaService captchaService;
    @Resource
    EmailApi emailApi;
    @RequestMapping("/getCaptcha")
    public Result sendCaptcha(String email){
        boolean res = captchaService.sendCaptcha(email);
        if(res){
            return new Result("发送成功",200,null);
        }
        return new Result("发送失败",500,null);
    }
}

7 创建LoginController 

UserSevice的东西都很简单,都是mybatisplus的内容,如果不太了解可以看我这篇文章【Spring】SpringBoot整合MybatisPlus的基本应用_简单的springboot+mybatisplus的应用程序-CSDN博客

我这里并没有用UserService封装认证的过程,直接写到controller中了,大家能看懂就好。仅供学习使用。 

@Controller
public class LoginController {
    @Autowired
    UserService userService;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @GetMapping("/")

    public String Login(){
        return "redirect:/pages/login.html";
    }

    @RequestMapping("/register")
    @ResponseBody
    public Result registerUser( UserParam user ){
        String email = user.getEmail();
        String emailCode = user.getEmailCode();
        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps("login:email:captcha:"+email);
        String code = hashOps.get("captcha");
        if(!Objects.equals(code, emailCode)){
            return new Result("验证码错误",400,null);
        }
        User user1 = userService.getOne(new LambdaQueryWrapper<User>()
                .eq(User::getAccount,user.getAccount()));
        //如果有这个用户的信息要拒绝注册
        if(user1!=null){
            return new Result("",100,null);
        }
        User saveUser = new User();
        BeanUtils.copyProperties(user,saveUser);
        System.out.println(user);
        System.out.println(saveUser);
        boolean save = userService.save(saveUser);
        if(!save){
            return new Result("注册失败",300,null);
        }
        return new Result("注册成功",200,null);
    }
}

到此为止,验证码的注册功能就已经实现完成了。

我们现在要做一个前端页面。

四、创建登陆页面 

在resources目录下创建static文件夹。在pages目录下添加login.html和register.html。至于jquery呢就要大家自己去找了。

6a71f75baaa54fae8b7c0bd5ab3e8d86.png

 

1 login.html 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../js/jquery.min.js"></script>

    <script>
        $(function() {
            $(".btn").on("click", function () {
                var account = $(".account").val()
                var password = $(".password").val()
                console.log(account)
                console.log(password)
                $.ajax({
                    type: "get",
                    url: "/login?&account=" + account + "&password=" + password,
                    success: function (data) {
                        if (data.code === 200) {
                            window.location.href = "../pages/index.html"
                        }else{
                            alert("登陆失败,请检查账号或者密码")
                        }
                    }
                })
            })
        });
    </script>

    <style>
        *{
            padding: 0;
            margin: 0px;
            text-decoration: none;
        }
        body {
            background-image: url(../images2/1.jpg);
            background-size:cover;
            background-attachment:fixed;
        }
        .loginbox {
            height: 280px;
            width: 26%;
            margin: 340px auto;
            background-color: rgb(158, 151, 158);
            opacity: 0.7;
            text-align: center;
        }
        .loginbox .top {
            display: block;
            height: 42px;
            background: linear-gradient(90deg,pink,red,pink,yellow,red);
            padding-top: 15px;
            font-size: 20px;
            font-weight: 600;
            color: black;
        }
        .loginbox span {
            color: black;
            font-weight: 500;
            font-size: 18px;
        }
        .loginbox input[type=text],
        .loginbox input[type=password] {
            height: 20px;
            border: none;
            border-radius: 4px;
            outline: none;
        }
        .loginbox input[type=button] {
            width: 40%;
            height: 24px;
            border: none;
            border-radius: 4px;
            margin: 0 auto;
            outline: none;
        }
        .loginbox .a {
            display: flex;
            margin: 20px auto;
            width: 45%;
            justify-content: space-between;
        }
        .loginbox .a a {
            color: white;
        }
        .loginbox .a a:hover {
            color: rgb(228, 221, 228);
        }
    </style>
</head>
<body>
    <div class="loginbox">
        <span class="top">邮箱简易发送系统</span><br>
        <span>账号:</span>
        <input type="text" placeholder="请输入账号" class="account"><br><br>
        <span>密码:</span>
        <input type="password" placeholder="请输入密码" class="password"><br><br>
        <input type="button" value="登录" class="btn">
        <div class="a">
            <a href="register.html">注册</a>
            <a href="">忘记密码</a>
        </div>

    </div>
</body>
</html>

2 register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="../css/register.css">
    <script src="../js/jquery.min.js"></script>

    <script>
        $(function(){
            // 注册按钮点击事件
            $(".btn").on("click",function(){
                var account = $(".account").val().trim();
                var password = $(".password").val().trim();
                var emailCode = $(".emailCode").val().trim();
                var email = $(".email").val().trim();
                $.ajax({
                    type:"post",
                    url:"/register",
                    data:{"account":account,"password":password,"emailCode":emailCode,"email":email},
                    success:function(data){
                        if(data.code=== 100){
                            alert("该账号已存在");
                        }else if(data.code === 300){
                            alert("账号注册失败");
                        }else if(data.code === 400){
                            alert("验证码错误");
                        }else{
                            alert("账号注册成功");
                            window.location.href="../pages/login.html";
                        }
                    }
                });
            });

            // 发送验证码按钮点击事件
            $(".sendCode").on("click", function(){
                var email = $(".email").val().trim();
                if(email === "") {
                    alert("请输入邮箱");
                    return;
                }

                // 发送验证码请求
                $.ajax({
                    type:"get",
                    url:"/captcha/getCaptcha?email="+email,
                    success:function(data){
                        console.log(data)
                        if(data.code === 200){
                            alert("验证码已发送到邮箱");
                            // 启动倒计时
                            startCountdown(60);
                        } else {
                            alert("验证码发送失败,请重试");
                        }
                    }
                });
            });

            // 启动倒计时函数
            function startCountdown(seconds) {
                var timer = seconds;
                $(".sendCode").prop("disabled", true);
                var countdown = setInterval(function() {
                    $(".sendCode").val(timer + "秒后可重新发送");
                    timer--;
                    if (timer < 0) {
                        clearInterval(countdown);
                        $(".sendCode").prop("disabled", false);
                        $(".sendCode").val("发送验证码");
                    }
                }, 1000);
            }
        });
    </script>
    <style>
        *{
            padding: 0;
            margin: 0px;
            text-decoration: none;
        }
        body {
            background-image: url(../images2/1.jpg);
            background-size: cover;
            background-attachment: fixed;
        }
        .loginbox {
            height: 380px; /* 调整高度以适应新字段 */
            width: 26%;
            margin: 340px auto;
            background-color: rgb(158, 151, 158);
            opacity: 0.7;
            text-align: center;
        }
        .loginbox .top {
            display: block;
            height: 42px;
            background: linear-gradient(90deg, pink, red, pink, yellow, red);
            padding-top: 15px;
            font-size: 20px;
            font-weight: 600;
            color: black;
        }
        .loginbox span {
            color: black;
            font-weight: 500;
            font-size: 18px;
        }
        .loginbox input[type=text],
        .loginbox input[type=password] {
            height: 20px;
            border: none;
            border-radius: 4px;
            outline: none;
        }
        .loginbox input[type=button] {
            width: 40%;
            height: 24px;
            border: none;
            border-radius: 4px;
            margin: 0 auto;
            outline: none;
        }
        .loginbox .a {
            display: flex;
            margin: 20px auto;
            width: 45%;
            justify-content: space-between;
        }
        .loginbox .a a {
            color: white;
        }
        .loginbox .a a:hover {
            color: rgb(228, 221, 228);
        }
    </style>
</head>
<body>
<div class="loginbox">
    <span class="top">邮箱简易发送系统</span><br>
    <span>账号:</span>
    <input type="text" placeholder="请输入账号" class="account"><br><br>
    <span>密码:</span>
    <input type="password" placeholder="请输入密码" class="password"><br><br>
    <span>邮箱:</span>
    <input type="text" placeholder="请输入邮箱" class="email"><br><br>
    <input type="button" value="发送验证码" class="sendCode"><br><br>
    <span>验证码:</span>
    <input type="text" placeholder="请输入验证码" class="emailCode"><br><br>
    <input type="button" value="注册" class="btn">
    <div class="a">
        <a href="login.html">返回</a>
    </div>
</div>
</body>
</html>

2625f728ad30493391e9248fc41e6579.png

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/759155.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java spring 拦截器和过滤器+过滤器处理数据

java spring 拦截器和过滤器过滤器处理数据 介绍Spring拦截器&#xff08;Interceptor&#xff09;导入依赖完整代码 Java过滤器&#xff08;Filter&#xff09;完整代码分为 2 个文件&#xff0c;请看下面BodyReaderHttpServletRequestWrapper.javaMyFilter.java Spring过滤器…

初识HTML

HTML语法规范 1、HTML标签是由尖括号包围的关键字&#xff0c;例如<html>。 2、HTML标签通常成对出现&#xff0c;例如<html></html>&#xff0c;此为双标签&#xff0c;标签对的第一个标签是开始标签&#xff0c;第二个标签是结束标签。 3、有些特殊标签…

揭秘多年免费听音乐、直播、影视的自用方案:手机、电视、电脑多平台0成本实现媒体自由(内含相关资源)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 多媒体自由 📒🎧 音乐资源📱安卓平台🍎 苹果平台💻 PC平台🎥 影视资源📱 安卓平台🍎 苹果平台📺 电视盒子💻 PC平台📥 电影下载📺 直播资源📱 手机平台💻 PC平台📺 电视盒子⚓️ 相关链接 ⚓️�…

【Python实战因果推断】9_元学习器4

目录 Double/Debiased Machine Learning Double/Debiased Machine Learning Double/Debiased ML 或 R-learner 可以看作是 FrischWaugh-Lovell 定理的改进版。其思路非常简单--在构建结果和治疗残差时使用 ML 模型 结果和干预残差&#xff1a; , 预估&#xff0c;预估 由于 …

Golang-slice理解

slice golang-slice语雀笔记整理 slicego为何设计slice&#xff1f;引用传递实现扩容机制 go为何设计slice&#xff1f; 切片对标其他语言的动态数组&#xff0c;底层通过数组实现&#xff0c;可以说是对数组的抽象&#xff0c;底层的内存是连续分配的所以效率高&#xff0c;可…

【操作系统】进程管理——进程控制和进程通信(个人笔记)

学习日期&#xff1a;2024.6.30 内容摘要&#xff1a;进程控制的概念&#xff0c;进程控制相关的“原语”&#xff0c;进程通信 进程控制 原语 进程控制用“原语”实现。原语是一种特殊的程序&#xff0c;它的执行具有原子性&#xff0c;也就是说&#xff0c;这段程序的执行…

计算机网络原理及应用

第一章 计算机网络概述 【1】局域网 局域网是指在某一区域内由多台计算机互联而成的计算机通信网络。 【1】互通 两个网络之间可以交换数据。 第二章 计算机网络的体系结构 【1】语义 何时发出何种控制信息&#xff0c;完成何种动作以及做出何种响应。 【2】简述网络协…

【Linux】性能分析器 perf 详解(一):简介、安装、stat命令演示

1、简介 perf 是由 Linux 官方提供的系统性能分析工具 。它包含两部分: perf_events ,Linux 内核中的一个子系统perf 命令,用户空间的应用程序内核子系统 perf_events 提供了性能计数器(hardware performance counters)和性能事件的支持,它以事件驱动型的方式工作,通过…

DarkGPT:基于GPT-4-200k设计的人工智能OSINT助手

关于DarkGPT DarkGPT是一款功能强大的人工智能安全助手&#xff0c;该工具基于GPT-4-200k设计并实现其功能&#xff0c;可以帮助广大研究人员针对泄露数据库进行安全分析和数据查询相关的OSINT操作。 工具要求 openai1.13.3 requests python-dotenv pydantic1.10.12 工具安装 …

【机器学习】在【Pycharm】中的应用:【线性回归模型】进行【房价预测】

专栏&#xff1a;机器学习笔记 pycharm专业版免费激活教程见资源&#xff0c;私信我给你发 python相关库的安装&#xff1a;pandas,numpy,matplotlib&#xff0c;statsmodels 1. 引言 线性回归&#xff08;Linear Regression&#xff09;是一种常见的统计方法和机器学习算法&a…

miniconda 弹出黑窗

etc\conda\activate.d 和 envs 中不同环境中的 etc\conda\activated.d&#xff0c;只保留下图中的三个文件即可。

Facebook的投流技巧有哪些?

相信大家都知道Facebook拥有着巨大的用户群体和高转化率&#xff0c;在国外社交推广中的影响不言而喻。但随着Facebook广告的竞争越来越激烈&#xff0c;在Facebook广告上获得高投资回报率也变得越来越困难。IPIDEA代理IP今天就教大家如何在Facebook上投放广告的技巧&#xff0…

p2p、分布式,区块链笔记:试用ZeroTier组网

ZeroTier 是一种用于创建和管理虚拟局域网&#xff08;Virtual Local Area Network&#xff0c;VLAN&#xff09;的软件定义网络&#xff08;SDN&#xff09;解决方案。它可以通过互联网将多个设备安全地连接在一起&#xff0c;就像它们在同一个本地网络上一样。主要开发语言为…

zfile文件共享系统使用

1.简介 zfile是一款基于java开源的文件共享软件&#xff0c;有两种部署&#xff08;jar包和war包&#xff09;,非常适合广大的Java开发人员使用&#xff08;当然其他人员也可使用&#xff09;&#xff0c;或者你也可以称它为网盘&#xff0c;可以在Windows、MacOS、Linux上部署…

【操作系统期末速成】 EP01 | 学习笔记(基于五道口一只鸭)

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、正文&#xff1a;☀️☀️☀️1.1 考点一&#xff1a;操作系统的概率及特征 三、总结&#xff1a;&#x1f353;&#x1f353;&#x1f353; 一、前言&#x1f680;&#x1f680;&#x1f680; ☀️ 回报不在行动…

javaSE知识点整理总结(下)、MySQL数据库

目录 一、异常 1.常见异常类型 2.异常体系结构 3.异常处理 &#xff08;1&#xff09;finally &#xff08;2&#xff09;throws 二、JDBC 1.JDBC搭建 2.执行SQL语句两种方法 三、MySQL数据库 1.ddl 2.dml 3.dql &#xff08;1&#xff09;字符函数 &#xff08;…

Unidbg调用-补环境V3-Hook

结合IDA和unidbg,可以在so的执行过程进行Hook,这样可以让我们了解并分析具体的执行步骤。 应用场景:基于unidbg调试执行步骤 或 还原算法(以Hookzz为例)。 1.大姨妈 1.1 0x1DA0 public void hook1() {

Unity之自定义Text组件默认属性值

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之自定义Text组件默认属性值 TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取&#xff01;…

48 - 按日期分组销售产品(高频 SQL 50 题基础版)

48 - 按日期分组销售产品 -- group_concat 分组拼接selectsell_date,count(distinct product) num_sold,group_concat(distinct product order by product separator ,) products fromActivities group bysell_date;

统计是一门艺术(参数假设检验)

1.参数假设检验 在总体分布已知的情况下&#xff0c;对分布中未知参数的检验。 &#xff08;1&#xff09;相关基本概念 零假设/原假设与对立假设/备择假设&#xff1a; 任务&#xff1a;根据样本作出是否接受H0 复合假设与简单假设&#xff1a; 否定域/拒绝域与接受域&…