feat: 登录即自动绑定云端并同步,免手动配置
This commit is contained in:
@@ -2,8 +2,11 @@
|
||||
// 实时性:本地变更 3 秒内推送;服务端变更通过 SSE 秒级通知其他端;60 秒轮询兜底
|
||||
import { db } from '../db/schema'
|
||||
import { getSetting, setSetting } from '../db/repo'
|
||||
import { getCloudCreds } from '../db/auth'
|
||||
import { nowLocalISO } from '../lib/date'
|
||||
|
||||
export const DEFAULT_SYNC_BASE = 'https://daywk.linyux.cn'
|
||||
|
||||
const TABLES = ['project', 'work_log', 'log_item', 'attendance', 'weekly_note', 'setting', 'memo', 'credential', 'transaction']
|
||||
|
||||
// 本地敏感或临时性的设置不参与同步
|
||||
@@ -63,13 +66,45 @@ export interface SyncConfig {
|
||||
password: string
|
||||
}
|
||||
|
||||
// 同步基址:设置页可覆盖,默认使用独立子域名
|
||||
export async function getSyncBase(): Promise<string> {
|
||||
return (await getSetting('sync_base_url', '')) || DEFAULT_SYNC_BASE
|
||||
}
|
||||
|
||||
async function loadSyncConfig(): Promise<SyncConfig> {
|
||||
const creds = getCloudCreds()
|
||||
if (!creds) throw new Error('未登录,无法同步')
|
||||
return { baseUrl: await getSyncBase(), username: creds.username, password: creds.password }
|
||||
}
|
||||
|
||||
// 使用当前登录凭据执行同步(供设置页与自动同步调用)
|
||||
export async function syncWithStoredConfig(): Promise<SyncResult> {
|
||||
return runSync(await loadSyncConfig())
|
||||
}
|
||||
|
||||
// 登录后自动绑定云端账号:云端不存在则注册,同名则校验密码一致
|
||||
export async function ensureCloudBinding(): Promise<void> {
|
||||
const creds = getCloudCreds()
|
||||
if (!creds) throw new Error('未登录')
|
||||
const base = await getSyncBase()
|
||||
const headers = { 'Content-Type': 'application/json' }
|
||||
const body = JSON.stringify({ username: creds.username, password: creds.password })
|
||||
let resp = await fetch(base + '/api/auth/register', { method: 'POST', headers, body })
|
||||
if (resp.status === 409) {
|
||||
resp = await fetch(base + '/api/auth/login', { method: 'POST', headers, body })
|
||||
if (!resp.ok) throw new Error('云端已存在同名账号且密码不一致,请检查')
|
||||
} else if (!resp.ok) {
|
||||
throw new Error('云端绑定失败:' + resp.status)
|
||||
}
|
||||
}
|
||||
|
||||
export function getCloudUser(): string | null {
|
||||
const c = getCloudCreds()
|
||||
return c ? c.username : null
|
||||
}
|
||||
|
||||
export async function isSyncConfigured(): Promise<boolean> {
|
||||
const values = await Promise.all([
|
||||
getSetting('sync_base_url', ''),
|
||||
getSetting('sync_username', ''),
|
||||
getSetting('sync_password', '')
|
||||
])
|
||||
return !!(values[0].trim() && values[1].trim() && values[2].trim())
|
||||
return getCloudCreds() != null
|
||||
}
|
||||
|
||||
export async function getSyncLastTime(): Promise<string> {
|
||||
@@ -135,12 +170,7 @@ export async function autoSyncNow(): Promise<void> {
|
||||
}
|
||||
try {
|
||||
if (!(await isSyncConfigured())) return
|
||||
const values = await Promise.all([
|
||||
getSetting('sync_base_url', ''),
|
||||
getSetting('sync_username', ''),
|
||||
getSetting('sync_password', '')
|
||||
])
|
||||
await runSync({ baseUrl: values[0], username: values[1], password: values[2] })
|
||||
await runSync(await loadSyncConfig())
|
||||
} catch { /* 自动同步静默失败,等待下次触发 */ }
|
||||
}
|
||||
|
||||
@@ -234,20 +264,17 @@ async function doRunSync(config: SyncConfig): Promise<SyncResult> {
|
||||
|
||||
// 2. 认证并与服务端交换(token 失效自动重登一次)
|
||||
const token = await ensureToken(base, config)
|
||||
let resp = await fetch(base + '/api/sync', {
|
||||
const doPush = (tk: string) => fetch(base + '/api/sync', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token },
|
||||
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + tk },
|
||||
body: JSON.stringify({ device: 'client', tables: pushTables, tombstones })
|
||||
})
|
||||
let resp = await doPush(token)
|
||||
if (resp.status === 401) {
|
||||
await setSetting('sync_token', '')
|
||||
const token2 = await loginCloud(base, config)
|
||||
await setSetting('sync_token', token2)
|
||||
resp = await fetch(base + '/api/sync', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token2 },
|
||||
body: JSON.stringify({ device: 'client', tables: pushTables, tombstones })
|
||||
})
|
||||
resp = await doPush(token2)
|
||||
}
|
||||
if (!resp.ok) throw new Error('同步服务返回 ' + resp.status)
|
||||
const data = await resp.json()
|
||||
|
||||
Reference in New Issue
Block a user