连接数据库并修改页面

This commit is contained in:
not-like-juvenile
2026-02-02 18:20:22 +08:00
parent 5f856a96c9
commit 8efe6d5e89
22 changed files with 1630 additions and 992 deletions

View File

@@ -0,0 +1,41 @@
**Supabase 会话恢复说明supa session recovery**
- **目的**: 说明为什么刷新后 `session` / `user` 变为 `null`,以及我在项目中做了哪些修改来改善会话恢复。
- **文件变更**:
- **`components/supadb/aksupainstance.uts`**: 统一 Supabase 实例导出,新增 `supaReady` 初始化流程。在初始化时尝试从持久化 token`AkReq` 存储)读取 `access_token` / `refresh_token`,将 refresh token 注入到 `supa.session` 并调用 `supa.refreshSession()` 以恢复会话;保留 `ensureSupabaseReady()` 作为兼容接口。
- **`utils/store.uts`**: 将 `import` 改为 `import supa, { supaReady }`,并在获取 session 前 `await supaReady`,确保恢复逻辑已经运行完毕。
- **`utils/sapi.uts`**、**`pages/sense/senseDataService.uts`** 及若干页面文件: 替换对旧 `ensureSupabaseReady` 的调用,改为 `await supaReady`,并使用默认导出的 `supa` 实例。
- **问题根本原因**:
1. 系统原先将会话信息保持在 `AkSupa.session` / `AkSupa.user`(内存)中,页面刷新或应用重启会清空内存,导致 `supa.getSession()` 返回 `null`
2. 虽然 `AkReq.setToken``access_token` / `refresh_token` 持久化到 `uni` storage`AkSupa` 在启动时没有读取这些持久化 token 并执行恢复/刷新流程(或未将 refresh token 注入到 `AkSupa`),因此无法重建会话。
3. 若 refresh token 过期或没有被正确持久化,也会导致恢复失败。
- **我做的修改说明(要点)**:
-`aksupainstance.uts` 添加 `supaReady`:这是一个 Promise模块初始化时会尝试读取 `AkReq.getRefreshToken()` / `AkReq.getToken()`;若找到 refresh token则把其临时赋到 `supa.session`,再调用 `supa.refreshSession()` 以更新内存中的 session 和 user。
- 在所有依赖会话的模块中,在执行数据库请求前 `await supaReady`,确保恢复尝试已完成,避免 race condition页面刷新后立即调用 supa API 导致 401/空 session
- 保留向后兼容接口 `ensureSupabaseReady()`(内部直接返回 `supaReady`)。
- **如何验证(开发环境)**:
1. 登录并确认本地存储中有 token通过控制台或在 uni 环境运行):
- `uni.getStorageSync('akreq_access_token')`
- `uni.getStorageSync('akreq_refresh_token')`
2. 刷新页面或重启应用,打开控制台查找初始化日志或错误(`Supabase instance init failed` 等)。
3. 在页面中(例如 `pages/mall/delivery/index.uvue`查看已有的调试输出search `supa session=` 的 console 日志,应显示非 null session若 refresh 成功)。
4. 若恢复失败,请检查后端 refresh 接口是否返回 200以及 refresh token 是否已过期。
- **后续建议(可选)**:
-`AkSupa.signIn` / `refreshSession` 成功时,将完整 session或至少 `refresh_token`)写入 `uni.setStorageSync`(持久化),并在 `signOut` 时清除,这样可以进一步减少恢复失败的情况。
-`supa.session` 的 JSON 快照也写入 storage作为额外冗余并在 init 时尝试直接恢复(注意安全和敏感信息保护)。
- 增加更详细的日志(成功/失败原因),并在 UI 层对“登录过期”做更友好的提示或自动跳转到登录页。
- **注意事项**:
- 切勿在代码中硬编码匿名 key 或生产密钥;请在 `ak/config.uts` 中正确配置 `SUPA_URL``SUPA_KEY`
- refresh token 本身也是敏感凭证,应妥善保管;如需长期保持登录,建议使用 refresh 流程并合理设置过期与刷新策略。
如果你需要,我可以继续:
-`AkSupa` 中实现“登录后持久化 session JSON”的补丁并在 `signOut` 时清理;
- 或添加更详细的调试输出帮助定位某次具体恢复失败的 HTTP 请求和响应。
文件路径components/supadb/SESSION_RECOVERY.md

View File

@@ -1,54 +1,63 @@
// /components/supadb/aksupainstance.uts
import { createClient } from './aksupa.uts'
// 创建并导出 Supabase 客户端实例
const supabaseUrl = 'https://your-project.supabase.co' // 替换为你的 Supabase URL
const supabaseAnonKey = 'your-anon-key' // 替换为你的匿名密钥
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
// 导出 Supabase 实例就绪状态
export const isSupabaseReady = true
// 如果有其他需要导出的函数,可以这样导出:
export function initializeSupabase(url: string, key: string) {
return createClient(url, key)
}
// 检查连接状态的函数
export function checkConnection() {
return new Promise((resolve) => {
// 模拟连接检查
setTimeout(() => {
resolve(true)
}, 500)
})
}
// 不再使用 supaready 变量,而是提供函数
export async function ensureSupabaseReady() {
return await checkConnection()
}
// components/supadb/aksupainstance.uts
import AkSupa from './aksupa.uts'
import { SUPA_URL, SUPA_KEY } from '@/ak/config.uts'
import { AkReq } from '@/uni_modules/ak-req/index.uts'
// 创建 AkSupa 实例(在全局复用)
const supa = new AkSupa(SUPA_URL, SUPA_KEY)
// Do not perform hard-coded auto sign-in during page preload (development mode may preload pages).
// Instead, mark supa as ready if an existing session is present; otherwise defer sign-in to explicit user action.
/**
* supaReady: 初始化时尝试从持久化 token 恢复会话。
* - 若内存中已有 session 则直接返回 true
* - 否则尝试读取 AkReq 中持久化的 refresh token 并调用 `refreshSession()` 恢复
*/
const supaReady: Promise<boolean> = (async () => {
try {
const sess = supa.getSession();
if (sess != null && sess.session != null) {
return true;
const cur = supa.getSession()
if (cur && cur.session != null && cur.session.access_token) {
return true
}
// No session found — do not auto sign-in with hard-coded credentials.
return true;
// 从持久化 storage 读取 token
const access = AkReq.getToken()
const refresh = AkReq.getRefreshToken()
const expiresAt = AkReq.getExpiresAt() ?? 0
if (refresh && refresh !== '') {
// 临时注入 session以便 refreshSession 使用 refresh_token
try {
supa.session = {
access_token: access ?? '',
refresh_token: refresh,
expires_at: expiresAt,
user: null,
token_type: '',
expires_in: 0,
raw: new UTSJSONObject({})
}
} catch (e) {
// 在某些环境 UTSJSONObject 构造可能不可用,忽略并继续
try { (supa as any).session = { access_token: access ?? '', refresh_token: refresh, expires_at: expiresAt } } catch (_) {}
}
const ok = await supa.refreshSession()
if (ok) return true
// 刷新失败,清空内存 session
try { supa.session = null; supa.user = null } catch (_) {}
}
return true
} catch (err) {
console.error('Supabase instance init failed', err)
return false;
return false
}
})()
// 向后兼容:保留 ensureSupabaseReady 接口
export async function ensureSupabaseReady() {
return await supaReady
}
export { supaReady }
export default supa