8.2 登录注册界面+后端修改,邮箱注册、添加验证码逻辑
This commit is contained in:
parent
b9501daf0a
commit
2c8fc752ca
33
Dockerfile
33
Dockerfile
@ -1,24 +1,39 @@
|
||||
# —— builder 阶段 ——
|
||||
# ---------- builder ----------
|
||||
FROM maven:3.8.7-eclipse-temurin-17-alpine AS builder
|
||||
WORKDIR /workspace
|
||||
|
||||
# ① 如没有私服,可删掉这行;否则保留
|
||||
# 如没有私服,可删掉
|
||||
COPY .mvn/settings.xml /root/.m2/settings.xml
|
||||
|
||||
# ② 先拷 POM 并预拉依赖,提升缓存命中率
|
||||
# 先拉依赖
|
||||
COPY pom.xml .
|
||||
RUN mvn -B dependency:go-offline
|
||||
|
||||
# ③ 再拷源码并真正打包,显式执行 spring-boot:repackage
|
||||
# 打包
|
||||
COPY src src
|
||||
RUN mvn -B clean package spring-boot:repackage -DskipTests
|
||||
|
||||
# —— runtime 阶段 ——
|
||||
FROM openjdk:17-jdk-slim
|
||||
ENV TZ=Asia/Shanghai
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
# 指向你在 pom.xml 里定义的 finalName
|
||||
# ---------- runtime ----------
|
||||
# 继续用 slim,体积小;手动装字体
|
||||
FROM openjdk:17-jdk-slim AS runtime
|
||||
ENV TZ=Asia/Shanghai \
|
||||
LANG=C.UTF-8 \
|
||||
LANGUAGE=C.UTF-8
|
||||
|
||||
# 换国内源,解决 timeout
|
||||
RUN sed -i 's@deb.debian.org@mirrors.tuna.tsinghua.edu.cn@g' /etc/apt/sources.list
|
||||
|
||||
# 安装 fontconfig + 基础西文 + CJK 字体
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
fontconfig fonts-dejavu-core fonts-noto-cjk; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 设置时区
|
||||
RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone
|
||||
|
||||
ARG JAR_FILE=smile-picture-backend.jar
|
||||
COPY --from=builder /workspace/target/${JAR_FILE} /app.jar
|
||||
|
||||
|
@ -14,6 +14,7 @@ create table if not exists user
|
||||
user_avatar varchar(1024) null comment '用户头像',
|
||||
user_profile varchar(512) null comment '用户简介',
|
||||
user_role varchar(256) default 'user' not null comment '用户角色:user/admin',
|
||||
user_email varchar(256) not null COMMENT '用户邮箱',
|
||||
edit_time datetime default CURRENT_TIMESTAMP not null comment '编辑时间',
|
||||
create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
|
||||
update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
|
||||
|
@ -1 +1 @@
|
||||
import{_ as o,c as s,a as t,o as a}from"./index-CrUyCRGJ.js";const n={},c={class:"about"};function r(_,e){return a(),s("div",c,e[0]||(e[0]=[t("h1",null,"This is an about page",-1)]))}const l=o(n,[["render",r]]);export{l as default};
|
||||
import{_ as o,c as s,a as t,o as a}from"./index-D10z2Ner.js";const n={},c={class:"about"};function r(_,e){return a(),s("div",c,e[0]||(e[0]=[t("h1",null,"This is an about page",-1)]))}const l=o(n,[["render",r]]);export{l as default};
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -6,8 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Smile云图库</title>
|
||||
<meta name="description" content="Smile云图库,海量图片素材免费获取">
|
||||
<script type="module" crossorigin src="/assets/index-CrUyCRGJ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Cidcg2WD.css">
|
||||
<script type="module" crossorigin src="/assets/index-D10z2Ner.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DlRSOPH_.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
11
pom.xml
11
pom.xml
@ -132,6 +132,17 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- 邮箱服务 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
</dependency>
|
||||
<!-- 图形验证码: https://github.com/ele-admin/EasyCaptcha -->
|
||||
<dependency>
|
||||
<groupId>com.github.whvcse</groupId>
|
||||
<artifactId>easy-captcha</artifactId>
|
||||
<version>1.6.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
|
@ -0,0 +1,144 @@
|
||||
package edu.whut.smilepicturebackend.common;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 邮箱工具类
|
||||
*/
|
||||
public class EmailUtils {
|
||||
public static void main(String[] args) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("code", 1111);
|
||||
System.out.println(emailContentTemplate("templates/EmailCodeTemplate.html", "BOOT_", "_END", map));
|
||||
System.out.println(emailContentTemplate("templates/EmailCodeTemplate.html", map));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送静态模板邮箱
|
||||
*
|
||||
* @param path 模板路径,模板需要放在resource下。
|
||||
* @return 内容
|
||||
*/
|
||||
public static String emailContentTemplate(String path) {
|
||||
// 读取邮件模板,该模板放在 templates/ 目录下,模板名为:EmailTemplate.html
|
||||
Resource resource = new ClassPathResource(path);
|
||||
InputStream inputStream = null;
|
||||
BufferedReader fileReader = null;
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
String line = "";
|
||||
try {
|
||||
inputStream = resource.getInputStream();
|
||||
fileReader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
while ((line = fileReader.readLine()) != null) {
|
||||
buffer.append(line);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("邮件模板读取失败", e);
|
||||
} finally {
|
||||
try {
|
||||
if (fileReader != null) {
|
||||
fileReader.close();
|
||||
}
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
// 替换html模板中的参数
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送动态模板邮箱
|
||||
*
|
||||
* @param path 模板路径,模板需要放在resource下。
|
||||
* @param paramMap 参数列表,按照Map中的Key进行替换。
|
||||
* @return 内容
|
||||
*/
|
||||
public static String emailContentTemplate(String path, Map<String, Object> paramMap) {
|
||||
return emailContentTemplate(path, "BOOT_", "_END", paramMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送动态模板邮箱
|
||||
*
|
||||
* @param path 模板路径,模板需要放在resource下。
|
||||
* @param prefix 前缀,需要替换的占位符。
|
||||
* @param suffix 后缀,需要替换的占位符。
|
||||
* @param paramMap 参数列表,按照Map中的Key进行替换。
|
||||
* @return 内容
|
||||
*/
|
||||
public static String emailContentTemplate(String path, String prefix, String suffix, Map<String, Object> paramMap) {
|
||||
String str = emailContentTemplate(path);
|
||||
List<String> targetList = getTargetString(str, prefix, suffix);
|
||||
if (targetList != null) {
|
||||
for (String tl : targetList) {
|
||||
Object o = paramMap.get(tl);
|
||||
if (o != null) {
|
||||
String s = prefix + tl + suffix;
|
||||
str = str.replaceAll(s, o.toString());
|
||||
} else {
|
||||
throw new RuntimeException("邮箱模板中不存在占位字符!");
|
||||
}
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取占位符字符
|
||||
*
|
||||
* @param str 原字符串
|
||||
* @param startStr 开始字符串
|
||||
* @param endStr 结束字符串
|
||||
* @return String[]
|
||||
*/
|
||||
private static List<String> getTargetString(String str, String startStr, String endStr) {
|
||||
// 获取头占位符
|
||||
List<Integer> startStrIndex = getTargetIndex(str, 0, startStr);
|
||||
// 获取尾占位符
|
||||
List<Integer> endStrIndex = getTargetIndex(str, 0, endStr);
|
||||
if (!startStrIndex.isEmpty() && !endStrIndex.isEmpty() && startStrIndex.size() != endStrIndex.size()) {
|
||||
return null;
|
||||
}
|
||||
List<String> strList = new ArrayList<>();
|
||||
for (int i = 0, num = startStrIndex.size(); i < num; i++) {
|
||||
strList.add(str.substring((startStrIndex.get(i) + startStr.length()), (endStrIndex.get(i))));
|
||||
}
|
||||
return strList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取占位字符的下标
|
||||
*
|
||||
* @param string 字符串
|
||||
* @param index 下标
|
||||
* @param findStr 指定字符串
|
||||
* @return List<Integer>
|
||||
*/
|
||||
private static List<Integer> getTargetIndex(String string, int index, String findStr) {
|
||||
List<Integer> list = new ArrayList<>();
|
||||
if (index != -1) {
|
||||
int num = string.indexOf(findStr, index);
|
||||
if (num == -1) {
|
||||
return list;
|
||||
}
|
||||
list.add(num);
|
||||
// 递归进行查找
|
||||
list.addAll(getTargetIndex(string, string.indexOf(findStr, num + 1), findStr));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
@ -6,6 +6,15 @@ import edu.whut.smilepicturebackend.exception.ErrorCode;
|
||||
*/
|
||||
public class ResultUtils {
|
||||
|
||||
/**
|
||||
* 成功
|
||||
*
|
||||
* @return 响应
|
||||
*/
|
||||
public static BaseResponse<Boolean> success() {
|
||||
return new BaseResponse<>(0, true, "ok");
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功
|
||||
*
|
||||
|
@ -0,0 +1,68 @@
|
||||
package edu.whut.smilepicturebackend.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* 线程池配置类
|
||||
*/
|
||||
@Configuration
|
||||
// @EnableAsync // 开启异步任务, 启动类已开启
|
||||
public class ThreadPoolConfig {
|
||||
/**
|
||||
* 邮件发送线程池
|
||||
*
|
||||
* @return 线程池任务执行器
|
||||
*/
|
||||
@Bean(name = "emailThreadPool")
|
||||
public ThreadPoolTaskExecutor emailThreadPool() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
// 设置核心线程数,它是可以同时被执行的线程数量
|
||||
executor.setCorePoolSize(2);
|
||||
// 设置最大线程数,缓冲队列满了之后会申请超过核心线程数的线程
|
||||
executor.setMaxPoolSize(10);
|
||||
// 设置缓冲队列容量,在执行任务之前用于保存任务
|
||||
executor.setQueueCapacity(50);
|
||||
// 设置线程生存时间(秒),当超过了核心线程出之外的线程在生存时间到达之后会被销毁
|
||||
executor.setKeepAliveSeconds(60);
|
||||
// 设置线程名称前缀
|
||||
executor.setThreadNamePrefix("emailPool-");
|
||||
// 设置拒绝策略
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
// 等待所有任务结束后再关闭线程池
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
// 初始化
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息发送线程池
|
||||
*
|
||||
* @return 线程池任务执行器
|
||||
*/
|
||||
@Bean(name = "messageThreadPool")
|
||||
public ThreadPoolTaskExecutor messageThreadPool() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
// 设置核心线程数,它是可以同时被执行的线程数量
|
||||
executor.setCorePoolSize(2);
|
||||
// 设置最大线程数,缓冲队列满了之后会申请超过核心线程数的线程
|
||||
executor.setMaxPoolSize(10);
|
||||
// 设置缓冲队列容量,在执行任务之前用于保存任务
|
||||
executor.setQueueCapacity(50);
|
||||
// 设置线程生存时间(秒),当超过了核心线程出之外的线程在生存时间到达之后会被销毁
|
||||
executor.setKeepAliveSeconds(60);
|
||||
// 设置线程名称前缀
|
||||
executor.setThreadNamePrefix("messagePool-");
|
||||
// 设置拒绝策略
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
// 等待所有任务结束后再关闭线程池
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
// 初始化
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package edu.whut.smilepicturebackend.constant;
|
||||
|
||||
public interface CacheConstant {
|
||||
/**
|
||||
* 邮箱验证码缓存 KEY 前缀
|
||||
*/
|
||||
String EMAIL_CODE_KEY = "EMAIL_CODE_KEY:%s:%s";
|
||||
|
||||
/**
|
||||
* 图形验证码缓存 KEY 前缀
|
||||
*/
|
||||
String CAPTCHA_CODE_KEY = "CAPTCHA_CODE_KEY:%s";
|
||||
}
|
@ -1,14 +1,19 @@
|
||||
package edu.whut.smilepicturebackend.controller;
|
||||
import edu.whut.smilepicturebackend.common.BaseResponse;
|
||||
import edu.whut.smilepicturebackend.common.ResultUtils;
|
||||
import edu.whut.smilepicturebackend.model.vo.CaptchaVO;
|
||||
import edu.whut.smilepicturebackend.service.MainApplicationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/")
|
||||
@RequiredArgsConstructor
|
||||
public class MainController {
|
||||
|
||||
private final MainApplicationService mainApplicationService;
|
||||
/**
|
||||
* 健康检查
|
||||
*/
|
||||
@ -23,4 +28,12 @@ public class MainController {
|
||||
// }).start();
|
||||
return ResultUtils.success("ok");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图形验证码
|
||||
*/
|
||||
@GetMapping("/captcha")
|
||||
public BaseResponse<CaptchaVO> captcha() {
|
||||
return ResultUtils.success(mainApplicationService.getCaptcha());
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package edu.whut.smilepicturebackend.controller;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.RegexPool;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import edu.whut.smilepicturebackend.annotation.AuthCheck;
|
||||
import edu.whut.smilepicturebackend.common.BaseResponse;
|
||||
@ -27,19 +29,47 @@ import java.util.List;
|
||||
@RequestMapping("/user")
|
||||
@RequiredArgsConstructor
|
||||
public class UserController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
public BaseResponse<Long> userRegister(@RequestBody UserRegisterRequest userRegisterRequest) {
|
||||
public BaseResponse<Boolean> userRegister(@RequestBody UserRegisterRequest userRegisterRequest) {
|
||||
ThrowUtils.throwIf(userRegisterRequest == null, ErrorCode.PARAMS_ERROR);
|
||||
String userAccount = userRegisterRequest.getUserAccount();
|
||||
String userPassword = userRegisterRequest.getUserPassword();
|
||||
String checkPassword = userRegisterRequest.getCheckPassword();
|
||||
long result = userService.userRegister(userAccount, userPassword, checkPassword);
|
||||
return ResultUtils.success(result);
|
||||
String userEmail = userRegisterRequest.getUserEmail();
|
||||
String codeKey = userRegisterRequest.getCodeKey();
|
||||
String codeValue = userRegisterRequest.getCodeValue();
|
||||
if (StrUtil.hasBlank(userEmail, codeKey, codeValue)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
|
||||
}
|
||||
// 校验邮箱格式
|
||||
if (StrUtil.isEmpty(userEmail) || !ReUtil.isMatch(RegexPool.EMAIL, userEmail)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "邮箱格式错误");
|
||||
}
|
||||
userService.userRegister(userEmail, codeKey, codeValue);
|
||||
return ResultUtils.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮箱验证码
|
||||
*
|
||||
* @param userRegisterRequest 用户注册请求
|
||||
* @return 验证码 key
|
||||
*/
|
||||
@PostMapping("/send/email/code")
|
||||
public BaseResponse<String> sendEmailCode(@RequestBody UserRegisterRequest userRegisterRequest) {
|
||||
ThrowUtils.throwIf(userRegisterRequest == null, ErrorCode.PARAMS_ERROR);
|
||||
String userEmail = userRegisterRequest.getUserEmail();
|
||||
if (StrUtil.isEmpty(userEmail)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "邮箱不能为空");
|
||||
}
|
||||
// 校验邮箱格式
|
||||
if (StrUtil.isEmpty(userEmail) || !ReUtil.isMatch(RegexPool.EMAIL, userEmail)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "邮箱格式错误");
|
||||
}
|
||||
return ResultUtils.success(userService.sendEmailCode(userEmail));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,7 +81,15 @@ public class UserController {
|
||||
ThrowUtils.throwIf(userLoginRequest == null, ErrorCode.PARAMS_ERROR);
|
||||
String userAccount = userLoginRequest.getUserAccount();
|
||||
String userPassword = userLoginRequest.getUserPassword();
|
||||
LoginUserVO loginUserVO = userService.userLogin(userAccount, userPassword, request);
|
||||
String captchaKey = userLoginRequest.getCaptchaKey();
|
||||
String captchaCode = userLoginRequest.getCaptchaCode();
|
||||
if (StrUtil.hasBlank(userAccount, userPassword, captchaKey, captchaCode)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
|
||||
}
|
||||
if (userAccount.length() < 4 || userPassword.length() < 8) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号或密码输入错误");
|
||||
}
|
||||
LoginUserVO loginUserVO = userService.userLogin(userAccount, userPassword, captchaKey, captchaCode,request);
|
||||
return ResultUtils.success(loginUserVO);
|
||||
}
|
||||
|
||||
@ -74,18 +112,19 @@ public class UserController {
|
||||
return ResultUtils.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建用户
|
||||
*/
|
||||
@PostMapping("/add")
|
||||
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
|
||||
public BaseResponse<Long> createUser(@RequestBody UserAddRequest userAddRequest) {
|
||||
// 参数非空校验
|
||||
ThrowUtils.throwIf(userAddRequest == null, ErrorCode.PARAMS_ERROR);
|
||||
// 调用 Service 完成业务逻辑
|
||||
long userId = userService.createUser(userAddRequest);
|
||||
return ResultUtils.success(userId);
|
||||
}
|
||||
// /**
|
||||
// * 创建用户
|
||||
// */
|
||||
// @PostMapping("/add")
|
||||
// @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
|
||||
// public BaseResponse<Boolean> createUser(@RequestBody UserAddRequest userAddRequest) {
|
||||
// // 参数非空校验
|
||||
// ThrowUtils.throwIf(userAddRequest == null, ErrorCode.PARAMS_ERROR);
|
||||
//
|
||||
// // 调用 Service 完成业务逻辑
|
||||
// boolean result = userService.createUser(userAddRequest);
|
||||
// return ResultUtils.success(result);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 根据 id 获取用户(仅管理员)
|
||||
|
@ -12,6 +12,7 @@ public enum ErrorCode {
|
||||
FORBIDDEN_ERROR(40300, "禁止访问"),
|
||||
NOT_FOUND_ERROR(40400, "请求数据不存在"),
|
||||
SYSTEM_ERROR(50000, "系统内部异常"),
|
||||
DATA_ERROR(404, "数据错误"),
|
||||
OPERATION_ERROR(50001, "操作失败");
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,151 @@
|
||||
package edu.whut.smilepicturebackend.manager.email;
|
||||
|
||||
import edu.whut.smilepicturebackend.common.EmailUtils;
|
||||
import edu.whut.smilepicturebackend.exception.BusinessException;
|
||||
import edu.whut.smilepicturebackend.exception.ErrorCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 邮箱服务
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class EmailManager {
|
||||
|
||||
private final JavaMailSender javaMailSender;
|
||||
|
||||
@Value("${spring.mail.nickname}")
|
||||
private String nickname;
|
||||
|
||||
@Value("${spring.mail.username}")
|
||||
private String from;
|
||||
|
||||
@Async(value = "emailThreadPool")
|
||||
public void sendEmailCode(String to, String subject, String code) {
|
||||
log.info("发送邮件验证码[{}]到[{}]", to, code);
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("code", code);
|
||||
sendEmailAsCode(to, subject, map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送验证码邮件
|
||||
*/
|
||||
public void sendEmailAsCode(String to, String subject, Map<String, Object> contentMap) {
|
||||
try {
|
||||
MimeMessage message = javaMailSender.createMimeMessage();
|
||||
// 组合邮箱发送的内容
|
||||
MimeMessageHelper messageHelper = new MimeMessageHelper(message, true);
|
||||
// 设置邮件发送者
|
||||
messageHelper.setFrom(nickname + "<" + from + ">");
|
||||
// 设置邮件接收者
|
||||
messageHelper.setTo(to);
|
||||
// 设置邮件标题
|
||||
messageHelper.setSubject(subject);
|
||||
// 设置邮件内容
|
||||
messageHelper.setText(EmailUtils.emailContentTemplate("templates/EmailCodeTemplate.html", contentMap), true);
|
||||
javaMailSender.send(message);
|
||||
} catch (MessagingException e) {
|
||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "发送邮件失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送注册成功邮件
|
||||
*/
|
||||
@Async(value = "emailThreadPool")
|
||||
public void sendEmailAsRegisterSuccess(String to, String subject) {
|
||||
try {
|
||||
// 创建MIME消息
|
||||
MimeMessage message = javaMailSender.createMimeMessage();
|
||||
// 组合邮箱发送的内容
|
||||
MimeMessageHelper messageHelper = new MimeMessageHelper(message, true);
|
||||
// 设置邮件发送者
|
||||
messageHelper.setFrom(nickname + "<" + from + ">");
|
||||
// 设置邮件接收者
|
||||
messageHelper.setTo(to);
|
||||
// 设置邮件主题
|
||||
messageHelper.setSubject(subject);
|
||||
// 设置邮件内容
|
||||
messageHelper.setText(EmailUtils.emailContentTemplate("templates/EmailRegisterSuccessTemplate.html"), true);
|
||||
// 发送邮件
|
||||
javaMailSender.send(message);
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "发送邮件失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送文本邮件
|
||||
*/
|
||||
@Async(value = "emailThreadPool")
|
||||
public void sendEmailAsText(String title, String text) {
|
||||
try {
|
||||
// 创建邮件对象
|
||||
SimpleMailMessage smm = new SimpleMailMessage();
|
||||
// 设置邮件发送者
|
||||
smm.setFrom(nickname + "<" + from + ">");
|
||||
// 设置邮件接收者
|
||||
smm.setTo("646228430@qq.com");
|
||||
// 设置邮件主题
|
||||
smm.setSubject(title);
|
||||
// 设置邮件内容
|
||||
smm.setText(text);
|
||||
// 发送邮件
|
||||
javaMailSender.send(smm);
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "发送邮件失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送审核邮件
|
||||
*/
|
||||
@Async(value = "emailThreadPool")
|
||||
public void sendEmailAsReview(List<String> tos, String subject, String reviewStatus) {
|
||||
tos.forEach(to->{
|
||||
sendEmailAsReview(to, subject, reviewStatus);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送审核邮件
|
||||
*/
|
||||
public void sendEmailAsReview(String to, String subject, String reviewStatus) {
|
||||
try {
|
||||
// 创建MIME消息
|
||||
MimeMessage message = javaMailSender.createMimeMessage();
|
||||
// 组合邮箱发送的内容
|
||||
MimeMessageHelper messageHelper = new MimeMessageHelper(message, true);
|
||||
// 设置邮件发送者
|
||||
messageHelper.setFrom(nickname + "<" + from + ">");
|
||||
// 设置邮件接收者
|
||||
messageHelper.setTo(to);
|
||||
// 设置邮件主题
|
||||
messageHelper.setSubject(subject);
|
||||
// 设置邮件内容
|
||||
Map<String, Object> contentMap = new HashMap<>();
|
||||
contentMap.put("reviewStatus", reviewStatus);
|
||||
messageHelper.setText(EmailUtils.emailContentTemplate("templates/EmailReviewTemplate.html", contentMap), true);
|
||||
// 发送邮件
|
||||
javaMailSender.send(message);
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "发送邮件失败");
|
||||
}
|
||||
}
|
||||
}
|
@ -21,4 +21,20 @@ public class UserLoginRequest implements Serializable {
|
||||
* 密码
|
||||
*/
|
||||
private String userPassword;
|
||||
|
||||
/**
|
||||
* 用户邮箱
|
||||
*/
|
||||
private String userEmail;
|
||||
|
||||
/**
|
||||
* 图形验证码 key
|
||||
*/
|
||||
private String captchaKey;
|
||||
|
||||
/**
|
||||
* 图形验证码 验证码
|
||||
*/
|
||||
private String captchaCode;
|
||||
|
||||
}
|
||||
|
@ -17,13 +17,19 @@ public class UserRegisterRequest implements Serializable {
|
||||
private String userAccount;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
* 用户邮箱
|
||||
*/
|
||||
private String userPassword;
|
||||
private String userEmail;
|
||||
|
||||
/**
|
||||
* 确认密码
|
||||
* 验证码 key
|
||||
*/
|
||||
private String checkPassword;
|
||||
private String codeKey;
|
||||
|
||||
/**
|
||||
* 验证码 value
|
||||
*/
|
||||
private String codeValue;
|
||||
|
||||
|
||||
}
|
||||
|
@ -29,6 +29,11 @@ public class User implements Serializable {
|
||||
*/
|
||||
private String userPassword;
|
||||
|
||||
/**
|
||||
* 用户邮箱
|
||||
*/
|
||||
private String userEmail;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
|
@ -0,0 +1,24 @@
|
||||
package edu.whut.smilepicturebackend.model.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 图形验证码VO
|
||||
*/
|
||||
@Data
|
||||
public class CaptchaVO implements Serializable {
|
||||
|
||||
/**
|
||||
* 图形验证码 key
|
||||
*/
|
||||
private String captchaKey;
|
||||
|
||||
/**
|
||||
* 图形验证码 base64 图片
|
||||
*/
|
||||
private String captchaImage;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package edu.whut.smilepicturebackend.service;
|
||||
|
||||
import edu.whut.smilepicturebackend.model.vo.CaptchaVO;
|
||||
|
||||
/**
|
||||
* 公共应用服务接口
|
||||
*/
|
||||
public interface MainApplicationService {
|
||||
/**
|
||||
* 获取图形验证码
|
||||
*
|
||||
* @return 图形验证码VO
|
||||
*/
|
||||
CaptchaVO getCaptcha();
|
||||
}
|
@ -25,7 +25,7 @@ public interface UserService extends IService<User> {
|
||||
* @param checkPassword 校验密码
|
||||
* @return 新用户 id
|
||||
*/
|
||||
long userRegister(String userAccount, String userPassword, String checkPassword);
|
||||
void userRegister(String userAccount, String userPassword, String checkPassword);
|
||||
/**
|
||||
* 获取加密后的密码
|
||||
*
|
||||
@ -41,14 +41,11 @@ public interface UserService extends IService<User> {
|
||||
*/
|
||||
String getEncryptPassword(String userPassword);
|
||||
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*
|
||||
* @param userAccount 用户账户
|
||||
* @param userPassword 用户密码
|
||||
* @return 脱敏后的用户信息
|
||||
*/
|
||||
LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request);
|
||||
LoginUserVO userLogin(String userAccount, String userPassword, String captchaKey, String captchaCode,HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 获得脱敏后的登录用户信息
|
||||
@ -105,5 +102,12 @@ public interface UserService extends IService<User> {
|
||||
*/
|
||||
boolean isAdmin(User user);
|
||||
|
||||
long createUser(UserAddRequest userAddRequest);
|
||||
/**
|
||||
* 发送邮箱验证码
|
||||
*
|
||||
* @param userEmail 用户邮箱
|
||||
* @return 验证码 key
|
||||
*/
|
||||
String sendEmailCode(String userEmail);
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
package edu.whut.smilepicturebackend.service.impl;
|
||||
import com.wf.captcha.SpecCaptcha;
|
||||
import com.wf.captcha.base.Captcha;
|
||||
import edu.whut.smilepicturebackend.constant.CacheConstant;
|
||||
import edu.whut.smilepicturebackend.model.vo.CaptchaVO;
|
||||
import edu.whut.smilepicturebackend.service.MainApplicationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 公共应用服务实现
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MainApplicationServiceImpl implements MainApplicationService {
|
||||
|
||||
private final StringRedisTemplate redisCache;
|
||||
|
||||
/**
|
||||
* 获取图形验证码
|
||||
*
|
||||
* @return 图形验证码VO
|
||||
*/
|
||||
@Override
|
||||
public CaptchaVO getCaptcha() {
|
||||
SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 4);
|
||||
specCaptcha.setCharType(Captcha.TYPE_ONLY_NUMBER);
|
||||
String captchaCode = specCaptcha.text().toLowerCase();
|
||||
String captchaImage = specCaptcha.toBase64();
|
||||
String captchaKey = UUID.randomUUID().toString();
|
||||
// 把验证码存入 Redis 并且设置 1 分钟过期
|
||||
redisCache.opsForValue().set(String.format(CacheConstant.CAPTCHA_CODE_KEY, captchaKey), captchaCode, 1, TimeUnit.MINUTES);
|
||||
CaptchaVO captchaVO = new CaptchaVO();
|
||||
captchaVO.setCaptchaKey(captchaKey);
|
||||
captchaVO.setCaptchaImage(captchaImage);
|
||||
return captchaVO;
|
||||
}
|
||||
}
|
@ -1,21 +1,22 @@
|
||||
package edu.whut.smilepicturebackend.service.impl;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.RegexPool;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import edu.whut.smilepicturebackend.annotation.AuthCheck;
|
||||
import edu.whut.smilepicturebackend.common.BaseResponse;
|
||||
import edu.whut.smilepicturebackend.common.ResultUtils;
|
||||
import edu.whut.smilepicturebackend.constant.CacheConstant;
|
||||
import edu.whut.smilepicturebackend.constant.UserConstant;
|
||||
import edu.whut.smilepicturebackend.exception.BusinessException;
|
||||
import edu.whut.smilepicturebackend.exception.ErrorCode;
|
||||
import edu.whut.smilepicturebackend.exception.ThrowUtils;
|
||||
import edu.whut.smilepicturebackend.manager.auth.StpKit;
|
||||
import edu.whut.smilepicturebackend.model.dto.user.UserAddRequest;
|
||||
import edu.whut.smilepicturebackend.manager.email.EmailManager;
|
||||
import edu.whut.smilepicturebackend.model.dto.user.UserQueryRequest;
|
||||
import edu.whut.smilepicturebackend.model.entity.User;
|
||||
import edu.whut.smilepicturebackend.model.enums.UserRoleEnum;
|
||||
@ -25,13 +26,15 @@ import edu.whut.smilepicturebackend.service.UserService;
|
||||
import edu.whut.smilepicturebackend.mapper.UserMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@ -45,49 +48,40 @@ import java.util.stream.Collectors;
|
||||
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
|
||||
implements UserService{
|
||||
private final BCryptPasswordEncoder passwordEncoder;
|
||||
|
||||
private final EmailManager emailManager;
|
||||
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*
|
||||
* @param userAccount 用户账户
|
||||
* @param userPassword 用户密码
|
||||
* @param checkPassword 校验密码
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public long userRegister(String userAccount, String userPassword, String checkPassword) {
|
||||
// 1. 校验参数
|
||||
if (StrUtil.hasBlank(userAccount, userPassword, checkPassword)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
|
||||
public void userRegister(String userEmail, String codeKey, String codeValue) {
|
||||
String KEY = String.format(CacheConstant.EMAIL_CODE_KEY, codeKey, userEmail);
|
||||
// 获取 Redis 中的验证码
|
||||
String code = stringRedisTemplate.opsForValue().get(KEY);
|
||||
// 删除验证码
|
||||
if (StrUtil.isEmpty(code) || !code.equals(codeValue)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "验证码错误");
|
||||
}
|
||||
if (userAccount.length() < 4) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");
|
||||
boolean existed = existedUserByEmail(userEmail);
|
||||
if (existed) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号已存在, 请直接登录!");
|
||||
}
|
||||
if (userPassword.length() < 8 || checkPassword.length() < 8) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
|
||||
}
|
||||
if (!userPassword.equals(checkPassword)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致");
|
||||
}
|
||||
// 2. 检查用户账号是否和数据库中已有的重复
|
||||
LambdaQueryWrapper<User> lambda = Wrappers.lambdaQuery();
|
||||
lambda.eq(User::getUserAccount, userAccount);
|
||||
long count = this.baseMapper.selectCount(lambda);
|
||||
if (count > 0) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
|
||||
}
|
||||
// 3. 密码一定要加密
|
||||
String encryptPassword = getEncryptPassword(userPassword);
|
||||
// 4. 插入数据到数据库中
|
||||
// 构建参数
|
||||
User user = new User();
|
||||
user.setUserAccount(userAccount);
|
||||
user.setUserPassword(encryptPassword);
|
||||
user.setUserName("无名");
|
||||
user.setUserRole(UserRoleEnum.USER.getValue());
|
||||
boolean saveResult = this.save(user);
|
||||
if (!saveResult) {
|
||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败,数据库错误");
|
||||
}
|
||||
return user.getId();
|
||||
user.setUserEmail(userEmail);
|
||||
// 默认值填充
|
||||
fillDefaultValue(user);
|
||||
|
||||
|
||||
boolean result = this.save(user);
|
||||
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR,"注册失败!");
|
||||
|
||||
stringRedisTemplate.delete(KEY);
|
||||
emailManager.sendEmailAsRegisterSuccess(userEmail, "Smile图库 - 注册成功通知");
|
||||
}
|
||||
/**
|
||||
* 获取加密后的密码
|
||||
@ -103,30 +97,34 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
|
||||
// return DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) {
|
||||
// 1. 校验
|
||||
if (StrUtil.hasBlank(userAccount, userPassword)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
|
||||
public LoginUserVO userLogin(String userAccount, String userPassword, String captchaKey, String captchaCode,HttpServletRequest request) {
|
||||
String KEY = String.format(CacheConstant.CAPTCHA_CODE_KEY, captchaKey);
|
||||
// 获取 Redis 中的验证码
|
||||
String code = stringRedisTemplate.opsForValue().get(KEY);
|
||||
// 删除验证码
|
||||
stringRedisTemplate.delete(KEY);
|
||||
if (StrUtil.isEmpty(code) || !code.equals(captchaCode)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "验证码错误");
|
||||
}
|
||||
if (userAccount.length() < 4) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号错误");
|
||||
User user;
|
||||
if (ReUtil.isMatch(RegexPool.EMAIL, userAccount)) {
|
||||
// 根据 userEmail 获取用户信息
|
||||
user = this.getOne(new LambdaQueryWrapper<User>().eq(User::getUserEmail, userAccount));
|
||||
} else {
|
||||
// 根据 userAccount 获取用户信息
|
||||
user = this.getOne(new LambdaQueryWrapper<User>().eq(User::getUserAccount, userAccount));
|
||||
}
|
||||
if (userPassword.length() < 8) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码错误");
|
||||
}
|
||||
// 2. 根据账号查询用户(不带密码)
|
||||
LambdaQueryWrapper<User> lambda = Wrappers.lambdaQuery();
|
||||
lambda.eq(User::getUserAccount, userAccount);
|
||||
User user = this.baseMapper.selectOne(lambda);
|
||||
if (user == null) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或者密码错误");
|
||||
throw new BusinessException(ErrorCode.DATA_ERROR, "用户不存在或密码错误");
|
||||
}
|
||||
// 3. 用 BCryptPasswordEncoder 去校验明文密码 vs 数据库里存的加密密码
|
||||
// 校验密码
|
||||
if (!passwordEncoder.matches(userPassword, user.getUserPassword())) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或者密码错误");
|
||||
}
|
||||
// 4. 保存用户的登录态
|
||||
// 存储用户登录态
|
||||
request.getSession().setAttribute(UserConstant.USER_LOGIN_STATE, user);
|
||||
// 记录用户登录态到 Sa-token,便于空间鉴权时使用,注意保证该用户信息与 SpringSession 中的信息过期时间一致
|
||||
StpKit.SPACE.login(user.getId());
|
||||
@ -257,23 +255,39 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
|
||||
return user != null && UserRoleEnum.ADMIN.getValue().equals(user.getUserRole());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建用户
|
||||
*/
|
||||
@Override
|
||||
public long createUser(UserAddRequest req) {
|
||||
// 1. DTO -> entity
|
||||
User user = new User();
|
||||
BeanUtil.copyProperties(req, user);
|
||||
public String sendEmailCode(String userEmail) {
|
||||
boolean existed = existedUserByEmail(userEmail);
|
||||
if (existed) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号已存在, 请直接登录!");
|
||||
}
|
||||
// 发送邮箱验证码
|
||||
String code = RandomUtil.randomNumbers(4);
|
||||
emailManager.sendEmailCode(userEmail, "Smile图库 - 注册验证码", code);
|
||||
// 生成一个唯一 ID, 后面注册前端需要带过来
|
||||
String key = UUID.randomUUID().toString();
|
||||
// 存入 Redis, 5 分钟过期
|
||||
stringRedisTemplate.opsForValue().set(String.format(CacheConstant.EMAIL_CODE_KEY, key, userEmail), code, 5, TimeUnit.MINUTES);
|
||||
return key;
|
||||
}
|
||||
|
||||
// 2. 设定默认密码并加密
|
||||
String encoded = passwordEncoder.encode(UserConstant.USER_DEFAULT);
|
||||
user.setUserPassword(encoded);
|
||||
|
||||
// 3.插入数据库
|
||||
boolean result = this.save(user);
|
||||
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
|
||||
return user.getId();
|
||||
public boolean existedUserByEmail(String userEmail) {
|
||||
return this.getBaseMapper()
|
||||
.exists(new QueryWrapper<User>()
|
||||
.eq("user_email", userEmail)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充默认值
|
||||
*/
|
||||
public void fillDefaultValue(User user) {
|
||||
String random = RandomUtil.randomString(6);
|
||||
user.setUserAccount("user_" + random);
|
||||
user.setUserName("用户_" + random);
|
||||
user.setUserRole(UserRoleEnum.USER.getText());
|
||||
user.setUserPassword(getEncryptPassword("12345678"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
server:
|
||||
port: 8123
|
||||
port: 8096
|
||||
servlet:
|
||||
context-path: /api
|
||||
# cookie 30 天过期
|
||||
@ -8,21 +8,19 @@ server:
|
||||
max-age: 2592000
|
||||
|
||||
spring:
|
||||
profiles:
|
||||
active: local
|
||||
application:
|
||||
name: smile-picture-backend
|
||||
# 数据库配置
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://localhost:3306/smile-picture
|
||||
url: jdbc:mysql://localhost:13308/smile-picture
|
||||
username: root
|
||||
password: 123456
|
||||
# Redis 配置
|
||||
redis:
|
||||
database: 1
|
||||
host: localhost
|
||||
port: 6379
|
||||
port: 36380
|
||||
password: 123456
|
||||
timeout: 5000
|
||||
# Session 配置
|
||||
@ -41,7 +39,7 @@ spring:
|
||||
smile-picture:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://localhost:3306/smile-picture
|
||||
url: jdbc:mysql://localhost:13308/smile-picture
|
||||
username: root
|
||||
password: 123456
|
||||
rules:
|
||||
@ -61,6 +59,29 @@ spring:
|
||||
algorithmClassName: edu.whut.smilepicturebackend.manager.sharding.PictureShardingAlgorithm
|
||||
props:
|
||||
sql-show: true #打印实际执行的sql
|
||||
|
||||
# 发送邮件配置(QQ邮箱)
|
||||
mail:
|
||||
host: ${smile-picture.email.host}
|
||||
port: ${smile-picture.email.port}
|
||||
nickname: Smile图库
|
||||
username: ${smile-picture.email.username}
|
||||
password: ${smile-picture.email.password}
|
||||
protocol: ${smile-picture.email.protocol}
|
||||
default-encoding: UTF-8
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
auth: true
|
||||
starttls:
|
||||
enable: true
|
||||
required: true
|
||||
ssl:
|
||||
enable: true
|
||||
socketFactory:
|
||||
port: ${smile-picture.email.port}
|
||||
class: javax.net.ssl.SSLSocketFactory
|
||||
|
||||
mybatis-plus:
|
||||
type-aliases-package: edu.whut.smilepicturebackend.model.entity
|
||||
configuration:
|
||||
|
@ -60,6 +60,29 @@ spring:
|
||||
algorithmClassName: edu.whut.smilepicturebackend.manager.sharding.PictureShardingAlgorithm
|
||||
props:
|
||||
sql-show: false # 生产建议关闭;需要排查时可临时改 true
|
||||
|
||||
# 发送邮件配置(QQ邮箱)
|
||||
mail:
|
||||
host: ${smile-picture.email.host}
|
||||
port: ${smile-picture.email.port}
|
||||
nickname: Smile图库
|
||||
username: ${smile-picture.email.username}
|
||||
password: ${smile-picture.email.password}
|
||||
protocol: ${smile-picture.email.protocol}
|
||||
default-encoding: UTF-8
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
auth: true
|
||||
starttls:
|
||||
enable: true
|
||||
required: true
|
||||
ssl:
|
||||
enable: true
|
||||
socketFactory:
|
||||
port: ${smile-picture.email.port}
|
||||
class: javax.net.ssl.SSLSocketFactory
|
||||
|
||||
mybatis-plus:
|
||||
type-aliases-package: edu.whut.smilepicturebackend.model.entity
|
||||
configuration:
|
||||
|
@ -9,6 +9,7 @@
|
||||
<result property="userAccount" column="user_account" jdbcType="VARCHAR"/>
|
||||
<result property="userPassword" column="user_password" jdbcType="VARCHAR"/>
|
||||
<result property="userName" column="user_name" jdbcType="VARCHAR"/>
|
||||
<result property="userEmail" column="user_email" jdbcType="VARCHAR"/>
|
||||
<result property="userAvatar" column="user_avatar" jdbcType="VARCHAR"/>
|
||||
<result property="userProfile" column="user_profile" jdbcType="VARCHAR"/>
|
||||
<result property="userRole" column="user_role" jdbcType="VARCHAR"/>
|
||||
@ -20,7 +21,7 @@
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
id,user_account,user_password,
|
||||
user_name,user_avatar,user_profile,
|
||||
user_name,user_emial,user_avatar,user_profile,
|
||||
user_role,edit_time,create_time,
|
||||
update_time,is_delete
|
||||
</sql>
|
||||
|
59
src/main/resources/templates/EmailCodeTemplate.html
Normal file
59
src/main/resources/templates/EmailCodeTemplate.html
Normal file
@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="description" content="email code">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<!--邮箱验证码模板-->
|
||||
<body>
|
||||
<div style="background-color:#ECECEC; padding: 35px;">
|
||||
<table cellpadding="0" align="center"
|
||||
style="width: 800px;height: 100%; margin: 0px auto; text-align: left; position: relative; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; font-size: 14px; font-family:微软雅黑, 黑体; line-height: 1.5; box-shadow: rgb(153, 153, 153) 0px 0px 5px; border-collapse: collapse; background-position: initial initial; background-repeat: initial initial;background:#fff;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th valign="middle"
|
||||
style="height: 25px; line-height: 25px; padding: 15px 35px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #409EFF; background-color: #409EFF; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px;">
|
||||
<font face="微软雅黑" size="5" style="color: rgb(255, 255, 255); ">Smile图库</font>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="word-break:break-all">
|
||||
<div style="padding:25px 35px 40px; background-color:#fff;opacity:0.8;">
|
||||
|
||||
<h2 style="margin: 5px 0px; ">
|
||||
<font color="#333333" style="line-height: 20px; ">
|
||||
<font style="line-height: 22px; " size="4">
|
||||
注册操作:</font>
|
||||
</font>
|
||||
</h2>
|
||||
<!-- 中文 -->
|
||||
<p>
|
||||
你好!你正在注册 <a href="https://picture.baolong.icu">Smile图库</a> ,你的验证码为
|
||||
<span style="font-size: 20px">BOOT_code_END</span>,验证码有效时间<span style="color: red">5分钟</span>!
|
||||
</p>
|
||||
<br>
|
||||
|
||||
<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>Smile</p>
|
||||
<p>Q Q: 646228430</p>
|
||||
<p>微信: This_isZhangsan</p>
|
||||
<p>联系邮箱:646228430@qq.com</p>
|
||||
<br>
|
||||
<p>此为系统邮件,请勿回复<br>
|
||||
Please do not reply to this system email
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="description" content="email code">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<!--邮箱验证码模板-->
|
||||
<body>
|
||||
<div style="background-color:#ECECEC; padding: 35px;">
|
||||
<table cellpadding="0" align="center"
|
||||
style="width: 800px;height: 100%; margin: 0px auto; text-align: left; position: relative; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; font-size: 14px; font-family:微软雅黑, 黑体; line-height: 1.5; box-shadow: rgb(153, 153, 153) 0px 0px 5px; border-collapse: collapse; background-position: initial initial; background-repeat: initial initial;background:#fff;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th valign="middle"
|
||||
style="height: 25px; line-height: 25px; padding: 15px 35px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #409EFF; background-color: #409EFF; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px;">
|
||||
<font face="微软雅黑" size="5" style="color: rgb(255, 255, 255); ">Smile图库</font>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="word-break:break-all">
|
||||
<div style="padding:25px 35px 40px; background-color:#fff;opacity:0.8;">
|
||||
|
||||
<h2 style="margin: 5px 0px; ">
|
||||
<font color="#333333" style="line-height: 20px; ">
|
||||
<font style="line-height: 22px; " size="4">
|
||||
注册成功:</font>
|
||||
</font>
|
||||
</h2>
|
||||
<!-- 中文 -->
|
||||
<p>
|
||||
你好!感谢你注册 <a href="https://picture.baolong.icu">Smile图库</a> ,您的初始密码为<span
|
||||
style="color: red; font-weight: bold">12345678</span>,为了确保你的账号安全, 请尽快登录修改密码!
|
||||
</p>
|
||||
<br>
|
||||
|
||||
<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>Smile</p>
|
||||
<p>Q Q: 646228430</p>
|
||||
<p>微信: This_isZhangsan</p>
|
||||
<p>联系邮箱:646228430@qq.com</p>
|
||||
<br>
|
||||
<p>此为系统邮件,请勿回复<br>
|
||||
Please do not reply to this system email
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
58
src/main/resources/templates/EmailReviewTemplate.html
Normal file
58
src/main/resources/templates/EmailReviewTemplate.html
Normal file
@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="description" content="email code">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<!--邮箱验证码模板-->
|
||||
<body>
|
||||
<div style="background-color:#ECECEC; padding: 35px;">
|
||||
<table cellpadding="0" align="center"
|
||||
style="width: 800px;height: 100%; margin: 0px auto; text-align: left; position: relative; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; font-size: 14px; font-family:微软雅黑, 黑体; line-height: 1.5; box-shadow: rgb(153, 153, 153) 0px 0px 5px; border-collapse: collapse; background-position: initial initial; background-repeat: initial initial;background:#fff;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th valign="middle"
|
||||
style="height: 25px; line-height: 25px; padding: 15px 35px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #409EFF; background-color: #409EFF; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px;">
|
||||
<font face="微软雅黑" size="5" style="color: rgb(255, 255, 255); ">Smile图库</font>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="word-break:break-all">
|
||||
<div style="padding:25px 35px 40px; background-color:#fff;opacity:0.8;">
|
||||
|
||||
<h2 style="margin: 5px 0px; ">
|
||||
<font color="#333333" style="line-height: 20px; ">
|
||||
<font style="line-height: 22px; " size="4">
|
||||
审核通知:</font>
|
||||
</font>
|
||||
</h2>
|
||||
<!-- 中文 -->
|
||||
<p>
|
||||
你好!您上传的图片,<span style="color: red; font-weight: bold">BOOT_reviewStatus_END</span>
|
||||
</p>
|
||||
<br>
|
||||
|
||||
<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>Smile</p>
|
||||
<p>Q Q: 646228430</p>
|
||||
<p>微信: This_isZhangsan</p>
|
||||
<p>联系邮箱:646228430@qq.com</p>
|
||||
<br>
|
||||
<p>此为系统邮件,请勿回复<br>
|
||||
Please do not reply to this system email
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
x
Reference in New Issue
Block a user