springboot使用TemplateEngine修改邮箱后发送验证码示例

实体类

验证码pojo

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("verification_code")
public class VerificationCode implements Serializable {
    
    

    @TableId
    //@GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String code;

    /** 使用场景,自己定义  */
    private String scenes;

    /** true 为有效,false 为无效,验证时状态+时间+具体的邮箱或者手机号 */
    private Boolean status = true;

    /** 类型 :phone 和 email */
    private String type;

    /** 具体的phone与email */
    private String value;

    /** 创建日期 */
    @TableField(fill = FieldFill.INSERT)
    // @Column(name = "create_time")
    private Timestamp createTime;

    public VerificationCode(String code, String scenes, @NotBlank String type, @NotBlank String value) {
    
    
        this.code = code;
        this.scenes = scenes;
        this.type = type;
        this.value = value;
    }
}

邮箱配置pojo:


@Data
@TableName("email_config")
public class EmailConfig implements Serializable {
    
    

    /** ID */
    @TableId
    //@GeneratedValue(strategy = GenerationType.IDENTITY)
    // @Column(name = "id")
    private Long id;


    /** 收件人 */
    // @Column(name = "from_user")
    private String fromUser;


    /** 邮件服务器SMTP地址 */
    // @Column(name = "host")
    private String host;


    /** 密码 */
    // @Column(name = "pass")
    private String pass;


    /** 端口 */
    // @Column(name = "port")
    private String port;


    /** 发件者用户名 */
    // @Column(name = "user")
    private String user;


    public void copy(EmailConfig source) {
    
    
        BeanUtil.copyProperties(source, this, CopyOptions.create().setIgnoreNullValue(true));
    }
}

sql:

DROP TABLE IF EXISTS `verification_code`;
CREATE TABLE `verification_code` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '验证码',
  `create_time` datetime DEFAULT NULL COMMENT '创建日期',
  `status` bit(1) DEFAULT NULL COMMENT '状态:1有效、0过期',
  `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '验证码类型:email或者短信',
  `value` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '接收邮箱或者手机号码',
  `scenes` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '业务名称:如重置邮箱、重置密码等',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='验证码';
DROP TABLE IF EXISTS `email_config`;
CREATE TABLE `email_config` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `from_user` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '收件人',
  `host` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '邮件服务器SMTP地址',
  `pass` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '密码',
  `port` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '端口',
  `user` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '发件者用户名',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='邮箱配置';

邮箱vo

@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmailVo {
    
    

    /** 收件人,支持多个收件人 */
    @NotEmpty
    private List<String> tos;

    @NotBlank
    private String subject;

    @NotBlank
    private String content;
}

controller:

@RestController
@RequestMapping("/api/code")
@Api(tags = "工具:验证码管理")
public class VerificationCodeController {
    
    
  @PostMapping(value = "/resetEmail")
    @ApiOperation("重置邮箱,发送验证码")
    public ResponseEntity<Object> resetEmail(@RequestBody VerificationCode code) throws Exception {
    
    
        code.setScenes(YshopConstant.RESET_MAIL);
        EmailVo emailVo = verificationCodeService.sendEmail(code);
        emailService.send(emailVo, emailService.find());
        return new ResponseEntity<>(HttpStatus.OK);
    }

前端页面(Vue)

<template>
  <div style="display: inline-block;">
    <el-dialog :visible.sync="dialog" :close-on-click-modal="false" :before-close="cancel" :title="title" append-to-body width="475px" @close="cancel">
      <el-form ref="form" :model="form" :rules="rules" size="small" label-width="88px">
        <el-form-item label="新邮箱" prop="email">
          <el-input v-model="form.email" auto-complete="on" style="width: 200px;" />
          <el-button :loading="codeLoading" :disabled="isDisabled" size="small" @click="sendCode">{
    
    {
    
     buttonName }}</el-button>
        </el-form-item>
        <el-form-item label="验证码" prop="code">
          <el-input v-model="form.code" style="width: 320px;" />
        </el-form-item>
        <el-form-item label="当前密码" prop="pass">
          <el-input v-model="form.pass" type="password" style="width: 320px;" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="text" @click="cancel">取消</el-button>
        <el-button :loading="loading" type="primary" @click="doSubmit">确认</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import store from '@/store'
import {
    
     validEmail } from '@/utils/validate'
import {
    
     updateEmail } from '@/api/system/user'
import {
    
     resetEmail } from '@/api/system/code'
export default {
    
    
  props: {
    
    
    email: {
    
    
      type: String,
      required: true
    }
  },
  data() {
    
    
    const validMail = (rule, value, callback) => {
    
    
      if (value === '' || value === null) {
    
    
        callback(new Error('新邮箱不能为空'))
      } else if (value === this.email) {
    
    
        callback(new Error('新邮箱不能与旧邮箱相同'))
      } else if (validEmail(value)) {
    
    
        callback()
      } else {
    
    
        callback(new Error('邮箱格式错误'))
      }
    }
    return {
    
    
      loading: false, dialog: false, title: '修改邮箱', form: {
    
     pass: '', email: '', code: '' },
      user: {
    
     email: '', password: '' }, codeLoading: false,
      codeData: {
    
     type: 'email', value: '' },
      buttonName: '获取验证码', isDisabled: false, time: 60,
      rules: {
    
    
        pass: [
          {
    
     required: true, message: '当前密码不能为空', trigger: 'blur' }
        ],
        email: [
          {
    
     required: true, validator: validMail, trigger: 'blur' }
        ],
        code: [
          {
    
     required: true, message: '验证码不能为空', trigger: 'blur' }
        ]
      }
    }
  },
  methods: {
    
    
    cancel() {
    
    
      this.resetForm()
    },
    sendCode() {
    
    
      if (this.form.email && this.form.email !== this.email) {
    
    
        this.codeLoading = true
        this.buttonName = '验证码发送中'
        this.codeData.value = this.form.email
        const _this = this
        resetEmail(this.codeData).then(res => {
    
    
          this.$message({
    
    
            showClose: true,
            message: '发送成功,验证码有效期5分钟',
            type: 'success'
          })
          this.codeLoading = false
          this.isDisabled = true
          this.buttonName = this.time-- + '秒后重新发送'
          this.timer = window.setInterval(function() {
    
    
            _this.buttonName = _this.time + '秒后重新发送'
            --_this.time
            if (_this.time < 0) {
    
    
              _this.buttonName = '重新发送'
              _this.time = 60
              _this.isDisabled = false
              window.clearInterval(_this.timer)
            }
          }, 1000)
        }).catch(err => {
    
    
          this.resetForm()
          this.codeLoading = false
          console.log(err.response.data.message)
        })
      }
    },
    doSubmit() {
    
    
      this.$refs['form'].validate((valid) => {
    
    
        if (valid) {
    
    
          this.loading = true
          updateEmail(this.form).then(res => {
    
    
            this.loading = false
            this.resetForm()
            this.$notify({
    
    
              title: '邮箱修改成功',
              type: 'success',
              duration: 1500
            })
            store.dispatch('GetInfo').then(() => {
    
    })
          }).catch(err => {
    
    
            this.loading = false
            console.log(err.response.data.message)
          })
        } else {
    
    
          return false
        }
      })
    },
    resetForm() {
    
    
      this.dialog = false
      this.$refs['form'].resetFields()
      window.clearInterval(this.timer)
      this.time = 60
      this.buttonName = '获取验证码'
      this.isDisabled = false
      this.form = {
    
     pass: '', email: '', code: '' }
    }
  }
}
</script>

<style scoped>

</style>

js:
import request from '@/utils/request'

export function resetEmail(data) {
    
    
  return request({
    
    
    url: 'api/code/resetEmail',
    method: 'post',
    data
  })
}

export function updatePass(pass) {
    
    
  return request({
    
    
    url: 'api/users/updatePass/' + pass,
    method: 'get'
  })
}

生成校验验证码service:


import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.template.Template;
import cn.hutool.extra.template.TemplateConfig;
import cn.hutool.extra.template.TemplateEngine;
import cn.hutool.extra.template.TemplateUtil;
import co.yixiang.common.service.impl.BaseServiceImpl;
import co.yixiang.exception.BadRequestException;
import co.yixiang.tools.domain.VerificationCode;
import co.yixiang.tools.domain.vo.EmailVo;
import co.yixiang.tools.service.VerificationCodeService;
import co.yixiang.tools.service.mapper.VerificationCodeMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class VerificationCodeServiceImpl extends BaseServiceImpl<VerificationCodeMapper, VerificationCode> implements VerificationCodeService {
    
    

    @Value("${code.expiration}")
    private Integer expiration;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public EmailVo sendEmail(VerificationCode code) {
    
    
        EmailVo emailVo;
        String content;
        VerificationCode verificationCode = this.getOne(new LambdaQueryWrapper<VerificationCode>()
                .eq(VerificationCode::getScenes, code.getScenes()).eq(VerificationCode::getType, code.getType()).eq(VerificationCode::getValue, code.getValue()));
        // 如果不存在有效的验证码,就创建一个新的
        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
        Template template = engine.getTemplate("email/email.ftl");
        if (verificationCode == null) {
    
    
            code.setCode(RandomUtil.randomNumbers(6));//package cn.hutool.core.util包下的RandomUtil工具
            content = template.render(Dict.create().set("code", code.getCode()));
            emailVo = new EmailVo(Collections.singletonList(code.getValue()), "yxiang后台管理系统", content);
            this.save(code);
            timedDestruction(code);
            // 存在就再次发送原来的验证码
        } else {
    
    
            content = template.render(Dict.create().set("code", verificationCode.getCode()));
            emailVo = new EmailVo(Collections.singletonList(verificationCode.getValue()), "yshop后台管理系统", content);
        }
        return emailVo;
    }

    @Override
    public void validated(VerificationCode code) {
    
    
        VerificationCode verificationCode = this.getOne(new LambdaQueryWrapper<VerificationCode>()
                .eq(VerificationCode::getScenes, code.getScenes()).eq(VerificationCode::getType, code.getType()).eq(VerificationCode::getValue, code.getValue())
                .eq(VerificationCode::getStatus, true));
        if (verificationCode == null || !verificationCode.getCode().equals(code.getCode())) {
    
    
            throw new BadRequestException("无效验证码");
        } else {
    
    
            verificationCode.setStatus(false);
            this.save(verificationCode);
        }
    }

    /**
     * 定时任务,指定分钟后改变验证码状态
     * @param verifyCode 验证码
     */
    private void timedDestruction(VerificationCode verifyCode) {
    
    
        //以下示例为程序调用结束继续运行
        ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
                new BasicThreadFactory.Builder().namingPattern("verifyCode-schedule-pool-%d").daemon(true).build());
        try {
    
    
            executorService.schedule(() -> {
    
    
                verifyCode.setStatus(false);
                this.save(verifyCode);
            }, expiration * 60 * 1000L, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

ftl模板:

在这里插入图片描述

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <style>
        @page {
      
      
            margin: 0;
        }
    </style>
</head>
<body style="margin: 0px;
            padding: 0px;
			font: 100% SimSun, Microsoft YaHei, Times New Roman, Verdana, Arial, Helvetica, sans-serif;
            color: #000;">
<div style="height: auto;
			width: 820px;
			min-width: 820px;
			margin: 0 auto;
			margin-top: 20px;
            border: 1px solid #eee;">
    <div style="padding: 10px;padding-bottom: 0px;">
        <p style="margin-bottom: 10px;padding-bottom: 0px;">尊敬的用户,您好:</p>
        <p style="text-indent: 2em; margin-bottom: 10px;">您正在申请邮箱验证,您的验证码为:</p>
        <p style="text-align: center;
			font-family: Times New Roman;
			font-size: 22px;
			color: #C60024;
			padding: 20px 0px;
			margin-bottom: 10px;
			font-weight: bold;
			background: #ebebeb;">${code}</p>


        <div class="foot-hr hr" style="margin: 0 auto;
			z-index: 111;
			width: 800px;
			margin-top: 30px;
			border-top: 1px solid #DA251D;">
        </div>


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

真正发送邮箱的EmailConfigService

public interface EmailConfigService extends BaseService<EmailConfig> {
    
    
    /**
     * 更新邮件配置
     * @param emailConfig 邮件配置
     * @param old 旧的配置
     * @return EmailConfig
     */
    void update(EmailConfig emailConfig, EmailConfig old);

    /**
     * 查询配置
     * @return EmailConfig 邮件配置
     */
    EmailConfig find();

    /**
     * 发送邮件
     * @param emailVo 邮件发送的内容
     * @param emailConfig 邮件配置
     * @throws Exception /
     */
    @Async
    void send(EmailVo emailVo, EmailConfig emailConfig) throws Exception;
}

import cn.hutool.extra.mail.Mail;
import cn.hutool.extra.mail.MailAccount;
@Service
@AllArgsConstructor
//@CacheConfig(cacheNames = "emailConfig")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class EmailConfigServiceImpl extends BaseServiceImpl<EmailConfigMapper, EmailConfig> implements EmailConfigService {
    
    

    private final IGenerator generator;

    @Override
//    @CachePut(key = "'1'")
    @Transactional(rollbackFor = Exception.class)
    public void update(EmailConfig emailConfig, EmailConfig old) {
    
    
        try {
    
    
            if (!emailConfig.getPass().equals(old.getPass())) {
    
    
                // 对称加密
                emailConfig.setPass(EncryptUtils.desEncrypt(emailConfig.getPass()));
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        this.save(emailConfig);
    }

    @Override
//    @Cacheable(key = "'1'")
    public EmailConfig find() {
    
    
        EmailConfig emailConfig = this.list().get(0);
        return emailConfig;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void send(EmailVo emailVo, EmailConfig emailConfig) {
    
    
        if (emailConfig == null) {
    
    
            throw new BadRequestException("请先配置,再操作");
        }
        // 封装
        MailAccount account = new MailAccount();
        account.setHost(emailConfig.getHost());
        account.setPort(Integer.parseInt(emailConfig.getPort()));
        account.setAuth(true);
        try {
    
    
            // 对称解密
            account.setPass(EncryptUtils.desDecrypt(emailConfig.getPass()));
        } catch (Exception e) {
    
    
            throw new BadRequestException(e.getMessage());
        }
        account.setFrom(emailConfig.getUser() + "<" + emailConfig.getFromUser() + ">");
        // ssl方式发送
        account.setSslEnable(true);
        // 使用STARTTLS安全连接
        account.setStarttlsEnable(true);
        String content = emailVo.getContent();
        // 发送
        try {
    
    
            int size = emailVo.getTos().size();
            Mail.create(account)
                    .setTos(emailVo.getTos().toArray(new String[size]))
                    .setTitle(emailVo.getSubject())
                    .setContent(content)
                    .setHtml(true)
                    //关闭session
                    .setUseGlobalSession(false)
                    .send();
        } catch (Exception e) {
    
    
            throw new BadRequestException(e.getMessage());
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_41358574/article/details/121885303