更新 ER diagram

This commit is contained in:
2026-04-24 20:39:45 +08:00
parent 7903d703b9
commit 33afef323c
4 changed files with 182 additions and 290 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 901 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -1,6 +1,6 @@
<mxfile host="drawio.ishenwei.online" agent="OpenCode">
<diagram name="Fonrey ER Diagram" id="fonrey-er-v1">
<mxGraphModel dx="5097" dy="959" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="3300" pageHeight="2340" math="0" shadow="0">
<mxGraphModel dx="9516" dy="5600" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="3300" pageHeight="2340" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
@@ -316,7 +316,7 @@
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="region-client" parent="1" style="swimlane;startSize=30;fillColor=#3d1f06;strokeColor=#fbbf24;fontColor=#fbbf24;fontSize=12;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=3;" value="CLIENT" vertex="1">
<mxGeometry height="1380" width="1060" x="2220" y="60" as="geometry" />
<mxGeometry height="1380" width="1060" x="2280" y="60" as="geometry" />
</mxCell>
<mxCell id="clients" parent="region-client" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;clients&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK agent_id → staff&#xa;client_type: private/public/closed&#xa;status: active/inactive/converted&#xa;name: varchar(50)&#xa;phone_enc: text [AES-256-GCM]&#xa;phone_hash: varchar(64) [SHA-256]&#xa;budget_min/max: numeric&#xa;activity_level: 1-5 [Celery每日计算]&#xa;is_protected: bool [防自动转公客]&#xa;transfer_to_public_type: auto/manual&#xa;last_follow_at: timestamptz&#xa;source: varchar(30)&#xa;remarks: text&#xa;created_at: timestamptz&#xa;deleted_at: timestamptz&#xa;🔗 FK created_by → staff&#xa;&lt;i&gt;[私客/公客/成交客 三态状态机]&lt;/i&gt;" vertex="1">
<mxGeometry height="360" width="370" x="30" y="60" as="geometry" />
@@ -499,123 +499,55 @@
<mxCell id="e-prop-folder-lbl" connectable="0" parent="e-prop-folder" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fb923c;" value="property_id" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- ═══════════════════════════════════════════════════════════════
PUBLIC SCHEMA SWIMLANE平台运营层
Color: slate-900 bg / cyan-400 accent (#0f172a / #22d3ee variant: #7dd3fc)
Positioned ABOVE existing content at y=-1160
════════════════════════════════════════════════════════════════ -->
<mxCell id="region-public" parent="1"
style="swimlane;startSize=36;fillColor=#0c1a2e;strokeColor=#7dd3fc;fontColor=#7dd3fc;fontSize=13;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=2;"
value="PUBLIC SCHEMA平台运营层" vertex="1">
<mxCell id="region-public" parent="1" style="swimlane;startSize=36;fillColor=#0c1a2e;strokeColor=#7dd3fc;fontColor=#7dd3fc;fontSize=13;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=2;" value="PUBLIC SCHEMA平台运营层" vertex="1">
<mxGeometry height="560" width="3400" x="-860" y="-1200" as="geometry" />
</mxCell>
<!-- ── 1. tenants ── -->
<mxCell id="pub-tenants" parent="region-public"
style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;"
value="&lt;b&gt;public.tenants&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;schema_name: varchar(63) [UNIQUE, immutable]&#xa;name: varchar(255)&#xa;short_name: varchar(100)&#xa;contact_name / contact_email&#xa;region: varchar(100)&#xa;plan: basic/professional/enterprise&#xa;status: creating/active/suspended/&#xa; pending_delete/deleted/failed&#xa;suspended_until: timestamptz&#xa;suspended_reason: varchar(50)&#xa;deleted_at: timestamptz&#xa;paid_until: date&#xa;on_trial: bool&#xa;is_canary: bool&#xa;created_at / updated_at: timestamptz&#xa;created_by: uuid&#xa;extra: jsonb" vertex="1">
<mxCell id="pub-tenants" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.tenants&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;schema_name: varchar(63) [UNIQUE, immutable]&#xa;name: varchar(255)&#xa;short_name: varchar(100)&#xa;contact_name / contact_email&#xa;region: varchar(100)&#xa;plan: basic/professional/enterprise&#xa;status: creating/active/suspended/&#xa; pending_delete/deleted/failed&#xa;suspended_until: timestamptz&#xa;suspended_reason: varchar(50)&#xa;deleted_at: timestamptz&#xa;paid_until: date&#xa;on_trial: bool&#xa;is_canary: bool&#xa;created_at / updated_at: timestamptz&#xa;created_by: uuid&#xa;extra: jsonb" vertex="1">
<mxGeometry height="310" width="290" x="30" y="50" as="geometry" />
</mxCell>
<!-- ── 2. domains ── -->
<mxCell id="pub-domains" parent="region-public"
style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;"
value="&lt;b&gt;public.domains&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants&#xa;domain: varchar(253) [UNIQUE]&#xa;is_primary: bool [UNIQUE partial]&#xa;created_at: timestamptz&#xa;⚠ domain 创建后不可修改" vertex="1">
<mxCell id="pub-domains" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.domains&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants&#xa;domain: varchar(253) [UNIQUE]&#xa;is_primary: bool [UNIQUE partial]&#xa;created_at: timestamptz&#xa;⚠ domain 创建后不可修改" vertex="1">
<mxGeometry height="135" width="250" x="340" y="50" as="geometry" />
</mxCell>
<!-- ── 3. tenant_status_logs ── -->
<mxCell id="pub-tenant-status-logs" parent="region-public"
style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;"
value="&lt;b&gt;public.tenant_status_logs&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants&#xa;from_status: varchar(20) [NULL=初始]&#xa;to_status: varchar(20)&#xa;reason: text&#xa;operator_id: uuid [NULL=Celery]&#xa;operator_name: varchar(100) [快照]&#xa;created_at: timestamptz&#xa;⚠ append-only — NO UPDATE/DELETE" vertex="1">
<mxCell id="pub-tenant-status-logs" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.tenant_status_logs&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants&#xa;from_status: varchar(20) [NULL=初始]&#xa;to_status: varchar(20)&#xa;reason: text&#xa;operator_id: uuid [NULL=Celery]&#xa;operator_name: varchar(100) [快照]&#xa;created_at: timestamptz&#xa;⚠ append-only — NO UPDATE/DELETE" vertex="1">
<mxGeometry height="175" width="270" x="340" y="200" as="geometry" />
</mxCell>
<!-- ── 4. platform_admins ── -->
<mxCell id="pub-admins" parent="region-public"
style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;"
value="&lt;b&gt;public.platform_admins&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;username: varchar(150) [UNIQUE]&#xa;email: varchar(254) [UNIQUE]&#xa;display_name: varchar(100)&#xa;password_hash: varchar(255)&#xa;role: super_admin/ops_operator/&#xa; read_only_auditor&#xa;is_active: bool&#xa;mfa_enabled: bool [TOTP 确认后→TRUE]&#xa;last_login_at: timestamptz&#xa;created_at / updated_at: timestamptz&#xa;🔗 FK created_by → platform_admins" vertex="1">
<mxCell id="pub-admins" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.platform_admins&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;username: varchar(150) [UNIQUE]&#xa;email: varchar(254) [UNIQUE]&#xa;display_name: varchar(100)&#xa;password_hash: varchar(255)&#xa;role: super_admin/ops_operator/&#xa; read_only_auditor&#xa;is_active: bool&#xa;mfa_enabled: bool [TOTP 确认后→TRUE]&#xa;last_login_at: timestamptz&#xa;created_at / updated_at: timestamptz&#xa;🔗 FK created_by → platform_admins" vertex="1">
<mxGeometry height="225" width="270" x="640" y="50" as="geometry" />
</mxCell>
<!-- ── 5. admin_mfa_devices ── -->
<mxCell id="pub-mfa" parent="region-public"
style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;"
value="&lt;b&gt;public.admin_mfa_devices&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK admin_id → platform_admins&#xa;device_name: varchar(100)&#xa;totp_secret: varchar(255) [加密]&#xa;is_confirmed: bool&#xa;created_at: timestamptz&#xa;last_used_at: timestamptz" vertex="1">
<mxCell id="pub-mfa" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.admin_mfa_devices&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK admin_id → platform_admins&#xa;device_name: varchar(100)&#xa;totp_secret: varchar(255) [加密]&#xa;is_confirmed: bool&#xa;created_at: timestamptz&#xa;last_used_at: timestamptz" vertex="1">
<mxGeometry height="150" width="250" x="930" y="50" as="geometry" />
</mxCell>
<!-- ── 6. admin_sessions ── -->
<mxCell id="pub-sessions" parent="region-public"
style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;"
value="&lt;b&gt;public.admin_sessions&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK admin_id → platform_admins&#xa;session_token: varchar(255) [UNIQUE]&#xa;ip_address: inet&#xa;user_agent: text&#xa;is_active: bool&#xa;created_at: timestamptz&#xa;expires_at: timestamptz [30min rolling]&#xa;revoked_at: timestamptz&#xa;🔗 FK revoked_by → platform_admins" vertex="1">
<mxCell id="pub-sessions" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.admin_sessions&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK admin_id → platform_admins&#xa;session_token: varchar(255) [UNIQUE]&#xa;ip_address: inet&#xa;user_agent: text&#xa;is_active: bool&#xa;created_at: timestamptz&#xa;expires_at: timestamptz [30min rolling]&#xa;revoked_at: timestamptz&#xa;🔗 FK revoked_by → platform_admins" vertex="1">
<mxGeometry height="190" width="255" x="930" y="220" as="geometry" />
</mxCell>
<!-- ── 7. ip_whitelist ── -->
<mxCell id="pub-ip" parent="region-public"
style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;"
value="&lt;b&gt;public.ip_whitelist&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;cidr: cidr [如 203.0.113.0/24]&#xa;label: varchar(100)&#xa;is_active: bool&#xa;created_at: timestamptz&#xa;🔗 FK created_by → platform_admins" vertex="1">
<mxCell id="pub-ip" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.ip_whitelist&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;cidr: cidr [如 203.0.113.0/24]&#xa;label: varchar(100)&#xa;is_active: bool&#xa;created_at: timestamptz&#xa;🔗 FK created_by → platform_admins" vertex="1">
<mxGeometry height="135" width="240" x="640" y="295" as="geometry" />
</mxCell>
<!-- ── 8. platform_audit_logs ── -->
<mxCell id="pub-audit" parent="region-public"
style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;"
value="&lt;b&gt;public.platform_audit_logs&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;operator_id: uuid [NULL=系统]&#xa;operator_name: varchar(100) [快照]&#xa;action_type: varchar(50)&#xa;target_type: varchar(30)&#xa;target_id / target_name: varchar&#xa;payload_summary: text&#xa;result: SUCCESS/FAILED&#xa;error_message: text&#xa;ip_address: inet&#xa;created_at: timestamptz&#xa;⚠ append-only — 建议月度 RANGE 分区" vertex="1">
<mxCell id="pub-audit" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.platform_audit_logs&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;operator_id: uuid [NULL=系统]&#xa;operator_name: varchar(100) [快照]&#xa;action_type: varchar(50)&#xa;target_type: varchar(30)&#xa;target_id / target_name: varchar&#xa;payload_summary: text&#xa;result: SUCCESS/FAILED&#xa;error_message: text&#xa;ip_address: inet&#xa;created_at: timestamptz&#xa;⚠ append-only — 建议月度 RANGE 分区" vertex="1">
<mxGeometry height="225" width="265" x="1210" y="50" as="geometry" />
</mxCell>
<!-- ── 9. backup_schedules ── -->
<mxCell id="pub-bk-schedules" parent="region-public"
style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;"
value="&lt;b&gt;public.backup_schedules&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants [NULL=全局]&#xa;frequency: hourly/daily/weekly&#xa;scheduled_time: time [UTC]&#xa;retention_count: int&#xa;storage_target: local/s3/r2/gcs&#xa;is_active: bool&#xa;created_at / updated_at: timestamptz&#xa;🔗 FK created_by → platform_admins&#xa;UNIQUE(tenant_id)" vertex="1">
<mxCell id="pub-bk-schedules" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.backup_schedules&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants [NULL=全局]&#xa;frequency: hourly/daily/weekly&#xa;scheduled_time: time [UTC]&#xa;retention_count: int&#xa;storage_target: local/s3/r2/gcs&#xa;is_active: bool&#xa;created_at / updated_at: timestamptz&#xa;🔗 FK created_by → platform_admins&#xa;UNIQUE(tenant_id)" vertex="1">
<mxGeometry height="195" width="265" x="1510" y="50" as="geometry" />
</mxCell>
<!-- ── 10. backup_records ── -->
<mxCell id="pub-bk-records" parent="region-public"
style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;"
value="&lt;b&gt;public.backup_records&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants&#xa;trigger_type: auto/manual/&#xa; pre_upgrade/pre_restore&#xa;status: pending/in_progress/&#xa; success/failed&#xa;storage_target / storage_path: text&#xa;size_bytes: bigint&#xa;started_at / completed_at&#xa;error_message: text&#xa;🔗 FK triggered_by → platform_admins&#xa;upgrade_event_id: uuid&#xa;created_at: timestamptz" vertex="1">
<mxCell id="pub-bk-records" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.backup_records&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants&#xa;trigger_type: auto/manual/&#xa; pre_upgrade/pre_restore&#xa;status: pending/in_progress/&#xa; success/failed&#xa;storage_target / storage_path: text&#xa;size_bytes: bigint&#xa;started_at / completed_at&#xa;error_message: text&#xa;🔗 FK triggered_by → platform_admins&#xa;upgrade_event_id: uuid&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="245" width="265" x="1510" y="270" as="geometry" />
</mxCell>
<!-- ── 11. export_tasks ── -->
<mxCell id="pub-exports" parent="region-public"
style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;"
value="&lt;b&gt;public.export_tasks&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants&#xa;🔗 FK requested_by → platform_admins&#xa;modules: text[]&#xa;format: csv/json/sql_dump&#xa;status: pending/in_progress/done/failed&#xa;storage_path / download_url: text&#xa;expires_at: timestamptz [24h]&#xa;size_bytes: bigint&#xa;started_at / completed_at&#xa;created_at: timestamptz" vertex="1">
<mxCell id="pub-exports" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.export_tasks&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants&#xa;🔗 FK requested_by → platform_admins&#xa;modules: text[]&#xa;format: csv/json/sql_dump&#xa;status: pending/in_progress/done/failed&#xa;storage_path / download_url: text&#xa;expires_at: timestamptz [24h]&#xa;size_bytes: bigint&#xa;started_at / completed_at&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="215" width="265" x="1210" y="300" as="geometry" />
</mxCell>
<!-- ── 12. system_versions ── -->
<mxCell id="pub-versions" parent="region-public"
style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;"
value="&lt;b&gt;public.system_versions&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;version_number: varchar(50) [UNIQUE]&#xa;release_notes: text&#xa;artifact_url: text&#xa;status: current/previous/archived&#xa; [UNIQUE partial: status='current']&#xa;released_at: timestamptz&#xa;🔗 FK created_by → platform_admins" vertex="1">
<mxCell id="pub-versions" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.system_versions&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;version_number: varchar(50) [UNIQUE]&#xa;release_notes: text&#xa;artifact_url: text&#xa;status: current/previous/archived&#xa; [UNIQUE partial: status=&#39;current&#39;]&#xa;released_at: timestamptz&#xa;🔗 FK created_by → platform_admins" vertex="1">
<mxGeometry height="165" width="265" x="1810" y="50" as="geometry" />
</mxCell>
<!-- ── 13. upgrade_events ── -->
<mxCell id="pub-upgrades" parent="region-public"
style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;"
value="&lt;b&gt;public.upgrade_events&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK from_version_id → system_versions&#xa;🔗 FK to_version_id → system_versions&#xa;event_type: upgrade/rollback&#xa;strategy: full/canary&#xa;status: pending/health_check/&#xa; in_progress/success/failed/rolled_back&#xa;tenant_progress: jsonb [{tenant,status,...}]&#xa;rollback_reason: text&#xa;incident_report: text&#xa;started_at / completed_at&#xa;🔗 FK initiated_by → platform_admins&#xa;created_at: timestamptz" vertex="1">
<mxCell id="pub-upgrades" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.upgrade_events&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK from_version_id → system_versions&#xa;🔗 FK to_version_id → system_versions&#xa;event_type: upgrade/rollback&#xa;strategy: full/canary&#xa;status: pending/health_check/&#xa; in_progress/success/failed/rolled_back&#xa;tenant_progress: jsonb [{tenant,status,...}]&#xa;rollback_reason: text&#xa;incident_report: text&#xa;started_at / completed_at&#xa;🔗 FK initiated_by → platform_admins&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="255" width="280" x="1810" y="240" as="geometry" />
</mxCell>
<!-- ── Internal edges (PUBLIC schema) ── -->
<!-- tenants → domains -->
<mxCell id="pe-tenant-domain" edge="1" parent="region-public" source="pub-tenants" target="pub-domains"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;">
<mxCell id="pe-tenant-domain" edge="1" parent="region-public" source="pub-tenants" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-domains">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-tenant-domain-lbl" connectable="0" parent="pe-tenant-domain"
style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxCell id="pe-tenant-domain-lbl" connectable="0" parent="pe-tenant-domain" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- tenants → tenant_status_logs -->
<mxCell id="pe-tenant-statuslog" edge="1" parent="region-public" source="pub-tenants" target="pub-tenant-status-logs"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;">
<mxCell id="pe-tenant-statuslog" edge="1" parent="region-public" source="pub-tenants" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-tenant-status-logs">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="320" y="295" />
@@ -623,24 +555,16 @@
</Array>
</mxGeometry>
</mxCell>
<mxCell id="pe-tenant-statuslog-lbl" connectable="0" parent="pe-tenant-statuslog"
style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N append-only" vertex="1">
<mxCell id="pe-tenant-statuslog-lbl" connectable="0" parent="pe-tenant-statuslog" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N append-only" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- platform_admins → admin_mfa_devices -->
<mxCell id="pe-admin-mfa" edge="1" parent="region-public" source="pub-admins" target="pub-mfa"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;">
<mxCell id="pe-admin-mfa" edge="1" parent="region-public" source="pub-admins" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-mfa">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-admin-mfa-lbl" connectable="0" parent="pe-admin-mfa"
style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxCell id="pe-admin-mfa-lbl" connectable="0" parent="pe-admin-mfa" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- platform_admins → admin_sessions -->
<mxCell id="pe-admin-session" edge="1" parent="region-public" source="pub-admins" target="pub-sessions"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;">
<mxCell id="pe-admin-session" edge="1" parent="region-public" source="pub-admins" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-sessions">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="905" y="235" />
@@ -648,14 +572,10 @@
</Array>
</mxGeometry>
</mxCell>
<mxCell id="pe-admin-session-lbl" connectable="0" parent="pe-admin-session"
style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxCell id="pe-admin-session-lbl" connectable="0" parent="pe-admin-session" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- platform_admins → ip_whitelist (created_by) -->
<mxCell id="pe-admin-ip" edge="1" parent="region-public" source="pub-admins" target="pub-ip"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;">
<mxCell id="pe-admin-ip" edge="1" parent="region-public" source="pub-admins" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-ip">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="755" y="280" />
@@ -663,14 +583,10 @@
</Array>
</mxGeometry>
</mxCell>
<mxCell id="pe-admin-ip-lbl" connectable="0" parent="pe-admin-ip"
style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="created_by" vertex="1">
<mxCell id="pe-admin-ip-lbl" connectable="0" parent="pe-admin-ip" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="created_by" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- tenants → backup_schedules -->
<mxCell id="pe-tenant-bksched" edge="1" parent="region-public" source="pub-tenants" target="pub-bk-schedules"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;">
<mxCell id="pe-tenant-bksched" edge="1" parent="region-public" source="pub-tenants" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-bk-schedules">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="320" y="430" />
@@ -679,14 +595,10 @@
</Array>
</mxGeometry>
</mxCell>
<mxCell id="pe-tenant-bksched-lbl" connectable="0" parent="pe-tenant-bksched"
style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N (NULL=全局)" vertex="1">
<mxCell id="pe-tenant-bksched-lbl" connectable="0" parent="pe-tenant-bksched" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N (NULL=全局)" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- tenants → backup_records -->
<mxCell id="pe-tenant-bkrec" edge="1" parent="region-public" source="pub-tenants" target="pub-bk-records"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;">
<mxCell id="pe-tenant-bkrec" edge="1" parent="region-public" source="pub-tenants" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-bk-records">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="320" y="480" />
@@ -695,14 +607,10 @@
</Array>
</mxGeometry>
</mxCell>
<mxCell id="pe-tenant-bkrec-lbl" connectable="0" parent="pe-tenant-bkrec"
style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxCell id="pe-tenant-bkrec-lbl" connectable="0" parent="pe-tenant-bkrec" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- tenants → export_tasks -->
<mxCell id="pe-tenant-export" edge="1" parent="region-public" source="pub-tenants" target="pub-exports"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;">
<mxCell id="pe-tenant-export" edge="1" parent="region-public" source="pub-tenants" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-exports">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="320" y="520" />
@@ -711,24 +619,16 @@
</Array>
</mxGeometry>
</mxCell>
<mxCell id="pe-tenant-export-lbl" connectable="0" parent="pe-tenant-export"
style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxCell id="pe-tenant-export-lbl" connectable="0" parent="pe-tenant-export" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- system_versions → upgrade_events (from/to) -->
<mxCell id="pe-ver-upgrade" edge="1" parent="region-public" source="pub-versions" target="pub-upgrades"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;">
<mxCell id="pe-ver-upgrade" edge="1" parent="region-public" source="pub-versions" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-upgrades">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-ver-upgrade-lbl" connectable="0" parent="pe-ver-upgrade"
style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="from/to version" vertex="1">
<mxCell id="pe-ver-upgrade-lbl" connectable="0" parent="pe-ver-upgrade" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="from/to version" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- upgrade_events → backup_records (upgrade_event_id) -->
<mxCell id="pe-upgrade-bkrec" edge="1" parent="region-public" source="pub-upgrades" target="pub-bk-records"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;">
<mxCell id="pe-upgrade-bkrec" edge="1" parent="region-public" source="pub-upgrades" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-bk-records">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1808" y="392" />
@@ -736,8 +636,7 @@
</Array>
</mxGeometry>
</mxCell>
<mxCell id="pe-upgrade-bkrec-lbl" connectable="0" parent="pe-upgrade-bkrec"
style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="upgrade_event_id" vertex="1">
<mxCell id="pe-upgrade-bkrec-lbl" connectable="0" parent="pe-upgrade-bkrec" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="upgrade_event_id" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>

View File

@@ -0,0 +1,128 @@
## 首页设置
## 房源设置
### 新增/编辑/查看
### 字段/标签设置
### 相关方设置
### 相关方保护规则设置
### 跟进/面访/回访
### 实勘视频/VR/实地核验
### 预约拍摄设置
### 钥匙/委托/政府核验
### 作业盘设置
### 维护人员设置
### 列表/房源/分级
### 营销设置
### 楼盘设置
### 资料房/业主委托/预录入
### 隐私保护及防骚扰
### 房源检查及纠错
## 新房设置
### 新房基本设置
### 新房参数设置
## 客源设置
### 客源基本配置
### 客源参数配置
### 客源相关方配置
### 客源行政跨部权限
## 交易设置
### 交易流程
### 二手售后流程
### 新房售后流程
### 参数&备件条件
## 财务设置
### 业绩管理
### 资金管理
### 结算设置
### 提成设置
## 人事OA设置
### 组织人事基本设置
### 员工自动升降级设置
### 审批流程设置
## 任务设置
### 参数设置
### 入职周年祝福设置
## 合同设置
### 合同基本设置
## 通用及移动端设置
### 指标设置
### 安全设置
### 其他设置
### 电话智能监控设置
### 黑名单设置
## 安装与登录设置

View File

@@ -15,6 +15,9 @@
- 客源 DATA_MODEL: `Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 楼盘 DATA_MODEL: `Project/fonrey/DATA_MODEL/DATA_MODEL_COMPLEX.md`
- 组织人事DATA_MODEL: `Project/fonrey/DATA_MODEL/DATA_MODEL_ORG.md`
- 权限DATA_MODEL:`Project/fonrey/DATA_MODEL/DATA_MODEL_PERMISSION.md`
- 系统管理DATA_MODEL: `Project/fonrey/DATA_MODEL/DATA_MODEL_PUBLIC.md`
- Fonrey DATA_MODEL diagram: `Project/fonrey/DATA_MODEL/diagram/fonrey-data-model.xml`
- 项目PRD 文档(Draft):
- 房源管理PRD: `Project/fonrey/PRD/房源管理/房源管理模块PRD.md`
- 楼盘管理PRD: `Project/fonrey/PRD/房源管理/楼盘管理模块PRD.md`
@@ -38,166 +41,16 @@
- 客源管理PRD: `Project/fonrey/PRD/客源管理/客源管理模块PRD.md`
- 组织人事管理PRD: `Project/fonrey/PRD/组织人事管理/组织人事管理模块PRD.md`
- 权限管理PRD: `Project/fonrey/PRD/权限管理/权限管理模块PRD.md`
- 现在请你继续分析整个SaaS application的系统管理需求, 输出到文档 `Project/fonrey/PRD/系统管理/系统管理模块PRD.md`
### 一、租户管理Tenant Management
- 系统管理PRD: `Project/fonrey/PRD/系统管理/系统管理模块PRD.md`
- 现在我要编写系统发布需求将来我希望以windows application的方式给终端客户用用户只要通过我给的网址下载最新版本的application直接安装就可打开系统的登录界面进行登录考虑到以后的升级和维护我希望这个client application能感知到最新版本后可以自动升级。这个client端应该包含一个兼容性强的浏览器无论客户的环境怎样复制都能应付自如。不受浏览器的影响。请根据我现在的技术栈和服务器端的技术选型帮我具体把这个需求写成文档。请帮忙编写PRD文档输出到 `Project/fonrey/PRD/发布管理/客户端发布管理模块PRD.md`
#### 1.1 租户生命周期管理
**新建租户**
- 创建新租户时需录入基本信息公司名称、联系人、联系邮箱、所在地区、订阅套餐Plan
- 自动完成租户数据库/Schema 初始化、默认配置注入、欢迎邮件发送
- 支持为租户分配自定义子域名(如 `company-name.platform.com`
- 新建完成后生成唯一 Tenant ID并记录创建时间与创建人
**挂起租户Suspend**
- 暂时冻结租户的所有功能入口,用户登录后显示"账号已暂停使用"提示
- 挂起期间数据完整保留,不影响后台数据查询与导出
- 支持设置挂起原因(欠费、违规、主动申请等)及挂起到期时间(到期自动恢复)
- 挂起与恢复操作均记录操作日志,并向租户管理员发送通知邮件
**删除租户**
- 删除操作分为软删除Soft Delete数据保留一定周期后再清除和硬删除Hard Delete立即清除所有数据
- 执行删除前必须完成数据导出确认,防止误操作
- 支持配置删除冷静期(如 30 天),期间可撤销删除操作
- 删除完成后释放该租户占用的子域名、存储资源及授权许可
#### 1.2 数据管理
**数据导出**
- 支持按租户导出全量数据,格式包括 CSV、JSON、SQL Dump
- 导出任务异步执行,完成后通过邮件或平台消息通知管理员下载
- 导出内容可按模块选择(如:客户数据、房源数据、交易记录、系统配置等)
- 导出文件加密存储,下载链接设置有效期(如 24 小时)
**数据备份(升级前快照)**
- 在对租户执行版本升级前,系统自动触发该租户的全量数据备份(快照)
- 支持手动发起单租户备份,备份内容包括数据库数据与文件存储(附件、图片等)
- 备份记录列表展示:备份时间、触发方式(自动/手动)、备份大小、备份状态
- 备份文件加密存储,支持设置保留策略(如保留最近 10 个备份版本)
**租户数据恢复Restore**
- 支持从历史备份快照将某租户的数据恢复至指定时间点
- 恢复操作前需二次确认,并自动对当前数据生成临时快照防止恢复失误
- 恢复过程中租户服务自动切换为维护模式,恢复完成后自动恢复服务
- 恢复结果生成操作报告,记录恢复前后的数据版本信息
#### 1.3 套餐与升级管理
**租户级别升级Plan Upgrade**
- 支持将租户从当前订阅套餐升级至更高套餐(如 Basic → Professional → Enterprise
- 升级前展示套餐差异对比功能项、用户数上限、存储空间、API 调用额度等)
- 升级操作支持立即生效或按账期生效两种模式
- 升级前自动触发数据备份(见 1.2),升级失败时支持一键回滚至备份版本
- 记录升级历史:升级时间、操作人、升级前套餐、升级后套餐
#### 1.4 用户与权限管理
**设置超级用户Tenant Admin**
- 为每个租户指定一名或多名超级用户Tenant Admin负责该租户内部的用户管理
- 超级用户可由平台管理员直接在管理后台创建或从现有用户中指定
- 支持查看当前租户的超级用户列表,并支持变更(替换、添加、撤销)
**设置超级用户权限**
- 支持对超级用户的权限范围进行精细化配置,例如:
- 是否允许创建/删除子用户
- 是否允许修改租户系统配置
- 是否允许查看账单与套餐信息
- 是否允许访问数据导出功能
- 权限配置基于 RBAC基于角色的访问控制模型支持自定义角色
**重置密码**
- 平台管理员可为任意租户的任意用户(包括 Tenant Admin发起密码重置
- 重置方式:发送重置链接至注册邮箱,或由管理员直接设置临时密码(首次登录必须修改)
- 所有密码重置操作记录在操作审计日志中
#### 1.5 租户监控与统计
**租户级别监控**
- 实时展示每个租户的资源使用情况CPU、内存、存储用量、API 调用次数
- 监控关键指标:活跃用户数、当日登录次数、异常请求数、慢查询数量
- 支持为关键指标设置告警阈值,超限时通过邮件/Webhook/短信通知管理员
**可用性统计Availability**
- 统计每个租户的服务可用率Uptime支持按日/周/月维度查看
- 记录故障事件:故障开始时间、恢复时间、持续时长、影响范围
- 提供 SLA 达标率报告,支持导出供客户服务团队使用
---
### 二、系统管理System Management
#### 2.1 版本升级与回滚
**系统升级**
- 支持对整个平台进行版本升级升级包通过后台上传或从制品库Artifact Registry拉取
- 升级前自动执行健康检查,确保所有服务处于正常状态
- 支持灰度升级策略:先对指定租户(如内测租户)升级,验证稳定后再全量推送
- 升级过程中展示实时进度,支持升级日志查看
**升级回滚Rollback**
- 若升级后检测到异常(自动检测或人工判断),支持一键回滚至上一稳定版本
- 回滚操作触发前自动保存当前状态快照
- 回滚完成后生成事件报告,记录升级失败原因、回滚耗时、影响租户范围
- 支持针对单个租户单独回滚(当某租户升级失败而其他租户正常时)
#### 2.2 定时备份Scheduled Backup
- 支持配置全平台级别的定时备份策略(如每日凌晨 2:00 全量备份)
- 支持针对单个租户配置独立的备份计划(覆盖全局策略)
- 备份策略配置项:备份频率(每小时/每日/每周)、保留数量、存储目标(本地/S3/OSS/GCS
- 备份任务执行记录:任务开始时间、完成时间、备份大小、成功/失败状态
- 备份失败时自动告警并支持手动重试
---
### 三、管理控制台Admin Console
#### 3.1 整体要求
- 提供独立的 Web 管理界面,与租户应用系统分离部署,仅供平台运营团队访问
- 管理控制台本身需支持多管理员账号,并具备管理员角色分级(超级管理员 / 运营人员 / 只读审计员)
- 所有操作均记录操作审计日志(操作人、操作时间、操作对象、操作内容、操作结果),支持日志查询与导出
- 界面支持中英文切换,响应式设计兼容主流桌面浏览器
#### 3.2 核心页面模块
|模块|主要功能|
|---|---|
|仪表盘Dashboard|全局租户数量、活跃租户数、系统健康状态、近期告警、资源使用概览|
|租户列表|分页/搜索/筛选(按状态、套餐、注册时间)、快捷操作入口|
|租户详情|基本信息、用户管理、套餐信息、监控数据、备份记录、操作历史|
|系统版本管理|当前版本、历史版本列表、升级入口、回滚入口|
|备份管理|全局备份计划配置、备份任务列表、手动触发备份、恢复操作入口|
|监控与告警|租户级/系统级监控图表、告警规则配置、告警历史|
|审计日志|全平台操作日志查询、筛选、导出|
|管理员设置|管理员账号管理、角色权限配置、登录安全设置MFA|
#### 3.3 安全要求
- 管理控制台访问需强制启用多因素认证MFA
- 支持 IP 白名单限制,仅允许指定网络范围访问
- 高危操作(删除租户、数据恢复、系统回滚)需二次身份验证确认
- 会话超时自动登出,支持强制登出指定管理员会话
## DATA_MODEL设计文档生成提示词
- 文档根目录是:`~/Workspace/nexus`
- 你是一名资深的系统架构师,现在我要你根据我提供的产品截图和信息帮我分析产品功能需求并写成需求文档
- 请你参考读取`Project/fonrey/prompt/engineering-backend-architect.md` 并用该文档提及架构师专业技能帮我进行项目设计
- 我的项目是开发一套房产经纪管理系统(Fonrey), 项目概览和技术栈包含在TECH_STACK 文档(Draft)`Project/fonrey/TECH_STACK/TECH_STACK.md`
- 现阶段已经完成需求分析如下,你可以参考这些文档了解项目具体的需求和模型
- 项目PRD 文档(Draft):
- 房源管理PRD: `Project/fonrey/PRD/房源管理/房源管理模块PRD.md`
@@ -215,8 +68,20 @@
- ER diagram 描述数据模型之间的关联关系(可以输出drawio的图形用不同颜色区分模型)
- 权限管理需要进一步讨论和设计,无需在此文档里做过多的描述
- 根据目前的权限管理PRD`Project/fonrey/PRD/权限管理/权限管理模块PRD.md`)的需求分析需要对整个系统的权限管理进行合理的设计。
- 现在我已经设计了第一个初始版本:`Project/fonrey/TECH_STACK/权限管理系统技术方案.md`
- 现阶段的DATA_MODEL设计如下
- DATA_MODEL文档(Draft): `Project/fonrey/DATA_MODEL/DATA_MODEL.md`
- 房源 DATA_MODDL: `Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 客源 DATA_MODEL: `Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 楼盘 DATA_MODEL: `Project/fonrey/DATA_MODEL/DATA_MODEL_COMPLEX.md`
- 组织人事DATA_MODEL: `Project/fonrey/DATA_MODEL/DATA_MODEL_ORG.md`
- 请用你的专业技能帮我review这个方案并告诉我是否有不足之处或是漏洞。或是和现在的数据模型有冲突的地方。 在正式开始项目实现前,我希望能做到最完善的设计。
- 最后请根据完善后的技术方案帮我创建权限模块的`DATA_MODEL_PERMISSION.md`
根据`Project/fonrey/PRD/系统管理/系统管理模块PRD` 的需求分析请更新DATA_MODEL.md里三、公共 SchemaShared / Public的设计。