仿12306项目(1)

news/2025/2/26 16:56:08

雪花算法

        为了高效的生成有序且唯一的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进行查询手机号,如果不存在,插入一条记录,最后生成验证码,代码如下:

java">    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<>返回给前端。 

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

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

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

java">    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。

java">    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,因此可以把它封装成一个工具类。

java">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校验


http://www.niftyadmin.cn/n/5868961.html

相关文章

物联网平台建设方案一

系统概述 构建物联网全域支撑服务能力&#xff0c;为实现学院涵盖物联网设备的全面感知、全域互联、全程智控、全域数字基底、全过程统筹管理奠定基础&#xff0c;为打造智能化提供坚实后台基石。 物联网平台向下接入各种传感器、终端和网关&#xff0c;向上通过开放的实施分…

大语言模型中的梯度值:深入理解与应用

1. 摘要 ​ 梯度是微积分中的一个基本概念&#xff0c;在机器学习和深度学习中扮演着至关重要的角色。特别是在大语言模型&#xff08;LLM&#xff09;的训练过程中&#xff0c;梯度指导着模型参数的优化方向。 本报告首先由浅入深地介绍梯度的概念&#xff0c;包括其数学定义…

llama.cpp 一键运行本地大模型 - Windows

文章目录 llama.cpp 一键运行本地大模型 - Windows嘿&#xff0c;咱来唠唠 llama.cpp 这玩意儿&#xff01;gguf 格式是啥&#xff1f;咱得好好说道说道基座模型咋选&#xff1f;所需物料&#xff0c;咱得准备齐全咯核心命令&#xff0c;得记牢啦运行方式咋选&#xff1f;测试应…

Pytorch实现之混合成员GAN训练自己的数据集

简介 简介:提出一种新的MMGAN架构,使用常见生成器分布的混合对每个数据分布进行建模。由于生成器在多个真实数据分布之间共享,高度共享的生成器(通过混合权重反映)捕获分布的公共方面,而非共享的生成器捕获独特方面。 论文题目:MIXED MEMBERSHIP GENERATIVE ADVERSARI…

html中的元素(2)

在用块级元素完成网页的组织和布局以后&#xff0c;要为其中的每一个小区块添加内容&#xff0c;就需要用到行内元素&#xff1a; 1.字体样式元素 <!DOCTYPE html> <html> <head><meta charset"utf-8"><title>HTML5 保留的文本格式元…

Apache SeaTunnel 构建实时数据同步管道(最新版)

文章作者 王海林 白鲸开源 数据集成引擎研发 Apache SeaTunnel Committer & PMC Member&#xff0c;Apache SkyWalking Committer&#xff0c;多年平台研发经验&#xff0c;目前专注于数据集成领域。 导读 在当今数字化快速发展的时代&#xff0c;数据已然成为企业决策…

鸿蒙5.0实战案例:har和hsp的转换

往期推文全新看点&#xff08;文中附带全新鸿蒙5.0全栈学习笔录&#xff09; ✏️ 鸿蒙&#xff08;HarmonyOS&#xff09;北向开发知识点记录~ ✏️ 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ ✏️ 鸿蒙应用开发与鸿蒙系统开发哪个更有前景&#…

大白话Vue 双向数据绑定的实现原理与数据劫持技术

咱们来好好唠唠Vue双向数据绑定的实现原理和数据劫持技术&#xff0c;我会用特别通俗的例子给你讲明白。 啥是双向数据绑定 你可以把双向数据绑定想象成一个神奇的“同步器”。在网页里有两部分&#xff0c;一部分是数据&#xff0c;就像你记在小本本上的信息&#xff1b;另一…