《SpringBoot项目实战》第二篇—接口用户上下文的设计与实现

news2024/12/22 9:10:58

系列文章导航
第一篇—接口参数的一些弯弯绕绕
第二篇—接口用户上下文的设计与实现
第三篇—留下用户调用接口的痕迹
第四篇—接口的权限控制
第五篇—接口发生异常如何统一处理

本文参考项目源码地址:summo-springboot-interface-demo

前言

大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。

考虑到文字太过寡淡,我先上一张图

在Spring Boot中,默认情况下,每个请求到达时都会分配一个单独的线程来处理,而且请求的发起人也不一定都是同一个人,所以一个请求对应一个用户上下文,并且要求线程隔离,即不同线程的用户上下文互不影响,最后用户上下文还需要随着线程的结束而删除。
本文我会从用户上下文如何构建、如何使用、如何删除这三个方面解释接口用户上下文的设计与实现。

一、接口用户上下文的构建、使用、清除

1. 利用Filter拦截到每一个请求

由于接口散落在各个Controller中,且绝大部分接口都是需要这个用户上下文的(注:也不排除不需要用户上下文的接口存在),所以这里需要统一入口进行创建、销毁。看起来可以使用AOP的方式来实现,
不过这里有一个更合适的方案,利用SpringBoot自带的Filter【javax.servlet.Filter】来实现。

实现起来非常简单,我这边自定义了一个WebFilter,代码如下:

WebFilter.java

package com.summo.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.summo.context.GlobalUserContext;
import com.summo.context.UserContext;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class WebFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {
        try {
            //获取本次接口的唯一码
            String token = java.util.UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
            MDC.put("requestId", token);
            //获取请求头
            HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
            HttpServletResponse httpServletResponse = (HttpServletResponse)servletResponse;
            log.info("当前请求链接为:[{}]", httpServletRequest.getRequestURL());
            //设置用户上下文
            UserContext userContext = new UserContext();
            userContext.setUserId(1L);
            GlobalUserContext.setUserContext(userContext);
            //执行doFilter,这行一定要加,否则程序会中断掉
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        } catch (Exception e) {
            log.error("do doFilter exception", e);
        } finally {
            GlobalUserContext.clear();
            MDC.remove("requestId");
        }
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

这段代码的核心方法是:public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
我们可以在这个方法里面获取到ServletRequest和ServletResponse,这两个类能获取到代表着我们可以操作整个请求过程,这里如何确定当前请求的用户?下面有一张流程图供大家参考:

还有一种做法是使用JWT来当做用户token,因为JWT本身就可以存储一些信息,所以我们就不需要去缓存用户信息了,直接解析JWT即可,这种做法在分布式应用中很常见。

2. 获取当前请求的线程

上面已经获取到用户信息了,现在需要将用户信息放入用户上下文中,但由于**请求的发起人不一定都是同一个人,所以一个请求对应着一个用户上下文,也即一个线程设置一个上下文。**那么这里就需要获取到当前线程才能设置上下文。

获取当前线程有很多办法,这里推荐使用阿里巴巴开源的TTL框架(TransmittableThreadLocal)来实现,功能强大且用法简单。

引入方法如下:

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>transmittable-thread-local</artifactId>
  <version>2.11.1</version>
</dependency>

使用方法如下:

 private static final TransmittableThreadLocal<UserContext> USER_HOLDER = new TransmittableThreadLocal<>();

直接new一个对象就行,而且支持泛型。

3. 用户上下文生命周期管理

对于用户上下文的生命周期管理需要定义3个方法:

  • 设置上下文用户信息;
  • 获取上下文用户信息
  • 清除上下文用户信息

以上方法均为静态方法。

下面是一个简单的例子:
GlobalUserContext.java

package com.summo.context;

import com.alibaba.ttl.TransmittableThreadLocal;

public class GlobalUserContext {

    private static final TransmittableThreadLocal<UserContext> USER_HOLDER = new TransmittableThreadLocal<>();

    /**
     * 设置上下文用户信息
     *
     * @param user 用户信息
     */
    public static void setUserContext(UserContext user) {
        USER_HOLDER.set(user);
    }

    /**
     * 获取上下文用户信息
     */
    public static UserContext getUserContext() {
        return USER_HOLDER.get();
    }

    /**
     * 清除上下文用户信息
     */
    public static void clear() {
        USER_HOLDER.remove();
    }
}

UserContext.java

package com.summo.context;

import lombok.Data;

@Data
public class UserContext {

    /**
     * 用户ID
     */
    private Long userId;

}

调用方式如下:

设置上下文用户信息:GlobalUserContext.setUserContext(userContext);
获取上下文用户信息:GlobalUserContext.getUserContext();
清除上下文用户信息:GlobalUserContext.clear();

4. 用户上下文的使用

获取用户上下文很方便,调用GlobalUserContext.getUserContext();就行了,这里我主要讲一下用户上下文的使用场景。

a. 身份认证

可以将用户的身份认证信息(如用户名、密码、权限等)保存在用户上下文中,在需要进行鉴权的地方进行验证。

b. 用户日志记录

正如《优化接口设计的思路》系列:第三篇—在用户使用系统过程中留下痕迹 的方法三.

c. 防止接口数据越权

举个例子,比如有些业务需要获取当前登录用户的信息、当前登录用户的收藏、当前登录用户的浏览记录,这样的接口总不能在接口上传一个userId吧?真要这样干了,非得给安全骂死。。。
利用用户上下文的话,接口就可以不用传递任何参数获取到当前用户的userId,实现你的需求啦。

d. 跨服务调用

在分布式系统中,可以将用户上下文信息传递给其他服务,以保持用户的一致性和连贯性。

e. 监控和统计

可以将用户上下文中的信息用于系统的监控和统计,如请求的处理时间、请求的次数等。

5. 用户上下文的删除

删除很简单,调用GlobalUserContext.clear();即可,详情可见WebFilter.java内容。

二. 用户登录&认证

上面主要是说怎么获取到接口请求的用户以及怎么设置用户上下文,但没说用户身份是什么时候确认的以及怎么确认的,这里说一下常见做法。
想要确认用户信息就不得不提到用户登录&认证这套东西了,登录的方式非常多,简单的有账号密码登录、手机验证码登录,复杂的就是单点登录、三方授权登录如微信扫码、支付宝扫码等。虽然方式多,但是结果都一样的:确认当前用户身份

当前用户身份确认好之后,系统一般会根据当前用户信息生成一个唯一的并带有时效性的token,放入下一次请求的cookie中。等到下一次请求来的时候,我们就可以从cookie中获取这个token,利用这个token获取这个用户的信息。

由于用户认证情况太多,这里我就不贴代码了,上面是账号密码登录用户认证的的时序图,供大家参考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1139497.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决:谷歌浏览器访问http时,自动转https访问的问题

问题背景&#xff1a;某个系统网站&#xff0c;之前一直用https域名访问&#xff0c;现在改成http域名后&#xff0c;用http访问&#xff0c;谷歌浏览器会自动跳转到https。 解决方法&#xff1a; 在浏览器中输入网址&#xff1a;chrome://net-internals/#hsts -》 在“Delete…

Response Header中不暴露Server(IIS)版本、ASP.NET及相关版本等信息

ASP MVC开发的Web默认情况下会在请求的回应中暴露Server、X-AspNet-Version、X-AspNetMvc-Version、X-Powered-By等相关服务端信息&#xff0c;公开这些敏感信息会存在一定的安全风险。 X-SourceFiles标头用于被IIS / IIS Express中某些调试模块理解&#xff0c;它包含到磁盘上…

首个集成AI的国产操作系统带来了哪些惊喜

在2023年的统信UOS技术开放日暨deepin Meetup北京站上&#xff0c;统信UOS展示了其最新的AI技术和应用&#xff0c;标志着国产操作系统进入了一个全新的AI时代。作为国内领先的操作系统&#xff0c;统信UOS不仅为用户提供了更智能、更高效的服务&#xff0c;还为开发者打开了强…

颈肩肌筋膜炎做什么检查

颈肩肌筋膜炎症状 颈肩背部广泛疼痛酸胀沉重感、麻木感&#xff0c;僵硬、活动受限&#xff0c;可向后头部及上臂放散。疼痛呈持续性&#xff0c;可因感染、疲劳、受凉、受潮等因素而加重。查体见颈部肌紧张&#xff0c;压痛点常在棘突及棘突旁斜方肌、菱形肌等&#xff0c;压…

语雀宕机8小时,是否说明现在高可用架构很脆弱?

系列文章目录 高并发架构去重难&#xff1f;架构必备技能 - 布隆过滤器 当Dubbo遇到高并发&#xff1a;探究流量控制解决方案 主从选举机制&#xff0c;架构高可用性的不二选择 面试Dubbo &#xff0c;却问我和Springcloud有什么区别&#xff1f; 消息队列选型——为什么选择R…

06 rpm和yum

rpm -ivh 软件包名 安装软件 yum仓库的配置和基本的命令使用 1 配置本地仓库文件 local.repo cd /etc/yum.repos.d/ vi local.repo 输入 [c6-local] nameCentOS-$releasever - local baseurlhttp://192.168.230.128/centos/6/os/x86_64 gpgcheck0 enabled1 gpgkeyfile:///et…

基于SpringBoot+SSM苍穹外卖之实战项目

功能介绍&#xff1a; 基于SpringBootSSM苍穹外卖之企业级实战项目。该系统定位的是一款为餐饮企业&#xff08;餐厅、饭店&#xff09;定制的软件产品&#xff0c;在线外卖系统&#xff0c;顾客可以通过网站或者手机 App 订购餐点。 主要功能&#xff1a; 管理端 模块描述登…

小红书为什么流量不好,小红书笔记质量评判标准有哪些?

我们都知道小红书平台强大的种草力与传播力&#xff0c;需要依靠优质笔记的输出来达成。但是很多时候&#xff0c;我们撰写了笔记&#xff0c;却无法被收录&#xff0c;获得流量&#xff0c;这都是因为笔记质量出现了问题。那么小红书为什么流量不好&#xff0c;小红书笔记质量…

20PIN直插百兆网络变压器 H82022D

这是一颗20PIN直插百兆网络变压器 PIN距为2.0 &#xff0c;与H82002D是一样的&#xff0c;但内部线路刚好相反 本产品我司可支持一件代发&#xff0c;并支持出口 专业网络变压器&#xff0c;尽在Hqst

微信小程序分销商城源码系统+带PC端+收银台+付费会员卡 带完整搭建教程

在当今数字化的世界里&#xff0c;微信小程序已成为商业发展的重要一环。而其中&#xff0c;分销商城更是备受商家和消费者青睐的一种业务模式。为了帮助您快速搭建起自己的微信小程序分销商城&#xff0c;小编来给大家分享一款微信小程序分销商城源码系统&#xff0c;带PC端的…

iOS发布证书.p12文件无密码解决办法及导出带密码的新.p12文件方法

摘要&#xff1a; 本文将以iOS技术博主身份&#xff0c;分享解决使用无密码的.p12文件发布应用时遇到的问题&#xff0c;并介绍如何以带密码的方式重新导出.p12文件的方法。通过本文提供的步骤&#xff0c;开发者可以顺利完成证书的发布流程。 引言 在iOS应用发布过程中&…

电脑监控软件系统有哪些

电脑监控软件系统在当今信息化时代的应用越来越广泛&#xff0c;它们可以帮助企业或个人解决许多问题。 电脑监控软件有哪些功能&#xff1a; 1、实时监控&#xff1a;对电脑的屏幕、键盘、鼠标等设备进行实时监控&#xff0c;以便及时发现异常情况并采取相应的措施。 2、数据采…

作为前端开发,你应该知道的这十几个在线免费工具

​偶然刷到知乎一位前端大佬 表歌 多篇优秀实用的文章&#xff0c;真的发现宝藏了 以下内容就是他在知乎分享的十几个在线免费工具 1. 页面设计检查清单&#xff1a;https://www.checklist.design/ 页面设计检查清单 通过清单可以检查一些常用容易忽略的设计要素。 2. 背景色…

[UDS] --- WriteDataByIdentifier 0x2E

1 0x2E功能描述 根据ISO14119-1标准中所述&#xff0c;诊断服务2E主要用于Client向Server(ECU)通过DID的方式写入相关的数据。 2 0x2E应用场景 一般而言&#xff0c;对于2E诊断服务&#xff0c;主要应用场景为以下场合&#xff1a; 在整车下线的过程中写入相关配置信息&…

AMEYA360:罗姆开发出LiDAR用的120W高输出功率激光二极管“RLD90QZW8”

全球知名半导体制造商ROHM(总部位于日本京都市)开发出一款高输出功率半导体激光二极管“RLD90QZW8”&#xff0c;该产品非常适用于搭载测距和空间识别用LiDAR*1的工业设备领域的AGV(Automated Guided Vehicle/无人搬运车)和服务机器人、消费电子领域的扫地机器人等应用。 近年来…

图片能预览但无法打开

# -*- coding: utf-8 -*- # Time : 2022/1/14 9:35 # Author : Cocktail_pyfrom PIL import Image import imghdrfilename xxx.jpg # 查看图片是什么类型 print(imghdr.what(filename))# 转换成能打开的类型 image Image.open(filename) image image.convert("RGB&…

leetcode:1323. 6 和 9 组成的最大数字(python3解法)

难度&#xff1a;简单 给你一个仅由数字 6 和 9 组成的正整数 num。 你最多只能翻转一位数字&#xff0c;将 6 变成 9&#xff0c;或者把 9 变成 6 。 请返回你可以得到的最大数字。 示例 1&#xff1a; 输入&#xff1a;num 9669 输出&#xff1a;9969 解释&#xff1a; 改变…

一文讲明白阿里云ace认证的含金量!

《中国云计算行业洞察与人才分析》显示&#xff0c;云计算领域人才月均薪酬在1W元以上的占比高达93.7%&#xff0c;3万元以上占比仍达24.7%。 相比之下&#xff0c;云计算人才远超互联网人才薪酬平均线&#xff0c;反映出市场对于其专业技术人才的需求。 云计算架构师、云计算…

设计师必备!5款一键抠图神器,让你轻松实现抠图需求!

但随着实用工具的不断进步&#xff0c;市场上出现了许多挖掘神器&#xff0c;利用人工智能技术轻松实现了一键挖掘的黑色技术操作。今天我就帮大家评价一下市面上80%的抠图工具&#xff0c;最后把这5个工具留在工具箱里&#xff0c;真的超级好用&#xff01;一键抠图5秒结束&am…