一、用户注册(手机验证码)

目前主流采用手机号注册方式,因为收集到手机号对用户推广、业务推广有极其重要的价值。结合上篇采用阿里云短信服务实现手机验证码的发送,这里整合实现用手机号实现用户注册。

思路:前端在输入手机号之后,需要对手机号进行校验,用户需要接收短信验证码并完成验证码校验之后即可成功注册。具体步骤:
1、判断当前手机号是否已注册;
2、调用阿里云短信服务api实现验证码发送;
3、验证码发送成功并存放至redis缓存,利用缓存淘汰机制(设置有效时间)实现验证码过期;
4、验证码校验,通过即注册成功。

1.1 用户实体类

package com.zhmsky.service_ucenter.entity;import com.baomidou.mybatisplus.annotation.*;import java.util.Date;import java.io.Serializable;import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;import lombok.Data;import lombok.EqualsAndHashCode;/** * 

* 会员表 *

* * @author zhmsky * @since 2022-07-16 */
@Data@EqualsAndHashCode(callSuper = false)@ApiModel(value="UcenterMember对象", description="会员表")public class UcenterMember implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "会员id") @TableId(value = "id", type = IdType.ASSIGN_ID) private String id; @ApiModelProperty(value = "微信openid") private String openid; @ApiModelProperty(value = "手机号") private String mobile; @ApiModelProperty(value = "密码") private String password; @ApiModelProperty(value = "昵称") private String nickname; @ApiModelProperty(value = "性别 1 女,2 男") private Integer sex; @ApiModelProperty(value = "年龄") private Integer age; @ApiModelProperty(value = "用户头像") private String avatar; @ApiModelProperty(value = "用户签名") private String sign; @ApiModelProperty(value = "是否禁用 1(true)已禁用, 0(false)未禁用") private Boolean isDisabled; @ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除") private Boolean isDeleted; @ApiModelProperty(value = "创建时间") @TableField(fill = FieldFill.INSERT) private Date createTime; @ApiModelProperty(value = "更新时间") @TableField(fill = FieldFill.UPDATE) private Date updateTime;}

1.2 注册用户视图对象

package com.zhmsky.service_ucenter.entity.VO;import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;import lombok.Data;/** * @author zhmsky * @date 2022/7/16 17:18 */@Data@ApiModel(value="注册对象", description="注册对象")public class RegisterVO {    @ApiModelProperty(value = "昵称")    private String nickname;    @ApiModelProperty(value = "手机号")    private String mobile;    @ApiModelProperty(value = "密码")    private String password;    @ApiModelProperty(value = "验证码")    private String code;}

1.3 短信验证码的发送

调用阿里云短信服务实现短信验证码发送

package com.zhmsky.msmService.service.impl;import com.aliyuncs.DefaultAcsClient;import com.aliyuncs.IAcsClient;import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;import com.aliyuncs.exceptions.ClientException;import com.aliyuncs.exceptions.ServerException;import com.aliyuncs.profile.DefaultProfile;import com.zhmsky.msmService.service.MsmService;import org.springframework.stereotype.Service;/** * @author zhmsky * @date 2022/7/6 21:01 * 短信验证码实现类 */@Servicepublic class MsmServiceImpl implements MsmService {    /**     * 发送短信验证码     * @param phone 手机号     * @param code 被发送的验证码     * @return     */    @Override    public String sendCodeMsg(String phone, String code) {        String checkCode="";        DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "LTAI5tDfHQPQ5WA5dBkrfFxu", "eRID7ZigveAH7fMRKbCDq92jjRl68R");        IAcsClient client = new DefaultAcsClient(profile);        SendSmsRequest request = new SendSmsRequest();        request.setSignName("阿里云短信测试");        request.setTemplateCode("SMS_154950909");        request.setPhoneNumbers(phone);        request.setTemplateParam("{\"code\":\""+code+"\"}");        try {            SendSmsResponse response = client.getAcsResponse(request);            checkCode = response.getCode();        } catch (ServerException e) {            e.printStackTrace();        } catch (ClientException e) {            System.out.println("ErrCode:" + e.getErrCode());            System.out.println("ErrMsg:" + e.getErrMsg());            System.out.println("RequestId:" + e.getRequestId());        }        return checkCode;    }}

短信验证码发送接口

package com.zhmsky.msmService.controller;import com.zhmsky.msmService.service.MsmService;import com.zhmsky.msmService.utils.RandomUtil;import com.zhmsky.result.Result;import com.zhmsky.result.ResultUtil;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.*;import java.util.HashMap;import java.util.Map;import java.util.concurrent.TimeUnit;/** * @author zhmsky * @date 2022/7/6 20:58 */@RestController@Api("短信注册控制器")@CrossOrigin@RequestMapping("/msmService")public class MsmController {    @Autowired    private MsmService msmService;    @Autowired    private RedisTemplate<String,String> redisTemplate;    @GetMapping("/send/{phone}")    @ApiOperation("发送短信验证码")    public Result<String> sendMsg(@PathVariable String phone){        //通过redis设置缓存时间实现验证码过期        String code = redisTemplate.opsForValue().get(phone);        if(!StringUtils.isEmpty(code)){            return new ResultUtil<String>().setSuccessMsg("请勿重复发送!");        }        /**  如果缓存里面没有那么就重新发送   **/        //生成随机验证码        String fourBitRandom = RandomUtil.getFourBitRandom();        //调用阿里云api实现短信发送        String checkCode = msmService.sendCodeMsg(phone, fourBitRandom);        if("OK".equals(checkCode)){            //将验证码保存到redis中并设置有效时间为5分钟            redisTemplate.opsForValue().set(phone,fourBitRandom,5, TimeUnit.MINUTES);            return new ResultUtil<String>().setData(fourBitRandom,"验证码发送成功!");        }else{            return new ResultUtil<String>().setErrorMsg("验证码发送给失败!");        }    }}

1.4 注册功能实现

1、接口保护,参数非空判断;
2、验证手机号是否已注册;
3、验证码校验;
4、入库

 /**     * 用户注册     * @param registerVO     * @return     */    @Override    public boolean register(RegisterVO registerVO) {        //获取注册数据,接口保护,参数校验        String code = registerVO.getCode();        String mobile = registerVO.getMobile();        String nickname = registerVO.getNickname();        String password = registerVO.getPassword();        if(StringUtils.isEmpty(mobile)||StringUtils.isEmpty(password)||StringUtils.isEmpty(code)||StringUtils.isEmpty(nickname)){            throw new MyException(20005,"注册失败!");        }        //判断手机号是否已注册        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();        wrapper.eq("mobile",mobile);        Long count = baseMapper.selectCount(wrapper);        if(count>0){            throw new MyException(20005,"注册失败!");        }        //验证码校验        //先从redis中获取验证码        String cacheCode = redisTemplate.opsForValue().get(mobile);        if(!code.equals(cacheCode)){           throw new MyException(20005,"注册失败!");        }        //入库        UcenterMember ucenterMember = new UcenterMember();        ucenterMember.setMobile(mobile);        ucenterMember.setNickname(nickname);        ucenterMember.setPassword(MD5.encrypt(password));        ucenterMember.setIsDisabled(false);        ucenterMember.setAvatar("http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132");        int insert = baseMapper.insert(ucenterMember);        if(insert>0){            return true;        }else{            return false;        }    }

二、用户登录

2.1 账号密码登录

登录流程:
1、调用登录接口并返回token字符串
2、将返回的token字符串放到cookie里面
3、创建前端拦截器,判断cookie里面是否有token字符串,如果有则将token放到request的header中
4、调用接口,根据token获取用户信息并再次放到cookie中(为了首页面展示)
5、再从cookie中取出用户信息进行展示
思路:
1、接口保护,参数非空校验;
2、验证账号是否注册;
3、验证账号是否禁用;
4、验证密码;
5、登录并返回token。

为了实现单点登录,采用token令牌方式,引入jwt
jwt工具类:

package com.zhmsky.jwt;import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jws;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;import java.util.Date;/** * @author zhmsky * @date 2022/7/6 17:53 */public class JwtUtils {    //设置token过期时间    public static final long EXPIRE = 1000 * 60 * 60 * 24;    //一天    //签名加密密钥    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";    /**     * 生成token     * @param id     * @param nickname     * @return     */    public static String getJwtToken(String id, String nickname){        String JwtToken = Jwts.builder()                //设置头信息                .setHeaderParam("typ", "JWT")                .setHeaderParam("alg", "HS256")                //设置token过期时间                .setSubject("user")   //随便写                .setIssuedAt(new Date())                //当前时间加上设置的过期时间                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))                //设置token主体,可存储用户信息                .claim("id", id)                .claim("nickname", nickname)                //签名哈希                .signWith(SignatureAlgorithm.HS256, APP_SECRET)                .compact();        return JwtToken;    }    /**     * 判断token是否存在与有效     * @param jwtToken     * @return     */    public static boolean checkToken(String jwtToken) {        if(StringUtils.isEmpty(jwtToken)) return false;        try {            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);        } catch (Exception e) {            e.printStackTrace();            return false;        }        return true;    }    /**     * 判断token是否存在与有效     * @param request     * @return     */    public static boolean checkToken(HttpServletRequest request) {        try {            String jwtToken = request.getHeader("token");            if(StringUtils.isEmpty(jwtToken)) return false;            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);        } catch (Exception e) {            e.printStackTrace();            return false;        }        return true;    }    /**     * 根据token获取会员id     * @param request     * @return     */    public static String getMemberIdByJwtToken(HttpServletRequest request) {        String jwtToken = request.getHeader("token");        if(StringUtils.isEmpty(jwtToken)) return "";        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);        Claims claims = claimsJws.getBody();        return (String)claims.get("id");    }}

登录业务:

 /**     * 用户登录     * @param member     * @return token     */    @Override    public String login(UcenterMember member) {       //获取账号和密码        String mobile = member.getMobile();        String password = member.getPassword();       if(StringUtils.isEmpty(mobile)||StringUtils.isEmpty(password)){           throw new MyException(20010,"账号和密码不能为空!");       }       //判断账号和密码是否存在        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();        wrapper.eq("mobile",mobile);        UcenterMember ucenterMember = baseMapper.selectOne(wrapper);        if(ObjectUtils.isEmpty(ucenterMember)){            throw new MyException(20011,"账号不存在,请重新输入!");        }        //判断该用户是否被禁用        Boolean isDisabled = ucenterMember.getIsDisabled();        if(isDisabled){            throw new MyException(20013,"该账号已禁用!");        }        //判断密码是否正确        //密码存储肯定是加密的,实际开发中数据库不会存储明文密码        //先将输入的密码加密,再和数据库密码比较        //MD5加密        String realPassword = ucenterMember.getPassword();        if(!MD5.encrypt(password).equals(realPassword)){            throw new MyException(20012,"密码错误,请重新输入!");        }        //登录成功,返回token(通过查出来的用户数据去生成token)        return JwtUtils.getJwtToken(ucenterMember.getId(), ucenterMember.getNickname());    }

登录接口:

    @PostMapping ("/login")    @ApiOperation("用户登录")    public Result<String> login(@RequestBody(required = false) UcenterMember ucenterMember){        //登录生成token并返回        String token = memberService.login(ucenterMember);        return new ResultUtil<String>().setData(token);    }

登录成功后,前端每次请求都携带token,从request对象中获取token再解析token获取用户信息。

    @GetMapping("/getUserInfo")    @ApiOperation("根据token获取用户信息")    public Result<UcenterMember> getUserInfo(HttpServletRequest httpServletRequest){        //从request对象中获取token,再根据token获取用户信息        String userId = JwtUtils.getMemberIdByJwtToken(httpServletRequest);        //根据用户id获取用户信息        UcenterMember ucenterMember = memberService.getById(userId);        return new ResultUtil<UcenterMember>().setData(ucenterMember);    }

2.2 微信扫码登录

2.2.1 准备工作:

网站应用微信登录是基于OAuth2.0协议标准构建的微信OAuth2.0授权登录系统。
在进行微信OAuth2.在进行微信OAuth2.0授权登录接入之前,在微信开放平台注册开发者帐号,并拥有一个已审核通过的网站应用,并获得相应的AppID和AppSecret,申请微信登录且通过审核后,可开始接入流程。

2.2.2 授权流程

  1. 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;
  2. 通过code参数加上AppID和AppSecret等,通过API换取access_token;
  3. 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

    第一步:请求生成微信二维码

    根据官方文档,直接请求微信开放平台固定地址https://open.weixin.qq.com/connect/qrconnect” />
    获取随机code,请求微信资源服务器固定地址拿到accessToken(访问凭证)和openId(用户唯一标识),再通过accessToken和openId请求微信资源服务器固定地址拿到扫码人的基本信息。获取到用户基本信息后就可以进行校验完成入库等操作。

       @GetMapping("/callback")    @ApiOperation("微信扫码确认后执行回调")    public String callback(String code,String state){        //1、接收code值        //用code去请求微信的固定地址,得到accessToken和openId        //向认证服务器发送请求换取access_token        String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +                "?appid=%s" +                "&secret=%s" +                "&code=%s" +                "&grant_type=authorization_code";        //带参数的真实认证服务器请求地址        String accessTokenUrl = String.format(baseAccessTokenUrl,                ConstWxUtil.WX_OPEN_APP_ID,                ConstWxUtil.WX_OPEN_APP_SECRET,                code        );        //2、请求认证服务器获取接口调用凭证access_token和用户唯一标识openId        try {            String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);            //将上述json字符串转换成map对象            /*            {                "access_token":"ACCESS_TOKEN",                "expires_in":7200,                "refresh_token":"REFRESH_TOKEN",                "openid":"OPENID",                "scope":"SCOPE",                "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"             }             */            Gson gson = new Gson();            HashMap mapAccessTokenInfo = gson.fromJson(accessTokenInfo, HashMap.class);            //取出的access_token            String access_token = (String)mapAccessTokenInfo.get("access_token");            //取出的openid            String openid = (String)mapAccessTokenInfo.get("openid");            //3、再通过获取出来的access_token和openid去请求微信开放平台服务器获取扫码人信息            //访问微信的资源服务器,获取用户信息            String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +                    "?access_token=%s" +                    "&openid=%s";            String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);            //发送请求获取扫码人基本信息            String userInfo = HttpClientUtils.get(userInfoUrl);            /*              {    "openid":"o3_SC5_eI--mIC9ikI2pvTuZhYnk",    "nickname":"Kong",    "sex":0,    "language":"",    "city":"",    "province":"",    "country":"",    "headimgurl":"https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/hAqkcbxzEJzic0WYl9pHDglAvYBI4iagLsSLXb2ialcxa3Au6UmwibSiadGMtbQia0oAzmzq26k2f1ES4q1HbS6aIYuA\/132",    "privilege":[],    "unionid":"oWgGz1OqVll-tTU4R_DM_zRp7Rjc"                }             */            //将上面的json字符串转换成map对象            HashMap mapUserInfo = gson.fromJson(userInfo, HashMap.class);            //扫码人基础信息            String nickname = (String)mapUserInfo.get("nickname");            String headImgurl=(String)mapUserInfo.get("headimgurl");            String openId=(String)mapUserInfo.get("openid");            //扫码后自动注册(入库)            //先判断是否已注册            boolean isExist = memberService.getUserOpenId(openId);            if(!isExist){                //入库                UcenterMember member = new UcenterMember();                member.setNickname(nickname);                member.setOpenid(openid);                member.setAvatar(headImgurl);                memberService.save(member);            }            UcenterMember ucenterMember = memberService.getUserByOpenId(openId);            String token = JwtUtils.getJwtToken(ucenterMember.getId(), ucenterMember.getNickname());            //因为端口号不同存在跨域问题,cookie不能跨域,所以这里使用url重写            return "redirect:http://localhost:3000?token="+token;        } catch (Exception e) {            e.printStackTrace();            throw new MyException(20010,"登录失败!");        }    }

    总结:
    实现微信授权登录就好比开宝箱,一共需要3把钥匙,第一把钥匙是appid(这个需要在微信开放平台完成注册和认证由平台颁发);通过appid请求固定地址可以生成微信二维码;用户扫描二维码授权后拿到第二把钥匙code(随机唯一值);再通过code去请求固定服务器地址拿到第三把钥匙openid(用户唯一标识)和accessToken(访问凭证);最后再通过openid和accessToken请求固定地址拿到微信用户基本信息。