一、执行注册流程

1. 目标:

如果针对注册操作所做的各项验证能够通过,则将Member信息存储到数据库中

2. 思路:

未命名图片.png

3. 代码

1) 设置数据表中loginacct 键 唯一

2) 编写mysql工程 的remote方法 【可以使用 postman 工具来测试】

  1. /**
  2. * 将用户信息添加到数据库表
  3. * @param memberPO 用户信息
  4. * @return
  5. */
  6. @RequestMapping("/register/member/by/remote")
  7. public ResultEntity<String> registerMember(@RequestBody MemberPO memberPO){
  8. // DuplicateKeyException 账号被占用异常
  9. try {
  10. memberService.saveMember(memberPO);
  11. }catch (Exception e){
  12. if (e instanceof DuplicateKeyException){
  13. return ResultEntity.failed(CrowdConstant.MESSAGE_LOGIN_ACCT_ALREADY_IN_USR);
  14. }
  15. return ResultEntity.failed(e.getMessage());
  16. }
  17. return ResultEntity.successWithoutData();
  18. }

3) mysql工程的service方法

/**
 * 因为类上面添加了 @Transactional(readOnly=true) 只读注解 所以这里需要重新改一下事务
 * @param memberPO
 * @return
 */
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
@Override
public Integer saveMember(MemberPO memberPO) {

    int i = memberPOMapper.insertSelective(memberPO);
    return i;
}

4) api工程的远程调用方法接口

/**
 * 将用户信息添加到数据库表
 * @param memberPO 用户信息
 * @return
 */
@RequestMapping("/register/member/by/remote")
public ResultEntity<String> registerMember(@RequestBody MemberPO memberPO);

5) 创建MemberVo 类 【保存视图传过来的数据】

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberVO {
    private String loginacct;

    private String userpswd;

    private String username;

    private String email;

    private String phoneNum;

    private String code;
}

6) authentication-consumer的controller方法

这里需要使用重定向 ,如果就会出现二次提交表单的问题 远程调用的 mysql 和 redis

/**
 * 用户注册
 * @param memberVO 页面数据
 * @param modelMap
 * @return
 */
@RequestMapping("/auth/to/member/register")
public String toMemberRegister(MemberVO memberVO, ModelMap modelMap){
    // 赋值属性 BeanUtils.copyProperties("源对象","目标对象");
    // 1.获取用户的手机号
    String phoneNum = memberVO.getPhoneNum();
    // 2.拼接出redis 的key
    String redisKey = CrowdConstant.REDIS_CODE_PREFIX + phoneNum;
    // 3.在redis中查询获取key
    ResultEntity<String> redisValueByKeyRemote = redisRemoteService.getRedisValueByKeyRemote(redisKey);
    // 4.判断查询结果是否有效,如果无效带着message回到注册页面
    if (ResultEntity.FAILED.equals(redisValueByKeyRemote.getResult())){
        modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE,redisValueByKeyRemote.getMessage());
        return "member-reg";
    }
    // 6.进行判断验证码
    // 获取用户输入的验证码
    String inputCode = memberVO.getCode();
    // 获取系统的验证码
    String systemCode = redisValueByKeyRemote.getData();
    // 如果系统验证码为null,则验证码已过期【手机号错误】
    if (systemCode == null){
        modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE,CrowdConstant.MESSAGE_CODE_NOT_EXISTS);
        return "member-reg";
    }
    // 如果验证码比对错误,则返回提示消息
    if (!Objects.equals(systemCode,inputCode)){
        modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE,CrowdConstant.MESSAGE_CODE_NOT_ERROR);
        return "member-reg";
    }
    // 比对完成后,将redis中的 key删除,不然用户将再次注册 直接用这个验证码 就出现bug了
    redisRemoteService.removeRedisValueByKeyRemote(redisKey);
    // 7.如果一致,将保存数据库 并且先对密码进行加密
    // 密码加密
    BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    // 获取密码
    String inputPassword = memberVO.getUserpswd();
    String encodePassword = bCryptPasswordEncoder.encode(inputPassword);
    memberVO.setUserpswd(encodePassword);
    // 将VO复制一份 PO
    MemberPO memberPO = new MemberPO();
    // 使用Spring提供的工具类
    BeanUtils.copyProperties(memberVO, memberPO);
    // 8.进行数据库保存操作
    ResultEntity<String> stringResultEntity = mySqlRemoteService.registerMember(memberPO);
    // 如果保存错误,返回注册页面并带回去信息
    if (ResultEntity.FAILED.equals(stringResultEntity.getResult())){
        modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE,stringResultEntity.getMessage());
        return "member-reg";
    }
    System.out.println(stringResultEntity.getMessage());

    // 使用重定向,将页面重定向到登录页面,避免F5刷新时在次提交表单
    return "redirect:/auth/member/to/login/page";
}

7) 到登录页面的 view-controller

// 添加 登录的view-controller
registry.addViewController("/auth/member/to/login/page").setViewName("member-login");

8) 给consumer和zuul的配置文件中 修改ribbon超时时间,默认的时间太短

【关于第一次请求超时:由于在第一次请求中需要建立缓存,建立连接,操作较多,所以比较耗时,如果按照默认的ribbon超时时间来工作,第一次请求会操作或者时间导致超时报错,
为了解决这个问题,将ribbon的超时时间延长】

ribbon:
  ReadTimeout: 10000
  ConnectTimeout: 10000

二、登录

1. 目标:

用户登录成功后,将用户信息存储session 表示用户已经登录

2. 思路

未命名图片.png

3. 代码

坑:这里比较密码使用BCryptPasswordEncoder对象的matches(form密码,数据库中密码) 方法
注意!:这里的form 不需要加密了,matches方法会自动加密

1) 修正一下 mysql-provider 的service方法 如果没查询到 直接返回null

@Override
public MemberPO getMemberByLoginAcct(String loginacct) {
    // 创建Example
    MemberPOExample memberPOExample = new MemberPOExample();
    // 指定条件
    MemberPOExample.Criteria criteria = memberPOExample.createCriteria();
    criteria.andLoginacctEqualTo(loginacct);
    List<MemberPO> memberPOList = memberPOMapper.selectByExample(memberPOExample);
    if (memberPOList == null || memberPOList.size() == 0) {
        return null;
    }
    return memberPOList.get(0);
}

2) MemberLoginVO 实体类 用来保存用户的登录信息

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberLoginVO {
    private Integer Id;

    private String loginacct;

    private String username;

    private String email;
}

3) authentication-consumer工程的controller方法 【登录路由】

/**
 * 用户登录逻辑
 * @param loginacct 登录账号
 * @param userpswd 登录密码
 * @param modelMap model map
 * @param session session域
 * @return
 */
@RequestMapping("/auth/member/do/login")
public String memberLogin(
        @RequestParam("loginacct") String loginacct,
        @RequestParam("userpswd") String userpswd,
        ModelMap modelMap,
        HttpSession session
){
    // 1.根据账号去数据库查询
    ResultEntity<MemberPO> memberByLoginAcctRemote = mySqlRemoteService.getMemberByLoginAcctRemote(loginacct);
    // 判断
    if (ResultEntity.FAILED.equals(memberByLoginAcctRemote.getResult())){
        modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE,CrowdConstant.MESSAGE_LOGIN_FAILED);
        return "member-login";
    }
    MemberPO memberPO = memberByLoginAcctRemote.getData();
    if (memberPO == null){
        modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE,CrowdConstant.MESSAGE_LOGIN_FAILED);
        return "member-login";
    }
    // 对密码进行比对 , 如果不对返回到登录页面
    String dataUserpswd = memberPO.getUserpswd();
    BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    boolean matches = bCryptPasswordEncoder.matches(userpswd, dataUserpswd);
    if (!matches){
        modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE,CrowdConstant.MESSAGE_LOGIN_FAILED);
        return "member-login";
    }
    // 如果一致把对象转换成MemberLoginVO对象存到Session域中
    MemberLoginVO memberLoginVO = new MemberLoginVO();
    memberLoginVO.setId(memberPO.getId());
    memberLoginVO.setLoginacct(memberPO.getLoginacct());
    memberLoginVO.setUsername(memberPO.getUsername());
    memberLoginVO.setEmail(memberPO.getEmail());
    session.setAttribute(CrowdConstant.ATTR_NAME_LOGIN_MEMBER,memberLoginVO);

    return "redirect:http://localhost/auth/member/to/center";
}

4) view-controller

// 添加 到用户主页的controller
registry.addViewController("/auth/member/to/center").setViewName("member-center");

5) 前端页面

适当位置使用[[${session.loginMember.username}]] 来获取存到session里面的用户信息

三、退出登录

1. 目标:

退出登录

2. 思路:

调用 session对象的 invalidate()方法,清空session里面的值

3. 代码:

/**
 * 用户退出逻辑
 * @param session
 * @return
 */
@RequestMapping("/auth/member/logout")
public String authMemberLogout(HttpSession session){
    session.invalidate();
    return "redirect:/auth/member/logout/to/portal";
}