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

开源项目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.dtcms.com/a/297073.html

相关文章:

  • 蓝光中的愧疚
  • MCP工具开发实战:打造智能体的“超能力“
  • 如何理解泊松分布
  • Vue基础(24)_VueCompinent构造函数、Vue实例对象与组件实例对象
  • UG创建的实体橘黄色实体怎么改颜色?
  • STM32项目实战:正弦波
  • 什么是JSON,如何与Java对象转化
  • QT跨平台应用程序开发框架(11)—— Qt网络编程
  • [NLP]一个完整的 UPF 文件示例
  • Vim 编辑器全模式操作指南
  • 纳米编辑器之Nano 编辑器退出**的详细操作指南
  • 【MacOS】发展历程
  • Linux 中 `chown`、`chgrp` 和 `chmod` 命令详解
  • openGauss数据库在CentOS 7 中的单机部署与配置
  • VMware虚拟出来的centos中设置静态ip
  • fish-speech 在50系列显卡使用 --compile加速兼容
  • Rust + Tauri 开发所需环境清单(以 Windows 为例)
  • 【unitrix】 6.16 非负整数类型( TUnsigned )特质(t_unsingned.rs)
  • [Rust 基础课程]猜数字游戏-获取用户输入并打印
  • 智能问答分类系统:基于SVM的用户意图识别
  • 弹性网:基于神经网络的多组分磁共振弹性成像波反演与不确定性量化|文献速递-医学影像算法文献分享
  • 奥比中光的dabai_dcw2相机彩色对齐方案
  • Android Camera setRepeatingRequest
  • 11. isaacsim4.2教程-Transform 树与Odometry
  • Java面试题(中等)
  • Cartographer安装测试与模块开发(三)--Cartographer在Gazebo仿真环境下的建图以及建图与定位阶段问题(实车也可参考)
  • 融合与智能:AI 浪潮驱动下数据库的多维度进化与产业格局重塑新范式
  • 深入解析Linux匿名管道机制与应用
  • 从数据孤岛到融合共生:KES V9 2025 构建 AI 时代数据基础设施
  • Lua 函数