首页 前端知识 仿12306项目(1)

仿12306项目(1)

2025-02-26 11:02:27 前端知识 前端哥 101 294 我要收藏

雪花算法

        为了高效的生成有序且唯一的ID,可以采用雪花算法来进行实现,为什么不去采用UUID呢?首先,UUID是一个128位的值,相较于雪花算法生成的64位的值,长了很多,在数据库中存储时耗费的时间更长,UUID生成后没有顺序关系,导致它不适合做主键,雪花算法排序具有可读性,在一些状况下更容易地追踪。

雪花算法的原理

IdUtil.getSnowflake有两个参数,第一个时数据中心的编号,第二个时机器的编号,可以包装每台机器生成的id不重复。最高位是0,1-bit,表示正数,之后41bit位的时间戳,后面的10bit位是数据中心和工作机器的id(保证每一台机器生成同一时间的id不同),最后12位是序列号,保证一个机器1ms内生成id不同,大约4096个,在并发的情况下。

登录和注册

        采用注册和登录二合一,当手机号不存在,把手机号增加到系统中,然后登录,手机号存在,直接登录。

短信验证码接口的开发

        用户输入手机号,对应的接口需要传递参数,不需要返回值,因为是发送到手机上的。为了规范,可能之后的开发设计到传入的参数不同,因此可以封装成一个sendReq专门用来传参,为了保证手机号是11位,在上面加上@Pattern(regexp = "^\\d{10}$",message="手机号格式错误")。

        service层调用mapper进行查询手机号,如果不存在,插入一条记录,最后生成验证码,代码如下:

    public void sendCode(MemberSendCodeReq req) {
        String mobile = req.getMobile();
        MemberExample memberExample = new MemberExample();
        memberExample.createCriteria().andMobileEqualTo(mobile);
        List<Member> list = memberMapper.selectByExample(memberExample);

        // 如何手机号不存在,插入一条记录
        if(CollUtil.isEmpty(list)) {
            LOG.info("手机号不存在,插入一条记录");
            Member member = new Member();
            member.setId(SnowUtil.getSnowflakeNextId());
            member.setMobile(mobile);
            memberMapper.insert(member);
        }else {
            LOG.info("手机号已存在");
        }
        // 生成验证码
        // String code= RandomUtil.randomString(4);
        // 为了测试
        String code = "8888";
        LOG.info("生成的短信验证码:{}",code);

    }

为了使前端能够更好的处理业务,将所有的统一封装成CommResp<>返回给前端。 

    @PostMapping("/send-code")
    public CommonResp<Long> sendCode(@Valid MemberSendCodeReq req) {
        memberService.sendCode(req);
        return new CommonResp<>();
    }

登录接口的话,是需要传手机号和短信验证码的。输入验证码,点击登录,后端的接口进行参数校验,需要检验手机号是否存在,校验短信验证码更新状态

由于在设计发送验证码时的需要查询数据库的是否存在某个用户,登录是也要查询,因此可以将其封装成一个方法,减少重复代码。

    private Member selectByMobile(String mobile) {
        MemberExample memberExample = new MemberExample();
        memberExample.createCriteria().andMobileEqualTo(mobile);
        List<Member> list = memberMapper.selectByExample(memberExample);
        if(CollUtil.isEmpty(list)) {
            return null;
        }else {
            return list.get(0);
        }
    }

登录时如果手机不存在,即手机号没有在页面显示,就抛出异常,验证码不一致亦需要抛出异常,用户有很多属性,需要登录成功后返给前端,由于有密码这样的敏感字段,因此可以将返回值进行一个封装。

登录方式的设计

单点登录的设计

        所谓的单点登录,就是一次登录,到处访问。方式1:redis+token,token是一个随机的字符串,为每个用户登录生成的,每一次登录生成一个新的token不能够使用用户id,因为用户id相同,r容易被破解,将token作为key,将用户信息作为value,存放到redis中去,这样后端将登录的信息缓存起来,再把token返给前端,前端每个请求把token给带上,这就实现了单点登陆的功能。jwt登录生成的用户token是有意义的,是用户信息的加密数据,通过加密数据可以反向解出当前登录的是哪一个数据。

JWT的原理

结构:Header头部信息,主要包含令牌类型和声明了JWT的签名算法等信息默认是使用HS265算法进行加密。。Payload载荷信息,主要承载各种声明并传递明文数据。Signatrue签名,用于校验数据。

流程:

  1. 认证请求

    用户尝试登录或执行需要认证的操作时,向服务器发送包含用户名和密码的请求。
  2. 生成JWT

    一旦用户的凭据被验证,服务器会创建一个JWT。此JWT包含了必要的声明,比如用户的ID或者角色,然后用服务器端的密钥对其进行签名。
  3. 返回JWT给客户端

    服务器将生成的JWT返回给客户端。客户端应该保存这个JWT(通常是在HTTP Only的cookie中或者本地存储中)。
  4. 访问受保护资源

    当客户端想要访问受保护的资源时,它会在请求头中携带这个JWT(通常是在Authorization头中,格式为Bearer <token>)。
  5. 验证JWT

    每次收到带有JWT的请求时,服务器都会检查JWT的有效性,包括验证签名是否正确、检查声明中的过期时间等。如果一切正常,服务器则允许访问请求的资源。

存在问题及解决方案

  • token被解密,可以加盐进行解决
  • token被拿到第三方去使用,可以使用限流

由于前端需要Token,因此可以生成后根据Resp返回给前端,登录成功后,进行setToken。

    MemberLoginResp memberLoginResp = BeanUtil.copyProperties(memberDB, MemberLoginResp.class);
        Map<String,Object> map = BeanUtil.beanToMap(memberLoginResp);
        String key = "month12306";
        String token = JWTUtil.createToken(map,key.getBytes());
        memberLoginResp.setToken(token);
        return memberLoginResp;

由于不仅仅一个地方使用到token,因此可以把它封装成一个工具类。

package com.month.train.common.util;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTPayload;
import cn.hutool.jwt.JWTUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

public class JwtUtil {
    private static final Logger LOG = LoggerFactory.getLogger(JwtUtil.class);
    private static final String key = "month12306";

    public static String createToken(Long id, String mobile) {
//        LOG.info("开始生成JWT token,id:{},mobile:{}", id, mobile);
//        GlobalBouncyCastleProvider.setUseBouncyCastle(false);
        DateTime now = DateTime.now();
        DateTime expTime = now.offsetNew(DateField.HOUR, 24);
        Map<String, Object> payload = new HashMap<>();
        // 签发时间
        payload.put(JWTPayload.ISSUED_AT, now);
        // 过期时间
        payload.put(JWTPayload.EXPIRES_AT, expTime);
        // 生效时间
        payload.put(JWTPayload.NOT_BEFORE, now);
        // 内容
        payload.put("id", id);
        payload.put("mobile", mobile);
        String token = JWTUtil.createToken(payload, key.getBytes());
        LOG.info("生成JWT token:{}", token);
        return token;
    }

    public static boolean validate(String token) {
//        LOG.info("开始JWT token校验,token:{}", token);
//        GlobalBouncyCastleProvider.setUseBouncyCastle(false);
        JWT jwt = JWTUtil.parseToken(token).setKey(key.getBytes());
        // validate包含了verify
        boolean validate = jwt.validate(0);
        LOG.info("JWT token校验结果:{}", validate);
        return validate;
    }

    public static JSONObject getJSONObject(String token) {
//        GlobalBouncyCastleProvider.setUseBouncyCastle(false);
        JWT jwt = JWTUtil.parseToken(token).setKey(key.getBytes());
        JSONObject payloads = jwt.getPayloads();
        payloads.remove(JWTPayload.ISSUED_AT);
        payloads.remove(JWTPayload.EXPIRES_AT);
        payloads.remove(JWTPayload.NOT_BEFORE);
        LOG.info("根据token获取原始内容:{}", payloads);
        return payloads;
    }

    public static void main(String[] args) {
        createToken(1L, "123");

        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE3MzU0NjIzMTYsIm1vYmlsZSI6IjEyMyIsImlkIjoxLCJleHAiOjE3MzU1NDg3MTYsImlhdCI6MTczNTQ2MjMxNn0.Xp6cMnjQnsizwqqxJf52yeDytkGyb5_XA-39j0K2jf4";
        validate(token);

        getJSONObject(token);
    }
}

对于正常的业务请求,需要增加一层登录校验,用来保证登录的人才能够操作,因此可以在gateway模块下增加登录拦截器以及JWT校验

转载请注明出处或者链接地址:https://www.qianduange.cn//article/21575.html
标签
评论
发布的文章

库制作与原理

2025-02-26 11:02:28

仿12306项目(1)

2025-02-26 11:02:27

2.25 链表 2 新建链表 82

2025-02-26 11:02:26

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!