邮箱
邮箱注册
用户视角:输入邮箱和密码(有校验)->填写个人信息->收到确认邮件->点击跳转链接
输入邮箱和密码(有校验)
输入邮箱密码,前端通过正则表达式校验密码格式,后端请求/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; }
|
注册逻辑+确认邮件的回调逻辑
- 查询用户信息:check校验的是是否有效。这里检查是否别的方式已经注册了(通过email字段查)
- 检查密码强度
- 邀请码(暂时不需要)
- 保存用户信息(两张表,一张存用户实体,另一张存账号密码和盐)
- 埋点上报
这里最重要的是“保存用户信息”。在注册之后会入库账号密码,但是未激活(就是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,最后得搭建公司邮件服务器。
邮箱登录
- 各种参数校验,包括上面一直在讲的激活状态。
- 密码校验(数据库盐是Base64,可以加密解密的): encode(用户输入+Base64Decode(数据库盐)) == 数据库加密后的密码。
- 校验成功则登录成功,首先解析ip到国家和省份保存。然后生成token作为session保存在redis里面,设定过期时间为7天。
- 将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()); } 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);
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的过程。