mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 21:00:53 +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