mirror of
https://github.com/continew-org/continew-admin.git
synced 2025-09-12 06:57:13 +08:00
新增:新增修改邮箱功能,并优化部分以往代码(引入 spring-boot-starter-mail 用于发送邮件验证码)
This commit is contained in:
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.cnadmin.webapi.controller.auth;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.wf.captcha.base.Captcha;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
|
||||
import top.charles7c.cnadmin.auth.config.properties.CaptchaProperties;
|
||||
import top.charles7c.cnadmin.auth.model.vo.CaptchaVO;
|
||||
import top.charles7c.cnadmin.common.model.vo.R;
|
||||
import top.charles7c.cnadmin.common.util.RedisUtils;
|
||||
|
||||
/**
|
||||
* 验证码 API
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2022/12/11 14:00
|
||||
*/
|
||||
@Tag(name = "验证码 API")
|
||||
@SaIgnore
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping(value = "/captcha", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public class CaptchaController {
|
||||
|
||||
private final CaptchaProperties captchaProperties;
|
||||
|
||||
@Operation(summary = "获取图片验证码", description = "获取图片验证码(Base64编码,带图片格式:data:image/gif;base64)")
|
||||
@GetMapping("/img")
|
||||
public R<CaptchaVO> getImageCaptcha() {
|
||||
// 生成验证码
|
||||
Captcha captcha = captchaProperties.getCaptcha();
|
||||
|
||||
// 保存验证码
|
||||
String uuid = IdUtil.fastSimpleUUID();
|
||||
String captchaKey = RedisUtils.formatKey(captchaProperties.getKeyPrefix(), uuid);
|
||||
RedisUtils.setCacheObject(captchaKey, captcha.text(),
|
||||
Duration.ofMinutes(captchaProperties.getExpirationInMinutes()));
|
||||
|
||||
// 返回验证码
|
||||
CaptchaVO captchaVo = new CaptchaVO().setUuid(uuid).setImg(captcha.toBase64());
|
||||
return R.ok(captchaVo);
|
||||
}
|
||||
}
|
@@ -31,11 +31,12 @@ import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
|
||||
import top.charles7c.cnadmin.auth.config.properties.CaptchaProperties;
|
||||
import top.charles7c.cnadmin.auth.model.request.LoginRequest;
|
||||
import top.charles7c.cnadmin.auth.model.vo.LoginVO;
|
||||
import top.charles7c.cnadmin.auth.model.vo.UserInfoVO;
|
||||
import top.charles7c.cnadmin.auth.service.LoginService;
|
||||
import top.charles7c.cnadmin.common.config.properties.CaptchaProperties;
|
||||
import top.charles7c.cnadmin.common.consts.CacheConstants;
|
||||
import top.charles7c.cnadmin.common.model.dto.LoginUser;
|
||||
import top.charles7c.cnadmin.common.model.vo.R;
|
||||
import top.charles7c.cnadmin.common.util.ExceptionUtils;
|
||||
@@ -64,11 +65,11 @@ public class LoginController {
|
||||
@PostMapping("/login")
|
||||
public R<LoginVO> login(@Validated @RequestBody LoginRequest loginRequest) {
|
||||
// 校验验证码
|
||||
String captchaKey = RedisUtils.formatKey(captchaProperties.getKeyPrefix(), loginRequest.getUuid());
|
||||
String captchaKey = RedisUtils.formatKey(CacheConstants.CAPTCHA_CACHE_KEY, loginRequest.getUuid());
|
||||
String captcha = RedisUtils.getCacheObject(captchaKey);
|
||||
ValidationUtils.exIfBlank(captcha, "验证码已失效");
|
||||
RedisUtils.deleteCacheObject(captchaKey);
|
||||
ValidationUtils.exIfCondition(() -> !captcha.equalsIgnoreCase(loginRequest.getCaptcha()), "验证码错误");
|
||||
ValidationUtils.exIfNotEqualIgnoreCase(loginRequest.getCaptcha(), captcha, "验证码错误");
|
||||
|
||||
// 用户登录
|
||||
String rawPassword =
|
||||
@@ -84,7 +85,6 @@ public class LoginController {
|
||||
in = ParameterIn.HEADER)
|
||||
@PostMapping("/logout")
|
||||
public R logout() {
|
||||
ValidationUtils.exIfCondition(() -> !StpUtil.isLogin(), "Token 无效");
|
||||
StpUtil.logout();
|
||||
return R.ok();
|
||||
}
|
||||
|
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.cnadmin.webapi.controller.common;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.wf.captcha.base.Captcha;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.lang.RegexPool;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
import top.charles7c.cnadmin.common.config.properties.CaptchaProperties;
|
||||
import top.charles7c.cnadmin.common.config.properties.ContinewAdminProperties;
|
||||
import top.charles7c.cnadmin.common.consts.CacheConstants;
|
||||
import top.charles7c.cnadmin.common.model.vo.CaptchaVO;
|
||||
import top.charles7c.cnadmin.common.model.vo.R;
|
||||
import top.charles7c.cnadmin.common.util.*;
|
||||
import top.charles7c.cnadmin.common.util.validate.ValidationUtils;
|
||||
|
||||
/**
|
||||
* 验证码 API
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2022/12/11 14:00
|
||||
*/
|
||||
@Tag(name = "验证码 API")
|
||||
@SaIgnore
|
||||
@Validated
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping(value = "/common/captcha", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public class CaptchaController {
|
||||
|
||||
private final CaptchaProperties captchaProperties;
|
||||
private final ContinewAdminProperties properties;
|
||||
|
||||
@Operation(summary = "获取图片验证码", description = "获取图片验证码(Base64编码,带图片格式:data:image/gif;base64)")
|
||||
@GetMapping("/img")
|
||||
public R<CaptchaVO> getImageCaptcha() {
|
||||
// 生成验证码
|
||||
CaptchaProperties.CaptchaImage captchaImage = captchaProperties.getImage();
|
||||
Captcha captcha = captchaImage.getCaptcha();
|
||||
|
||||
// 保存验证码
|
||||
String uuid = IdUtil.fastSimpleUUID();
|
||||
String captchaKey = RedisUtils.formatKey(CacheConstants.CAPTCHA_CACHE_KEY, uuid);
|
||||
RedisUtils.setCacheObject(captchaKey, captcha.text(),
|
||||
Duration.ofMinutes(captchaImage.getExpirationInMinutes()));
|
||||
|
||||
// 返回验证码
|
||||
CaptchaVO captchaVo = new CaptchaVO().setUuid(uuid).setImg(captcha.toBase64());
|
||||
return R.ok(captchaVo);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取邮箱验证码", description = "发送验证码到指定邮箱")
|
||||
@GetMapping("/mail")
|
||||
public R getMailCaptcha(
|
||||
@NotBlank(message = "邮箱不能为空") @Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误") String email)
|
||||
throws MessagingException {
|
||||
// 校验
|
||||
String limitCacheKey = CacheConstants.LIMIT_CACHE_KEY;
|
||||
String captchaCacheKey = CacheConstants.CAPTCHA_CACHE_KEY;
|
||||
String limitCaptchaKey = RedisUtils.formatKey(limitCacheKey, captchaCacheKey, email);
|
||||
long limitTimeInMillisecond = RedisUtils.getTimeToLive(limitCaptchaKey);
|
||||
ValidationUtils.exIfCondition(() -> limitTimeInMillisecond > 0,
|
||||
String.format("发送邮箱验证码过于频繁,请您 %ds 后再试", limitTimeInMillisecond / 1000));
|
||||
|
||||
// 生成验证码
|
||||
CaptchaProperties.CaptchaMail captchaMail = captchaProperties.getMail();
|
||||
String captcha = RandomUtil.randomNumbers(captchaMail.getLength());
|
||||
|
||||
// 发送验证码
|
||||
Long expirationInMinutes = captchaMail.getExpirationInMinutes();
|
||||
String content = TemplateUtils.render(captchaMail.getTemplatePath(),
|
||||
Dict.create().set("captcha", captcha).set("expiration", expirationInMinutes));
|
||||
MailUtils.sendHtml(email, String.format("【%s】邮箱验证码", properties.getName()), content);
|
||||
|
||||
// 保存验证码
|
||||
String captchaKey = RedisUtils.formatKey(CacheConstants.CAPTCHA_CACHE_KEY, email);
|
||||
RedisUtils.setCacheObject(captchaKey, captcha, Duration.ofMinutes(expirationInMinutes));
|
||||
RedisUtils.setCacheObject(limitCaptchaKey, captcha, Duration.ofSeconds(captchaMail.getLimitInSeconds()));
|
||||
return R.ok(String.format("发送成功,验证码有效期 %s 分钟", expirationInMinutes));
|
||||
}
|
||||
}
|
@@ -34,15 +34,18 @@ import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import top.charles7c.cnadmin.common.config.properties.LocalStorageProperties;
|
||||
import top.charles7c.cnadmin.common.consts.CacheConstants;
|
||||
import top.charles7c.cnadmin.common.consts.FileConstants;
|
||||
import top.charles7c.cnadmin.common.consts.RegExpConstants;
|
||||
import top.charles7c.cnadmin.common.model.vo.R;
|
||||
import top.charles7c.cnadmin.common.util.ExceptionUtils;
|
||||
import top.charles7c.cnadmin.common.util.RedisUtils;
|
||||
import top.charles7c.cnadmin.common.util.SecureUtils;
|
||||
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
|
||||
import top.charles7c.cnadmin.common.util.validate.ValidationUtils;
|
||||
import top.charles7c.cnadmin.system.model.entity.SysUser;
|
||||
import top.charles7c.cnadmin.system.model.request.UpdateBasicInfoRequest;
|
||||
import top.charles7c.cnadmin.system.model.request.UpdateEmailRequest;
|
||||
import top.charles7c.cnadmin.system.model.request.UpdatePasswordRequest;
|
||||
import top.charles7c.cnadmin.system.model.vo.AvatarVO;
|
||||
import top.charles7c.cnadmin.system.service.UserService;
|
||||
@@ -111,4 +114,24 @@ public class UserCenterController {
|
||||
userService.updatePassword(rawOldPassword, rawNewPassword, LoginHelper.getUserId());
|
||||
return R.ok("修改成功");
|
||||
}
|
||||
|
||||
@Operation(summary = "修改邮箱", description = "修改用户邮箱")
|
||||
@PatchMapping("/email")
|
||||
public R updateEmail(@Validated @RequestBody UpdateEmailRequest updateEmailRequest) {
|
||||
// 解密
|
||||
String rawCurrentPassword =
|
||||
ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateEmailRequest.getCurrentPassword()));
|
||||
ValidationUtils.exIfBlank(rawCurrentPassword, "当前密码解密失败");
|
||||
|
||||
// 校验
|
||||
String captchaKey = RedisUtils.formatKey(CacheConstants.CAPTCHA_CACHE_KEY, updateEmailRequest.getNewEmail());
|
||||
String captcha = RedisUtils.getCacheObject(captchaKey);
|
||||
ValidationUtils.exIfBlank(captcha, "验证码已失效");
|
||||
ValidationUtils.exIfNotEqualIgnoreCase(updateEmailRequest.getCaptcha(), captcha, "验证码错误");
|
||||
RedisUtils.deleteCacheObject(captchaKey);
|
||||
|
||||
// 修改邮箱
|
||||
userService.updateEmail(updateEmailRequest.getNewEmail(), rawCurrentPassword, LoginHelper.getUserId());
|
||||
return R.ok("修改成功");
|
||||
}
|
||||
}
|
||||
|
@@ -69,6 +69,48 @@ spring:
|
||||
# 是否开启 SSL
|
||||
ssl: false
|
||||
|
||||
--- ### 邮件配置
|
||||
spring:
|
||||
mail:
|
||||
# 根据需要更换
|
||||
host: smtp.126.com
|
||||
port: 465
|
||||
username: 你的邮箱
|
||||
password: 你的邮箱授权码
|
||||
default-encoding: utf-8
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
auth: true
|
||||
socketFactory:
|
||||
class: javax.net.ssl.SSLSocketFactory
|
||||
port: 465
|
||||
|
||||
--- ### 验证码配置
|
||||
captcha:
|
||||
## 图片验证码配置
|
||||
image:
|
||||
# 类型
|
||||
type: SPEC
|
||||
# 内容长度
|
||||
length: 4
|
||||
# 过期时间
|
||||
expirationInMinutes: 2
|
||||
# 宽度
|
||||
width: 111
|
||||
# 高度
|
||||
height: 36
|
||||
## 邮箱验证码配置
|
||||
mail:
|
||||
# 内容长度
|
||||
length: 6
|
||||
# 过期时间
|
||||
expirationInMinutes: 5
|
||||
# 限制时间
|
||||
limitInSeconds: 60
|
||||
# 模板路径
|
||||
templatePath: mail/captcha.ftl
|
||||
|
||||
--- ### 安全配置
|
||||
security:
|
||||
# 排除路径配置
|
||||
@@ -95,21 +137,6 @@ rsa:
|
||||
# 私钥
|
||||
privateKey: MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAznV2Bi0zIX61NC3zSx8U6lJXbtru325pRV4Wt0aJXGxy6LMTsfxIye1ip+f2WnxrkYfk/X8YZ6FWNQPaAX/iRwIDAQABAkEAk/VcAusrpIqA5Ac2P5Tj0VX3cOuXmyouaVcXonr7f+6y2YTjLQuAnkcfKKocQI/juIRQBFQIqqW/m1nmz1wGeQIhAO8XaA/KxzOIgU0l/4lm0A2Wne6RokJ9HLs1YpOzIUmVAiEA3Q9DQrpAlIuiT1yWAGSxA9RxcjUM/1kdVLTkv0avXWsCIE0X8woEjK7lOSwzMG6RpEx9YHdopjViOj1zPVH61KTxAiBmv/dlhqkJ4rV46fIXELZur0pj6WC3N7a4brR8a+CLLQIhAMQyerWl2cPNVtE/8tkziHKbwW3ZUiBXU24wFxedT9iV
|
||||
|
||||
--- ### 验证码配置
|
||||
captcha:
|
||||
# 类型
|
||||
type: SPEC
|
||||
# 缓存键的前缀
|
||||
keyPrefix: CAPTCHA
|
||||
# 过期时间
|
||||
expirationInMinutes: 2
|
||||
# 内容长度
|
||||
length: 4
|
||||
# 宽度
|
||||
width: 111
|
||||
# 高度
|
||||
height: 36
|
||||
|
||||
--- ### 接口文档配置
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
|
@@ -69,6 +69,48 @@ spring:
|
||||
# 是否开启 SSL
|
||||
ssl: false
|
||||
|
||||
--- ### 邮件配置
|
||||
spring:
|
||||
mail:
|
||||
# 根据需要更换
|
||||
host: smtp.126.com
|
||||
port: 465
|
||||
username: 你的邮箱
|
||||
password: 你的邮箱授权码
|
||||
default-encoding: utf-8
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
auth: true
|
||||
socketFactory:
|
||||
class: javax.net.ssl.SSLSocketFactory
|
||||
port: 465
|
||||
|
||||
--- ### 验证码配置
|
||||
captcha:
|
||||
## 图片验证码配置
|
||||
image:
|
||||
# 类型
|
||||
type: SPEC
|
||||
# 内容长度
|
||||
length: 4
|
||||
# 过期时间
|
||||
expirationInMinutes: 2
|
||||
# 宽度
|
||||
width: 111
|
||||
# 高度
|
||||
height: 36
|
||||
## 邮箱验证码配置
|
||||
mail:
|
||||
# 内容长度
|
||||
length: 6
|
||||
# 过期时间
|
||||
expirationInMinutes: 5
|
||||
# 限制时间
|
||||
limitInSeconds: 60
|
||||
# 模板路径
|
||||
templatePath: mail/captcha.ftl
|
||||
|
||||
--- ### 安全配置
|
||||
security:
|
||||
# 排除路径配置
|
||||
@@ -88,21 +130,6 @@ rsa:
|
||||
# 私钥
|
||||
privateKey: MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAznV2Bi0zIX61NC3zSx8U6lJXbtru325pRV4Wt0aJXGxy6LMTsfxIye1ip+f2WnxrkYfk/X8YZ6FWNQPaAX/iRwIDAQABAkEAk/VcAusrpIqA5Ac2P5Tj0VX3cOuXmyouaVcXonr7f+6y2YTjLQuAnkcfKKocQI/juIRQBFQIqqW/m1nmz1wGeQIhAO8XaA/KxzOIgU0l/4lm0A2Wne6RokJ9HLs1YpOzIUmVAiEA3Q9DQrpAlIuiT1yWAGSxA9RxcjUM/1kdVLTkv0avXWsCIE0X8woEjK7lOSwzMG6RpEx9YHdopjViOj1zPVH61KTxAiBmv/dlhqkJ4rV46fIXELZur0pj6WC3N7a4brR8a+CLLQIhAMQyerWl2cPNVtE/8tkziHKbwW3ZUiBXU24wFxedT9iV
|
||||
|
||||
--- ### 验证码配置
|
||||
captcha:
|
||||
# 类型
|
||||
type: SPEC
|
||||
# 缓存键的前缀
|
||||
keyPrefix: CAPTCHA
|
||||
# 过期时间
|
||||
expirationInMinutes: 2
|
||||
# 内容长度
|
||||
length: 4
|
||||
# 宽度
|
||||
width: 111
|
||||
# 高度
|
||||
height: 36
|
||||
|
||||
--- ### 接口文档配置
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
|
@@ -1,13 +1,13 @@
|
||||
--- ### 项目配置
|
||||
continew-admin:
|
||||
# 名称
|
||||
name: ContiNew-Admin
|
||||
name: ContiNew Admin
|
||||
# 应用名称
|
||||
appName: continew-admin
|
||||
# 版本
|
||||
version: 0.0.1-SNAPSHOT
|
||||
# 描述
|
||||
description: ContiNew-Admin (incubating) 中后台管理框架,Continue New Admin,持续以最新流行技术栈构建。
|
||||
description: ContiNew Admin 中后台管理框架(孵化中),Continue New Admin,持续以最新流行技术栈构建。
|
||||
# URL
|
||||
url: https://github.com/Charles7c/continew-admin
|
||||
## 作者信息配置
|
||||
@@ -65,7 +65,7 @@ knife4j:
|
||||
# 是否自定义 footer(默认 false 非自定义)
|
||||
enable-footer-custom: true
|
||||
# 自定义 footer 内容,支持 Markdown 语法
|
||||
footer-custom-content: '[Apache-2.0](https://github.com/Charles7c/continew-admin/blob/dev/LICENSE) | Copyright © 2022-present [ContiNew-Admin](https://github.com/Charles7c/continew-admin)'
|
||||
footer-custom-content: 'Copyright © 2022-present Charles7c ⋅ [ContiNew Admin](https://github.com/Charles7c/continew-admin)'
|
||||
|
||||
--- ### Sa-Token 配置
|
||||
sa-token:
|
||||
|
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content="邮箱验证码">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<base target="_blank">
|
||||
<style>::-webkit-scrollbar{ display: none; }</style>
|
||||
</head>
|
||||
<body tabindex="0">
|
||||
<div style="background-color: #ECECEC; padding: 25px;">
|
||||
<div style="margin: 0 auto; text-align: left; position: relative; border-radius: 5px; border-collapse: collapse; box-shadow: rgb(153, 153, 153) 0px 0px 5px; background: #fff; font-family: 微软雅黑, 黑体, sans-serif; font-size: 14px; line-height: 1.5;">
|
||||
<div style="height: 29px; line-height: 25px; padding: 15px 30px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #307AF2; background: #00308f; border-radius: 5px 5px 0 0;">
|
||||
<div style="font-size: 24px; font-weight: bolder; color: #fff; display: inline-flex; align-items: center;">
|
||||
<a href="https://cnadmin.charles7c.top/">
|
||||
<img src="https://cnadmin.charles7c.top/logo.svg" alt="ContiNew Admin" style="vertical-align: middle;">
|
||||
</a>
|
||||
<a href="https://cnadmin.charles7c.top/" style="margin-left: 4px; text-decoration: none; color: #fff;">ContiNew Admin</a>
|
||||
</div>
|
||||
</div>
|
||||
<div style="word-break: break-word;">
|
||||
<div style="border-radius: 5px; padding: 25px 30px 11px; background-color: #fff; opacity: 0.8;">
|
||||
<h2 style="margin: 5px 0; font-size: 18px; line-height: 22px; color: #333;">亲爱的用户:</h2>
|
||||
<p>
|
||||
您好!感谢您使用 <a href="https://github.com/Charles7c/continew-admin" style="color: #333;">ContiNew Admin</a>,本次请求的验证码为:<span style="font-size: 16px; color: #ff8c00;">${captcha}</span>,请在 ${expiration} 分钟内使用此验证码完成验证。
|
||||
</p>
|
||||
<br>
|
||||
<h2 style="margin: 5px 0; font-size: 18px; line-height: 22px; color: #333;">Dear user:</h2>
|
||||
<p>
|
||||
Hello! Thanks for using ContiNew Admin, The verification code for this request is: <span style="font-size: 16px; color: #ff8c00;">${captcha}</span>, please use this verification code to complete the verification within ${expiration} minutes.
|
||||
</p>
|
||||
<div style="width: 100%; margin: 0 auto;">
|
||||
<div style="padding: 10px 10px 0; border-top: 1px solid #ccc; color: #747474; margin-bottom: 20px; line-height: 1.3em; font-size: 12px;">
|
||||
<p>
|
||||
若非本人操作,请忽略此邮件。此邮件由系统自动发送,请勿直接回复该邮件。<br>
|
||||
Please ignore this email if not by yourself. This email is sent automatically by the system, please do not reply to this email directly.
|
||||
</p>
|
||||
<p>Copyright © 2022-present Charles7c</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user