注册一个系统成为用户,一般会要求用户留下一个邮件地址作为联系方式,就象我们去银行开户时银行会让我们留个手机号码一样。为了证明注册的邮箱地址是本人的,系统会向邮箱发送一串验证码,用户收取该验证码后在注册页面上输入验证码连同其他信息发往后台进行验证。
前后端分离目前已成为互联网项目开发的业界标准,其核心思想就是前端(APP、小程序、H5页面等)通过调用后端的API接口,提交及返回JSON数据进行交互。
在前后端分离项目中,首先要解决的就是登录及授权的问题。微服务架构下,传统的session认证限制了应用的扩展能力,无状态的JWT认证方法应运而生,该认证机制特别适用于分布式站点的单点登录(SSO)场景
关于SpringBoot实现JWT的具体细节,请参考本人博文:
《SpringBoot整合SpringSecurity实现JWT认证》
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
spring:
datasource:
druid:
db-type: com.alibaba.druid.pool.DruidDataSource
driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
url: jdbc:log4jdbc:mysql://localhost:3306/startup_backend?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
/** * 用户表 * * @author zhuhuix * @date 2020-04-03 */
@ApiModel(value = "用户信息")
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_user")
public class SysUser implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String userName;
@JsonIgnore
private String password;
private String nickName;
/** * 性别 0-未知 1-male,2-female */
private Integer gender;
/** * 头像地址 */
private String avatarUrl;
private String country;
private String province;
private String city;
@Email
private String email;
private String phone;
private String remarks;
private Boolean enabled;
private Timestamp lastPasswordResetTime;
@Builder.Default
private Timestamp createTime = Timestamp.valueOf(LocalDateTime.now());
@Builder.Default
private Timestamp updateTime = Timestamp.valueOf(LocalDateTime.now());
}
/** * 用户DAO接口 * * @author zhuhuix * @date 2021-07-19 */
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
}
/** * 用户增删改查服务接口 * * @author zhuhuix * @date 2020-04-03 */
public interface SysUserService {
/** * 增加用户 * * @param user 待新增的用户 * @return 增加成功的用户 */
SysUser create(SysUser user);
/** * 删除用户 * * @param user 待删除的用户 * @return 删除成功的用户 */
Result<SysUser> delete(SysUser user);
/** * 修改用户 * * @param user 待修改的用户 * @return 修改成功的用户 */
Result<SysUser> update(SysUser user);
/** * 根据userName查找用户 * * @param userName 用户帐号 * @return 用户帐号对应的用户 */
SysUser findByUserName(String userName);
/** * 判断注册使用的邮箱是否存在 * * @param email 邮箱号 * @return 是否找到 */
boolean registerEmailExist(String email);
}
/** * 用户增删改查实现类 * * @author zhuhuix * @date 2020-04-03 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class SysUserServiceImpl implements SysUserService {
private final SysUserMapper sysUserMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public SysUser create(SysUser user) {
return sysUserMapper.insert(user) > 0 ? user : null;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<SysUser> delete(SysUser user) {
QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(SysUser::getUserName, user.getUserName());
return sysUserMapper.delete(queryWrapper) > 0 ? new Result<SysUser>().ok(user) : new Result<SysUser>().error("删除用户失败");
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<SysUser> update(SysUser user) {
return sysUserMapper.updateById(user) > 0 ? new Result<SysUser>().ok(user) : new Result<SysUser>().error("更新用户失败");
}
@Override
public SysUser findByUserName(String userName) {
return sysUserMapper.selectOne(new QueryWrapper<SysUser>().lambda().eq(SysUser::getUserName, userName));
}
@Override
public boolean registerEmailExist(String email) {
QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(SysUser::getEmail, email);
return sysUserMapper.selectOne(queryWrapper) != null;
}
}
/** * 邮件信息 * @author zhuhuix * @date 2021-07-19 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmailDto {
/** * 发送邮箱列表 */
@NotEmpty
private List<String> tos;
/** * 主题 */
@NotBlank
private String subject;
/** * 内容 */
@NotBlank
private String content;
}
/** * 邮箱服务接口 * * @author zhuhuix * @date 2021-07-19 */
public interface EmailService {
/** * 发送邮件 * @param emailDto 邮箱列表 */
void send(EmailDto emailDto);
}
/** * 邮箱发送接口实现类 * * @author zhuhuix * @date 2021-07-19 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class EmailServiceImpl implements EmailService {
@Value("${spring.mail.email}")
private String email;
@Value("${spring.mail.host}")
private String host;
@Value("${spring.mail.port}")
private String port;
@Value("${spring.mail.username}")
private String username;
@Value("${spring.mail.password}")
private String password;
@Override
public void send(EmailDto emailDto) {
// 读取邮箱配置
if (email == null || host == null || port == null || username == null || password == null) {
throw new RuntimeException("邮箱配置异常");
}
// 设置
MailAccount account = new MailAccount();
account.setHost(host);
account.setPort(Integer.parseInt(port));
// 设置发送人邮箱
account.setFrom(username + "<" + email + ">");
// 设置发送人名称
account.setUser(username);
// 设置发送授权码
account.setPass(password);
account.setAuth(true);
// ssl方式发送
account.setSslEnable(true);
// 使用安全连接
account.setStarttlsEnable(true);
// 发送邮件
try {
int size = emailDto.getTos().size();
Mail.create(account)
.setTos(emailDto.getTos().toArray(new String[size]))
.setTitle(emailDto.getSubject())
.setContent(emailDto.getContent())
.setHtml(true)
//关闭session
.setUseGlobalSession(false)
.send();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
# application.yml
server:
port: 8000
spring:
mail:
email: xxxx@163.com
host: smtp.163.com
port: 465
username: xxxx
# 授权密码, 非邮箱密码,授权码是用于登录第三方邮件客户端的专用密码。
password: xxxxxxxx
<!--模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
– 设置模板
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<style> @page { margin: 0; } </style>
</head>
<body>
<div class="header">
<div style="padding: 10px;padding-bottom: 0px;">
<p style="margin-bottom: 10px;padding-bottom: 0px;">尊敬的用户,您好:</p>
<p style="text-indent: 2em; margin-bottom: 10px;">您正在申请邮箱验证,您的验证码为:</p>
<p class="code-text">${code}</p>
<div class="footer">
</div>
</div>
</div>
</body>
</html>
<style lang="css"> body { margin: 0px; padding: 0px; font: 100% SimSun, Microsoft YaHei, Times New Roman, Verdana, Arial, Helvetica, sans-serif; color: #000; } .header { height: auto; width: 820px; min-width: 820px; margin: 0 auto; margin-top: 20px; border: 1px solid #eee; } .code-text { text-align: center; font-family: Times New Roman; font-size: 22px; color: #C60024; padding: 20px 0px; margin-bottom: 10px; font-weight: bold; background: #ebebeb; } .footer { margin: 0 auto; z-index: 111; width: 800px; margin-top: 30px; border-top: 1px solid #DA251D; } </style>
– 编写调用模板引擎发送邮件的方法
public void sendMailCode(String email) {
// 获取发送邮箱验证码的HTML模板
TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
Template template = engine.getTemplate("email-code.ftl");
// 发送验证码
emailService.send(new EmailDto(Collections.singletonList(email),
"邮箱验证码", template.render(Dict.create().set("code", code))));
}
/** * 登录授权服务接口 * * @author zhuhuix * @date 2020-04-07 */
public interface AuthService {
/** * 向指定邮箱发送验证码 * * @param email 邮箱号 */
void sendMailCode(String email);
/** * 注册 * * @param authUserDto 认证用户请求信息 * @return 是否成功 */
boolean register(AuthUserDto authUserDto);
}
```java
/** * 认证用户 * * @author zhuhuix * @date 2020-04-03 */
@ApiModel(value = "授权用户信息")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AuthUserDto {
@ApiModelProperty(value = "用户名")
private String userName;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "临时登录凭证")
private String code;
@ApiModelProperty(value = "邮箱")
private String email ;
}
/** * 授权登录接口实现类 * * @author zhuhuix * @date 2020-06-15 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class AuthServiceImpl implements AuthService {
// 验证码放入redis缓存过期时间
@Value("${code.expiration}")
private Long expiration;
private final RedisUtils redisUtils;
private final EmailService emailService;
private final SysUserService sysUserService;
@Override
public void sendMailCode(String email) {
// 查看注册邮箱是否存在
if (sysUserService.registerEmailExist(email)) {
throw new RuntimeException("注册邮箱已存在");
}
// 获取发送邮箱验证码的HTML模板
TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
Template template = engine.getTemplate("email-code.ftl");
// 从redis缓存中尝试获取验证码
Object code = redisUtils.get(email);
if (code == null) {
// 如果在缓存中未获取到验证码,则产生6位随机数,放入缓存中
code = RandomUtil.randomNumbers(6);
if (!redisUtils.set(email, code, expiration)) {
throw new RuntimeException("后台缓存服务异常");
}
}
// 发送验证码
emailService.send(new EmailDto(Collections.singletonList(email),
"邮箱验证码", template.render(Dict.create().set("code", code))));
}
}
/** * 授权登录接口实现类 * * @author zhuhuix * @date 2020-06-15 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class AuthServiceImpl implements AuthService {
@Value("${rsa.private-key}")
private String privateKey;
private final RedisUtils redisUtils;
private final EmailService emailService;
private final PasswordEncoder passwordEncoder;
private final SysUserService sysUserService;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean register(AuthUserDto authUserDto) {
// 通过email获取redis中的code
Object value = redisUtils.get(authUserDto.getEmail());
if (value == null || !value.toString().equals(authUserDto.getCode())) {
throw new RuntimeException("无效验证码");
} else {
redisUtils.del(authUserDto.getEmail());
}
// 如果前端没有传入用户名,则以邮箱号作为用户名进行注册
String userName = StringUtils.isEmpty(authUserDto.getUserName()) ? authUserDto.getEmail() : authUserDto.getUserName();
if (userService.findByUserName(userName) != null) {
throw new RuntimeException("用户名已存在");
}
// 创建用户
SysUser sysUser = new SysUser();
sysUser.setUserName(userName);
try {
sysUser.setPassword(passwordEncoder.encode(RsaUtils.decryptByPrivateKey(privateKey, authUserDto.getPassword())));
} catch (Exception e) {
throw new RuntimeException("注册密码异常");
}
sysUser.setEmail(authUserDto.getEmail());
return sysUserService.create(sysUser) != null;
}
}
/** * api登录授权 * * @author zhuhuix * @date 2020-03-30 */
@Slf4j
@RestController
@RequestMapping("/api/auth")
@Api(tags = "系统授权接口")
public class AuthController {
private final AuthService authService;
public AuthController(AuthService authService) {
this.authService = authService;
}
@ApiOperation("发送邮箱验证码")
@PostMapping(value = "/getEmailCode")
public ResponseEntity<Object> getEmailCode(@RequestParam String email) {
authService.sendMailCode(email);
return new ResponseEntity<>(HttpStatus.OK);
}
@ApiOperation("注册")
@PostMapping(value = "/register")
public ResponseEntity<Object> register(@RequestBody AuthUserDto authUserDto) {
return ResponseEntity.ok(authService.register(authUserDto));
}
}
import request from '@/utils/request'
export function getEmailCode(email) {
return request({
url: '/api/auth/getEmailCode?email=' + email,
method: 'post'
})
}
export function register(data) {
return request({
url: '/api/auth/register',
method: 'post',
data
})
}
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://zhuhuix.blog.csdn.net/article/details/119533833
内容来源于网络,如有侵权,请联系作者删除!