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:
2026-06-11 08:58:24 +08:00
parent ae9b415822
commit 61f9b33f8c
13 changed files with 2178 additions and 138 deletions

View File

@@ -1,55 +1,212 @@
classDiagram
class Tag {
+int id
+int user_id
+string name
+string color
+string created_at
direction TB
class TokenConfig {
+TOKEN_SECRET: string
+ACCESS_TOKEN_EXPIRY: number
+REFRESH_TOKEN_EXPIRY: number
}
class TransactionTag {
+int transaction_id
+int tag_id
class AuthMiddleware {
+authMiddleware(req, res, next): void
-verifyHmacToken(token): userId
-isTokenExpired(timestamp): boolean
}
class Transaction {
+int id
+int user_id
+int amount
+string type
+int category_id
+string note
+string date
+int group_id
+int recurring_id
+Tag[] tags
class AuthRoute {
+POST /login: LoginResult
+POST /demo-login: LoginResult
+POST /refresh: RefreshResult
-signToken(userId): string
-generateRefreshToken(userId): string
-hashToken(token): string
-autoSetAdmin(userId): void
}
class Feedback {
+int id
+int user_id
+string type
+string content
+string contact
+string status
+string admin_reply
+string created_at
class LoginResult {
+accessToken: string
+refreshToken: string
+expiresIn: number
+userId: number
+nickname: string
+avatar_url: string
+role: string
}
class Category {
+int id
+int user_id
+string name
+string icon
+string color
+string type
+int sort_order
+int is_custom
class RefreshResult {
+accessToken: string
+refreshToken: string
+expiresIn: number
}
Transaction "1" --o "*" TransactionTag : has
Tag "1" --o "*" TransactionTag : referenced_by
Transaction ..> Category : belongs_to
class RefreshTokenRow {
+id: number
+user_id: number
+token_hash: string
+expires_at: Date
+revoked_at: Date|null
+created_at: Date
}
note for Tag "每用户最多 20 个标签\n8 色预设颜色选择器\n标签不区分收支类型"
note for TransactionTag "复合主键 (transaction_id, tag_id)\n每笔交易最多 5 个标签"
class DownloadTokenRow {
+id: number
+token: string
+user_id: number
+resource_type: string
+resource_id: string
+expires_at: Date
+used_at: Date|null
+created_at: Date
}
class DownloadTokenRoute {
+POST /download-tokens: GenerateDownloadToken
+GET /download/:token: FileStream
-validateToken(token): DownloadTokenRow
-markUsed(tokenId): void
-cleanupExpired(): void
}
class AdminRoute {
+GET /dashboard: DashboardData
+GET /users: UserList
+PUT /users/:id/status: void
+DELETE /users/:id: void~~软删除
+POST /users/:id/restore: void~~恢复
}
class UserRow {
+id: number
+openid: string
+nickname: string
+avatar_url: string
+role: string
+deleted_at: Date|null
+created_at: Date
}
class ManageListComponent {
+items: T[]
+displayField: string
+colorField: string
+showDrag: boolean
+showEdit: boolean
+showDelete: boolean
+emptyText: string
+emit_add(): void
+emit_edit(item): void
+emit_delete(item): void
+emit_sort(ids): void
}
class ColorPickerComponent {
+modelValue: string
+colors: string[]
+emit_update:modelValue(color): void
}
class EditModalComponent {
+visible: boolean
+title: string
+fields: EditField[]
+emit_confirm(data): void
}
class SnackbarComponent {
+message: string
+actionText: string
+duration: number
+visible: boolean
+emit_action(): void
+show(msg, action): void
+hide(): void
}
class OfflineQueue {
-pendingOps: PendingOp[]
-isProcessing: boolean
+enqueue(op): void
+dequeue(id): void
+processPendingOps(): Promise~void~
+isOnline(): boolean
+startNetworkListener(): void
}
class PendingOp {
+id: string
+type: string
+data: object
+createdAt: number
+status: string
}
class SchedulerService {
+start(): void
+stop(): void
-weeklyReportCron(): void
-monthlyReportCron(): void
-dailyBudgetScanCron(): void
}
class ReportService {
+generateWeeklyReport(userId): WeeklyReport
+generateMonthlyReport(userId): MonthlyReport
-queryPeriodData(userId, start, end): PeriodData
}
class BudgetAlertService {
+checkBudgetAlert(userId, month): void
-calculateUsageRatio(userId, month): number
-sendNotification(userId, level): void
-sendWechatMessage(userId, level): void
-hasAlerted(userId, month, level): boolean
}
class WechatSubscribeService {
+sendMessage(userId, templateId, data): void
-getAccessToken(): string
}
class BudgetAlertRow {
+id: number
+user_id: number
+month: string
+level: string
+group_id: number|null
+created_at: Date
}
class UserSettings {
+user_id: number
+report_push_enabled: boolean
+updated_at: Date
}
class StatsRoute {
+GET /overview: Overview
+GET /category: CategoryStat[]
+GET /trend: TrendPoint[]
+GET /dashboard: DashboardData
-buildWhereClause(userId, groupId): WhereResult
}
class DashboardData {
+overview: Overview
+category: CategoryStat[]
+trend: TrendPoint[]
}
TokenConfig <-- AuthMiddleware : uses
TokenConfig <-- AuthRoute : uses
AuthRoute --> LoginResult : returns
AuthRoute --> RefreshResult : returns
AuthRoute --> RefreshTokenRow : creates/revokes
DownloadTokenRoute --> DownloadTokenRow : creates/validates
AdminRoute --> UserRow : soft deletes
SchedulerService --> ReportService : triggers
SchedulerService --> BudgetAlertService : triggers
ReportService --> WechatSubscribeService : uses
BudgetAlertService --> WechatSubscribeService : uses
BudgetAlertService --> BudgetAlertRow : creates
BudgetAlertService --> UserSettings : checks
StatsRoute --> DashboardData : returns