夜雨聆风学习资料网

ARTICLE · 1151104

uni-app 登录双模式 + 会话恢复 + 配置分层

uni-app 登录双模式 + 会话恢复 + 配置分层

收官篇,写这个 Immich 小程序的登录与配置体系。它同时支持两种方式登录(API Key 和账号密码),用 Pinia 管全局状态,配置还做了 H5/小程序分层。里面有好设计,也有和之前系列(40 OAuth2、45 SPA 密钥暴露)一脉相承的安全隐患。

一、双登录模式:API Key 与密码

store 里有两个入口,对应两种身份凭证:

API Key 模式(connect):用户填服务器地址 + API Key,验证方式是调 /api/users/me,能拿到用户信息就说明 Key 有效:

async function connect(url, key) {   const finalUrl = url || config.IMMICH_SERVER_URL   const finalKey = key || config.IMMICH_API_KEY   serverUrl.value = finalUrl; apiKey.value = finalKey   authMethod.value = 'apiKey'   const user = await immichApi.getUserInfo()   // 用 Key 试拉用户信息,成功即连通   userInfo.value = user; isConnected.value = true   return { success: true, user } }

密码模式(loginWithPassword):调 /api/auth/login 拿 accessToken,然后把 token 存进同一个 apiKey 字段,只是标 authMethod='password'。前面 48 篇讲过,读取时靠 method 决定塞 x-api-key 还是 Bearer 头。

两种模式的取舍:API Key 是长期静态凭证,适合"设备级"信任;密码登录拿的是短期 token,更适合"用户级"会话。但本项目把 token 当永久 Key 存、没有刷新机制——401 时直接踢回登录页重输密码。对自托管个人项目够用,企业级就得上 refresh token(呼应 40 篇的 OAuth2 设计)。

二、会话恢复:为什么"不兜底 config"

restoreSession 在 App 启动时从 Storage 恢复登录态。看它的 Else 分支:

function restoreSession() {   const storageUrl = uni.getStorageSync('immich_server_url')   const storageKey = uni.getStorageSync('immich_api_key')   if (storageUrl && storageKey) {     serverUrl.value = storageUrl; apiKey.value = storageKey     authMethod.value = storageAuth || 'apiKey'; isConnected.value = true   } else {     // 注意:只从 config 读地址当默认值,绝不把 config 的 Key 当已登录!     serverUrl.value = config.IMMICH_SERVER_URL || ''     apiKey.value = ''     isConnected.value = false   } }

这个细节很关键:没登录时,只把 config 里的服务器地址拿出来当登录页默认值,但绝不把 config 里的 API Key 视为"已连接"。否则会出现诡异现象——清掉了 Storage,结果因为包里编译进了 Key,一启动又"自动登录"了,用户永远退不干净。这种"config 兜底"的坑,在很多把密钥写死进包的项目里都出现过。

三、配置分层:index → dev/prod → server

配置入口 config/index.js 按 process.env.NODE_ENV 选 dev/prod,两者都引用 server.js。而 server.js 的值来自 import.meta.env.VITE_*,并带硬编码兜底:

// server.js const serverUrl = import.meta.env.VITE_IMMICH_SERVER_URL || 'https://ddhsea.cloud' const apiKey = import.meta.env.VITE_IMMICH_API_KEY || '' export default { IMMICH_SERVER_URL: serverUrl, IMMICH_API_KEY: apiKey }

这里有两层隐患:

① 服务器地址被编译进包:'https://ddhsea.cloud' 这个默认值会写死到小程序产物里。对自用项目无所谓,但意味着这个包"绑定"了某台服务器。更干净的做法是地址也留空、强制用户填。

② API Key 绝不该进仓库/进包:虽然这里默认是空,但如果有人图省事在 .env 里填了 Key 再构建,Key 就会随包发布。和之前 45 篇讲 SPA 的 import.meta.env 密钥泄密是同一个坑——构建时环境变量会被内联进前端产物,谁反编译都能看到。正确姿势:Key 永远由用户在运行时输入、存 Storage,绝不要 build 进包。

四、退出登录:先清图片缓存再清凭证

logout 的顺序值得学——先清业务缓存(图片),再清凭证,最后跳转:

function logout() {   imageLoader.clearAllCache()                 // 1. 清图片内存+磁盘缓存   uni.removeStorageSync('immich_server_url')  // 2. 清凭证   uni.removeStorageSync('immich_api_key')   uni.removeStorageSync('immich_auth_method')   serverUrl.value = ''; apiKey.value = ''; isConnected.value = false   uni.reLaunch({ url: '/pages/login/login' }) // 3. 跳登录页 }

先清图片缓存很重要:否则磁盘上可能残留已登录用户的照片缩略图,下一个用这台手机的人(或越权)能在文件管理器里翻到。隐私类 App,退出时必须销毁本地资产。

五、登录页的"已登录直接跳走"

登录页 onMounted 里有个小优化:如果 Pinia 已经是 isConnected,直接 switchTab 进首页,省得用户每次都看登录页。配合 App 启动时 restoreSession,形成"启动→恢复会话→进首页 / 没会话→登录页"的闭环。

六、小结

这套登录/配置体系的可取之处:双凭证模式共用一个字段靠 method 区分、会话恢复不兜底 config 保证能真正退出、退出时先清本地图片缓存护隐私。

相关学习资料