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

news2024/11/17 7:19:32

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

前言

大家好!我是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/1012189.html

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

相关文章

财富潮涌:银行发展绿色经济创新路径

在《玩转金融新“绿”潮&#xff0c;银行纷纷亮大招》内容中&#xff0c;我们带大家了解了商业银行有关绿色信贷产品、绿色债券产品及绿色投资的内容。今天&#xff0c;我们将继续带大家了解商业银行绿色金融实践路径中关于绿色财富、绿色服务和绿色运营的分析。 在绿色金融实…

数字中国背景下,企业数字化转型需要“强IT”

随着科技的快速发展和全球商业环境的不断变化&#xff0c;中国企业对灵活性、创新性、全球化和效率的需求是迫切的&#xff0c;进行数字化转型来支撑企业的业务变革、组织优化已是业界共识。如何根据企业的实际情况进行数字化转型对企业管理层&#xff0c;特别是CIO提出了新的挑…

解决 Axios 跨域问题,轻松实现接口调用

跨域是指访问另外一个域的资源&#xff0c;由于浏览器的同源策略&#xff0c;默认情况下使用 XMLHttpRequest 和 Fetch 请求时是不允许跨域的。跨域的根本原因是浏览器的同源策略&#xff0c;这是由浏览器对 JavaScript 施加的安全限制。 Axios 跨域常见报错 跨域请求被阻止 (…

电子凭证试点深化后,企业如何应对?百望云提供电子凭证一体化解决方案!

今年5月&#xff0c;财政部、税务总局、人民银行、国务院国资委、国家档案局、标准委、国电联办、民航局、国铁集团九单位组织召开电子凭证会计标准深化试点启动会。 电子会计凭证的格式非常多样化&#xff0c;电子会计凭证的处理一直是企业的难点。此次试点工作虽然对企业的财…

SSL证书为什么要选付费?

SSL证书已经越来越多的使用在网站&#xff0c;小程序和APP上&#xff0c;对于保障网络安全&#xff0c;加密数据信息有至关重要的作用。随着SSL证书市场的发展&#xff0c;各种类型的证书产品也在不断丰富&#xff0c;用户在选择SSL证书时&#xff0c;除了可以购买付费证书&…

2023年8月京东白酒行业数据分析(京东数据开放平台)

中秋、国庆“双节”即将到来之际&#xff0c;白酒市场中越来越多的促销手段浮现&#xff0c;除了线下门店&#xff0c;线上电商也推出“百款直降”“限量预约抢购”“百亿补贴”等活动&#xff0c;如习酒消费满额享华为手机&#xff0c;五粮液指定产品满219减120元&#xff0c;…

Spring系列文章:Bean的作⽤域

1、singleton 默认情况下&#xff0c;Spring的IoC容器创建的Bean对象是单例的 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSche…

会声会影2023免费版电脑视频剪辑软件

会声会影视频剪辑软件对硬件配置要求不高&#xff0c;功能强大且涵盖面广&#xff0c;能为用户节省出大量的硬件成本与学习时间成本。大多数用户仅需一周左右的时间就可以掌握会声会影的基本功能模块&#xff0c;会声会影视频剪辑软件&#xff0c;已经拥有了超过20年的品牌历史…

微软hotmail邮箱的存储空间查询

我最近注册了hotmail邮箱&#xff0c;查询到的存储空间如下&#xff1a; 我们关注的存储应该是下面的电子邮箱存储&#xff0c;15 GB&#xff0c;还是比较大的。 而上面的存储是Microsoft账户存储。 我打算用hotmail邮箱以邮件的形式存储一些重要资料。

2024年浙江财经大学MBA招生简章发布:有哪些看点?

2024年研究生招生简章近期正在陆续发布&#xff0c;作为立足浙江十余年的专业机构&#xff0c;杭州达立易考教育带领考生们来认真分析一下各MBA项目的招生政策以及趋势&#xff0c;以方面在接下来的全国研究生考试网报期间顺利选到适合自己的项目&#xff01;本期围绕浙江财经大…

南京融资融券(信用账户)开户利率最低能做到多少?无门槛利率5%!

南京融资融券(信用账户)开户利率最低能做到多少?无门槛利率5%! 信用账户是指一种可以在购物和信贷过程中使用的账户。它通常与信用卡或贷款相关联&#xff0c;在购物或信贷过程中可以使用它来进行支付。 融资融券是一种证券投资方式&#xff0c;可以通过券商开通该服务。以下…

《DevOps实践指南》- 读书笔记(九)

DevOps实践指南 25. 附录附录 1 DevOps 的大融合精益运动敏捷运动Velocity 大会运动敏捷基础设施运动持续交付运动丰田套路运动精益创业运动精益用户体验运动Rugged Computing 运动 附录 2 约束理论和核心的长期冲突附录 3 恶性循环列表附录 4 交接和队列的危害附录 5 工业安全…

【HBuilderX】解决黑色主题中的注释颜色太浅的问题(代码示例)

打开后&#xff0c;搜索Comment&#xff0c;修改&#xff0c;一个是//的颜色&#xff0c;一个是注释内容的颜色

LOG方案整理(持续更新)

LOG方案整理&#xff08;持续更新&#xff09; 一.日志文件格式 基本日志格式主要包含四种内容 1. 事件发生时间 2. 发生事件的主机名 3. 发生事件的服务或程序&#xff08;或内核&#xff09;&#xff0c;包含进程PID 4. 事件内容 二.日志文件分析 内核及大多数系统消息 内核及…

(二十九)大数据实战——kafka集群节点服役与退役案例实战

前言 本节内容是关于kafka集群节点的服役与退役&#xff0c;从而实现kafka集群的缩容与扩容。在开始本节内容之前&#xff0c;我们要预先安装好kafka集群&#xff0c;并准备一台空余的服务器用来完成我们扩容与缩容的案例。关于kafka集群的安装内容这里不在赘述&#xff0c;相…

在微信公众号怎么实现每日签到功能

在微信公众号中实现每日签到功能&#xff0c;可以为企业或公众号运营者带来许多好处。每日签到功能不仅可以增加用户粘性&#xff0c;提高用户参与度&#xff0c;还可以为公众号带来更多的流量和曝光度。那么&#xff0c;如何在微信公众号中实现每日签到功能呢&#xff1f;本文…

Docker中安装Jenkins

本篇主要讲如何在Docker中安装Jenkins&#xff0c;如果Docker未安装&#xff0c;可以先参考上一篇文章进行Docker安装。 【学习Docker&#xff08;一&#xff09;】centos系统 Docker 安装与卸载 安装 拉取镜像 docker pull jenkins/jenkins1 创建 Jenkins 挂载目录 mkdi…

滑动时间窗口的思想和实现,环形数组,golang

固定时间窗口 在开发限流组件的时候&#xff0c;我们需要统计一个时间区间内的请求数&#xff0c;比如以分钟为单位。所谓固定时间窗口&#xff0c;就是根据时间函数得到当前请求落在哪个分钟之内&#xff0c;我们在统计的时候只关注当前分钟之内的数量&#xff0c;即 [0s, 60…

​重生奇迹MU之亚特兰蒂斯​

如果你不喜欢一个玩家&#xff0c;那就让他去亚特兰蒂斯的中心区域去挂机吧&#xff01;因为&#xff0c;那里就是一个禁区般的存在&#xff0c;虽说还没有到达海魔希特拉的老巢&#xff0c;但是那里却驻扎着它最为忠心耿耿的一批手下&#xff0c;而且还是一群拥有法术技能的巫…

基于SSM的化妆品配方及工艺管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…