feat: 添加短信和邮件发送

This commit is contained in:
ray
2024-08-18 23:58:16 +08:00
parent 082e8012f4
commit b9917e96f5
11 changed files with 502 additions and 8 deletions

View File

@@ -0,0 +1,31 @@
package com.youlai.system.service;
/**
* 邮件服务接口层
*
* @author Ray
* @since 2024/8/17
*/
public interface MailService {
/**
* 发送简单文本邮件
*
* @param to 收件人地址
* @param subject 邮件主题
* @param text 邮件内容
*/
boolean sendSimpleMail(String to, String subject, String text) ;
/**
* 发送带附件的邮件
*
* @param to 收件人地址
* @param subject 邮件主题
* @param text 邮件内容
* @param filePath 附件路径
*/
boolean sendMailWithAttachment(String to, String subject, String text, String filePath);
}

View File

@@ -0,0 +1,22 @@
package com.youlai.system.service;
/**
* 短信服务接口层
* <p>
* SMS = Short Message Service 短信服务
*
* @author Ray
* @since 2024/8/17
*/
public interface SmsService {
/**
* 发送短信
*
* @param mobile 手机号 13388886666
* @param templateCode 短信模板 SMS_194640010
* @param templateParam 模板参数 "[{"code":"123456"}]"
* @return boolean 是否发送成功
*/
boolean sendSms(String mobile, String templateCode, String templateParam);
}

View File

@@ -3,9 +3,9 @@ package com.youlai.system.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.system.enums.ContactType;
import com.youlai.system.model.entity.SysUser;
import com.youlai.system.model.form.PasswordChangeForm;
import com.youlai.system.model.form.PasswordResetForm;
import com.youlai.system.model.form.UserForm;
import com.youlai.system.model.dto.UserAuthInfo;
import com.youlai.system.model.form.UserProfileForm;
@@ -127,4 +127,13 @@ public interface SysUserService extends IService<SysUser> {
* @return
*/
boolean resetPassword(Long userId, String password);
/**
* 发送验证码
*
* @param contact 联系方式
* @param type 联系方式类型
* @return
*/
boolean sendVerificationCode(String contact, ContactType type);
}

View File

@@ -8,8 +8,11 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.system.common.constant.RedisConstants;
import com.youlai.system.common.constant.SystemConstants;
import com.youlai.system.config.property.AliyunSmsProperties;
import com.youlai.system.converter.UserConverter;
import com.youlai.system.enums.ContactType;
import com.youlai.system.exception.BusinessException;
import com.youlai.system.model.form.PasswordChangeForm;
import com.youlai.system.model.form.PasswordResetForm;
@@ -28,6 +31,8 @@ import com.youlai.system.model.vo.UserPageVO;
import com.youlai.system.security.service.PermissionService;
import com.youlai.system.service.*;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -35,6 +40,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -59,6 +65,14 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
private final PermissionService permissionService;
private final SmsService smsService;
private final MailService mailService;
private final AliyunSmsProperties aliyunSmsProperties;
private final StringRedisTemplate redisTemplate;
/**
* 获取用户分页列表
*
@@ -286,6 +300,10 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
throw new BusinessException("原密码错误");
}
// 新旧密码不能相同
if (passwordEncoder.matches(data.getNewPassword(), user.getPassword())) {
throw new BusinessException("新密码不能与原密码相同");
}
String newPassword = data.getNewPassword();
return this.update(new LambdaUpdateWrapper<SysUser>()
@@ -297,8 +315,8 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
/**
* 重置密码
*
* @param userId 用户ID
* @param password 密码重置表单数据
* @param userId 用户ID
* @param password 密码重置表单数据
* @return
*/
@Override
@@ -308,4 +326,38 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
.set(SysUser::getPassword, passwordEncoder.encode(password))
);
}
/**
* 发送验证码
*
* @param contact 联系方式 手机号/邮箱
* @param type 联系方式类型 {@link ContactType}
* @return
*/
@Override
public boolean sendVerificationCode(String contact, ContactType type) {
// 随机生成4位验证码
String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000));
// 发送验证码
String verificationCodePrefix = null;
switch (type) {
case MOBILE:
// 获取修改密码的模板code
String changePasswordSmsTemplateCode = aliyunSmsProperties.getTemplateCodes().get("changePassword");
smsService.sendSms(contact, changePasswordSmsTemplateCode, "[{\"code\":\"" + code + "\"}]");
verificationCodePrefix = RedisConstants.MOBILE_VERIFICATION_CODE_PREFIX;
break;
case EMAIL:
mailService.sendSimpleMail(contact, "验证码", "您的验证码是:" + code);
verificationCodePrefix = RedisConstants.EMAIL_VERIFICATION_CODE_PREFIX;
break;
default:
throw new BusinessException("不支持的联系方式类型");
}
// 存入 redis 用于校验, 5分钟有效
redisTemplate.opsForValue().set(verificationCodePrefix + contact, code, 5, TimeUnit.MINUTES );
return true;
}
}

View File

@@ -0,0 +1,83 @@
package com.youlai.system.service.impl.mail;
import com.youlai.system.config.property.MailProperties;
import com.youlai.system.service.MailService;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import java.io.File;
/**
* 邮件服务实现类
*
* @author Ray
* @since 2024/8/17
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class MailServiceImpl implements MailService {
private final JavaMailSender mailSender;
private final MailProperties mailProperties;
/**
* 发送简单文本邮件
*
* @param to 收件人地址
* @param subject 邮件主题
* @param text 邮件内容
*/
@Override
public boolean sendSimpleMail(String to, String subject, String text) {
try {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(mailProperties.getFrom());
message.setTo(to);
message.setSubject(subject);
message.setText(text);
mailSender.send(message);
return true;
} catch (Exception e) {
e.printStackTrace();
log.error("发送邮件失败{}", e.getMessage());
return false;
}
}
/**
* 发送带附件的邮件
*
* @param to 收件人地址
* @param subject 邮件主题
* @param text 邮件内容
* @param filePath 附件路径
*/
@Override
public boolean sendMailWithAttachment(String to, String subject, String text, String filePath) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(mailProperties.getFrom());
helper.setTo(to);
helper.setSubject(subject);
helper.setText(text, true); // true表示支持HTML内容
FileSystemResource file = new FileSystemResource(new File(filePath));
helper.addAttachment(file.getFilename(), file);
mailSender.send(message);
return true;
} catch (MessagingException e) {
return false;
}
}
}

View File

@@ -0,0 +1,77 @@
package com.youlai.system.service.impl.sms;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.youlai.system.config.property.AliyunSmsProperties;
import com.youlai.system.service.SmsService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* 阿里云短信业务类
*
* @author Ray
* @since 2024/8/17
*/
@Service
@RequiredArgsConstructor
public class AliyunSmsService implements SmsService {
private final AliyunSmsProperties aliyunSmsProperties;
/**
* 发送短信验证码
*
* @param mobile 手机号 13388886666
* @param templateCode 短信模板 SMS_194640010
* @param templateParam 模板参数 "[{"code":"123456"}]"
*
* @return boolean 是否发送成功
*/
@Override
public boolean sendSms(String mobile,String templateCode,String templateParam) {
DefaultProfile profile = DefaultProfile.getProfile(aliyunSmsProperties.getRegionId(),
aliyunSmsProperties.getAccessKeyId(), aliyunSmsProperties.getAccessKeySecret());
IAcsClient client = new DefaultAcsClient(profile);
// 创建通用的请求对象
CommonRequest request = new CommonRequest();
// 指定请求方式
request.setSysMethod(MethodType.POST);
// 短信api的请求地址(固定)
request.setSysDomain(aliyunSmsProperties.getDomain());
// 签名算法版(固定)
request.setSysVersion("2017-05-25");
// 请求 API 的名称(固定)
request.setSysAction("SendSms");
// 指定地域名称
request.putQueryParameter("RegionId", aliyunSmsProperties.getRegionId());
// 要给哪个手机号发送短信 指定手机号
request.putQueryParameter("PhoneNumbers", mobile);
// 您的申请签名
request.putQueryParameter("SignName", aliyunSmsProperties.getSignName());
// 您申请的模板 code
request.putQueryParameter("TemplateCode", templateCode);
request.putQueryParameter("TemplateParam", templateParam);
try {
CommonResponse response = client.getCommonResponse(request);
return response.getHttpResponse().isSuccess();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
}
return false;
}
}