Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
查尔斯-BUG万象集
Continew Admin
提交
8b825578
Continew Admin
项目概览
查尔斯-BUG万象集
/
Continew Admin
8 个月 前同步成功
通知
1
Star
1
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
分析
仓库
DevOps
项目成员
Pages
Continew Admin
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
8b825578
编写于
1月 14, 2023
作者:
查尔斯-BUG万象集
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
新增:新增修改邮箱功能,并优化部分以往代码(引入 spring-boot-starter-mail 用于发送邮件验证码)
上级
73fadb83
变更
44
隐藏空白更改
内联
并排
Showing
44 changed file
with
1298 addition
and
188 deletion
+1298
-188
.github/workflows/deploy.yml
.github/workflows/deploy.yml
+1
-0
README.md
README.md
+12
-8
continew-admin-common/pom.xml
continew-admin-common/pom.xml
+17
-0
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/config/properties/CaptchaProperties.java
...c/cnadmin/common/config/properties/CaptchaProperties.java
+76
-39
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/consts/CacheConstants.java
...a/top/charles7c/cnadmin/common/consts/CacheConstants.java
+10
-0
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/handler/GlobalExceptionHandler.java
...rles7c/cnadmin/common/handler/GlobalExceptionHandler.java
+2
-2
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/vo/CaptchaVO.java
...java/top/charles7c/cnadmin/common/model/vo/CaptchaVO.java
+1
-1
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/MailUtils.java
...ain/java/top/charles7c/cnadmin/common/util/MailUtils.java
+244
-0
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/TemplateUtils.java
...java/top/charles7c/cnadmin/common/util/TemplateUtils.java
+53
-0
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/validate/CheckUtils.java
...op/charles7c/cnadmin/common/util/validate/CheckUtils.java
+59
-7
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/validate/ValidationUtils.java
...arles7c/cnadmin/common/util/validate/ValidationUtils.java
+59
-7
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/validate/Validator.java
...top/charles7c/cnadmin/common/util/validate/Validator.java
+77
-24
continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/interceptor/LogInterceptor.java
...charles7c/cnadmin/monitor/interceptor/LogInterceptor.java
+13
-10
continew-admin-system/pom.xml
continew-admin-system/pom.xml
+0
-6
continew-admin-system/src/main/java/top/charles7c/cnadmin/auth/service/impl/LoginServiceImpl.java
...charles7c/cnadmin/auth/service/impl/LoginServiceImpl.java
+1
-1
continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/UpdateBasicInfoRequest.java
.../cnadmin/system/model/request/UpdateBasicInfoRequest.java
+1
-1
continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/UpdateEmailRequest.java
...es7c/cnadmin/system/model/request/UpdateEmailRequest.java
+66
-0
continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/UserService.java
...ava/top/charles7c/cnadmin/system/service/UserService.java
+12
-0
continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/UserServiceImpl.java
...harles7c/cnadmin/system/service/impl/UserServiceImpl.java
+22
-1
continew-admin-ui/package.json
continew-admin-ui/package.json
+1
-1
continew-admin-ui/src/api/auth/login.ts
continew-admin-ui/src/api/auth/login.ts
+0
-8
continew-admin-ui/src/api/common/captcha.ts
continew-admin-ui/src/api/common/captcha.ts
+22
-0
continew-admin-ui/src/api/system/user-center.ts
continew-admin-ui/src/api/system/user-center.ts
+9
-0
continew-admin-ui/src/components/footer/index.vue
continew-admin-ui/src/components/footer/index.vue
+2
-0
continew-admin-ui/src/components/navbar/index.vue
continew-admin-ui/src/components/navbar/index.vue
+1
-1
continew-admin-ui/src/hooks/axios.d.ts
continew-admin-ui/src/hooks/axios.d.ts
+1
-1
continew-admin-ui/src/hooks/user.ts
continew-admin-ui/src/hooks/user.ts
+1
-1
continew-admin-ui/src/store/modules/login/index.ts
continew-admin-ui/src/store/modules/login/index.ts
+1
-1
continew-admin-ui/src/views/dashboard/monitor/components/studio.vue
...dmin-ui/src/views/dashboard/monitor/components/studio.vue
+1
-1
continew-admin-ui/src/views/login/components/login-form.vue
continew-admin-ui/src/views/login/components/login-form.vue
+4
-4
continew-admin-ui/src/views/system/user/center/components/basic-info.vue
...ui/src/views/system/user/center/components/basic-info.vue
+4
-4
continew-admin-ui/src/views/system/user/center/components/security-settings/update-email.vue
...user/center/components/security-settings/update-email.vue
+190
-2
continew-admin-ui/src/views/system/user/center/components/security-settings/update-pwd.vue
...m/user/center/components/security-settings/update-pwd.vue
+19
-17
continew-admin-ui/src/views/system/user/center/components/user-panel.vue
...ui/src/views/system/user/center/components/user-panel.vue
+2
-2
continew-admin-ui/src/views/system/user/center/locale/en-US.ts
...new-admin-ui/src/views/system/user/center/locale/en-US.ts
+18
-0
continew-admin-ui/src/views/system/user/center/locale/zh-CN.ts
...new-admin-ui/src/views/system/user/center/locale/zh-CN.ts
+18
-0
continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/auth/LoginController.java
...les7c/cnadmin/webapi/controller/auth/LoginController.java
+4
-4
continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/common/CaptchaController.java
...c/cnadmin/webapi/controller/common/CaptchaController.java
+116
-0
continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/system/UserCenterController.java
...nadmin/webapi/controller/system/UserCenterController.java
+23
-0
continew-admin-webapi/src/main/resources/application-dev.yml
continew-admin-webapi/src/main/resources/application-dev.yml
+42
-15
continew-admin-webapi/src/main/resources/application-prod.yml
...inew-admin-webapi/src/main/resources/application-prod.yml
+42
-15
continew-admin-webapi/src/main/resources/application.yml
continew-admin-webapi/src/main/resources/application.yml
+3
-3
continew-admin-webapi/src/main/resources/templates/mail/captcha.ftl
...dmin-webapi/src/main/resources/templates/mail/captcha.ftl
+47
-0
pom.xml
pom.xml
+1
-1
未找到文件。
.github/workflows/deploy.yml
浏览文件 @
8b825578
...
...
@@ -49,6 +49,7 @@ jobs:
script
:
|
cd /docker
docker-compose up --force-recreate --build -d continew-admin-server
docker images | grep none | awk '{print $3}' | xargs docker rmi
# 部署前端
deploy-web
:
...
...
README.md
浏览文件 @
8b825578
# ContiNew
-
Admin 中后台管理框架
# ContiNew
Admin 中后台管理框架
[
![License
](
https://img.shields.io/badge/License-Apache%202.0-green.svg
)
](https://github.com/Charles7c/continew-admin/blob/dev/LICENSE)
![
SNAPSHOT
](
https://img.shields.io/badge/SNAPSHOT-v0.0.1-%23ff3f59.svg
)
...
...
@@ -7,7 +7,7 @@
## 简介
ContiNew
-Admin (incubating) 中后台管理框架
,Continue New Admin,持续以最新流行技术栈构建。当前阶段采用的技术栈:Vue3、TypeScript、Arco Design Pro Vue、Spring Boot、Undertow、Sa-Token、JWT、MariaDB、MyBatis Plus、Redis、Redisson、Hutool 等。
ContiNew
Admin 中后台管理框架(孵化中)
,Continue New Admin,持续以最新流行技术栈构建。当前阶段采用的技术栈:Vue3、TypeScript、Arco Design Pro Vue、Spring Boot、Undertow、Sa-Token、JWT、MariaDB、MyBatis Plus、Redis、Redisson、Hutool 等。
## 开始
...
...
@@ -21,11 +21,11 @@ git clone https://github.com/Charles7c/continew-admin.git
# 2.在 IDE(IntelliJ IDEA/Eclipse)中打开本项目
# 3.修改配置文件中的
Redis 配置信息
# 3.修改配置文件中的
数据源配置信息、Redis 配置信息、邮件配置信息等
# [3.也可以在 IntelliJ IDEA 中直接配置程序启动环境变量(DB_HOST、DB_PORT、DB_USER、DB_PWD、DB_NAME;REDIS_HOST、REDIS_PORT、REDIS_PWD、REDIS_DB)]
# 4.启动程序
# 4.1 启动成功:访问 http://localhost:8000/,页面输出:ContiNew
-
Admin backend service started successfully.
# 4.1 启动成功:访问 http://localhost:8000/,页面输出:ContiNew
Admin backend service started successfully.
# 4.2 接口文档:http://localhost:8000/doc.html
# 5.部署
...
...
@@ -72,7 +72,7 @@ yarn dev
| :----------------------------------------------------------- | :----------- | :----------------------------------------------------------- |
|
[
Vue
](
https://cn.vuejs.org/
)
| 3.2.45 | 渐进式 JavaScript 框架,易学易用,性能出色,适用场景丰富的 Web 前端框架。 |
|
[
TypeScript
](
https://www.typescriptlang.org/zh/
)
| 4.9.4 | TypeScript 是微软开发的一个开源的编程语言,通过在 JavaScript 的基础上添加静态类型定义构建而成。 |
|
[
Arco Design Pro Vue
](
http://pro.arco.design/
)
| 2.
5.15
| 基于 Arco Design Vue 组件库的开箱即用的中后台前端解决方案。 |
|
[
Arco Design Pro Vue
](
http://pro.arco.design/
)
| 2.
6.0
| 基于 Arco Design Vue 组件库的开箱即用的中后台前端解决方案。 |
|
[
Spring Boot
](
https://spring.io/projects/spring-boot
)
| 2.7.7 | 简化新 Spring 应用的初始搭建以及开发过程。 |
|
[
Undertow
](
https://undertow.io/
)
| 2.2.22.Final | 采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制。 |
|
[
Sa-Token + JWT
](
https://sa-token.dev33.cn/
)
| 1.33.0 | 轻量级 Java 权限认证框架,让鉴权变得简单、优雅。 |
...
...
@@ -110,10 +110,14 @@ continew-admin # 全局通用项目配置及依赖版本管理
│ │ ├─ webapi
│ │ │ └─ controller
│ │ │ ├─ auth
# 认证相关 API
│ │ │ ├─ common
# 公共相关 API(例如:验证码 API 等)
│ │ │ └─ system
# 系统管理相关 API
│ │ └─ ContinewAdminApplication.java
# 启动入口
│ └─ resources
# 工程配置目录
│ └─ db.changelog.v0.0.1
# 数据库脚本文件
│ ├─ db.changelog
# 数据库脚本文件
│ │ └─ v0.0.1
# v0.0.1 版本数据库脚本文件
│ └─ templates
# 模板文件
│ └─ mail
# 邮件模板
├─ continew-admin-monitor
# 系统监控模块(存放系统监控模块相关功能,例如:日志管理、服务监控等)
│ └─ src
│ └─ main
...
...
@@ -144,8 +148,7 @@ continew-admin # 全局通用项目配置及依赖版本管理
│ │ └─ cnadmin
│ │ ├─ auth
# 系统认证相关业务及配置
│ │ │ ├─ config
# 系统认证相关配置
│ │ │ │ ├─ satoken
# Sa-Token 配置
│ │ │ │ └─ properties
# 系统认证相关配置属性
│ │ │ │ └─ satoken
# Sa-Token 配置
│ │ │ ├─ model
# 系统认证相关模型
│ │ │ │ ├─ request
# 系统认证相关请求对象
│ │ │ │ └─ vo
# 系统认证相关 VO(View Object)
...
...
@@ -197,6 +200,7 @@ continew-admin
├─ src
│ ├─ api
# 请求接口
│ │ ├─ auth
# 认证模块
│ │ ├─ common
# 公共模块
│ │ └─ system
# 系统管理模块
│ ├─ assets
# 静态资源
│ │ ├─ images
# 图片资源
...
...
continew-admin-common/pom.xml
浏览文件 @
8b825578
...
...
@@ -59,6 +59,17 @@ limitations under the License.
</exclusions>
</dependency>
<!-- Java 邮件支持 -->
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-mail
</artifactId>
</dependency>
<!-- FreeMarker(模板引擎) -->
<dependency>
<groupId>
org.freemarker
</groupId>
<artifactId>
freemarker
</artifactId>
</dependency>
<!-- Hibernate Validator -->
<dependency>
<groupId>
org.springframework.boot
</groupId>
...
...
@@ -126,5 +137,11 @@ limitations under the License.
<groupId>
org.redisson
</groupId>
<artifactId>
redisson-spring-boot-starter
</artifactId>
</dependency>
<!-- Easy Captcha(Java 图形验证码,支持 gif、中文、算术等类型,可用于 Java Web、JavaSE 等项目) -->
<dependency>
<groupId>
com.github.whvcse
</groupId>
<artifactId>
easy-captcha
</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
continew-admin-
system/src/main/java/top/charles7c/cnadmin/auth
/config/properties/CaptchaProperties.java
→
continew-admin-
common/src/main/java/top/charles7c/cnadmin/common
/config/properties/CaptchaProperties.java
浏览文件 @
8b825578
...
...
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package
top.charles7c.cnadmin.
auth
.config.properties
;
package
top.charles7c.cnadmin.
common
.config.properties
;
import
java.awt.*
;
...
...
@@ -43,65 +43,102 @@ import cn.hutool.core.util.StrUtil;
public
class
CaptchaProperties
{
/**
*
类型
*
图片验证码配置
*/
private
Captcha
TypeEnum
typ
e
;
private
Captcha
Image
imag
e
;
/**
*
缓存键的前缀
*
邮箱验证码配置
*/
private
String
keyPrefix
;
private
CaptchaMail
mail
;
/**
*
过期时间
*
图片验证码配置
*/
private
Long
expirationInMinutes
=
2L
;
@Data
public
static
class
CaptchaImage
{
/**
* 类型
*/
private
CaptchaImageTypeEnum
type
;
/**
* 内容长度
*/
private
int
length
=
4
;
/**
* 内容长度
*/
private
int
length
;
/**
* 宽度
*/
private
int
width
=
111
;
/**
* 过期时间
*/
private
long
expirationInMinutes
;
/**
* 高
度
*/
private
int
height
=
36
;
/**
* 宽
度
*/
private
int
width
=
111
;
/**
* 字体
*/
private
String
fontName
;
/**
* 高度
*/
private
int
height
=
36
;
/**
* 字体大小
*/
private
int
fontSize
=
25
;
/**
* 字体
*/
private
String
fontName
;
/**
* 字体大小
*/
private
int
fontSize
=
25
;
/**
* 获取图片验证码对象
*
* @return 验证码对象
*/
public
Captcha
getCaptcha
()
{
Captcha
captcha
=
ReflectUtil
.
newInstance
(
type
.
getClazz
(),
this
.
width
,
this
.
height
);
captcha
.
setLen
(
length
);
if
(
StrUtil
.
isNotBlank
(
this
.
fontName
))
{
captcha
.
setFont
(
new
Font
(
this
.
fontName
,
Font
.
PLAIN
,
this
.
fontSize
));
}
return
captcha
;
}
}
/**
* 获取验证码对象
*
* @return 验证码对象
* 邮箱验证码配置
*/
public
Captcha
getCaptcha
()
{
Captcha
captcha
=
ReflectUtil
.
newInstance
(
type
.
getClazz
(),
this
.
width
,
this
.
height
);
captcha
.
setLen
(
length
);
if
(
StrUtil
.
isNotBlank
(
this
.
fontName
))
{
captcha
.
setFont
(
new
Font
(
this
.
fontName
,
Font
.
PLAIN
,
this
.
fontSize
));
}
return
captcha
;
@Data
public
static
class
CaptchaMail
{
/**
* 内容长度
*/
private
int
length
;
/**
* 过期时间
*/
private
long
expirationInMinutes
;
/**
* 限制时间
*/
private
long
limitInSeconds
;
/**
* 模板路径
*/
private
String
templatePath
;
}
/**
* 验证码类型枚举
*
图片
验证码类型枚举
*/
@Getter
@RequiredArgsConstructor
p
ublic
enum
Captcha
TypeEnum
{
p
rivate
enum
CaptchaImage
TypeEnum
{
/**
* 算术
...
...
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/consts/CacheConstants.java
浏览文件 @
8b825578
...
...
@@ -33,4 +33,14 @@ public class CacheConstants {
*/
public
static
final
String
LOGIN_USER_CACHE_KEY
=
"LOGIN_USER"
;
/**
* 验证码缓存键
*/
public
static
final
String
CAPTCHA_CACHE_KEY
=
"CAPTCHA"
;
/**
* 限流缓存键
*/
public
static
final
String
LIMIT_CACHE_KEY
=
"LIMIT"
;
}
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/handler/GlobalExceptionHandler.java
浏览文件 @
8b825578
...
...
@@ -162,8 +162,8 @@ public class GlobalExceptionHandler {
@ResponseStatus
(
HttpStatus
.
UNAUTHORIZED
)
@ExceptionHandler
(
NotLoginException
.
class
)
public
R
handleNotLoginException
(
NotLoginException
e
,
HttpServletRequest
request
)
{
log
.
error
(
"请求地址'{}',认证失败
'{}',无法访问系统资源"
,
request
.
getRequestURI
(),
e
.
getMessage
()
);
return
R
.
fail
(
HttpStatus
.
UNAUTHORIZED
.
value
(),
"
认证失败,无法访问系统资源
"
);
log
.
error
(
"请求地址'{}',认证失败
,无法访问系统资源"
,
request
.
getRequestURI
(),
e
);
return
R
.
fail
(
HttpStatus
.
UNAUTHORIZED
.
value
(),
"
登录状态已过期,请重新登录
"
);
}
/**
...
...
continew-admin-
system/src/main/java/top/charles7c/cnadmin/auth
/model/vo/CaptchaVO.java
→
continew-admin-
common/src/main/java/top/charles7c/cnadmin/common
/model/vo/CaptchaVO.java
浏览文件 @
8b825578
...
...
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package
top.charles7c.cnadmin.
auth
.model.vo
;
package
top.charles7c.cnadmin.
common
.model.vo
;
import
java.io.Serializable
;
...
...
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/MailUtils.java
0 → 100644
浏览文件 @
8b825578
/*
* 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.common.util
;
import
java.io.File
;
import
java.nio.charset.StandardCharsets
;
import
java.util.Collection
;
import
java.util.List
;
import
javax.mail.MessagingException
;
import
javax.mail.internet.MimeMessage
;
import
lombok.AccessLevel
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
org.springframework.mail.javamail.JavaMailSender
;
import
org.springframework.mail.javamail.MimeMessageHelper
;
import
cn.hutool.core.collection.CollUtil
;
import
cn.hutool.core.util.ArrayUtil
;
import
cn.hutool.core.util.CharUtil
;
import
cn.hutool.core.util.StrUtil
;
import
cn.hutool.extra.spring.SpringUtil
;
import
top.charles7c.cnadmin.common.util.validate.CheckUtils
;
/**
* 邮件工具类
*
* @author Charles7c
* @since 2023/1/12 23:25
*/
@Data
@NoArgsConstructor
(
access
=
AccessLevel
.
PRIVATE
)
public
class
MailUtils
{
private
static
final
JavaMailSender
MAIL_SENDER
=
SpringUtil
.
getBean
(
JavaMailSender
.
class
);
/**
* 发送文本邮件给单个人
*
* @param subject
* 主题
* @param content
* 内容
* @param to
* 收件人
* @throws MessagingException
* /
*/
public
static
void
sendText
(
String
to
,
String
subject
,
String
content
)
throws
MessagingException
{
send
(
splitAddress
(
to
),
null
,
null
,
subject
,
content
,
false
);
}
/**
* 发送 HTML 邮件给单个人
*
* @param subject
* 主题
* @param content
* 内容
* @param to
* 收件人
* @throws MessagingException
* /
*/
public
static
void
sendHtml
(
String
to
,
String
subject
,
String
content
)
throws
MessagingException
{
send
(
splitAddress
(
to
),
null
,
null
,
subject
,
content
,
true
);
}
/**
* 发送 HTML 邮件给单个人
*
* @param subject
* 主题
* @param content
* 内容
* @param to
* 收件人
* @param files
* 附件列表
* @throws MessagingException
* /
*/
public
static
void
sendHtml
(
String
to
,
String
subject
,
String
content
,
File
...
files
)
throws
MessagingException
{
send
(
splitAddress
(
to
),
null
,
null
,
subject
,
content
,
true
,
files
);
}
/**
* 发送 HTML 邮件给多个人
*
* @param subject
* 主题
* @param content
* 内容
* @param tos
* 收件人列表
* @param files
* 附件列表
* @throws MessagingException
* /
*/
public
static
void
sendHtml
(
Collection
<
String
>
tos
,
String
subject
,
String
content
,
File
...
files
)
throws
MessagingException
{
send
(
tos
,
null
,
null
,
subject
,
content
,
true
,
files
);
}
/**
* 发送 HTML 邮件给多个人
*
* @param subject
* 主题
* @param content
* 内容
* @param tos
* 收件人列表
* @param ccs
* 抄送人列表
* @param files
* 附件列表
* @throws MessagingException
* /
*/
public
static
void
sendHtml
(
Collection
<
String
>
tos
,
Collection
<
String
>
ccs
,
String
subject
,
String
content
,
File
...
files
)
throws
MessagingException
{
send
(
tos
,
ccs
,
null
,
subject
,
content
,
true
,
files
);
}
/**
* 发送 HTML 邮件给多个人
*
* @param subject
* 主题
* @param content
* 内容
* @param tos
* 收件人列表
* @param ccs
* 抄送人列表
* @param bccs
* 密送人列表
* @param files
* 附件列表
* @throws MessagingException
* /
*/
public
static
void
sendHtml
(
Collection
<
String
>
tos
,
Collection
<
String
>
ccs
,
Collection
<
String
>
bccs
,
String
subject
,
String
content
,
File
...
files
)
throws
MessagingException
{
send
(
tos
,
ccs
,
bccs
,
subject
,
content
,
true
,
files
);
}
/**
* 发送邮件给多个人
*
* @param tos
* 收件人列表
* @param ccs
* 抄送人列表
* @param bccs
* 密送人列表
* @param subject
* 主题
* @param content
* 内容
* @param isHtml
* 是否是 HTML
* @param files
* 附件列表
* @throws MessagingException
* /
*/
public
static
void
send
(
Collection
<
String
>
tos
,
Collection
<
String
>
ccs
,
Collection
<
String
>
bccs
,
String
subject
,
String
content
,
boolean
isHtml
,
File
...
files
)
throws
MessagingException
{
CheckUtils
.
exIfCondition
(()
->
CollUtil
.
isEmpty
(
tos
),
"请至少指定一名收件人"
);
MimeMessage
mimeMessage
=
MAIL_SENDER
.
createMimeMessage
();
MimeMessageHelper
messageHelper
=
new
MimeMessageHelper
(
mimeMessage
,
true
,
StandardCharsets
.
UTF_8
.
displayName
());
// 设置基本信息
messageHelper
.
setFrom
(
SpringUtil
.
getProperty
(
"spring.mail.username"
));
messageHelper
.
setSubject
(
subject
);
messageHelper
.
setText
(
content
,
isHtml
);
// 设置收信人
// 抄送人
if
(
CollUtil
.
isNotEmpty
(
ccs
))
{
messageHelper
.
setCc
(
ccs
.
toArray
(
new
String
[
0
]));
}
// 密送人
if
(
CollUtil
.
isNotEmpty
(
bccs
))
{
messageHelper
.
setBcc
(
bccs
.
toArray
(
new
String
[
0
]));
}
// 收件人
messageHelper
.
setTo
(
tos
.
toArray
(
new
String
[
0
]));
// 设置附件
if
(
ArrayUtil
.
isNotEmpty
(
files
))
{
for
(
File
file
:
files
)
{
messageHelper
.
addAttachment
(
file
.
getName
(),
file
);
}
}
// 发送邮件
MAIL_SENDER
.
send
(
mimeMessage
);
}
/**
* 将多个联系人转为列表,分隔符为逗号或者分号
*
* @param addresses
* 多个联系人,如果为空返回null
* @return 联系人列表
*/
private
static
List
<
String
>
splitAddress
(
String
addresses
)
{
if
(
StrUtil
.
isBlank
(
addresses
))
{
return
null
;
}
List
<
String
>
result
;
if
(
StrUtil
.
contains
(
addresses
,
CharUtil
.
COMMA
))
{
result
=
StrUtil
.
splitTrim
(
addresses
,
CharUtil
.
COMMA
);
}
else
if
(
StrUtil
.
contains
(
addresses
,
';'
))
{
result
=
StrUtil
.
splitTrim
(
addresses
,
';'
);
}
else
{
result
=
CollUtil
.
newArrayList
(
addresses
);
}
return
result
;
}
}
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/TemplateUtils.java
0 → 100644
浏览文件 @
8b825578
/*
* 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.common.util
;
import
java.util.Map
;
import
lombok.AccessLevel
;
import
lombok.NoArgsConstructor
;
import
cn.hutool.extra.template.Template
;
import
cn.hutool.extra.template.TemplateConfig
;
import
cn.hutool.extra.template.TemplateEngine
;
import
cn.hutool.extra.template.TemplateUtil
;
/**
* 模板工具类
*
* @author Charles7c
* @since 2023/1/13 20:37
*/
@NoArgsConstructor
(
access
=
AccessLevel
.
PRIVATE
)
public
class
TemplateUtils
{
private
static
final
String
TEMPLATE_PARENT_PATH
=
"templates"
;
/**
* 将模板与绑定参数融合后返回为字符串
*
* @param bindingMap
* 绑定的参数,此Map中的参数会替换模板中的变量
* @return 融合后的内容
*/
public
static
String
render
(
String
templatePath
,
Map
<?,
?>
bindingMap
)
{
TemplateEngine
engine
=
TemplateUtil
.
createEngine
(
new
TemplateConfig
(
TEMPLATE_PARENT_PATH
,
TemplateConfig
.
ResourceMode
.
CLASSPATH
));
Template
template
=
engine
.
getTemplate
(
templatePath
);
return
template
.
render
(
bindingMap
);
}
}
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/validate/CheckUtils.java
浏览文件 @
8b825578
...
...
@@ -38,25 +38,25 @@ public class CheckUtils extends Validator {
/**
* 如果为空,抛出异常
*
* @param
obj
* 被检测的
对象
* @param
str
* 被检测的
字符串
* @param message
* 错误信息
*/
public
static
void
exIf
Null
(
Object
obj
,
String
message
)
{
exIf
Null
(
obj
,
message
,
EXCEPTION_TYPE
);
public
static
void
exIf
Blank
(
CharSequence
str
,
String
message
)
{
exIf
Blank
(
str
,
message
,
EXCEPTION_TYPE
);
}
/**
* 如果为空,抛出异常
* 如果
不
为空,抛出异常
*
* @param str
* 被检测的字符串
* @param message
* 错误信息
*/
public
static
void
exIfBlank
(
CharSequence
str
,
String
message
)
{
exIfBlank
(
str
,
message
,
EXCEPTION_TYPE
);
public
static
void
exIf
Not
Blank
(
CharSequence
str
,
String
message
)
{
exIf
Not
Blank
(
str
,
message
,
EXCEPTION_TYPE
);
}
/**
...
...
@@ -87,6 +87,58 @@ public class CheckUtils extends Validator {
exIfNotEqual
(
obj1
,
obj2
,
message
,
EXCEPTION_TYPE
);
}
/**
* 如果相同,抛出异常(不区分大小写)
*
* @param str1
* 要比较的字符串1
* @param str2
* 要比较的字符串2
* @param message
* 错误信息
*/
public
static
void
exIfEqualIgnoreCase
(
CharSequence
str1
,
CharSequence
str2
,
String
message
)
{
exIfEqualIgnoreCase
(
str1
,
str2
,
message
,
EXCEPTION_TYPE
);
}
/**
* 如果不相同,抛出异常(不区分大小写)
*
* @param str1
* 要比较的字符串1
* @param str2
* 要比较的字符串2
* @param message
* 错误信息
*/
public
static
void
exIfNotEqualIgnoreCase
(
CharSequence
str1
,
CharSequence
str2
,
String
message
)
{
exIfNotEqualIgnoreCase
(
str1
,
str2
,
message
,
EXCEPTION_TYPE
);
}
/**
* 如果为空,抛出异常
*
* @param obj
* 被检测的对象
* @param message
* 错误信息
*/
public
static
void
exIfNull
(
Object
obj
,
String
message
)
{
exIfNull
(
obj
,
message
,
EXCEPTION_TYPE
);
}
/**
* 如果不为空,抛出异常
*
* @param obj
* 被检测的对象
* @param message
* 错误信息
*/
public
static
void
exIfNotNull
(
Object
obj
,
String
message
)
{
exIfNotNull
(
obj
,
message
,
EXCEPTION_TYPE
);
}
/**
* 如果条件成立,抛出异常
*
...
...
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/validate/ValidationUtils.java
浏览文件 @
8b825578
...
...
@@ -38,25 +38,25 @@ public class ValidationUtils extends Validator {
/**
* 如果为空,抛出异常
*
* @param
obj
* 被检测的
对象
* @param
str
* 被检测的
字符串
* @param message
* 错误信息
*/
public
static
void
exIf
Null
(
Object
obj
,
String
message
)
{
exIf
Null
(
obj
,
message
,
EXCEPTION_TYPE
);
public
static
void
exIf
Blank
(
CharSequence
str
,
String
message
)
{
exIf
Blank
(
str
,
message
,
EXCEPTION_TYPE
);
}
/**
* 如果为空,抛出异常
* 如果
不
为空,抛出异常
*
* @param str
* 被检测的字符串
* @param message
* 错误信息
*/
public
static
void
exIfBlank
(
CharSequence
str
,
String
message
)
{
exIfBlank
(
str
,
message
,
EXCEPTION_TYPE
);
public
static
void
exIf
Not
Blank
(
CharSequence
str
,
String
message
)
{
exIf
Not
Blank
(
str
,
message
,
EXCEPTION_TYPE
);
}
/**
...
...
@@ -87,6 +87,58 @@ public class ValidationUtils extends Validator {
exIfNotEqual
(
obj1
,
obj2
,
message
,
EXCEPTION_TYPE
);
}
/**
* 如果相同,抛出异常(不区分大小写)
*
* @param str1
* 要比较的字符串1
* @param str2
* 要比较的字符串2
* @param message
* 错误信息
*/
public
static
void
exIfEqualIgnoreCase
(
CharSequence
str1
,
CharSequence
str2
,
String
message
)
{
exIfEqualIgnoreCase
(
str1
,
str2
,
message
,
EXCEPTION_TYPE
);
}
/**
* 如果不相同,抛出异常(不区分大小写)
*
* @param str1
* 要比较的字符串1
* @param str2
* 要比较的字符串2
* @param message
* 错误信息
*/
public
static
void
exIfNotEqualIgnoreCase
(
CharSequence
str1
,
CharSequence
str2
,
String
message
)
{
exIfNotEqualIgnoreCase
(
str1
,
str2
,
message
,
EXCEPTION_TYPE
);
}
/**
* 如果为空,抛出异常
*
* @param obj
* 被检测的对象
* @param message
* 错误信息
*/
public
static
void
exIfNull
(
Object
obj
,
String
message
)
{
exIfNull
(
obj
,
message
,
EXCEPTION_TYPE
);
}
/**
* 如果不为空,抛出异常
*
* @param obj
* 被检测的对象
* @param message
* 错误信息
*/
public
static
void
exIfNotNull
(
Object
obj
,
String
message
)
{
exIfNotNull
(
obj
,
message
,
EXCEPTION_TYPE
);
}
/**
* 如果条件成立,抛出异常
*
...
...
continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/validate/Validator.java
浏览文件 @
8b825578
...
...
@@ -25,6 +25,8 @@ import cn.hutool.core.util.ReflectUtil;
import
cn.hutool.core.util.StrUtil
;
/**
* 校验器
*
* @author Charles7c
* @since 2023/1/2 22:12
*/
...
...
@@ -35,22 +37,19 @@ public class Validator {
/**
* 如果为空,抛出异常
*
* @param
obj
* 被检测的
对象
* @param
str
* 被检测的
字符串
* @param message
* 错误信息
* @param exceptionType
* 异常类型
*/
protected
static
void
exIfNull
(
Object
obj
,
String
message
,
Class
<?
extends
RuntimeException
>
exceptionType
)
{
if
(
obj
==
null
)
{
log
.
error
(
message
);
throw
ReflectUtil
.
newInstance
(
exceptionType
,
message
);
}
protected
static
void
exIfBlank
(
CharSequence
str
,
String
message
,
Class
<?
extends
RuntimeException
>
exceptionType
)
{
exIfCondition
(()
->
StrUtil
.
isBlank
(
str
),
message
,
exceptionType
);
}
/**
* 如果为空,抛出异常
* 如果
不
为空,抛出异常
*
* @param str
* 被检测的字符串
...
...
@@ -59,11 +58,9 @@ public class Validator {
* @param exceptionType
* 异常类型
*/
public
static
void
exIfBlank
(
CharSequence
str
,
String
message
,
Class
<?
extends
RuntimeException
>
exceptionType
)
{
if
(
StrUtil
.
isBlank
(
str
))
{
log
.
error
(
message
);
throw
ReflectUtil
.
newInstance
(
exceptionType
,
message
);
}
protected
static
void
exIfNotBlank
(
CharSequence
str
,
String
message
,
Class
<?
extends
RuntimeException
>
exceptionType
)
{
exIfCondition
(()
->
StrUtil
.
isNotBlank
(
str
),
message
,
exceptionType
);
}
/**
...
...
@@ -78,12 +75,9 @@ public class Validator {
* @param exceptionType
* 异常类型
*/
p
ublic
static
void
exIfEqual
(
Object
obj1
,
Object
obj2
,
String
message
,
p
rotected
static
void
exIfEqual
(
Object
obj1
,
Object
obj2
,
String
message
,
Class
<?
extends
RuntimeException
>
exceptionType
)
{
if
(
ObjectUtil
.
equals
(
obj1
,
obj2
))
{
log
.
error
(
message
);
throw
ReflectUtil
.
newInstance
(
exceptionType
,
message
);
}
exIfCondition
(()
->
ObjectUtil
.
equal
(
obj1
,
obj2
),
message
,
exceptionType
);
}
/**
...
...
@@ -98,12 +92,71 @@ public class Validator {
* @param exceptionType
* 异常类型
*/
p
ublic
static
void
exIfNotEqual
(
Object
obj1
,
Object
obj2
,
String
message
,
p
rotected
static
void
exIfNotEqual
(
Object
obj1
,
Object
obj2
,
String
message
,
Class
<?
extends
RuntimeException
>
exceptionType
)
{
if
(
ObjectUtil
.
notEqual
(
obj1
,
obj2
))
{
log
.
error
(
message
);
throw
ReflectUtil
.
newInstance
(
exceptionType
,
message
);
}
exIfCondition
(()
->
ObjectUtil
.
notEqual
(
obj1
,
obj2
),
message
,
exceptionType
);
}
/**
* 如果相同,抛出异常(不区分大小写)
*
* @param str1
* 要比较的字符串1
* @param str2
* 要比较的字符串2
* @param message
* 错误信息
* @param exceptionType
* 异常类型
*/
protected
static
void
exIfEqualIgnoreCase
(
CharSequence
str1
,
CharSequence
str2
,
String
message
,
Class
<?
extends
RuntimeException
>
exceptionType
)
{
exIfCondition
(()
->
StrUtil
.
equalsIgnoreCase
(
str1
,
str2
),
message
,
exceptionType
);
}
/**
* 如果不相同,抛出异常(不区分大小写)
*
* @param str1
* 要比较的字符串1
* @param str2
* 要比较的字符串2
* @param message
* 错误信息
* @param exceptionType
* 异常类型
*/
protected
static
void
exIfNotEqualIgnoreCase
(
CharSequence
str1
,
CharSequence
str2
,
String
message
,
Class
<?
extends
RuntimeException
>
exceptionType
)
{
exIfCondition
(()
->
!
StrUtil
.
equalsIgnoreCase
(
str1
,
str2
),
message
,
exceptionType
);
}
/**
* 如果为空,抛出异常
*
* @param obj
* 被检测的对象
* @param message
* 错误信息
* @param exceptionType
* 异常类型
*/
protected
static
void
exIfNull
(
Object
obj
,
String
message
,
Class
<?
extends
RuntimeException
>
exceptionType
)
{
exIfCondition
(()
->
obj
==
null
,
message
,
exceptionType
);
}
/**
* 如果不为空,抛出异常
*
* @param obj
* 被检测的对象
* @param message
* 错误信息
* @param exceptionType
* 异常类型
*/
protected
static
void
exIfNotNull
(
Object
obj
,
String
message
,
Class
<?
extends
RuntimeException
>
exceptionType
)
{
exIfCondition
(()
->
obj
!=
null
,
message
,
exceptionType
);
}
/**
...
...
@@ -116,7 +169,7 @@ public class Validator {
* @param exceptionType
* 异常类型
*/
p
ublic
static
void
exIfCondition
(
java
.
util
.
function
.
BooleanSupplier
conditionSupplier
,
String
message
,
p
rotected
static
void
exIfCondition
(
java
.
util
.
function
.
BooleanSupplier
conditionSupplier
,
String
message
,
Class
<?
extends
RuntimeException
>
exceptionType
)
{
if
(
conditionSupplier
!=
null
&&
conditionSupplier
.
getAsBoolean
())
{
log
.
error
(
message
);
...
...
continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/interceptor/LogInterceptor.java
浏览文件 @
8b825578
...
...
@@ -28,6 +28,7 @@ import lombok.extern.slf4j.Slf4j;
import
io.swagger.v3.oas.annotations.Operation
;
import
org.springframework.core.annotation.AnnotationUtils
;
import
org.springframework.lang.NonNull
;
import
org.springframework.stereotype.Component
;
import
org.springframework.web.method.HandlerMethod
;
import
org.springframework.web.servlet.HandlerInterceptor
;
...
...
@@ -65,20 +66,21 @@ import top.charles7c.cnadmin.monitor.model.entity.SysLog;
public
class
LogInterceptor
implements
HandlerInterceptor
{
private
final
LogProperties
operationLogProperties
;
private
static
final
String
ENCRYPT_SYMBOL
=
"****************"
;
@Override
public
boolean
preHandle
(
HttpServletRequest
request
,
HttpServletResponse
response
,
Object
handler
)
{
if
(!
checkIsNeedRecord
(
handler
,
request
))
{
return
true
;
public
boolean
preHandle
(
@NonNull
HttpServletRequest
request
,
@NonNull
HttpServletResponse
response
,
@NonNull
Object
handler
)
{
if
(
checkIsNeedRecord
(
handler
,
request
))
{
// 记录操作时间
this
.
logCreateTime
();
}
// 记录操作时间
this
.
logCreateTime
();
return
true
;
}
@Override
public
void
afterCompletion
(
HttpServletRequest
request
,
HttpServletResponse
response
,
Object
handler
,
Exception
e
)
{
public
void
afterCompletion
(
@NonNull
HttpServletRequest
request
,
@NonNull
HttpServletResponse
response
,
@NonNull
Object
handler
,
Exception
e
)
{
// 记录请求耗时及异常信息
SysLog
sysLog
=
this
.
logElapsedTimeAndException
();
if
(
sysLog
==
null
)
{
...
...
@@ -203,6 +205,7 @@ public class LogInterceptor implements HandlerInterceptor {
* 待脱敏数据
* @return 脱敏后的 JSON 字符串数据
*/
@SuppressWarnings
(
"unchecked"
)
private
String
desensitize
(
Map
waitDesensitizeData
)
{
String
desensitizeDataStr
=
JSONUtil
.
toJsonStr
(
waitDesensitizeData
);
try
{
...
...
@@ -211,9 +214,9 @@ public class LogInterceptor implements HandlerInterceptor {
}
for
(
String
desensitizeProperty
:
operationLogProperties
.
getDesensitize
())
{
waitDesensitizeData
.
computeIfPresent
(
desensitizeProperty
,
(
k
,
v
)
->
"****************"
);
waitDesensitizeData
.
computeIfPresent
(
desensitizeProperty
.
toLowerCase
(),
(
k
,
v
)
->
"****************"
);
waitDesensitizeData
.
computeIfPresent
(
desensitizeProperty
.
toUpperCase
(),
(
k
,
v
)
->
"****************"
);
waitDesensitizeData
.
computeIfPresent
(
desensitizeProperty
,
(
k
,
v
)
->
ENCRYPT_SYMBOL
);
waitDesensitizeData
.
computeIfPresent
(
desensitizeProperty
.
toLowerCase
(),
(
k
,
v
)
->
ENCRYPT_SYMBOL
);
waitDesensitizeData
.
computeIfPresent
(
desensitizeProperty
.
toUpperCase
(),
(
k
,
v
)
->
ENCRYPT_SYMBOL
);
}
return
JSONUtil
.
toJsonStr
(
waitDesensitizeData
);
}
catch
(
Exception
ignored
)
{
...
...
continew-admin-system/pom.xml
浏览文件 @
8b825578
...
...
@@ -32,12 +32,6 @@ limitations under the License.
<description>
系统管理模块(存放系统管理模块相关功能,例如:部门管理、角色管理、用户管理等)
</description>
<dependencies>
<!-- Easy Captcha(Java 图形验证码,支持 gif、中文、算术等类型,可用于 Java Web、JavaSE 等项目) -->
<dependency>
<groupId>
com.github.whvcse
</groupId>
<artifactId>
easy-captcha
</artifactId>
</dependency>
<!-- 公共模块(存放公共工具类,公共配置等) -->
<dependency>
<groupId>
top.charles7c
</groupId>
...
...
continew-admin-system/src/main/java/top/charles7c/cnadmin/auth/service/impl/LoginServiceImpl.java
浏览文件 @
8b825578
...
...
@@ -52,7 +52,7 @@ public class LoginServiceImpl implements LoginService {
// 校验
ValidationUtils
.
exIfNull
(
sysUser
,
"用户名或密码错误"
);
Long
userId
=
sysUser
.
getUserId
();
ValidationUtils
.
exIfNotEqual
(
sysUser
.
getPassword
(),
SecureUtils
.
md5Salt
(
password
,
userId
.
toString
()
),
ValidationUtils
.
exIfNotEqual
(
SecureUtils
.
md5Salt
(
password
,
userId
.
toString
()),
sysUser
.
getPassword
(
),
"用户名或密码错误"
);
ValidationUtils
.
exIfEqual
(
DisEnableStatusEnum
.
DISABLE
,
sysUser
.
getStatus
(),
"此账号已被禁用,如有疑问,请联系管理员"
);
...
...
continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/UpdateBasicInfoRequest.java
浏览文件 @
8b825578
...
...
@@ -52,6 +52,6 @@ public class UpdateBasicInfoRequest implements Serializable {
* 性别(0未知 1男 2女)
*/
@Schema
(
description
=
"性别(0未知 1男 2女)"
,
type
=
"Integer"
,
allowableValues
=
{
"0"
,
"1"
,
"2"
})
@NotNull
(
message
=
"
非法性别
"
)
@NotNull
(
message
=
"
性别非法
"
)
private
GenderEnum
gender
;
}
continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/UpdateEmailRequest.java
0 → 100644
浏览文件 @
8b825578
/*
* 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.system.model.request
;
import
java.io.Serializable
;
import
javax.validation.constraints.NotBlank
;
import
javax.validation.constraints.Pattern
;
import
lombok.Data
;
import
io.swagger.v3.oas.annotations.media.Schema
;
import
org.hibernate.validator.constraints.Length
;
import
cn.hutool.core.lang.RegexPool
;
/**
* 修改邮箱信息
*
* @author Charles7c
* @since 2023/1/12 20:18
*/
@Data
@Schema
(
description
=
"修改邮箱信息"
)
public
class
UpdateEmailRequest
implements
Serializable
{
private
static
final
long
serialVersionUID
=
1L
;
/**
* 新邮箱
*/
@Schema
(
description
=
"新邮箱"
)
@NotBlank
(
message
=
"新邮箱不能为空"
)
@Pattern
(
regexp
=
RegexPool
.
EMAIL
,
message
=
"邮箱格式错误"
)
private
String
newEmail
;
/**
* 验证码
*/
@Schema
(
description
=
"验证码"
)
@NotBlank
(
message
=
"验证码不能为空"
)
@Length
(
max
=
6
,
message
=
"验证码非法"
)
private
String
captcha
;
/**
* 当前密码(加密后)
*/
@Schema
(
description
=
"当前密码(加密后)"
)
@NotBlank
(
message
=
"当前密码不能为空"
)
private
String
currentPassword
;
}
continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/UserService.java
浏览文件 @
8b825578
...
...
@@ -67,4 +67,16 @@ public interface UserService {
* 用户 ID
*/
void
updatePassword
(
String
oldPassword
,
String
newPassword
,
Long
userId
);
/**
* 修改邮箱
*
* @param newEmail
* 新邮箱
* @param currentPassword
* 当前密码
* @param userId
* 用户ID
*/
void
updateEmail
(
String
newEmail
,
String
currentPassword
,
Long
userId
);
}
continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/UserServiceImpl.java
浏览文件 @
8b825578
...
...
@@ -104,7 +104,7 @@ public class UserServiceImpl implements UserService {
@Transactional
(
rollbackFor
=
Exception
.
class
)
public
void
updatePassword
(
String
oldPassword
,
String
newPassword
,
Long
userId
)
{
SysUser
sysUser
=
this
.
getById
(
userId
);
ValidationUtils
.
exIfNotEqual
(
sysUser
.
getPassword
(),
SecureUtils
.
md5Salt
(
oldPassword
,
userId
.
toString
()
),
ValidationUtils
.
exIfNotEqual
(
SecureUtils
.
md5Salt
(
oldPassword
,
userId
.
toString
()),
sysUser
.
getPassword
(
),
"当前密码错误"
);
// 更新密码和密码重置时间
...
...
@@ -120,6 +120,27 @@ public class UserServiceImpl implements UserService {
LoginHelper
.
updateLoginUser
(
loginUser
);
}
@Override
@Transactional
(
rollbackFor
=
Exception
.
class
)
public
void
updateEmail
(
String
newEmail
,
String
currentPassword
,
Long
userId
)
{
// 校验
SysUser
sysUser
=
this
.
getById
(
userId
);
ValidationUtils
.
exIfNotEqual
(
SecureUtils
.
md5Salt
(
currentPassword
,
userId
.
toString
()),
sysUser
.
getPassword
(),
"当前密码错误"
);
Long
count
=
userMapper
.
selectCount
(
Wrappers
.<
SysUser
>
lambdaQuery
().
eq
(
SysUser:
:
getEmail
,
newEmail
));
ValidationUtils
.
exIfCondition
(()
->
count
>
0
,
"邮箱已绑定其他账号,请更换其他邮箱"
);
ValidationUtils
.
exIfEqual
(
newEmail
,
sysUser
.
getEmail
(),
"新邮箱不能与当前邮箱相同"
);
// 更新邮箱
userMapper
.
update
(
null
,
new
LambdaUpdateWrapper
<
SysUser
>().
set
(
SysUser:
:
getEmail
,
newEmail
).
eq
(
SysUser:
:
getUserId
,
userId
));
// 更新登录用户信息
LoginUser
loginUser
=
LoginHelper
.
getLoginUser
();
loginUser
.
setEmail
(
newEmail
);
LoginHelper
.
updateLoginUser
(
loginUser
);
}
/**
* 根据 ID 查询
*
...
...
continew-admin-ui/package.json
浏览文件 @
8b825578
{
"name"
:
"continew-admin-ui"
,
"description"
:
"ContiNew
-Admin (incubating) 中后台管理框架
,Continue New Admin,持续以最新流行技术栈构建。"
,
"description"
:
"ContiNew
Admin 中后台管理框架(孵化中)
,Continue New Admin,持续以最新流行技术栈构建。"
,
"version"
:
"0.0.1-SNAPSHOT"
,
"private"
:
true
,
"author"
:
"Charles7c"
,
...
...
continew-admin-ui/src/api/auth/login.ts
浏览文件 @
8b825578
...
...
@@ -2,14 +2,6 @@ import axios from 'axios';
import
type
{
RouteRecordNormalized
}
from
'
vue-router
'
;
import
{
UserState
}
from
'
@/store/modules/login/types
'
;
export
interface
ImageCaptchaRes
{
uuid
:
string
;
img
:
string
;
}
export
function
getImageCaptcha
()
{
return
axios
.
get
<
ImageCaptchaRes
>
(
'
/captcha/img
'
);
}
export
interface
LoginReq
{
username
:
string
;
password
:
string
;
...
...
continew-admin-ui/src/api/common/captcha.ts
0 → 100644
浏览文件 @
8b825578
import
axios
from
'
axios
'
;
import
qs
from
'
query-string
'
;
export
interface
ImageCaptchaRes
{
uuid
:
string
;
img
:
string
;
}
export
function
getImageCaptcha
()
{
return
axios
.
get
<
ImageCaptchaRes
>
(
'
/common/captcha/img
'
);
}
export
interface
MailCaptchaReq
{
email
:
string
;
}
export
function
getMailCaptcha
(
params
:
MailCaptchaReq
)
{
return
axios
.
get
(
'
/common/captcha/mail
'
,
{
params
,
paramsSerializer
:
(
obj
)
=>
{
return
qs
.
stringify
(
obj
);
},
});
}
continew-admin-ui/src/api/system/user-center.ts
浏览文件 @
8b825578
...
...
@@ -27,4 +27,13 @@ export interface UpdatePasswordReq {
}
export
function
updatePassword
(
req
:
UpdatePasswordReq
)
{
return
axios
.
patch
(
'
/system/user/center/password
'
,
req
);
}
export
interface
UpdateEmailReq
{
newEmail
:
string
;
captcha
:
string
;
currentPassword
:
string
;
}
export
function
updateEmail
(
req
:
UpdateEmailReq
)
{
return
axios
.
patch
(
'
/system/user/center/email
'
,
req
);
}
\ No newline at end of file
continew-admin-ui/src/components/footer/index.vue
浏览文件 @
8b825578
...
...
@@ -2,6 +2,8 @@
<a-layout-footer
class=
"footer"
>
{{
`Copyright © 2022-${new Date().getFullYear()
}
Charles7c`
}}
<
span
>&
nbsp
;
⋅
&
nbsp
;
<
/span
>
<
a
href
=
"
https://github.com/Charles7c/continew-admin
"
target
=
"
_blank
"
>
{{
$t
(
'
title
'
)
}}
<
/a
>
<
span
>&
nbsp
;
⋅
&
nbsp
;
<
/span
>
<
a
href
=
"
https://beian.miit.gov.cn
"
target
=
"
_blank
"
>
津ICP备2022005864号
-
2
<
/a
>
<
/a-layout-footer
>
<
/template
>
...
...
continew-admin-ui/src/components/navbar/index.vue
浏览文件 @
8b825578
...
...
@@ -190,7 +190,7 @@
import
useLocale
from
'
@/hooks/locale
'
;
import
useUser
from
'
@/hooks/user
'
;
import
Menu
from
'
@/components/menu/index.vue
'
;
import
getAvatar
from
"
@/utils/avatar
"
;
import
getAvatar
from
'
@/utils/avatar
'
;
import
MessageBox
from
'
../message-box/index.vue
'
;
const
appStore
=
useAppStore
();
...
...
continew-admin-ui/src/hooks/axios.d.ts
浏览文件 @
8b825578
import
axios
,
{
Axios
,
AxiosResponse
,
AxiosRequestConfig
}
from
"
axios
"
;
import
axios
,
{
Axios
,
AxiosResponse
,
AxiosRequestConfig
}
from
'
axios
'
;
declare
module
"
axios
"
{
interface
AxiosResponse
<
T
=
any
>
{
...
...
continew-admin-ui/src/hooks/user.ts
浏览文件 @
8b825578
import
{
useRouter
}
from
'
vue-router
'
;
import
{
useI18n
}
from
"
vue-i18n
"
;
import
{
useI18n
}
from
'
vue-i18n
'
;
import
{
Message
}
from
'
@arco-design/web-vue
'
;
import
{
useLoginStore
}
from
'
@/store
'
;
...
...
continew-admin-ui/src/store/modules/login/index.ts
浏览文件 @
8b825578
import
{
defineStore
}
from
'
pinia
'
;
import
{
getImageCaptcha
as
getCaptcha
,
login
as
userLogin
,
logout
as
userLogout
,
getUserInfo
,
LoginReq
,
}
from
'
@/api/auth/login
'
;
import
{
getImageCaptcha
as
getCaptcha
}
from
'
@/api/common/captcha
'
;
import
{
setToken
,
clearToken
}
from
'
@/utils/auth
'
;
import
{
removeRouteListener
}
from
'
@/utils/route-listener
'
;
import
{
UserState
}
from
'
./types
'
;
...
...
continew-admin-ui/src/views/dashboard/monitor/components/studio.vue
浏览文件 @
8b825578
...
...
@@ -29,7 +29,7 @@
<
script
lang=
"ts"
setup
>
import
{
useLoginStore
}
from
'
@/store
'
;
import
getAvatar
from
"
@/utils/avatar
"
;
import
getAvatar
from
'
@/utils/avatar
'
;
const
userInfo
=
useLoginStore
();
</
script
>
...
...
continew-admin-ui/src/views/login/components/login-form.vue
浏览文件 @
8b825578
...
...
@@ -37,7 +37,7 @@
:placeholder=
"$t('login.form.placeholder.password')"
size=
"large"
allow-clear
max-length=
"
50
"
max-length=
"
32
"
>
<
template
#prefix
>
<icon-lock
/>
...
...
@@ -82,13 +82,13 @@
</template>
<
script
lang=
"ts"
setup
>
import
{
ref
,
reactive
,
computed
,
onMounted
}
from
"
vue
"
;
import
{
ref
,
reactive
,
computed
,
onMounted
}
from
'
vue
'
;
import
{
useRouter
}
from
'
vue-router
'
;
import
{
FieldRule
,
Message
}
from
"
@arco-design/web-vue
"
;
import
{
FieldRule
,
Message
}
from
'
@arco-design/web-vue
'
;
import
{
ValidatedError
}
from
'
@arco-design/web-vue/es/form/interface
'
;
import
{
useI18n
}
from
'
vue-i18n
'
;
// import debug from '@/utils/env';
import
{
encryptByRsa
}
from
"
@/utils/encrypt
"
;
import
{
encryptByRsa
}
from
'
@/utils/encrypt
'
;
import
{
useStorage
}
from
'
@vueuse/core
'
;
import
{
useLoginStore
}
from
'
@/store
'
;
import
useLoading
from
'
@/hooks/loading
'
;
...
...
continew-admin-ui/src/views/system/user/center/components/basic-info.vue
浏览文件 @
8b825578
...
...
@@ -53,14 +53,14 @@
</
template
>
<
script
lang=
"ts"
setup
>
import
{
ref
,
computed
}
from
"
vue
"
;
import
{
useI18n
}
from
"
vue-i18n
"
;
import
{
ref
,
computed
}
from
'
vue
'
;
import
{
useI18n
}
from
'
vue-i18n
'
;
import
{
useLoginStore
}
from
'
@/store
'
;
import
{
updateBasicInfo
}
from
'
@/api/system/user-center
'
;
import
useLoading
from
'
@/hooks/loading
'
;
import
{
FormInstance
}
from
'
@arco-design/web-vue/es/form
'
;
import
{
BasicInfoModel
}
from
'
@/api/system/user-center
'
;
import
{
FieldRule
,
Message
}
from
"
@arco-design/web-vue
"
;
import
{
FieldRule
,
Message
}
from
'
@arco-design/web-vue
'
;
const
{
t
}
=
useI18n
();
const
{
loading
,
setLoading
}
=
useLoading
();
...
...
@@ -84,8 +84,8 @@
// 保存
const
save
=
async
()
=>
{
const
errors
=
await
formRef
.
value
?.
validate
();
if
(
loading
.
value
)
return
;
const
errors
=
await
formRef
.
value
?.
validate
();
if
(
!
errors
)
{
setLoading
(
true
);
try
{
...
...
continew-admin-ui/src/views/system/user/center/components/security-settings/update-email.vue
浏览文件 @
8b825578
...
...
@@ -15,18 +15,206 @@
</a-typography-paragraph>
</div>
<div
class=
"operation"
>
<a-link>
<a-link
@
click=
"toUpdate"
>
{{
$t
(
'
userCenter.securitySettings.button.update
'
)
}}
</a-link>
</div>
</
template
>
</a-list-item-meta>
<a-modal
v-model:visible=
"visible"
:title=
"$t('userCenter.securitySettings.updateEmail.modal.title')"
:mask-closable=
"false"
@
cancel=
"handleCancel"
@
before-ok=
"handleUpdate"
>
<a-form
ref=
"formRef"
:model=
"formData"
:rules=
"rules"
>
<a-form-item
field=
"newEmail"
:validate-trigger=
"['change', 'blur']"
:label=
"$t('userCenter.securitySettings.updateEmail.form.label.newEmail')"
>
<a-input
v-model=
"formData.newEmail"
:placeholder=
"$t('userCenter.securitySettings.updateEmail.form.placeholder.newEmail')"
size=
"large"
allow-clear
>
</a-input>
</a-form-item>
<a-form-item
field=
"captcha"
:validate-trigger=
"['change', 'blur']"
:label=
"$t('userCenter.securitySettings.updateEmail.form.label.captcha')"
>
<a-input
v-model=
"formData.captcha"
:placeholder=
"$t('userCenter.securitySettings.updateEmail.form.placeholder.captcha')"
size=
"large"
style=
"width: 80%"
allow-clear
max-length=
"6"
>
</a-input>
<a-button
class=
"captcha-btn"
type=
"primary"
size=
"large"
:loading=
"captchaLoading"
:disabled=
"captchaDisable"
@
click=
"sendCaptcha"
>
{{ captchaBtnName }}
</a-button>
</a-form-item>
<a-form-item
field=
"currentPassword"
:validate-trigger=
"['change', 'blur']"
:label=
"$t('userCenter.securitySettings.updateEmail.form.label.currentPassword')"
>
<a-input-password
v-model=
"formData.currentPassword"
:placeholder=
"$t('userCenter.securitySettings.updateEmail.form.placeholder.currentPassword')"
size=
"large"
allow-clear
max-length=
"32"
>
</a-input-password>
</a-form-item>
</a-form>
</a-modal>
</template>
<
script
lang=
"ts"
setup
>
import
{
ref
,
reactive
,
computed
}
from
'
vue
'
;
import
{
useI18n
}
from
'
vue-i18n
'
;
import
{
useLoginStore
}
from
'
@/store
'
;
import
{
FormInstance
}
from
'
@arco-design/web-vue/es/form
'
;
import
useLoading
from
'
@/hooks/loading
'
;
import
{
FieldRule
,
Message
}
from
'
@arco-design/web-vue
'
;
import
{
getMailCaptcha
}
from
'
@/api/common/captcha
'
;
import
{
updateEmail
}
from
'
@/api/system/user-center
'
;
import
{
encryptByRsa
}
from
'
@/utils/encrypt
'
;
const
{
t
}
=
useI18n
();
const
{
loading
,
setLoading
}
=
useLoading
();
const
loginStore
=
useLoginStore
();
const
visible
=
ref
(
false
);
const
captchaBtnNameKey
=
ref
(
'
userCenter.securitySettings.updateEmail.form.sendCaptcha
'
);
const
captchaBtnName
=
computed
(()
=>
t
(
captchaBtnNameKey
.
value
));
const
captchaLoading
=
ref
(
false
);
const
captchaDisable
=
ref
(
false
);
const
captchaTime
=
ref
(
60
);
const
captchaTimer
=
ref
();
const
formRef
=
ref
<
FormInstance
>
();
const
formData
=
reactive
({
newEmail
:
''
,
captcha
:
''
,
currentPassword
:
''
,
});
const
rules
=
computed
(():
Record
<
string
,
FieldRule
[]
>
=>
{
return
{
newEmail
:
[
{
required
:
true
,
message
:
t
(
'
userCenter.securitySettings.updateEmail.form.error.required.newEmail
'
)
},
{
type
:
'
email
'
,
message
:
t
(
'
userCenter.securitySettings.updateEmail.form.error.match.newEmail
'
)
},
{
validator
:
(
value
,
callback
)
=>
{
if
(
value
===
loginStore
.
email
)
{
callback
(
t
(
'
userCenter.securitySettings.updateEmail.form.error.validator.newEmail
'
))
}
else
{
callback
()
}
}
}
],
captcha
:
[
{
required
:
true
,
message
:
t
(
'
userCenter.securitySettings.updateEmail.form.error.required.captcha
'
)
}
],
currentPassword
:
[
{
required
:
true
,
message
:
t
(
'
userCenter.securitySettings.updateEmail.form.error.required.currentPassword
'
)
}
]
}
});
// 重置验证码相关
const
resetCaptcha
=
()
=>
{
window
.
clearInterval
(
captchaTimer
.
value
);
captchaTime
.
value
=
60
;
captchaBtnNameKey
.
value
=
'
userCenter.securitySettings.updateEmail.form.sendCaptcha
'
;
captchaDisable
.
value
=
false
;
}
// 发送验证码
const
sendCaptcha
=
async
()
=>
{
if
(
captchaLoading
.
value
)
return
;
const
errors
=
await
formRef
.
value
?.
validateField
(
'
newEmail
'
);
if
(
errors
)
return
;
captchaLoading
.
value
=
true
;
captchaBtnNameKey
.
value
=
'
userCenter.securitySettings.updateEmail.form.loading.sendCaptcha
'
;
try
{
const
res
=
await
getMailCaptcha
({
email
:
formData
.
newEmail
});
if
(
res
.
success
)
{
captchaLoading
.
value
=
false
;
captchaDisable
.
value
=
true
;
captchaBtnNameKey
.
value
=
`
${
t
(
'
userCenter.securitySettings.updateEmail.form.reSendCaptcha
'
)}
(
${
captchaTime
.
value
-=
1
}
s)`
;
Message
.
success
(
res
.
msg
);
captchaTimer
.
value
=
window
.
setInterval
(
function
()
{
captchaTime
.
value
-=
1
;
captchaBtnNameKey
.
value
=
`
${
t
(
'
userCenter.securitySettings.updateEmail.form.reSendCaptcha
'
)}
(
${
captchaTime
.
value
}
s)`
;
if
(
captchaTime
.
value
<
0
)
{
window
.
clearInterval
(
captchaTimer
.
value
);
captchaTime
.
value
=
60
;
captchaBtnNameKey
.
value
=
t
(
'
userCenter.securitySettings.updateEmail.form.reSendCaptcha
'
);
captchaDisable
.
value
=
false
;
}
},
1000
)
}
}
catch
(
err
)
{
resetCaptcha
();
captchaLoading
.
value
=
false
;
console
.
log
((
err
as
Error
));
}
};
// 确定修改
const
handleUpdate
=
async
()
=>
{
if
(
loading
.
value
)
return
false
;
const
errors
=
await
formRef
.
value
?.
validate
();
if
(
errors
)
return
false
;
setLoading
(
true
);
try
{
const
res
=
await
updateEmail
({
newEmail
:
formData
.
newEmail
,
captcha
:
formData
.
captcha
,
currentPassword
:
encryptByRsa
(
formData
.
currentPassword
)
||
''
,
});
await
loginStore
.
getInfo
();
if
(
res
.
success
)
Message
.
success
(
res
.
msg
);
}
finally
{
setLoading
(
false
);
}
return
true
;
};
// 取消修改
const
handleCancel
=
()
=>
{
visible
.
value
=
false
;
formRef
.
value
?.
resetFields
();
resetCaptcha
();
};
// 打开修改窗口
const
toUpdate
=
()
=>
{
visible
.
value
=
true
;
};
</
script
>
<
style
scoped
lang=
"less"
></
style
>
<
style
scoped
lang=
"less"
>
.captcha-btn {
margin-left: 5px;
}
</
style
>
continew-admin-ui/src/views/system/user/center/components/security-settings/update-pwd.vue
浏览文件 @
8b825578
...
...
@@ -22,12 +22,14 @@
</
template
>
</a-list-item-meta>
<a-modal
v-model:visible=
"visible"
:title=
"$t('userCenter.securitySettings.updatePwd.modal.title')"
@
cancel=
"handleCancel"
@
before-ok=
"handleUpdate"
>
<a-form
ref=
"formRef"
:model=
"formData"
:rules=
"rules"
>
<a-modal
v-model:visible=
"visible"
:title=
"$t('userCenter.securitySettings.updatePwd.modal.title')"
:mask-closable=
"false"
@
cancel=
"handleCancel"
@
before-ok=
"handleUpdate"
>
<a-form
ref=
"formRef"
:model=
"formData"
:rules=
"rules"
>
<a-form-item
field=
"oldPassword"
:validate-trigger=
"['change', 'blur']"
...
...
@@ -38,7 +40,7 @@
:placeholder=
"$t('userCenter.securitySettings.updatePwd.form.placeholder.oldPassword')"
size=
"large"
allow-clear
max-length=
"
50
"
max-length=
"
32
"
>
</a-input-password>
</a-form-item>
...
...
@@ -52,7 +54,7 @@
:placeholder=
"$t('userCenter.securitySettings.updatePwd.form.placeholder.newPassword')"
size=
"large"
allow-clear
max-length=
"
50
"
max-length=
"
32
"
>
</a-input-password>
</a-form-item>
...
...
@@ -66,7 +68,7 @@
:placeholder=
"$t('userCenter.securitySettings.updatePwd.form.placeholder.rePassword')"
size=
"large"
allow-clear
max-length=
"
50
"
max-length=
"
32
"
>
</a-input-password>
</a-form-item>
...
...
@@ -75,14 +77,14 @@
</template>
<
script
lang=
"ts"
setup
>
import
{
ref
,
reactive
,
computed
}
from
"
vue
"
;
import
{
useI18n
}
from
"
vue-i18n
"
;
import
{
ref
,
reactive
,
computed
}
from
'
vue
'
;
import
{
useI18n
}
from
'
vue-i18n
'
;
import
{
useLoginStore
}
from
'
@/store
'
;
import
{
FormInstance
}
from
"
@arco-design/web-vue/es/form
"
;
import
useLoading
from
"
@/hooks/loading
"
;
import
{
FieldRule
,
Message
}
from
"
@arco-design/web-vue
"
;
import
{
updatePassword
}
from
"
@/api/system/user-center
"
;
import
{
encryptByRsa
}
from
"
@/utils/encrypt
"
;
import
{
FormInstance
}
from
'
@arco-design/web-vue/es/form
'
;
import
useLoading
from
'
@/hooks/loading
'
;
import
{
FieldRule
,
Message
}
from
'
@arco-design/web-vue
'
;
import
{
updatePassword
}
from
'
@/api/system/user-center
'
;
import
{
encryptByRsa
}
from
'
@/utils/encrypt
'
;
const
{
t
}
=
useI18n
();
const
{
loading
,
setLoading
}
=
useLoading
();
...
...
@@ -129,8 +131,8 @@
// 确定修改
const
handleUpdate
=
async
()
=>
{
const
errors
=
await
formRef
.
value
?.
validate
();
if
(
loading
.
value
)
return
false
;
const
errors
=
await
formRef
.
value
?.
validate
();
if
(
errors
)
return
false
;
setLoading
(
true
);
try
{
...
...
continew-admin-ui/src/views/system/user/center/components/user-panel.vue
浏览文件 @
8b825578
...
...
@@ -61,8 +61,8 @@
}
from
'
@arco-design/web-vue/es/upload/interfaces
'
;
import
{
useLoginStore
}
from
'
@/store
'
;
import
{
uploadAvatar
}
from
'
@/api/system/user-center
'
;
import
getAvatar
from
"
@/utils/avatar
"
;
import
{
Message
}
from
"
@arco-design/web-vue
"
;
import
getAvatar
from
'
@/utils/avatar
'
;
import
{
Message
}
from
'
@arco-design/web-vue
'
;
const
loginStore
=
useLoginStore
();
const
avatar
=
{
...
...
continew-admin-ui/src/views/system/user/center/locale/en-US.ts
浏览文件 @
8b825578
...
...
@@ -63,5 +63,23 @@ export default {
'
userCenter.securitySettings.updateEmail.placeholder.error.email
'
:
'
You have not set a mailbox yet. The mailbox binding can be used to retrieve passwords and receive notifications.
'
,
'
userCenter.securitySettings.updateEmail.modal.title
'
:
'
Update email
'
,
'
userCenter.securitySettings.updateEmail.form.label.newEmail
'
:
'
New email
'
,
'
userCenter.securitySettings.updateEmail.form.label.captcha
'
:
'
Captcha
'
,
'
userCenter.securitySettings.updateEmail.form.label.currentPassword
'
:
'
Current password
'
,
'
userCenter.securitySettings.updateEmail.form.sendCaptcha
'
:
'
Send captcha
'
,
'
userCenter.securitySettings.updateEmail.form.reSendCaptcha
'
:
'
Resend captcha
'
,
'
userCenter.securitySettings.updateEmail.form.loading.sendCaptcha
'
:
'
Sending...
'
,
'
userCenter.securitySettings.updateEmail.form.placeholder.newEmail
'
:
'
Please enter new email
'
,
'
userCenter.securitySettings.updateEmail.form.placeholder.captcha
'
:
'
Please enter email captcha
'
,
'
userCenter.securitySettings.updateEmail.form.placeholder.currentPassword
'
:
'
Please enter current password
'
,
'
userCenter.securitySettings.updateEmail.form.error.required.newEmail
'
:
'
Please enter new email
'
,
'
userCenter.securitySettings.updateEmail.form.error.match.newEmail
'
:
'
Please enter the correct email
'
,
'
userCenter.securitySettings.updateEmail.form.error.validator.newEmail
'
:
'
New email cannot be the same as the old email
'
,
'
userCenter.securitySettings.updateEmail.form.error.required.captcha
'
:
'
Please enter email captcha
'
,
'
userCenter.securitySettings.updateEmail.form.error.required.currentPassword
'
:
'
Please enter current password
'
,
'
userCenter.securitySettings.button.update
'
:
'
Update
'
,
};
continew-admin-ui/src/views/system/user/center/locale/zh-CN.ts
浏览文件 @
8b825578
...
...
@@ -63,5 +63,23 @@ export default {
'
userCenter.securitySettings.updateEmail.placeholder.error.email
'
:
'
您暂未设置邮箱,绑定邮箱可以用来找回密码、接收通知等。
'
,
'
userCenter.securitySettings.updateEmail.modal.title
'
:
'
修改邮箱
'
,
'
userCenter.securitySettings.updateEmail.form.label.newEmail
'
:
'
新邮箱
'
,
'
userCenter.securitySettings.updateEmail.form.label.captcha
'
:
'
验证码
'
,
'
userCenter.securitySettings.updateEmail.form.label.currentPassword
'
:
'
当前密码
'
,
'
userCenter.securitySettings.updateEmail.form.sendCaptcha
'
:
'
发送验证码
'
,
'
userCenter.securitySettings.updateEmail.form.reSendCaptcha
'
:
'
重新发送
'
,
'
userCenter.securitySettings.updateEmail.form.loading.sendCaptcha
'
:
'
发送中...
'
,
'
userCenter.securitySettings.updateEmail.form.placeholder.newEmail
'
:
'
请输入新邮箱
'
,
'
userCenter.securitySettings.updateEmail.form.placeholder.captcha
'
:
'
请输入邮箱验证码
'
,
'
userCenter.securitySettings.updateEmail.form.placeholder.currentPassword
'
:
'
请输入当前密码
'
,
'
userCenter.securitySettings.updateEmail.form.error.required.newEmail
'
:
'
请输入新邮箱
'
,
'
userCenter.securitySettings.updateEmail.form.error.match.newEmail
'
:
'
请输入正确的邮箱
'
,
'
userCenter.securitySettings.updateEmail.form.error.validator.newEmail
'
:
'
新邮箱不能与当前邮箱相同
'
,
'
userCenter.securitySettings.updateEmail.form.error.required.captcha
'
:
'
请输入邮箱验证码
'
,
'
userCenter.securitySettings.updateEmail.form.error.required.currentPassword
'
:
'
请输入当前密码
'
,
'
userCenter.securitySettings.button.update
'
:
'
修改
'
,
};
continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/auth/LoginController.java
浏览文件 @
8b825578
...
...
@@ -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
.
exIf
Condition
(()
->
!
captcha
.
equalsIgnoreCase
(
loginRequest
.
getCaptcha
())
,
"验证码错误"
);
ValidationUtils
.
exIf
NotEqualIgnoreCase
(
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
();
}
...
...
continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/
auth
/CaptchaController.java
→
continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/
common
/CaptchaController.java
浏览文件 @
8b825578
...
...
@@ -14,16 +14,21 @@
* limitations under the License.
*/
package
top.charles7c.cnadmin.webapi.controller.
auth
;
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
;
...
...
@@ -31,12 +36,18 @@ 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.auth.config.properties.CaptchaProperties
;
import
top.charles7c.cnadmin.auth.model.vo.CaptchaVO
;
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.RedisUtils
;
import
top.charles7c.cnadmin.common.util.*
;
import
top.charles7c.cnadmin.common.util.validate.ValidationUtils
;
/**
* 验证码 API
...
...
@@ -46,27 +57,60 @@ import top.charles7c.cnadmin.common.util.RedisUtils;
*/
@Tag
(
name
=
"验证码 API"
)
@SaIgnore
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping
(
value
=
"/captcha"
,
produces
=
MediaType
.
APPLICATION_JSON_VALUE
)
@RequestMapping
(
value
=
"/c
ommon/c
aptcha"
,
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
()
{
// 生成验证码
Captcha
captcha
=
captchaProperties
.
getCaptcha
();
CaptchaProperties
.
CaptchaImage
captchaImage
=
captchaProperties
.
getImage
();
Captcha
captcha
=
captchaImage
.
getCaptcha
();
// 保存验证码
String
uuid
=
IdUtil
.
fastSimpleUUID
();
String
captchaKey
=
RedisUtils
.
formatKey
(
captchaProperties
.
getKeyPrefix
()
,
uuid
);
String
captchaKey
=
RedisUtils
.
formatKey
(
CacheConstants
.
CAPTCHA_CACHE_KEY
,
uuid
);
RedisUtils
.
setCacheObject
(
captchaKey
,
captcha
.
text
(),
Duration
.
ofMinutes
(
captcha
Properties
.
getExpirationInMinutes
()));
Duration
.
ofMinutes
(
captcha
Image
.
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
));
}
}
continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/system/UserCenterController.java
浏览文件 @
8b825578
...
...
@@ -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
(
"修改成功"
);
}
}
continew-admin-webapi/src/main/resources/application-dev.yml
浏览文件 @
8b825578
...
...
@@ -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
:
...
...
continew-admin-webapi/src/main/resources/application-prod.yml
浏览文件 @
8b825578
...
...
@@ -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
:
...
...
continew-admin-webapi/src/main/resources/application.yml
浏览文件 @
8b825578
---
### 项目配置
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
:
...
...
continew-admin-webapi/src/main/resources/templates/mail/captcha.ftl
0 → 100644
浏览文件 @
8b825578
<!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>
\ No newline at end of file
pom.xml
浏览文件 @
8b825578
...
...
@@ -25,7 +25,7 @@ limitations under the License.
<packaging>
pom
</packaging>
<name>
${project.artifactId}
</name>
<description>
ContiNew
-Admin (incubating) 中后台管理框架
,Continue New Admin,持续以最新流行技术栈构建。
</description>
<description>
ContiNew
Admin 中后台管理框架(孵化中)
,Continue New Admin,持续以最新流行技术栈构建。
</description>
<url>
https://github.com/Charles7c/continew-admin
</url>
<modules>
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录