当前位置: 首页 > article >正文

如何实现H5端对接钉钉登录并优雅扩展其他平台

如何实现H5端对接钉钉登录并优雅扩展其他平台

    • 钉钉H5登录逻辑
    • 后端代码如何实现?
    • 本次采用策略模式+工厂方式进行
      • 定义接口确定会使用的基本鉴权步骤
      • 具体逻辑类进行实现
      • 采用注册表模式(Registry Pattern)
      • 抽象工厂进行基本逻辑定义
      • 具体工厂进行对接口中的逻辑步骤具体----实例化----逻辑进行重写
    • 总结

钉钉H5登录逻辑

下图中需要说明的一点是,准确来说步骤3来说是钉钉API返回给前端,前端携带一次性校验码token给后端进行后续的鉴权。
还有一点需要注意获得权限之后,如果前端需要回调接口获取用户信息,则需要增加上下文中的用户信息存储

image-20250407212849539

后端代码如何实现?

具体的伪代码如下所述,下面细聊一下如何进行实现获取用户信息这一步。其中本次采用了设计模式进行实现。

    public Result<LoginResp> h5Login(LoginH5UserReq loginH5UserReq) throws ApiException {// 获取租户信息xxxxx// 查询三方鉴权配置信息xxxxx// 获取用户信息 这一步很关键后面细说如何实现H5AuthHandler H5AuthHandler = H5AuthHandlerRegistry.createHandler(loginH5UserReq.getTypePlatForm());String userUniqueIdentifier = H5AuthHandler.getUserDetail(loginH5UserReq);// 系统校验根据手机号查询用户信息SysUser sysUser = sysUserMapper.selectOne(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getTel, userUniqueIdentifier), false);// 使用断言进行优雅校验Assert.notNull(sysUser, () -> new BizException(ErrorCodeEnum.NOT_AVAILABLE));// 校验通过下发tokenString accessToken = StpUtil.getTokenInfo().getTokenValue();xxxxreturn Result.success(loginResult);}

本次我的思路是实现针对不同平台,例如对接钉钉、企业微信、飞书、三方,具体的逻辑是不一样的,使用设计模式中的工厂模式进行构建,实现不同的逻辑进行创建不同类进行完成。

简单罗列一下可以采用的设计模式的具体之间的区别

image-20250407214413483

本次采用策略模式+工厂方式进行

定义接口确定会使用的基本鉴权步骤

public interface AuthHandler {// 获取访问令牌(需处理OAuth2 code校验)String getAccessToken(String code) throws AuthException;// 使用令牌换取用户唯一标识(需处理令牌失效场景)String getUserId(String token) throws AuthException;// 获取用户详细信息(需处理多层级JSON解析)UserDetail getUserDetail(String userId) throws AuthException;
}

具体逻辑类进行实现

下面代码是大致思路展示,直接run是会出现问题。涉及公司保密协议不可以直接上我的源码望读者朋友见谅~

public class DingTalkAuthHandler implements AuthHandler {private static final String API_HOST = "https://oapi.dingtalk.com";private final String appKey;private final String appSecret;// 依赖配置注入(参考网页6的钉钉配置)public DingTalkAuthHandler(String appKey, String appSecret) {this.appKey = appKey;this.appSecret = appSecret;}@Overridepublic String getAccessToken(String code) {// 构建认证请求参数(参考网页7的code交换逻辑)Map<String, String> params = new HashMap<>();params.put("appkey", appKey);params.put("appsecret", appSecret);// 调用钉钉API(网页6的接口文档)String url = API_HOST + "/gettoken?" + buildQueryString(params);JsonNode response = HttpUtil.get(url);// 错误码校验(参考网页6的errcode处理)if(response.get("errcode").asInt() != 0) {throw new DingTalkAuthException(response.get("errmsg").asText());}return response.get("access_token").asText();}@Overridepublic String getUserId(String token) {// 安全域名验证(参考网页7的domain校验)String url = API_HOST + "/user/getuserinfo?access_token=" + token;JsonNode userInfo = HttpUtil.get(url);return userInfo.get("userid").asText();}@Overridepublic UserDetail getUserDetail(String userId) {// 多层级数据解析(参考网页6的JSON结构)String url = API_HOST + "/user/get?userid=" + userId;JsonNode data = HttpUtil.get(url).get("result");return UserDetail.builder().mobile(data.at("/mobile").asText()) // JSONPath定位.name(data.get("name").asText()).avatar(data.get("avatar").asText()).build();}// 私有方法封装请求构建private String buildQueryString(Map<String, String> params) {return params.entrySet().stream().map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8)).collect(Collectors.joining("&"));}
}

上述代码通过接口 + 实现类的方式进行大致逻辑的定义,具体逻辑的展开,不是本次的重点,主要想记录一下如何实现下述的调用:

// 需要实现根据loginH5UserReq.getTypePlatForm() 传入不同的类型,实现实例化对应的实体类进行处理对应逻辑
H5AuthHandler H5AuthHandler = H5AuthHandlerRegistry.createHandler(loginH5UserReq.getTypePlatForm());
// 得到具体逻辑类之后根据请求信息返回用户唯一的id进行后续鉴权
String userUniqueIdentifier = H5AuthHandler.getUserDetail(loginH5UserReq);

采用注册表模式(Registry Pattern)

集中管理平台与工厂映射关系,提供统一访问入口

@Component
// 为什么要采用ApplicationContextAware?文末解释
public class H5AuthHandlerRegistry implements ApplicationContextAware {private static final Map<String, H5AuthHandlerFactory<?>> REGISTRY = new ConcurrentHashMap<>();private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext context) {applicationContext = context;// Spring容器初始化完成后动态注册平台registerPlatforms();}// 具体平台注册private void registerPlatforms() {// 钉钉平台注册(依赖注入已生效)H5DingTalkAuthFactory dingTalkFactory = new H5DingTalkAuthFactory(applicationContext);REGISTRY.put(Platforms.DING_TALK.name(), dingTalkFactory);// 其他平台注册xxxxxxx}// 获取处理器工厂public static H5AuthHandlerFactory<?> getFactory(String platform) {return Optional.ofNullable(REGISTRY.get(platform)).orElseThrow(() -> new IllegalArgumentException("未注册的平台: " + platform));}// 全局同意访问入口public static H5AuthHandler createHandler(String platform) {return getFactory(platform).createHandler();}
}

抽象工厂进行基本逻辑定义

为什么这里要使用抽象类?

首先我想定义基本的创建逻辑,其次抽象类不能被实例化。还有抽象类一般用于设计模式中一种通用写法规范,为子类提供公共的代码实现(如非抽象方法)和强制约束(如抽象方法),子类继承并实现所有抽象方法后才能实例化。

public abstract class H5AuthHandlerFactory<T extends H5AuthHandler> {private final Class<T> handlerClass;protected H5AuthHandlerFactory(Class<T> handlerClass) {this.handlerClass = handlerClass;}// 定义基本创建逻辑,采用反射方式进行。支持反射创建(需无参构造)// PS:如果具体进行逻辑类不涉及采用spring容器管理类,可以使用直接newInstance。不然会出现创建失败,spring容器ioc和Java创建对象是割裂的两派public T createHandler() {try {return handlerClass.getDeclaredConstructor().newInstance();} catch (Exception e) {throw new RuntimeException("H5端登录逻辑抽象工厂---H5AuthHandlerFactory---处理器实例化失败", e);}}}

具体工厂进行对接口中的逻辑步骤具体----实例化----逻辑进行重写

public class H5DingTalkAuthFactory extends H5AuthHandlerFactory<H5DingTalkAuthHandler> {private final ApplicationContext context;// 这里是因为具体实例化处理钉钉H5登录逻辑类会使用到spring容器中的类,所以需要采用上下文的方式public H5DingTalkAuthFactory(ApplicationContext context) {super(H5DingTalkAuthHandler.class);this.context = context;}@Overridepublic H5DingTalkAuthHandler createHandler() {// 从Spring容器获取依赖项ThreePartyLoginRuleConfig ruleConfig = context.getBean(ThreePartyLoginRuleConfig.class);ObjectMapper objectMapper = context.getBean(ObjectMapper.class);// 通过构造器注入依赖return new H5DingTalkAuthHandler(ruleConfig, objectMapper);}
}

总结

总体来说,要实现其他平台的扩展。本次的使用中,由于对接不同平台,具体逻辑中涉及了配置文件配置不同平台JSON数据的解析,所以会使用sping中IOC功能,所以在工厂类中存在上下文部分。

扩展其他平台部分就需要创建两个类,一个类是集成抽象工厂实现其中的createHandler()方法,还有一个是实现接口中定义的三部曲。

H5xxxxxxxAuthFactory extends H5AuthHandlerFactory
H5xxxxxxxxAuthHandler implements H5AuthHandler

相关文章:

如何实现H5端对接钉钉登录并优雅扩展其他平台

如何实现H5端对接钉钉登录并优雅扩展其他平台 钉钉H5登录逻辑后端代码如何实现&#xff1f;本次采用策略模式工厂方式进行定义接口确定会使用的基本鉴权步骤具体逻辑类进行实现采用注册表模式&#xff08;Registry Pattern&#xff09;抽象工厂进行基本逻辑定义具体工厂进行对接…...

Android MediaStore访问的外部存储公共空间都不需要申请权限,这些目录具体指的是哪些

在 Android 10 及更高版本中&#xff0c;通过 MediaStore 访问以下 ​​外部存储公共目录​​ 时&#xff0c;如果操作的是应用自己创建的文件&#xff0c;则​​无需申请存储权限​​。这些目录属于系统明确定义的媒体集合&#xff0c;具体包括&#xff1a; 1. 媒体类型目录​…...

Java中的Exception和Error有什么区别?还有更多扩展

概念 在Java中&#xff0c;Exception和Error都是Throwable的子类&#xff0c;用于处理程序中的错误和异常情况。 然而&#xff0c;它们在用途和处理方式上有显著的不同&#xff1a; Exception&#xff1a; 用于表示程序在正常运行过程中可能出现的错误&#xff0c;如文件未找…...

LabVIEW真空度监测与控制系统

开发了一种基于LabVIEW的真空度信号采集与管理系统&#xff0c;该系统通过图形化编程语言实现了真空度的高精度测量和控制。利用LabVIEW的强大功能&#xff0c;研制了相应的硬件并设计了完整的软件解决方案&#xff0c;以满足工业应用中对真空度监测的精确要求。 项目背景 随着…...

虚拟dom工作原理以及渲染过程

浏览器渲染引擎工作流程都差不多&#xff0c;大致分为5步&#xff0c;创建DOM树——创建StyleRules——创建Render树——布局Layout——绘制Painting 第一步&#xff0c;用HTML分析器&#xff0c;分析HTML元素&#xff0c;构建一颗DOM树(标记化和树构建)。 第二步&#xff0c;用…...

数据采集爬虫三要素:User-Agent、随机延迟、代理ip

做爬虫的朋友都懂&#xff1a;你刚打开一个页面&#xff0c;还没来得及发第二个请求&#xff0c;服务器已经把你当成了“可疑流量”。403、429、验证码、JS挑战……这些“欢迎仪式”你是不是也经常收到&#xff1f;防爬策略越来越猛&#xff0c;采集工程师越来越秃。 但别慌&am…...

汽车的四大工艺

文章目录 冲压工艺核心流程关键技术 焊接工艺核心流程 涂装工艺核心流程 总装工艺核心流程终检与测试静态检查动态检查四轮定位制动转鼓测试淋雨测试总结 简单总结下汽车的四大工艺&#xff08;从网上找了一张图&#xff0c;感觉挺全面的&#xff09;。 冲压工艺 将金属板材通过…...

【JVM是什么?JVM解决什么问题?JVM在JDK体系中是什么?虚拟机和JVM、操作系统是什么关系?】

1. JVM 是什么&#xff1f; JVM&#xff08;Java Virtual Machine&#xff0c;Java 虚拟机&#xff09; 是一个虚拟的计算机程序&#xff0c;它是 Java 程序运行的核心环境。JVM 的主要职责是加载、验证、解释或编译 Java 字节码&#xff08;.class 文件&#xff09;&#xff…...

21 天 Python 计划:MySQL中DML与权限管理

文章目录 前言一、介绍二、MySQL数据操作&#xff1a;DML2.1 插入数据&#xff08;INSERT&#xff09;2.1.1 插入完整数据&#xff08;顺序插入&#xff09;2.1.2 指定字段插入数据2.1.3 插入多条记录2.1.4 插入查询结果 2.2 更新数据&#xff08;UPDATE&#xff09;2.3 删除数…...

10-MySQL-性能优化思路

1、优化思路 当我们发现了一个慢SQL的问题的时候,需要做性能优化,一般我们是为了提高SQL查询更快,一个查询的流程由下图的各环节组成,每个环节都会消耗时间,要减少消耗时候需要从各个环节都分析一遍。 2 连接配置优化 第一个环节是客户端连接到服务端,这块可能会出现服务…...

MySQL学习笔记十

第十二章汇总数据 12.1聚集函数 聚集函数运行在行组上&#xff0c;计算和返回单个值。 12.1.1AVG()函数 输入&#xff1a; SELECT AVG(prod_price) AS avg_price FROM products; 输出&#xff1a; 说明&#xff1a;AVG()函数通过对表中行数计数并计算特定列值之和&#…...

在Halcon的语义分割中,过度拟合解决方法

在Halcon语义分割中出现过拟合是比较常见的问题&#xff0c;以下是一些解决方法。 数据方面 - 扩大数据集&#xff1a;收集更多不同场景、角度、光照条件下的图像数据。例如&#xff0c;在做工业零件语义分割时&#xff0c;如果仅用少量固定角度和光照下的零件图像训练&#xf…...

Active Directory 域服务

1.活动目录有什么特点 1. 目录服务 集中管理&#xff1a;提供集中式的用户、计算机、组和其他资源的管理。 结构化存储&#xff1a;以层次结构的方式存储信息&#xff0c;便于组织和检索。 2. 域和林结构 域&#xff08;Domain&#xff09;&#xff1a;一个逻辑分组&#x…...

Redis快的原因

1、基于内存实现 Redis将所有数据存储在内存中&#xff0c;因此它可以非常快速地读取和写入数据&#xff0c;而无需像传统数据库那样将数据从磁盘读取和写入磁盘&#xff0c;这样也就不受I/O限制。 2、I/O多路复用 多路指的是多个socket连接&#xff1b;复用指的是复用一个线…...

Android 自己的智能指针

在 Android 系统中&#xff0c;强指针模板类&#xff08;sp<T>&#xff09; 是一种基于引用计数的智能指针实现&#xff0c;专门用于管理对象的生命周期。它被广泛用于 Android Framework 的底层&#xff08;Native 层/C 代码&#xff09;&#xff0c;尤其是与 Binder 通…...

如何在React中集成 PDF.js?构建支持打印下载的PDF阅读器详解

本文深入解析基于 React 和 PDF.js 构建 PDF 查看器的实现方案&#xff0c;该组件支持 PDF 渲染、图片打印和下载功能&#xff0c;并包含完整的加载状态与错误处理机制。 完整代码在最后 一个PDF 文件&#xff1a; https://mozilla.github.io/pdf.js/web/compressed.tracemo…...

【完美解决】VSCode连接HPC节点,已配置密钥却还是提示需要输入密码

目录 问题描述软件版本原因分析错误逻辑链 解决方案总结 问题描述 本人在使用 ​​VSCode Remote-SSH 插件​​连接超算集群节点时&#xff0c;遇到以下问题&#xff1a;已正确配置 SSH 密钥&#xff0c;且 VSCode 能识别密钥文件&#xff08;如图1&#xff09;&#xff0c;但在…...

智能DNS解析:解决高防IP地区访问异常的实战指南

摘要&#xff1a;针对高防IP在部分地区无法访问的问题&#xff0c;本文设计基于智能DNS的流量调度方案&#xff0c;提供GeoDNS配置与故障切换代码示例。 一、问题背景 运营商误拦截或线路波动可能导致高防IP在福建、江苏等地访问异常。传统切换方案成本高&#xff0c;智能DNS可…...

【JSON2WEB】16 login.html 登录密码加密传输

【JSON2WEB】系列目录 【JSON2WEB】01 WEB管理信息系统架构设计 【JSON2WEB】02 JSON2WEB初步UI设计 【JSON2WEB】03 go的模板包html/template的使用 【JSON2WEB】04 amis低代码前端框架介绍 【JSON2WEB】05 前端开发三件套 HTML CSS JavaScript 速成 【JSON2WEB】06 JSO…...

ruby超高级语法

以下是 Ruby 中一些 极度硬核 的语法和底层特性&#xff0c;涉及元编程的深渊、虚拟机原理、语法黑魔法等&#xff0c;适用于追求极限的 Ruby 开发者&#xff1a; 高级语法一 一、语法核弹级操作 1. 动态修改继承链 class A; def foo; "A"; end end class B; def …...

第十二天 - Flask/Django基础 - REST API开发 - 练习:运维管理后台API

从零开始用Flask/Django构建运维管理后台API&#xff08;实战指南&#xff09; 前言&#xff1a;为什么选择Python Web框架&#xff1f; 在运维自动化领域&#xff0c;构建管理后台是每个运维工程师的必修课。本文将通过Flask和Django两个主流框架&#xff0c;手把手教你构建…...

Docker 容器内运行程序的性能开销

在 Docker 容器内运行程序通常会有一定的性能开销&#xff0c;但具体损失多少取决于多个因素。以下是详细分析&#xff1a; 1. CPU 性能 理论开销&#xff1a;容器直接共享宿主机的内核&#xff0c;CPU 调度由宿主机管理&#xff0c;因此 CPU 运算性能几乎与原生环境一致&…...

从递归入手一维动态规划

从递归入手一维动态规划 1. 509. 斐波那契数 1.1 思路 递归 F(i) F(i-1) F(i-2) 每个点都往下展开两个分支&#xff0c;时间复杂度为 O(2n) 。 在上图中我们可以看到 F(6) F(5) F(4)。 计算 F(6) 的时候已经展开计算过 F(5)了。而在计算 F(7)的时候&#xff0c;还需要…...

【2025年认证杯数学中国数学建模网络挑战赛】A题解题思路与模型代码

【2025年认证杯数学建模挑战赛】A题 该题为典型的空间几何建模轨道动力学建模预测问题。 ⚙ 问题一&#xff1a;利用多个天文台的同步观测&#xff0c;确定小行星与地球的相对距离 问题分析 已知若干地面天文台的观测数据&#xff1a;方位角 (Azimuth) 和 高度角 (Altitude)&…...

蓝桥杯备赛 Day16 单调数据结构

单调栈和单调队列能够动态的维护&#xff0c;还需用1-2两个数组在循环时从单调栈和单调队列中记录答案 单调栈 要点 1.时刻保持内部元素具有单调性质的栈(先进后出),核心是:入栈时逐个删除所有"更差的点",一般可分为单调递减栈、单调递增栈、单调不减栈、单调不增…...

轻量级爬虫框架Feapder入门:快速搭建企业级数据管道

一、目标与前置知识 1. 目标概述 本教程的主要目标是&#xff1a; 介绍轻量级爬虫框架 Feapder 的基本使用方式。快速搭建一个采集豆瓣电影数据的爬虫&#xff0c;通过电影名称查找对应的电影详情页并提取相关信息&#xff08;电影名称、导演、演员、剧情简介、评分&#xf…...

golang gmp模型分析

思维导图&#xff1a; 1. 发展过程 思维导图&#xff1a; 在单机时代是没有多线程、多进程、协程这些概念的。早期的操作系统都是顺序执行 单进程的缺点有&#xff1a; 单一执行流程、计算机只能一个任务一个任务进行处理进程阻塞所带来的CPU时间的浪费 处于对CPU资源的利用&…...

深入理解Java Optional:告别NullPointerException的优雅方式

大家好&#xff01;今天我们来聊聊Java 8引入的一个超实用类 - Optional。不是那个让你重启电脑的CtrlAltDel哦&#xff01;&#x1f604; 这是一个能让我们优雅处理null值的工具类&#xff0c;彻底告别烦人的NullPointerException&#xff01; 一、为什么需要Optional&#x…...

【算法竞赛】树上最长公共路径前缀(蓝桥杯2024真题·团建·超详细解析)

目录 一、题目 二、思路 1. 问题转化&#xff1a;同步DFS走树 2. 优化&#xff1a;同步DFS匹配 3. 状态设计&#xff1a;dfs参数含义 4. 匹配过程&#xff1a;用 map 建立权值索引 5. 终止条件&#xff1a;无法匹配则更新答案 6. 总结 三、完整代码 四、知识点总…...

【windows10】基于SSH反向隧道公网ip端口实现远程桌面

【windows10】基于SSH反向隧道公网ip端口实现远程桌面 1.背景2.SSH反向隧道3.远程连接电脑 1.背景 ‌Windows 10远程桌面协议的简称是RDP&#xff08;Remote Desktop Protocol&#xff09;‌。 RDP是一种网络协议&#xff0c;允许用户远程访问和操作另一台计算机。 远程桌面功…...