一个关于面经的网站开发全程记录--3

154 阅读5分钟

一个关于面经的网站开发全程记录--3

总览:搞定登录注册、整合短信接口实现手机号验证码注册(用的是简单的身份验证appcode)、单点登录(采用Token的方式实现,也有其他方法流入session广播,redis+cookie)

前提:准备相应的工具类

JWTUtils(网上大把写好的,拿过来直接用即可)

public class JwtUtils {

    //常量
    public static final long EXPIRE = 1000 * 60 * 60 * 24; //token过期时间
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; //秘钥

    //生成token字符串的方法
    public static String getJwtToken(String id, String nickname){

        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")

                .setSubject("guli-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))

                .claim("id", id)  //设置token主体部分 ,存储用户信息
                .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");
    }
}

MD5Utils进行密码加密,数据库中存储密文,也是网上找的

public static String encrypt(String strSrc) {
    try {
        char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
                '9', 'a', 'b', 'c', 'd', 'e', 'f' };
        byte[] bytes = strSrc.getBytes();
        MessageDigest md = MessageDigest.getInstance("MD5");
        md.update(bytes);
        bytes = md.digest();
        int j = bytes.length;
        char[] chars = new char[j * 2];
        int k = 0;
        for (int i = 0; i < bytes.length; i++) {
            byte b = bytes[i];
            chars[k++] = hexChars[b >>> 4 & 0xf];
            chars[k++] = hexChars[b & 0xf];
        }
        return new String(chars);
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        throw new RuntimeException("MD5加密出错!!+" + e);
    }
}

随机生成验证码,自己写也可以

public class RandomUtil {

   private static final Random random = new Random();

   private static final DecimalFormat fourdf = new DecimalFormat("0000");

   private static final DecimalFormat sixdf = new DecimalFormat("000000");

   public static String getFourBitRandom() {
      return fourdf.format(random.nextInt(10000));
   }

   public static String getSixBitRandom() {
      return sixdf.format(random.nextInt(1000000));
   }

   /**
    * 给定数组,抽取n个数据
    * @param list
    * @param n
    * @return
    */
   public static ArrayList getRandom(List list, int n) {

      Random random = new Random();

      HashMap<Object, Object> hashMap = new HashMap<Object, Object>();

      // 生成随机数字并存入HashMap
      for (int i = 0; i < list.size(); i++) {

         int number = random.nextInt(100) + 1;

         hashMap.put(number, i);
      }

      // 从HashMap导入数组
      Object[] robjs = hashMap.values().toArray();

      ArrayList r = new ArrayList();

      // 遍历数组并打印数据
      for (int i = 0; i < n; i++) {
         r.add(list.get((int) robjs[i]));
         System.out.print(list.get((int) robjs[i]) + "\t");
      }
      System.out.print("\n");
      return r;
   }
}

RedisTemplate,搞一个redis配置类,写一个自己的redisTemplate,可以网上找

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    RedisSerializer<String> redisSerializer = new StringRedisSerializer();
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);
    template.setConnectionFactory(factory);
    //key序列化方式
    template.setKeySerializer(redisSerializer);
    //value序列化
    template.setValueSerializer(jackson2JsonRedisSerializer);
    //value hashmap序列化
    template.setHashValueSerializer(jackson2JsonRedisSerializer);
    return template;
}

正题:

controller接口:(具体干啥看注解)

@ApiOperation(value = "实现单点登录")
@PostMapping(value = "/login")
public R login(@RequestBody LoginVo loginVo){
    //LoginVo 是封装的vo类,用于接收前端传递过来的数据,用于登录
    String token = userService.login(loginVo);
    //返回一个token字符串即可,token字符串中包含了用户的登录信息,前端验证token即可实现
    return R.ok().data("token",token);
}

@ApiOperation(value ="用户注册")
@PostMapping(value = "register")
public R register(@RequestBody RegisterVo registerVo){
    //RegisterVo封装的注册vo类,包换注册时的输入信息
    userService.register(registerVo);
    return R.ok();
}

@ApiOperation(value = "根据token获取登录的部分用户信息")
@GetMapping(value = "user/getUserInfo")
public R getUserInfo(HttpServletRequest request){
    try {
        //用jwt工具类中的方法,解析前端的请求,从token中获取到当前用户id
        String userId = JwtUtils.getMemberIdByJwtToken(request);
        //根据用户id查询数据库得到用户的详细信息  这边是返回了一个LoginVo,放回一个User其实更好
        LoginVo loginVo = userService.getUserInfo(userId);
        return R.ok().data("info",loginVo);
    } catch (Exception e) {
        e.printStackTrace();
        throw new InterviewException(20001,"error");
    }
}


@ApiOperation(value = "发送短信接口")
@PostMapping(value = "send/{phone}")
public R sendSmsCode(@PathVariable String phone){
    //如果在redis中可以找到对应的key 返回其value值,进行比较判断 相等的话返回ok
    String code = redisTemplate.opsForValue().get(phone);
    if(!StringUtils.isEmpty(code)) return R.ok();
    //如果在redis中找不到,随机生成验证码
    code = RandomUtil.getFourBitRandom();
    //发送验证码
    boolean isSend = userService.send(phone,code);
    if(isSend){
        //设置存入redis中并设置过期时间
        redisTemplate.opsForValue().set(phone,code,1, TimeUnit.MINUTES);
        return R.ok();
    } else{
        return R.error().message("发送短信新失败");
    }
}


@ApiOperation(value = "根据用户id修改用户信息")
@PostMapping(value = "updateUserInfo")
public R updateUserInfo(@RequestBody User user){
    boolean res = userService.updateById(user);
    if(res){
        return R.ok();
    }else{
        return R.error();
    }
}

service层

@Autowired
private RedisTemplate<String,String> redisTemplate;

@Override
public String login(LoginVo loginVo) {
    //从登录的vo类中获取属性
    String phone = loginVo.getPhone();
    String password = loginVo.getPassword();

    //判断是否为空
    if(phone.isEmpty() || password.isEmpty()) {
        throw new InterviewException(20001,"error");
    }

    //获取会员
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.eq("phone",phone);
    User user = baseMapper.selectOne(wrapper);
    if(user == null){
        throw new InterviewException(20001,"error");
    }

    //校验密码   加密之后判断
    if(!MD5.encrypt(password).equals(user.getPassword())){
        throw new InterviewException(20001,"error");
    }

    //前边都成功之后使用JWT生成字符串   将用户id和用户名称放入到token中
    String token = JwtUtils.getJwtToken(user.getId(),user.getUsername());

    return token;
}

//返回类型为LoginVo,其实感觉返回User更好
@Override
public LoginVo getUserInfo(String userId) {
    //根据用户id查询用户信息
    User user = baseMapper.selectById(userId);
    LoginVo loginVo = new LoginVo();
    //拷贝 源 -> 目标
    BeanUtils.copyProperties(user,loginVo);
    return loginVo;
}

@Override
public void register(RegisterVo registerVo) {

    //获取前端传过来的注册信息
    String username = registerVo.getUsername();
    String phone = registerVo.getPhone();
    String password = registerVo.getPassword();
    String code = registerVo.getCode();

    //avatar注册时候有一个默认头像TODO默认头像存储在 阿里OSS服务中

    //校验参数
    if(username.isEmpty() || phone.isEmpty()
    || password.isEmpty() || code.isEmpty()){
        throw new InterviewException(20001,"error");
    }

    //校验验证码
    String smscode = redisTemplate.opsForValue().get(phone);
    if(!code.equals(smscode)){
        throw new InterviewException(20001,"error");
    }

    //查询数据库中是否存在相同手机号
    Integer count = baseMapper.selectCount(new QueryWrapper<User>().eq("phone", phone));
    if(count.intValue() > 0) {
        throw new InterviewException(20001,"error");
    }

    //拆分微服务时候利用雪花算法生成全局唯一用户ID
    //目前使用MP注解   @TableId(value = "id", type = IdType.ID_WORKER_STR)  自动填充ID


    //利用MD5进行密码加密,数据库存储密文
    password = MD5.encrypt(password);

    //添加注册到数据库
    User user = new User();
    BeanUtils.copyProperties(registerVo,user);
    baseMapper.insert(user);
}

@Override
public boolean send(String phone, String code) {

    //设置自己短信服务的  我直接用云市场的现成的 直接用即可
    String host = SmsConstProperty.APP_HOST;
    String path = SmsConstProperty.APP_PATH;
    String method = SmsConstProperty.APP_METHOD;
    String appcode = SmsConstProperty.APP_CODE;
    
    //下边的代码都是商家弄好的模板
    Map<String, String> headers = new HashMap<String, String>();
    //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
    headers.put("Authorization", "APPCODE " + appcode);


    Map<String, String> querys = new HashMap<String, String>();
    //注册的手机号
    querys.put("mobile", phone);
    //短信模板需要自己设置  用自己的模板
    querys.put("templateId", "M72CB42894");
    //验证码这边我测试用的是固定的数字,前边有随机生成验证码的类,直接使用
    querys.put("value", "1234");

    Map<String, String> bodys = new HashMap<String, String>();
    try {
        /**
         * 重要提示如下:
         * HttpUtils请从
         * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
         * 下载
         *
         * 相应的依赖请参照
         * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
         */
        HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
        StatusLine statusLine = response.getStatusLine();
        int statusCode = statusLine.getStatusCode();
        if(statusCode == 200){
            return true;
        }
    } catch (Exception e) {
        e.printStackTrace();
        throw new InterviewException(20001,"error");
    }
    return false;
}

进行测试即可

以上就是单点登陆,短信验证注册等功能接口的实现

今天就到这

欢迎指正