开源项目XBuilder的user逻辑

  • stores \ user
    • query-keys.ts 统一管理Vue Query(TanStack Query的Vue适配版本)缓存键,在下面的文件中复用
    • index.ts 入口文件,统一用户信息查询
    • signed-in.ts 登录状态管理、认证逻辑

在用户登录后,系统颁发一个令牌,用于在后续的请求中证明“我是谁”

signed-in.js

const casdoorAuthRedirectPath = ‘/signed-in/callback’

定义认证回调路径,用户认证成功后重定向到此路径

const casdoorSdk = new Sdk({

…casdoorConfig,

redirectPath: casdoorAuthRedirectPath

})

创建 Casdoor SDK 实例,展开配置并设置重定向路径

const userStateStorageKey = ‘spx-user’

定义本地存储键名,用于持久化用户状态

const userState = reactive({ // 返回Proxy代理对象,即所有属性都变为响应式

accessToken: null as string | null, // 访问令牌,用于API调用认证

// TS的类型注解语法,显式声明属性的类型

初始化为 null ,然后后面只能改为类型为 string 或 null 类型的值

accessTokenExpiresAt: null as number | null, // 令牌过期时间戳:只能变成number类型

refreshToken: null as string | null // 刷新令牌,用于获取新的访问令牌

})

export function initUserState() {const stored = localStorage.getItem(userStateStorageKey)if (stored != null) {Object.assign(userState, JSON.parse(stored))}watchEffect(() => localStorage.setItem(userStateStorageKey, JSON.stringify(userState)))
}

持久化:从本地 localStorage 中恢复登录状态,从而减少没必要的登录请求和响应,提升用户体验,并在离线状态下使用某些功能

并用 watchEffect 监听状态变化,自动同步到本地存储

interface TokenResponse {access_token: stringexpires_in: numberrefresh_token: string
}

定义了令牌响应接口,描述了从认证服务器返回的令牌数据结构

(接口是一个契约,定义了对象有哪些对象以及分别是什么类型,而类定义了对象是如何创建、有哪些行为,提供了运行时逻辑)

function handleTokenResponse(resp: TokenResponse) {userState.accessToken = resp.access_tokenuserState.accessTokenExpiresAt = resp.expires_in ? Date.now() + resp.expires_in * 1000 : nulluserState.refreshToken = resp.refresh_token
}

令牌处理函数(令牌响应)

更新用户的认证令牌

计算绝对过期时间:当前时间+有效期(记得乘以1000

更新用户的刷新令牌

export function initiateSignIn(returnTo: string = window.location.pathname + window.location.search + window.location.hash
) {// Workaround for casdoor-js-sdk not supporting override of `redirectPath` in `signin_redirect`.const casdoorSdk = new Sdk({...casdoorConfig,redirectPath: `${casdoorAuthRedirectPath}?returnTo=${encodeURIComponent(returnTo)}`})casdoorSdk.signin_redirect()
}

发起登录流程(returnTo属性名称:string类型 =赋值 当前页面的路径名+查询参数+哈希值拼接结果,其中 window.location是浏览器环境的全局对象,表示当前页面的 URL信息)

创建新的SDK实例

并通过 encodeURIComponent 对URI进行编码防止XSS攻击

通过新的SDK实例实现动态重定向

其中signin_redirect函数定义为

    async signin_redirect(additionalParams) {window.location.assign(this.pkce.authorizeUrl(additionalParams));}

通过挂载在 window.location 上的 assign 方法将用户的浏览器重定向到指定的 URL ,后面的 PKCE (Proof Key for Code Exchange) 授权是为了帮助客户端安全地获取访问令牌

export async function completeSignIn() {const resp = await casdoorSdk.exchangeForAccessToken()handleTokenResponse(resp)
}

完成登录流程,通过授权码交换获取访问令牌(函数在sdk.js中定义了),处理返回的令牌数据

export function signInWithAccessToken(accessToken: string) {userState.accessToken = accessTokenuserState.accessTokenExpiresAt = nulluserState.refreshToken = null
}
  • 使用现有访问令牌直接登录
  • 清空过期时间和刷新令牌(假设外部令牌管理)

登出函数

export function signOut() {userState.accessToken = nulluserState.accessTokenExpiresAt = nulluserState.refreshToken = null
}
  • 清空所有用户状态
  • 实现完全登出

令牌刷新机制

const tokenExpiryDelta = 60 * 1000 // 1 minute in milliseconds
let tokenRefreshPromise: Promise<string | null> | null = null
  • 定义令牌过期缓冲时间(1分钟)
  • 防止并发刷新的 Promise 变量
export async function ensureAccessToken(): Promise<string | null> {if (isAccessTokenValid()) return userState.accessTokenif (tokenRefreshPromise != null) return tokenRefreshPromiseif (userState.refreshToken == null) {signOut()return null}tokenRefreshPromise = (async () => {try {const resp = await casdoorSdk.refreshAccessToken(userState.refreshToken!)handleTokenResponse(resp)} catch (e) {console.error('failed to refresh access token', e)throw e}if (!isAccessTokenValid()) {signOut()return null}return userState.accessToken})()return tokenRefreshPromise.finally(() => (tokenRefreshPromise = null))
}
  • 确保访问令牌有效的核心函数
  • 如果令牌有效,直接返回
  • 防止并发刷新(通过 Promise 缓存)
  • 无刷新令牌时直接登出
  • 刷新失败或新令牌无效时登出用户
  • 使用 finally 清理 Promise 缓存

状态检查函数

function isAccessTokenValid(): boolean {return !!(userState.accessToken &&(userState.accessTokenExpiresAt === null || userState.accessTokenExpiresAt - tokenExpiryDelta > Date.now()))
}
  • 检查访问令牌是否有效
  • 令牌存在且未过期(考虑缓冲时间)
  • 使用双重否定 !! 确保返回布尔值
export function isSignedIn(): boolean {return isAccessTokenValid() || userState.refreshToken != null
}
  • 检查用户是否已登录
  • 有效访问令牌或存在刷新令牌都视为已登录

用户信息获取

export function getSignedInUsername(): string | null {if (!isSignedIn()) return nullif (!userState.accessToken) return nullconst decoded = jwtDecode<{ name: string }>(userState.accessToken)return decoded.name
}
  • 从 JWT 令牌中解析用户名
  • 先检查登录状态和令牌存在性
  • 使用泛型指定 JWT 载荷类型

查询相关功能

const signedInUserStaleTime = 60 * 1000 // 1minexport function getSignedInUserQueryKey() {return [...getUserQueryKey(getSignedInUsername() ?? ''), 'signed-in']
}
  • 定义用户信息缓存时间(1分钟)
  • 生成查询键,包含用户名和 'signed-in' 标识
export function useSignedInUser() {const queryKey = computed(() => getSignedInUserQueryKey())return useQueryWithCache({queryKey: queryKey,async queryFn() {if (!isSignedIn()) return nullreturn apis.getSignedInUser()},failureSummaryMessage: {en: 'Failed to load signed-in user information',zh: '加载当前用户信息失败'},staleTime: signedInUserStaleTime})
}
  • Vue 组合式函数,用于获取当前用户信息
  • 使用计算属性动态生成查询键
  • 支持国际化错误消息
  • 设置缓存失效时间
export function useUpdateSignedInUser() {const queryCache = useQueryCache()return useAction(async function updateSignedInUser(params: apis.UpdateSignedInUserParams) {const updated = await apis.updateSignedInUser(params)queryCache.invalidate(getUserQueryKey(getSignedInUsername()!))return updated},{ en: 'Failed to update profile', zh: '更新个人信息失败' })
}
  • Vue 组合式函数,用于更新用户信息
  • 更新成功后使查询缓存失效,触发重新获取
  • 使用非空断言操作符 ! 确保用户名存在
  • 支持国际化错误消息

总结

这个文件实现了一个完整的用户认证系统,包括:

  1. 状态管理: 使用 Vue 3 响应式系统管理用户状态
  2. 持久化: 自动同步状态到本地存储
  3. 认证流程: 支持 OAuth 2.0 授权码流程
  4. 令牌管理: 自动刷新访问令牌,防止并发刷新
  5. 错误处理: 完善的错误处理和用户登出机制
  6. 缓存集成: 与查询缓存系统集成,优化数据获取
  7. 国际化: 支持中英文错误消息

代码设计良好,考虑了安全性、性能和用户体验等多个方面。

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

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

相关文章

第十五章 SEO的简单免费工具

SEO的基础工具和检测 前文中主要是讲一些SEO的网站基本功&#xff0c;而在这一章那&#xff0c;会讲到一些非常基本的工具&#xff0c;主要是关于&#xff1a;网站的流量、停留时长、关键词密度、内容、以及Google的站长工具。 Google Search Console Google Search Console这是…

SSL 证书与 HTTPS 的关系:一文理清核心关联

HTTPS&#xff08;Hypertext Transfer Protocol Secure&#xff09;和 SSL 证书&#xff08;Secure Sockets Layer Certificate&#xff09;是网络安全的两大基石&#xff0c;它们共同保障了互联网通信的安全性和可信度。以下从定义、功能、关系及实际应用层面进行解析&#xf…

使用Jmeter参数化实现接口自动化测试

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 本文记录如何使用Jmeter参数化&#xff08;csv)实现接口自动化——测试Token不同入参情况下&#xff0c;接口请求能够返回正确的结果1. 首先需要使用Jmeter获取一个…

X-plore File Manager v4.34.02 修改版:安卓设备上的全能文件管理器

在使用安卓设备时&#xff0c;文件管理是日常操作中不可或缺的一部分。X-plore File Manager 作为一款功能强大的文件管理器&#xff0c;凭借其丰富的功能和便捷的操作&#xff0c;成为安卓用户管理文件的首选工具之一。最新版 v4.34.02 修改版更是解锁了更多高级功能&#xff…

React+threejs两种3D多场景渲染方案

在现代 Web 开发中&#xff0c;3D 可视化需求日益增长&#xff0c;特别是在 React 生态系统中实现多 3D 场景的展示与交互。本文通过对比两种实现方案&#xff0c;探讨 React 中构建多 3D 场景的最佳实践&#xff0c;分析它们的技术特点、性能表现和适用场景。方案一&#xff1…

React性能优化终极指南:memo、useCallback、useMemo全解析

掌握 React.memo、useCallback、useMemo 的正确使用姿势&#xff0c;让你的 React 应用性能飞起来&#xff01; &#x1f3af; React.memo 作用 React.memo 是一个高阶组件&#xff0c;用于函数组件&#xff0c;通过浅比较 props 的变化来决定是否重新渲染。如果 props 没有变…

借助 VR 消防技术开展应急演练,检验完善应急预案​

应急演练是企业应对火灾事故的重要手段&#xff0c;而 VR 消防技术的应用&#xff0c;为应急演练带来了全新的体验和更高的效率。VR 消防技术通过虚拟现实技术模拟逼真的火灾场景&#xff0c;让参与者能够身临其境地感受火灾发生时的紧张氛围。某知名物流企业&#xff0c;仓库众…

【电赛学习笔记】MaxiCAM 项目实践——二维云台追踪指定目标

前言 本文是对视觉模块MaixCam实现二维云台人脸跟踪_哔哩哔哩_bilibili大佬的项目实践整理与拓展&#xff0c;侵权即删。 单路舵机基本控制 #导入必要模块 from maix import pwm, time , pinmap#定义全局变量&#xff0c;设初值 SERVO_FREQ 50 #主频 SERVO_MIN_DUT…

深入解析 ArkUI 触摸事件机制:从点击到滑动的开发全流程

摘要 随着 HarmonyOS NEXT 的不断发展&#xff0c;ArkUI 逐渐成为主流的 UI 构建方式。而用户交互在任何应用中都是基础而又关键的一环&#xff0c;如何利用 ArkUI 提供的触摸事件机制&#xff0c;如 onTouch、onClick、onSwipe 等&#xff0c;来实现自然、顺滑、用户友好的交互…

Tailwind CSS 自定义工具类与主题配置指南

一、自定义工具类配置在 src/tailwind.css 文件中&#xff0c;我们可以通过 layer utilities 指令添加自定义工具类&#xff1a;tailwind base; tailwind components; tailwind utilities;layer utilities {/* 自定义工具 上下浮动效果 */.animate-floatY {animation: floatY 3…

【代码随想录二刷|704.二分查找、27.移除元素、977.有序数组的平方】

704.二分查找 题目链接&#xff1a;704. 二分查找 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:int search(vector<int>& nums, int target) {//不用二分查找&#xff0c;直接求// for(int i0;i<nums.size();i){// if(nums[i]target)…

基于Vue的工业设备大屏可视化模板(含设备地图分布+宣传模块+报表展示+三维模型加载预览)

场景 为实现工业设备可视化大屏需求&#xff0c;可实现基于地图的设备数据管理&#xff0c;点击具体设备可进行详细介绍和三维模型展示。 可播放宣传视频&#xff0c;可展示PM数据报表等数据&#xff0c;可接受报警数据提醒、统计等数据。 基于现有开源平台框架进行二次改造…

堆(Heap)优先级队列(Priority Queue)

一、堆的概念堆&#xff08;Heap&#xff09;是一种特殊的基于树的数据结构&#xff0c;通常分为最大堆和最小堆两种类型。它满足堆属性&#xff1a;对于最大堆&#xff0c;父节点的值总是大于或等于其子节点的值&#xff1b;而对于最小堆&#xff0c;父节点的值总是小于或等于…

踩坑记录:因版本不匹配导致 Boost 1.85 编译失败的完整解决过程

踩坑记录&#xff1a;因版本不匹配导致 Boost 1.85 编译失败的完整解决过程 转载请注明出处&#xff0c;欢迎评论区交流。 背景 最近在 Windows 11 VS2022 环境下尝试用 b2 编译 Boost 1.85.0&#xff0c;结果一路踩坑&#xff0c;最后发现罪魁祸首是 Boost.Build 自带的 msv…

InfluxDB Line Protocol 协议深度剖析(二)

四、Line Protocol 写入操作与实践&#xff08;一&#xff09;HTTP API 写入使用 HTTP API 是通过 Line Protocol 写入数据到 InfluxDB 的常用方式。InfluxDB 1.x&#xff1a;请求方式为 POST&#xff0c;URL 格式为 “http://host:port/write?dbdatabase_name”。其中&#x…

负载均衡:提升业务性能的关键技术

一.负载均衡&#xff08;3.6 &#xff09;1.1.什么是负载均衡&#xff1a;负载均衡&#xff1a;Load Balance&#xff0c;简称LB&#xff0c;是一种服务或基于硬件设备等实现的高可用反向代理技术&#xff0c;负载均 衡将特定的业务(web服务、网络流量等)分担给指定的一个或多个…

【STM32项目】智能家居(版本1)

✌️✌️大家好&#xff0c;这里是5132单片机毕设设计项目分享&#xff0c;今天给大家分享的是基于《基于STM32的智能家居设计》。 目录 1、系统功能 2.1、硬件清单 2.2、功能介绍 2.3、控制模式 2、演示视频和实物 3、系统设计框图 4、软件设计流程图 5、原理图 6、主…

OpenSCA开源社区每日安全漏洞及投毒情报资讯—2025年7月24日

2025年7月24日安全风险情报资讯在野漏洞风险&#xff08;CVE未收录&#xff09;&#xff1a;1公开漏洞精选&#xff1a;2组件投毒情报&#xff1a;2在野漏洞风险&#xff08;CVE未收录&#xff09;1.1 gemini-cli项目潜在命令注入漏洞项目详情项目描述&#xff1a;gemini-cli是…

飞算 JavaAI 深度实战:从老项目重构到全栈开发的降本增效密码

飞算 JavaAI 深度实战&#xff1a;从老项目重构到全栈开发的降本增效密码引言正文一、智能引导模块&#xff1a;老项目重构的 “手术刀” 级解决方案1.1 本地化智能分析&#xff1a;IDEA 插件实操演示1.1.1 &#x1f4cc; IDEA 插件安装步骤1.1.1.1 首先打开idea工具&#xff0…

分布式推客系统开发全解:微服务拆分、佣金结算与风控设计

一、推客系统概述与市场背景推客系统&#xff08;也称为分销系统或社交电商系统&#xff09;已成为现代电商平台和内容平台的重要增长引擎。根据最新统计数据&#xff0c;2023年社交电商市场规模已突破3万亿元&#xff0c;占整体电商市场份额的25%以上。推客系统的核心价值在于…