- S2: logs.ts LIMIT/OFFSET SQL parameterization (? placeholders) - S3: checkTokenSecret() startup validation, JWT_SECRET→TOKEN_SECRET in backup.ts - S3: Access Token expiry shortened to 2h (from 30d) - S4: Export download uses HMAC signed short-term token (/prepare endpoint) - S4: Frontend export.ts adapted for prepare-then-download flow - .env.example reorganized with security notes - Added PRD and architecture docs for iteration v2
93 lines
3.3 KiB
Plaintext
93 lines
3.3 KiB
Plaintext
sequenceDiagram
|
||
participant C as Client (request.ts)
|
||
participant S as Server (auth.ts)
|
||
participant M as Server (auth middleware)
|
||
participant DB as MySQL (refresh_tokens)
|
||
|
||
Note over C,S: 1. 正常登录
|
||
C->>S: POST /auth/login { code }
|
||
S->>DB: INSERT refresh_tokens (user_id, token_hash, expires_at)
|
||
S-->>C: { accessToken(2h), refreshToken(30d), userId }
|
||
|
||
Note over C,S: 2. API 调用(Access Token 有效)
|
||
C->>M: GET /api/xxx Authorization: Bearer accessToken
|
||
M->>M: 验证 HMAC 签名 + 有效期
|
||
M-->>C: 200 OK { code: 0, data }
|
||
|
||
Note over C,S: 3. Access Token 过期
|
||
C->>M: GET /api/xxx Authorization: Bearer expiredToken
|
||
M->>M: 签名有效但已过期
|
||
M-->>C: 401 { code: 40101, message: token已过期 }
|
||
|
||
Note over C,S: 4. 自动刷新
|
||
C->>S: POST /auth/refresh { refreshToken }
|
||
S->>DB: SELECT WHERE token_hash=SHA256(refreshToken) AND revoked_at IS NULL AND expires_at>NOW()
|
||
DB-->>S: 找到记录
|
||
S->>DB: UPDATE refresh_tokens SET revoked_at=NOW() WHERE id=oldId
|
||
S->>DB: INSERT refresh_tokens (新 token_hash)
|
||
S-->>C: { accessToken, refreshToken, expiresIn }
|
||
C->>C: 存储新 Token,重试原请求
|
||
|
||
Note over C,S: 5. Refresh Token 也过期
|
||
C->>S: POST /auth/refresh { expiredRefreshToken }
|
||
S->>DB: SELECT WHERE token_hash=SHA256(...) AND expires_at>NOW()
|
||
DB-->>S: 未找到记录
|
||
S-->>C: 401 { code: 40101, message: refresh token已过期 }
|
||
C->>C: 清除所有 Token,跳转登录页
|
||
|
||
Note over C,S: 6. 删除撤销流程
|
||
participant U as User
|
||
participant P as BillsPage
|
||
participant ST as TransactionStore
|
||
participant SN as Snackbar
|
||
participant API as Server API
|
||
|
||
U->>P: 左滑删除记录
|
||
P->>ST: pendingDelete(id, item, index)
|
||
ST->>ST: 从 transactions 列表移除
|
||
ST->>SN: 显示 Snackbar 已删除1条记录 撤销
|
||
|
||
alt 3秒内点击撤销
|
||
U->>SN: 点击 撤销
|
||
SN->>ST: cancelDelete(id)
|
||
ST->>ST: 恢复到列表原位置
|
||
else 3秒超时
|
||
SN->>ST: confirmDelete(id)
|
||
ST->>API: DELETE /api/transactions/id
|
||
alt 删除成功
|
||
API-->>ST: 200 OK
|
||
ST->>ST: 清除 pendingDelete 记录
|
||
else 删除失败
|
||
API-->>ST: 500 Error
|
||
ST->>ST: 恢复到列表原位置
|
||
ST->>U: toast 删除失败
|
||
end
|
||
end
|
||
|
||
Note over C,S: 7. 预算预警流程
|
||
participant T as Transaction Route
|
||
participant BA as BudgetAlert Service
|
||
participant NS as Notification System
|
||
participant WX as WeChat API
|
||
|
||
C->>T: POST /api/transactions { amount, type, date }
|
||
T->>DB: INSERT INTO transactions
|
||
T->>BA: checkBudgetAlert(userId, month)
|
||
BA->>DB: SELECT budget FROM budgets WHERE user_id=? AND month=?
|
||
BA->>DB: SELECT SUM(amount) FROM transactions WHERE user_id=? AND type=expense
|
||
|
||
alt 支出/预算 >= 80% 且未推送过
|
||
BA->>DB: INSERT budget_alerts (level=80)
|
||
BA->>NS: 创建站内通知
|
||
else 支出/预算 >= 100%
|
||
BA->>DB: INSERT budget_alerts (level=100)
|
||
BA->>NS: 创建站内通知
|
||
BA->>WX: 发送微信订阅消息
|
||
else 支出/预算 >= 120%
|
||
BA->>DB: INSERT budget_alerts (level=120)
|
||
BA->>NS: 创建紧急站内通知 (is_urgent=true)
|
||
BA->>WX: 发送微信订阅消息
|
||
end
|
||
|
||
T-->>C: { code: 0, data: { id } }
|