paascloud开源项目学习(1) -- 用户邮箱注册可靠消息服务流程

用户注册

本篇文章目的是理解该项目可靠消息服务中心(TCP)发送消息、消费消息的流程,用户注册发送激活邮箱和激活后发送注册成功邮箱都是利用可靠消息服务来解决分布式事务,理解了该流程也就弄懂了该项目中其他业务流程。

发送激活邮箱过程

  • 消息生产端:UAC
  • 可靠消息服务:TPC
  • 消息服务端:OPC

用户注册后,向注册邮箱发送一封激活邮箱。

消息生产端(UAC)

大致流程为:

  1. 本地服务 UAC 先持久化 预发送消息(等待确认消息),表pc_mq_message_data
  2. 调用远端可靠消息服务TPC持久化预发送消息,可靠消息表pc_tpc_mq_message
  3. 执行本地事务保存用户信息
  4. 调用远端可靠消息服务TPC更新第2步中的等待确认状态为发送中sending
  5. 同时创建消费待确认列表,即持久化该Topic类型的消息被哪些消费者订阅监听的所有消费待确认列表,状态为未确认,表pc_tpc_mq_confirm
  6. 完成上面操作后,发送消息到 RocketMQ
controller层
  • AuthRestController.java
@PostMapping(value = "/register")
@ApiOperation(httpMethod = "POST", value = "注册用户")
public Wrapper registerUser(UserRegisterDto user) {
   
    uacUserService.register(user);
    return WrapMapper.ok();
}
service层
  1. 用户ID生成:雪花算法生成分布式唯一 ID
  2. 用户密码加密SpringSecurity BCryptPasswordEncoder 强哈希方法加密,每次加密的结果都不一样。

bcrypt 可以有效抵御彩虹表暴力破解,其原理就是在加盐的基础上多次 hash,关于密码参考:https://mp.weixin.qq.com/s/DkHlZs1HgZmGC9r7WaEDeQ

  1. Redis存储激活邮箱token:key(active_token):email:过期时间1天,即激活接口参数:activeUserToken
  2. 生成邮件发送模板(freeMarker):activeUserTemplate.ftl
  3. 根据上面模板和发送邮件参数生成实体:MqMessageData(pc_mq_message_data)

各个子系统消息落地的消息表,比如用户服务系统主要就是邮件消息、短信消息等。

@Override
public void register(UserRegisterDto registerDto) {
   
    // 校验注册信息
    validateRegisterInfo(registerDto);
    String mobileNo = registerDto.getMobileNo();
    String email = registerDto.getEmail();
    Date row = new Date();
    String salt = String.valueOf(generateId());
    // 封装注册信息
    long id = generateId();	// id 雪花算法生成
    UacUser uacUser = new UacUser();
    uacUser.setLoginName(registerDto.getLoginName());
    uacUser.setSalt(salt);
    uacUser.setLoginPwd(Md5Util.encrypt(registerDto.getLoginPwd()));
    uacUser.setMobileNo(mobileNo);
    uacUser.setStatus(UacUserStatusEnum.DISABLE.getKey());
    uacUser.setUserSource(UacUserSourceEnum.REGISTER.getKey());
    uacUser.setCreatedTime(row);
    uacUser.setUpdateTime(row);
    uacUser.setEmail(email);
    uacUser.setId(id);
    uacUser.setCreatorId(id);
    uacUser.setCreator(registerDto.getLoginName());
    uacUser.setLastOperatorId(id);
    uacUser.setUserName(registerDto.getLoginName());
    uacUser.setLastOperator(registerDto.getLoginName());

    // 发送激活邮件
    String activeToken = PubUtils.uuid() + super.generateId();
    redisService.setKey(RedisKeyUtil.getActiveUserKey(activeToken), email, 1, TimeUnit.DAYS);

    Map<String, Object> param = Maps.newHashMap();
    param.put("loginName", registerDto.getLoginName());
    param.put("email", registerDto.getEmail());
    param.put("activeUserUrl", activeUserUrl + activeToken);
    param.put("dateTime", DateUtil.formatDateTime(new Date()));

    Set<String> to = Sets.newHashSet();
    to.add(registerDto.getEmail());

    MqMessageData mqMessageData = emailProducer.sendEmailMq(to, UacEmailTemplateEnum.ACTIVE_USER, AliyunMqTopicConstants.MqTagEnum.ACTIVE_USER, param);
    // 即下面的第6步
    userManager.register(mqMessageData, uacUser);
}
  1. userManager.register() 通过注解 @MqProducerStore 发送消息服务。

执行该方法前,先进入切面编程

@MqProducerStore
public void register(final MqMessageData mqMessageData, final UacUser uacUser) {
   
    log.info("注册用户. mqMessageData={}, user={}", mqMessageData, uacUser);
    uacUserMapper.insertSelective(uacUser);
}
@Target({
   ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MqProducerStore {
   
    // WAIT_CONFIRM:等待确认;SAVE_AND_SEND:直接发送;
	MqSendTypeEnum sendType() default MqSendTypeEnum.WAIT_CONFIRM;
    // ORDER(1):有序;DIS_ORDER(0):无序
	MqOrderTypeEnum orderType() default MqOrderTypeEnum.ORDER;
    // Rocketmq 默认延时级别
    // ZERO(0, 不延时);ONE(1, 1秒)....EIGHTEEN(18, 2小时)
	DelayLevelEnum delayLevel() default DelayLevelEnum.ZERO;
}
  1. 切面中,因为邮件激活发送消息类型为默认的:等待确认
    • 此处本地服务 UAC 消息落地:保存待确认消息 MqMessageData 到 mysql,表pc_mq_message_data
    • 发送待确认消息到可靠消息系统(TPC):发送预发送状态的消息给消息中心
// 切面
MqMessageData domain = null;
for (Object object : args) {
   
    if (object instanceof MqMessageData) {
   
        domain = (MqMessageData) object;
        break;
    }
}
domain.setOrderType(orderType);
domain.setProducerGroup(producerGroup);
// 1. 等待确认
if (type == MqSendTypeEnum.WAIT_CONFIRM) {
   
    if (delayLevelEnum != DelayLevelEnum.ZERO) {
   
        domain.setDelayLevel(delayLevelEnum.delayLevel());
    }
    // 1.1 发送待确认消息到可靠消息系统
    // 本地服务消息落地,可靠消息服务中心也持久化预发送消息,但是不发送
    mqMessageService.saveWaitConfirmMessage(domain);
}
result = joinPoint.proceed();	// 返回注解方法,执行业务
@Override
@Transactional(rollbackFor = Exception.class)
public void saveWaitConfirmMessage(final MqMessageData mqMessageData) {
   
    // 1. 持久化到本地mysql
    this.saveMqProducerMessage(mqMessageData);
    // 2. 发送预发送状态的消息给消息中心
    TpcMqMessageDto tpcMqMessageDto = mqMessageData.getTpcMqMessageDto();
    // 3. 调用远端可靠消息服务(tpc),持久化等待确认消息
    tpcMqMessageFeignApi.
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值