feat(iter-v2): T01 partial - TOKEN_SECRET check, SQL param, export credential, docs
- 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
This commit is contained in:
@@ -1,53 +1,92 @@
|
||||
sequenceDiagram
|
||||
participant U as 用户
|
||||
participant P as category-manage
|
||||
participant D as DragSortList
|
||||
participant S as categoryStore
|
||||
participant A as sortCategories API
|
||||
participant B as PUT /categories/sort
|
||||
participant C as Client (request.ts)
|
||||
participant S as Server (auth.ts)
|
||||
participant M as Server (auth middleware)
|
||||
participant DB as MySQL (refresh_tokens)
|
||||
|
||||
U->>P: 长按分类项进入排序模式
|
||||
P->>P: showSortMode = true
|
||||
P->>D: 渲染 DragSortList (items=currentCategories)
|
||||
U->>D: touchstart (记录起始位置)
|
||||
U->>D: touchmove (计算偏移, 交换元素位置)
|
||||
D->>D: 实时更新 items 数组顺序
|
||||
U->>D: touchend (拖拽结束)
|
||||
D->>P: @change事件 (新顺序ids)
|
||||
P->>S: sortCategories(newIds)
|
||||
S->>A: sortCategories(ids)
|
||||
A->>B: PUT /categories/sort { ids }
|
||||
B-->>A: { code: 0 }
|
||||
A-->>S: 成功
|
||||
S->>S: fetchCategories() 刷新
|
||||
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 }
|
||||
|
||||
U->>P2 as data-import: 选择 JSON 文件
|
||||
P2->>P2: uni.chooseFile / 读取文件内容
|
||||
P2->>P2: 解析 JSON,显示预览(条数、日期范围)
|
||||
U->>P2: 确认导入
|
||||
P2->>A2 as transaction API: importTransactions(items)
|
||||
A2->>S2 as POST /transactions/import: POST { items }
|
||||
S2->>S2: 校验每条记录格式
|
||||
S2->>DB as MySQL: 查询已有记录 (去重比对)
|
||||
S2->>DB: 批量 INSERT (每批100条)
|
||||
S2->>DB: 写入 transaction_tags (如有 tag_names)
|
||||
S2-->>A2: { total, imported, skipped, errors }
|
||||
A2-->>P2: 导入结果
|
||||
P2->>U: 显示导入结果(成功X条, 跳过Y条)
|
||||
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,重试原请求
|
||||
|
||||
U->>P3 as add/index: 点击"添加标签"
|
||||
P3->>P3: 显示标签选择面板(已有标签 + 新建入口)
|
||||
U->>P3: 选择标签(最多5个)
|
||||
P3->>P3: selectedTagIds 更新
|
||||
U->>P3: 保存交易
|
||||
P3->>A3 as transaction API: createTransaction({...data, tagIds})
|
||||
A3->>S3 as POST /transactions: POST { amount, type, ..., tagIds }
|
||||
S3->>S3: 校验 tagIds (≤5, 属于当前用户)
|
||||
S3->>DB: INSERT INTO transactions
|
||||
S3->>DB: INSERT INTO transaction_tags (批量)
|
||||
S3-->>A3: { id }
|
||||
A3-->>P3: 成功
|
||||
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 } }
|
||||
|
||||
Reference in New Issue
Block a user