邮箱

邮箱注册

用户视角:输入邮箱和密码(有校验)->填写个人信息->收到确认邮件->点击跳转链接

输入邮箱和密码(有校验)

输入邮箱密码,前端通过正则表达式校验密码格式,后端请求/email/check来检查账号是否启用。这里启用指的是已经点击过验证邮件的或者用oauth已经校验过的。这里没校验过邮件的应该还是可以重新发起注册的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public EmailSignUpCheckResp emailSignUpCheck(EmailSignupCheckReq emailSignupCheckReq, HttpServletRequest request) {
EmailSignUpCheckResp emailSignUpCheckResp = EmailSignUpCheckResp.builder()
.emailSignUpCheckStatus(EmailSignUpCheckStatus.UN_SIGN_UP)
.build();
// 校验注册邮箱
UserEntity user = userServiceImpl.getUserEntityByLoginUsername(emailSignupCheckReq.getEmail());
if (Objects.nonNull(user)) {
if (UserState.OPEN.getCode() == user.getState()) {
emailSignUpCheckResp.setEmailSignUpCheckStatus(EmailSignUpCheckStatus.ALREADY_VERIFIED);
} else {
emailSignUpCheckResp.setEmailSignUpCheckStatus(EmailSignUpCheckStatus.ALREADY_SIGN_UP);
}
}

return emailSignUpCheckResp;
}

注册逻辑+确认邮件的回调逻辑

  1. 查询用户信息:check校验的是是否有效。这里检查是否别的方式已经注册了(通过email字段查)
  2. 检查密码强度
  3. 邀请码(暂时不需要)
  4. 保存用户信息(两张表,一张存用户实体,另一张存账号密码和盐)
  5. 埋点上报

这里最重要的是“保存用户信息”。在注册之后会入库账号密码,但是未激活(就是check检查的那个字段,只有点击了邮件链接才会接收到)。

随后生成一个UUID作为参数传给邮件,同时缓存在redis中。在邮件里面放这个链接和参数,点击的时候自动就会去请求“/verify/email?cs=”。
这个方法会解析加密参数,与redis中进行比较。校验成功后激活用户状态,最后走获取token的逻辑,那部分等讲登录的时候再说。

邮箱相关

发送的是html,这里用到了FreeMarker,模板里面只有两个需要动态换的,一个是称呼一个是对应的重定向链接。

1
2
3
4
5
6
7
8
9
10
11
12
13
Map<String, Object> templateParams = Maps.newHashMap();
templateParams.put("accountName", "test");
templateParams.put("registerLink", "https://www.bilibili.com");
Resource resource = resourceLoader.getResource("classpath:template/"+registerTemplate);
byte[] fileData = FileCopyUtils.copyToByteArray(resource.getInputStream());
String template = new String(fileData, StandardCharsets.UTF_8);
textPart.setContent(template, "text/html;charset=utf-8");
try (InputStream inputStream = new ByteArrayInputStream(template.getBytes())) {
String content = FreemarkerUtils.process(inputStream, templateParams);
System.out.println(content);
} catch (Exception e) {
throw new RuntimeException(e);
}

然后发送邮件配置就完了,这里暂时用的是gmail,最后得搭建公司邮件服务器。

邮箱登录

  1. 各种参数校验,包括上面一直在讲的激活状态。
  2. 密码校验(数据库盐是Base64,可以加密解密的): encode(用户输入+Base64Decode(数据库盐)) == 数据库加密后的密码。
  3. 校验成功则登录成功,首先解析ip到国家和省份保存。然后生成token作为session保存在redis里面,设定过期时间为7天。
  4. 将token返回给前端,每次请求都要携带。

oauth

谷歌code解析

类似微信小程序,前端先去请求谷歌登录,谷歌会返回一个code,后端拿到这个code去oauth里获得用户登录信息。拿这个信息去业务数据库里查询是否有记录来判断是注册还是登录。

如果没有实际用户说明是注册,如果有还需要判断注册方式是否是oauth,分为其他方式注册和直接登录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public OAuthLoginUser getAuthUserInfo(OAuthUserInfoReq req) {
if (!oAuthRequestFactory.support().contains(req.getOAuthType().toUpperCase())) {
log.error("getAuthUserInfo, source not supported, req: {}", req);
throw new BizException("unsupported oauth type", BizError.ENUM_PARAM_ERR.getCode());
}
//拿着oauth去解析,获得用户数据
AuthRequest authRequest = oAuthRequestFactory.create(req.getOAuthType());
AuthResponse<AuthUser> authResponse = authRequest.login(AuthCallback.builder().code(req.getCode()).build());
AuthUser authUserInfo = authResponse.getData();
if (Objects.isNull(authUserInfo)) {
log.error("getAuthUserInfo, authUserInfo is null, req: {}", req);
throw new BizException(BizError.COMMON_ERR.getName(), BizError.COMMON_ERR.getCode());
}

// 缓存获取的用户信息
String accessCode = CodeUtils.generateOAuthSignupCode();
redisManager.addOAuthUser(accessCode, authUserInfo);

// 判断当前OAuth状态
OAuthType oAuthType = OAuthType.of(req.getOAuthType());
String userIdentify = authUserInfo.getUsername();
OAuthStatus oAuthStatus;
UserEntity user = userServiceImpl.getUserEntityByLoginUsername(userIdentify);
//用户实体表没有就是没注册
if (Objects.nonNull(user)) {
//查指定枚举信息的
UserAuthEntity auth = userAuthServiceImpl.getByIdentifier(userIdentify,
IdentityType.convert(oAuthType).getCode());
if (Objects.nonNull(auth)) {
// 同种类型重复注册
oAuthStatus = OAuthStatus.LOGIN;
} else {
// 已经存在其他方式注册
oAuthStatus = OAuthStatus.ANOTHER_SIGNUP_WAY;
}
} else {
oAuthStatus = OAuthStatus.SIGNUP;
}

return OAuthLoginUser.convert(authUserInfo, accessCode, oAuthStatus);
}

前端可能会根据三种枚举值来走登录、注册和提示逻辑。

oauth注册

注册逻辑基本一致,多了一个从redis中查询oauth的用户信息的过程(例如google保存的头像链接),如果没查到就是验证超时,如果查到了但是用户不匹配就抛异常。最后给token,然后删除oauth的验证信息。

oauth登录

基本一致,也是多了一个从redis里面拿accesscode的过程。