《优化接口设计的思路》系列:第二篇—接口用户上下文的设计与实现

在这里插入图片描述
系列文章导航
《优化接口设计的思路》系列:第一篇—接口参数的一些弯弯绕绕
《优化接口设计的思路》系列:第二篇—接口用户上下文的设计与实现

前言

大家好!我是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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);}@Overridepublic 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");}}@Overridepublic 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.rhkb.cn/news/133077.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Peppertype.ai:人工智能内容营销平台

【产品介绍】 名称 Peppertype.ai 具体描述 Peppertype.ai是一个AI驱动的文章生成工具&#xff0c;可以帮助你在几秒钟内为各种渠道创建吸引人 的内容。无论你是想要写广告文案、社交媒体标题、博客大纲还是网站内容&#xff0c;Peppertype…

基于SpringBoot蜗牛兼职网的设计与实现【附PPT|万字文档(LW)和搭建文档】

主要功能 前台界面&#xff1a; ①首页、兼职信息推荐、查看更多等 ②职位申请、申请日期、上传简历、点击下载简历、留言反馈等 ③个人中心、上传图片、更新信息等 后台登录&#xff1a; ①用户登录&#xff1a; 个人中心、修改密码、个人信息、职位申请管理 ②企业登录&…

java在mysql中查询内容无法塞入实体类中,报错 all elements are null

目录 一、问题描述二、解决方案 一、问题描述 java项目中整体配置了mysql的驼峰式字段匹配规则。 mybatis.configuration.map-underscore-to-camel-casetrue由于项目需求&#xff0c;需要返回字段为file_id&#xff0c;file_url&#xff0c;并且放入实体类中&#xff0c;实体…

赢麻了!smardaten闷声干大事,竟然用无代码开发了复杂小程序!

本文目录 一、【前言】二、移动端项目实战&#xff1a;女性关爱云服务平台2.1 项目背景2.2 6大场景功能拆解&#xff08;1&#xff09;场景1-首页&#xff08;2&#xff09;场景2-找活动&#xff08;3&#xff09;场景3-找组织&#xff08;4&#xff09;场景4-找服务&#xff0…

无频闪护眼灯哪个好一点?盘点无频闪减蓝光护眼灯

可以肯定的是&#xff0c;护眼灯一般可以达到护眼的效果。看书和写字时&#xff0c;光线应适度&#xff0c;不宜过强或过暗&#xff0c;护眼灯光线较柔和&#xff0c;通常并不刺眼&#xff0c;眼球容易适应&#xff0c;可以防止光线过强或过暗导致的用眼疲劳。如果平时生活中需…

Unity中 UI Shader的基本功能

文章目录 前言一、实现思路1、暴露一个 2D 类型的属性来接受UI的纹理2、设置shader的层级为TransParent半透明渲染层级&#xff0c;一般UI都是在这个渲染层级3、更改混合模式&#xff0c;是 UI 使用的纹理&#xff0c;该透明的地方透明 二、代码实现 前言 Unity中 UI Shader的…

视频怎么压缩?把视频压缩的小一点这样做

视频压缩在我们的生活和工作中有着广泛的应用需求&#xff0c;是一种减少视频文件大小的方法&#xff0c;可以给我们带来以下几个方面的作用&#xff1a; 1、减少存储空间占用&#xff1a;视频压缩可以显著减少视频的大小&#xff0c;从而腾出更多的存储空间&#xff0c;对于手…

创建一个简单的外卖订餐系统

在今天的快节奏生活中&#xff0c;外卖订餐系统已经成为了人们日常生活中不可或缺的一部分。这些系统通过在线点餐和配送服务&#xff0c;为用户提供了便捷的用餐体验。在本文中&#xff0c;我们将创建一个简单的外卖订餐系统&#xff0c;使用Python和Flask框架构建后端&#x…

华为数通方向HCIP-DataCom H12-821题库(单选题:341-360)

第341题 在BGP中代表对等体之间已经建立连接的状态是以下哪一种? A、Active B、Connect C、Established D、Open 答案:C 第342题 以下关于路由选择工具的描述,错误的是哪一项? A、访问控制列表用于匹配路由信息或者数据包的地址,过滤不符合条件的路由信息或数据包 …

EXE文件加密器

EXE文件加密器V3.0&#xff0c;主要是用于对EXE文件进行加密 有需要的朋友下载 点我下载

Writesonic:博客和内容创作者的终极写作助手

【产品介绍】 产品名称 Writesonic 上线时间 成立于2020年 具体介绍 Writesonic是一个强大的人工智能写作助手&#xff0c;它使用自然语言处理&#xff08;NLP&#xff09;和机器学习算法来生成内容&#xff0c;这些内容不仅写得好&#xff0c;而且还为SEO和转…

归并排序和快速排序的两种实现

在此之前我们已经介绍过归并排序和快速排序&#xff1a;浅谈归并排序与快速排序&#xff0c;但其中的实现都是基于递归的。本文将重新温故这两种算法并给出基于迭代的实现。 目录 1. 归并排序1.1 基于递归1.2 基于迭代 2. 快速排序2.1 基于递归2.2 基于迭代 1. 归并排序 1.1 基…

JavaEE初阶(5)多线程案例(定时器、标准库中的定时器、实现定时器、线程池、标准库中的线程池、实现线程池)

接上次博客&#xff1a;JavaEE初阶&#xff08;4&#xff09;&#xff08;线程的状态、线程安全、synchronized、volatile、wait 和 notify、多线程的代码案例&#xff1a;单例模式——饿汉懒汉、阻塞队列&#xff09;_di-Dora的博客-CSDN博客 目录 多线程案例 定时器 标准…

MFC网络编程2——异步套接字

从上一节&#xff08;MFC网络编程1——网络基础及套接字 &#xff09;中&#xff0c;我们了解了网络的部分基础知识以及套接字的使用&#xff0c;这一节&#xff0c;我们学习异步套接字的使用。  Windows套接字在两种模式下执行I/O操作&#xff0c;阻塞模式和非阻塞模式。在阻…

麒麟v10安装Redis(ARM架构)

下载Redis安装包 华为开源镜像站_软件开发服务_华为云 上面的选择一个下载 或者用命令下载 wget https://repo.huaweicloud.com/kunpeng/yum/el/7/aarch64/Packages/bigdata/redis-5.0.5-1.el7.aarch64.rpm 检查是否已经安装Redis rpm -qa | grep redis将包卸载掉 rpm -e -…

探索数据库管理的利器 - PHPMyAdmin

有一个项目&#xff0c;后端由博主独自负责&#xff0c;最近需要将项目交接给另一位同事。在项目初期&#xff0c;博主直接在数据库中使用工具创建了相关表格&#xff0c;并在完成后利用PhpMyAdmin生成了一份数据字典&#xff0c;供团队使用。然而&#xff0c;在随后的开发过程…

Linux与shell命令行学习

文章目录 走进shell基本的bash shell命令2.1 遍历目录 cd2.2 查看文件和目录列表 ls2.3 创建文件 touch2.4 复制文件 cp2.5 自动补全 tab2.6 链接文件 ln2.7 文件重命名 mv2.8 删除文件 rm2.9 创建目录 mkdir2.10 删除目录 rmdir2.11 查看文件类型 file2.12 查看整个文件 cat、…

常用的管理方法论分享

在多年的软件研发团队管理过程中&#xff0c;我积累了一些方法&#xff0c;跟大家分享。 软件研发团队非常注重成本和效率&#xff0c;大多数管理者是从一线开发者升上去的。往往非常善于解决技术问题&#xff0c;但不知道如何管理下属&#xff0c;如何对待上级&#xff0c;如…

【深度学习】 Python 和 NumPy 系列教程(廿四):Matplotlib详解:2、3d绘图类型(10)3D箱线图(3D Box Plot)

目录 一、前言 二、实验环境 三、Matplotlib详解 1、2d绘图类型 2、3d绘图类型 0. 设置中文字体 1. 3D线框图&#xff08;3D Line Plot&#xff09; 2. 3D散点图&#xff08;3D Scatter Plot&#xff09; 3. 3D条形图&#xff08;3D Bar Plot&#xff09; 4. 3D曲面图…

mysql的索引结构

索引概述 索引&#xff08; index &#xff09;是帮助 MySQL 高效获取数据的数据结构 ( 有序 ) 。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&#xff08;指向&#xff09;数据&#xff0c; 这样就可以在这些…