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,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
|
||||
|
||||
Reference in New Issue
Block a user